use crate::ast::*;
use crate::reader::LineCol;
use crate::syms::{CallError, CallableMetadata, Function, Symbol, Symbols};
use crate::value;
use async_recursion::async_recursion;
use std::rc::Rc;
#[derive(Debug, thiserror::Error)]
#[error("{}:{}: {}", pos.line, pos.col, message)]
pub struct Error {
pos: LineCol,
message: String,
}
impl Error {
pub(crate) fn new<S: Into<String>>(pos: LineCol, message: S) -> Self {
Self { pos, message: message.into() }
}
fn from_call_error(md: &CallableMetadata, e: CallError, pos: LineCol) -> Self {
let message = match e {
CallError::ArgumentError(pos, e) => {
format!("In call to {}: {}:{}: {}", md.name(), pos.line, pos.col, e)
}
CallError::EvalError(e) => format!("In call to {}: {}", md.name(), e),
CallError::InternalError(pos, e) => {
format!("In call to {}: {}:{}: {}", md.name(), pos.line, pos.col, e)
}
CallError::IoError(e) => format!("In call to {}: {}", md.name(), e),
CallError::NestedError(e) => e,
CallError::SyntaxError if md.syntax().is_empty() => {
format!("In call to {}: expected no arguments", md.name())
}
CallError::SyntaxError => {
format!("In call to {}: expected {}", md.name(), md.syntax())
}
};
Self { pos, message }
}
pub fn from_value_error(e: value::Error, pos: LineCol) -> Self {
Self { pos, message: e.message }
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[async_recursion(?Send)]
pub async fn eval_all(exprs: &[Expr], syms: &mut Symbols) -> Result<Vec<Value>> {
let mut values = Vec::with_capacity(exprs.len());
for expr in exprs {
values.push(expr.eval(syms).await?);
}
Ok(values)
}
impl Expr {
pub fn start_pos(&self) -> LineCol {
match self {
Expr::Boolean(span) => span.pos,
Expr::Double(span) => span.pos,
Expr::Integer(span) => span.pos,
Expr::Text(span) => span.pos,
Expr::Symbol(span) => span.pos,
Expr::And(span) => span.lhs.start_pos(),
Expr::Or(span) => span.lhs.start_pos(),
Expr::Xor(span) => span.lhs.start_pos(),
Expr::Not(span) => span.pos,
Expr::ShiftLeft(span) => span.lhs.start_pos(),
Expr::ShiftRight(span) => span.lhs.start_pos(),
Expr::Equal(span) => span.lhs.start_pos(),
Expr::NotEqual(span) => span.lhs.start_pos(),
Expr::Less(span) => span.lhs.start_pos(),
Expr::LessEqual(span) => span.lhs.start_pos(),
Expr::Greater(span) => span.lhs.start_pos(),
Expr::GreaterEqual(span) => span.lhs.start_pos(),
Expr::Add(span) => span.lhs.start_pos(),
Expr::Subtract(span) => span.lhs.start_pos(),
Expr::Multiply(span) => span.lhs.start_pos(),
Expr::Divide(span) => span.lhs.start_pos(),
Expr::Modulo(span) => span.lhs.start_pos(),
Expr::Power(span) => span.lhs.start_pos(),
Expr::Negate(span) => span.pos,
Expr::Call(span) => span.pos,
}
}
async fn eval_array_args(
&self,
syms: &mut Symbols,
span: &FunctionCallSpan,
) -> Result<Vec<i32>> {
let values = eval_all(&span.args, syms).await?;
let mut subscripts = Vec::with_capacity(span.args.len());
for (e, v) in span.args.iter().zip(values) {
match v {
Value::Integer(i) => subscripts.push(i),
_ => return Err(Error::new(e.start_pos(), "Array subscripts must be integers")),
}
}
Ok(subscripts)
}
#[async_recursion(?Send)]
async fn eval_function_call(
&self,
syms: &mut Symbols,
span: &FunctionCallSpan,
f: Rc<dyn Function>,
) -> Result<Value> {
let metadata = f.metadata();
if !span.fref.accepts(metadata.return_type()) {
return Err(Error::new(span.pos, "Incompatible type annotation for function call"));
}
let result = f.exec(span, syms).await;
match result {
Ok(value) => {
debug_assert!(metadata.return_type() != VarType::Auto);
let fref = VarRef::new(span.fref.name(), metadata.return_type());
if !fref.accepts(value.as_vartype()) {
return Err(Error::new(
span.pos,
format!(
"Value returned by {} is incompatible with its type definition",
fref.name(),
),
));
}
Ok(value)
}
Err(e) => Err(Error::from_call_error(metadata, e, span.pos)),
}
}
#[async_recursion(?Send)]
pub async fn eval(&self, syms: &mut Symbols) -> Result<Value> {
match self {
Expr::Boolean(span) => Ok(Value::Boolean(span.value)),
Expr::Double(span) => Ok(Value::Double(span.value)),
Expr::Integer(span) => Ok(Value::Integer(span.value)),
Expr::Text(span) => Ok(Value::Text(span.value.clone())),
Expr::Symbol(span) => {
match syms.get(&span.vref).map_err(|e| Error::from_value_error(e, span.pos))? {
Some(Symbol::Variable(v)) => Ok(v.clone()),
Some(Symbol::Function(f)) => {
if !f.metadata().is_argless() {
return Err(Error::new(
span.pos,
format!("{} requires one or more arguments", span.vref.name()),
));
}
let f = f.clone();
let span = FunctionCallSpan {
fref: span.vref.clone(),
args: vec![],
pos: span.pos,
};
Ok(self.eval_function_call(syms, &span, f).await?)
}
Some(_) => {
Err(Error::new(span.pos, format!("{} is not a variable", span.vref.name())))
}
None => Err(Error::new(
span.pos,
format!("Undefined variable {}", span.vref.name()),
)),
}
}
Expr::And(span) => {
Ok(Value::and(&span.lhs.eval(syms).await?, &span.rhs.eval(syms).await?)
.map_err(|e| Error::from_value_error(e, span.pos))?)
}
Expr::Or(span) => {
Ok(Value::or(&span.lhs.eval(syms).await?, &span.rhs.eval(syms).await?)
.map_err(|e| Error::from_value_error(e, span.pos))?)
}
Expr::Xor(span) => {
Ok(Value::xor(&span.lhs.eval(syms).await?, &span.rhs.eval(syms).await?)
.map_err(|e| Error::from_value_error(e, span.pos))?)
}
Expr::Not(span) => Ok(Value::not(&span.expr.eval(syms).await?)
.map_err(|e| Error::from_value_error(e, span.pos))?),
Expr::ShiftLeft(span) => {
Ok(Value::shl(&span.lhs.eval(syms).await?, &span.rhs.eval(syms).await?)
.map_err(|e| Error::from_value_error(e, span.pos))?)
}
Expr::ShiftRight(span) => {
Ok(Value::shr(&span.lhs.eval(syms).await?, &span.rhs.eval(syms).await?)
.map_err(|e| Error::from_value_error(e, span.pos))?)
}
Expr::Equal(span) => {
Ok(Value::eq(&span.lhs.eval(syms).await?, &span.rhs.eval(syms).await?)
.map_err(|e| Error::from_value_error(e, span.pos))?)
}
Expr::NotEqual(span) => {
Ok(Value::ne(&span.lhs.eval(syms).await?, &span.rhs.eval(syms).await?)
.map_err(|e| Error::from_value_error(e, span.pos))?)
}
Expr::Less(span) => {
Ok(Value::lt(&span.lhs.eval(syms).await?, &span.rhs.eval(syms).await?)
.map_err(|e| Error::from_value_error(e, span.pos))?)
}
Expr::LessEqual(span) => {
Ok(Value::le(&span.lhs.eval(syms).await?, &span.rhs.eval(syms).await?)
.map_err(|e| Error::from_value_error(e, span.pos))?)
}
Expr::Greater(span) => {
Ok(Value::gt(&span.lhs.eval(syms).await?, &span.rhs.eval(syms).await?)
.map_err(|e| Error::from_value_error(e, span.pos))?)
}
Expr::GreaterEqual(span) => {
Ok(Value::ge(&span.lhs.eval(syms).await?, &span.rhs.eval(syms).await?)
.map_err(|e| Error::from_value_error(e, span.pos))?)
}
Expr::Add(span) => {
Ok(Value::add(&span.lhs.eval(syms).await?, &span.rhs.eval(syms).await?)
.map_err(|e| Error::from_value_error(e, span.pos))?)
}
Expr::Subtract(span) => {
Ok(Value::sub(&span.lhs.eval(syms).await?, &span.rhs.eval(syms).await?)
.map_err(|e| Error::from_value_error(e, span.pos))?)
}
Expr::Multiply(span) => {
Ok(Value::mul(&span.lhs.eval(syms).await?, &span.rhs.eval(syms).await?)
.map_err(|e| Error::from_value_error(e, span.pos))?)
}
Expr::Divide(span) => {
Ok(Value::div(&span.lhs.eval(syms).await?, &span.rhs.eval(syms).await?)
.map_err(|e| Error::from_value_error(e, span.pos))?)
}
Expr::Modulo(span) => {
Ok(Value::modulo(&span.lhs.eval(syms).await?, &span.rhs.eval(syms).await?)
.map_err(|e| Error::from_value_error(e, span.pos))?)
}
Expr::Power(span) => {
Ok(Value::pow(&span.lhs.eval(syms).await?, &span.rhs.eval(syms).await?)
.map_err(|e| Error::from_value_error(e, span.pos))?)
}
Expr::Negate(span) => Ok(Value::neg(&span.expr.eval(syms).await?)
.map_err(|e| Error::from_value_error(e, span.pos))?),
Expr::Call(span) => {
match syms.get(&span.fref).map_err(|e| Error::from_value_error(e, span.pos))? {
Some(Symbol::Array(_)) => (), Some(Symbol::Function(f)) => {
if f.metadata().is_argless() {
return Err(Error::new(
span.pos,
format!(
"In call to {}: expected no arguments nor parenthesis",
f.metadata().name()
),
));
}
let f = f.clone();
return self.eval_function_call(syms, span, f).await;
}
Some(_) => {
return Err(Error::new(
span.pos,
format!("{} is not an array or a function", span.fref),
))
}
None => {
return Err(Error::new(
span.pos,
format!("Unknown function or array {}", span.fref),
))
}
}
let subscripts = self.eval_array_args(syms, span).await?;
match syms.get(&span.fref).map_err(|e| Error::from_value_error(e, span.pos))? {
Some(Symbol::Array(array)) => {
Ok(array
.index(&subscripts)
.map(|v| v.clone())
.map_err(|e| Error::from_value_error(e, span.pos))?)
}
_ => unreachable!(),
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::VarRef;
use crate::reader::LineCol;
use crate::testutils::*;
use futures_lite::future::block_on;
use std::cell::RefCell;
fn lc(line: usize, col: usize) -> LineCol {
LineCol { line, col }
}
fn expr_boolean(value: bool) -> Expr {
let pos = LineCol { line: 1234, col: 5768 };
Expr::Boolean(BooleanSpan { value, pos })
}
fn expr_double(value: f64) -> Expr {
let pos = LineCol { line: 1234, col: 5768 };
Expr::Double(DoubleSpan { value, pos })
}
fn expr_integer(value: i32) -> Expr {
let pos = LineCol { line: 1234, col: 5768 };
Expr::Integer(IntegerSpan { value, pos })
}
fn expr_text<S: Into<String>>(value: S) -> Expr {
let pos = LineCol { line: 1234, col: 5768 };
Expr::Text(TextSpan { value: value.into(), pos })
}
fn expr_symbol(vref: VarRef) -> Expr {
let pos = LineCol { line: 1234, col: 5768 };
Expr::Symbol(SymbolSpan { vref, pos })
}
#[test]
fn test_expr_literals() {
let mut syms = Symbols::default();
assert_eq!(Value::Boolean(true), block_on(expr_boolean(true).eval(&mut syms)).unwrap());
assert_eq!(Value::Double(0.0), block_on(expr_double(0.0).eval(&mut syms)).unwrap());
assert_eq!(Value::Integer(0), block_on(expr_integer(0).eval(&mut syms)).unwrap());
assert_eq!(Value::Text("z".to_owned()), block_on(expr_text("z").eval(&mut syms)).unwrap());
}
#[test]
fn test_expr_symbol() {
let bool_ref = VarRef::new("a_boolean", VarType::Auto);
let bool_val = Value::Boolean(true);
let double_ref = VarRef::new("a_double", VarType::Auto);
let double_val = Value::Double(0.0);
let int_ref = VarRef::new("an_integer", VarType::Auto);
let int_val = Value::Integer(0);
let text_ref = VarRef::new("a_string", VarType::Auto);
let text_val = Value::Text("x".to_owned());
let mut syms = Symbols::default();
syms.set_var(&bool_ref, bool_val.clone()).unwrap();
syms.set_var(&double_ref, double_val.clone()).unwrap();
syms.set_var(&int_ref, int_val.clone()).unwrap();
syms.set_var(&text_ref, text_val.clone()).unwrap();
assert_eq!(bool_val, block_on(expr_symbol(bool_ref).eval(&mut syms)).unwrap());
assert_eq!(double_val, block_on(expr_symbol(double_ref).eval(&mut syms)).unwrap());
assert_eq!(int_val, block_on(expr_symbol(int_ref).eval(&mut syms)).unwrap());
assert_eq!(text_val, block_on(expr_symbol(text_ref).eval(&mut syms)).unwrap());
assert_eq!(
"7:6: Undefined variable x",
format!(
"{}",
block_on(
Expr::Symbol(SymbolSpan {
vref: VarRef::new("x", VarType::Auto),
pos: lc(7, 6)
})
.eval(&mut syms)
)
.unwrap_err()
)
);
}
#[test]
fn test_expr_logical_ops() {
let binary_args = Box::from(BinaryOpSpan {
lhs: expr_boolean(false),
rhs: expr_integer(0),
pos: lc(3, 2),
});
let unary_args = Box::from(UnaryOpSpan { expr: expr_double(0.0), pos: lc(7, 4) });
let mut syms = Symbols::default();
assert_eq!(
"3:2: Cannot AND FALSE and 0",
format!("{}", block_on(Expr::And(binary_args.clone()).eval(&mut syms)).unwrap_err())
);
assert_eq!(
"3:2: Cannot OR FALSE and 0",
format!("{}", block_on(Expr::Or(binary_args.clone()).eval(&mut syms)).unwrap_err())
);
assert_eq!(
"3:2: Cannot XOR FALSE and 0",
format!("{}", block_on(Expr::Xor(binary_args).eval(&mut syms)).unwrap_err())
);
assert_eq!(
"7:4: Cannot apply NOT to 0.0",
format!("{}", block_on(Expr::Not(unary_args).eval(&mut syms)).unwrap_err())
);
}
#[test]
fn test_expr_relational_ops() {
let binary_args = Box::from(BinaryOpSpan {
lhs: expr_boolean(false),
rhs: expr_integer(0),
pos: lc(5, 8),
});
let mut syms = Symbols::default();
assert_eq!(
"5:8: Cannot compare FALSE and 0 with =",
format!("{}", block_on(Expr::Equal(binary_args.clone()).eval(&mut syms)).unwrap_err())
);
assert_eq!(
"5:8: Cannot compare FALSE and 0 with <>",
format!(
"{}",
block_on(Expr::NotEqual(binary_args.clone()).eval(&mut syms)).unwrap_err()
)
);
assert_eq!(
"5:8: Cannot compare FALSE and 0 with <",
format!("{}", block_on(Expr::Less(binary_args.clone()).eval(&mut syms)).unwrap_err())
);
assert_eq!(
"5:8: Cannot compare FALSE and 0 with <=",
format!(
"{}",
block_on(Expr::LessEqual(binary_args.clone()).eval(&mut syms)).unwrap_err()
)
);
assert_eq!(
"5:8: Cannot compare FALSE and 0 with >",
format!(
"{}",
block_on(Expr::Greater(binary_args.clone()).eval(&mut syms)).unwrap_err()
)
);
assert_eq!(
"5:8: Cannot compare FALSE and 0 with >=",
format!("{}", block_on(Expr::GreaterEqual(binary_args).eval(&mut syms)).unwrap_err())
);
}
#[test]
fn test_expr_arithmetic_ops() {
let binary_args = Box::from(BinaryOpSpan {
lhs: expr_boolean(false),
rhs: expr_integer(0),
pos: lc(5, 8),
});
let unary_args = Box::from(UnaryOpSpan { expr: expr_boolean(false), pos: lc(2, 1) });
let mut syms = Symbols::default();
assert_eq!(
"5:8: Cannot add FALSE and 0",
format!("{}", block_on(Expr::Add(binary_args.clone()).eval(&mut syms)).unwrap_err())
);
assert_eq!(
"5:8: Cannot subtract 0 from FALSE",
format!(
"{}",
block_on(Expr::Subtract(binary_args.clone()).eval(&mut syms)).unwrap_err()
)
);
assert_eq!(
"5:8: Cannot multiply FALSE by 0",
format!(
"{}",
block_on(Expr::Multiply(binary_args.clone()).eval(&mut syms)).unwrap_err()
)
);
assert_eq!(
"5:8: Cannot divide FALSE by 0",
format!("{}", block_on(Expr::Divide(binary_args.clone()).eval(&mut syms)).unwrap_err())
);
assert_eq!(
"5:8: Cannot modulo FALSE by 0",
format!("{}", block_on(Expr::Modulo(binary_args.clone()).eval(&mut syms)).unwrap_err())
);
assert_eq!(
"5:8: Cannot raise FALSE to the power of 0",
format!("{}", block_on(Expr::Power(binary_args).eval(&mut syms)).unwrap_err())
);
assert_eq!(
"2:1: Cannot negate FALSE",
format!("{}", block_on(Expr::Negate(unary_args).eval(&mut syms)).unwrap_err())
);
}
#[test]
fn test_expr_various_ops_and_vars() {
let xref = VarRef::new("x", VarType::Integer);
let yref = VarRef::new("y", VarType::Integer);
let mut syms = Symbols::default();
syms.set_var(&xref, Value::Integer(10)).unwrap();
syms.set_var(&yref, Value::Integer(3)).unwrap();
assert_eq!(
Value::Integer(36),
block_on(
Expr::Multiply(Box::from(BinaryOpSpan {
lhs: Expr::Add(Box::from(BinaryOpSpan {
lhs: expr_symbol(xref.clone()),
rhs: expr_integer(2),
pos: lc(0, 0),
})),
rhs: expr_symbol(yref.clone()),
pos: lc(0, 0),
}))
.eval(&mut syms)
)
.unwrap()
);
assert_eq!(
Value::Boolean(true),
block_on(
Expr::Equal(Box::from(BinaryOpSpan {
lhs: expr_symbol(xref),
rhs: Expr::Add(Box::from(BinaryOpSpan {
lhs: expr_integer(7),
rhs: expr_symbol(yref),
pos: lc(0, 0),
})),
pos: lc(0, 0),
}))
.eval(&mut syms)
)
.unwrap()
);
assert_eq!(
"3:4: Cannot add 7 and TRUE",
format!(
"{}",
block_on(
Expr::Equal(Box::from(BinaryOpSpan {
lhs: expr_integer(3),
rhs: Expr::Add(Box::from(BinaryOpSpan {
lhs: expr_integer(7),
rhs: expr_boolean(true),
pos: lc(3, 4),
})),
pos: lc(1, 2),
}))
.eval(&mut syms)
)
.unwrap_err()
)
);
}
#[test]
fn test_expr_array_simple() {
let xref = VarRef::new("x", VarType::Integer);
let mut syms = Symbols::default();
syms.dim_array("x", VarType::Integer, vec![2, 4]).unwrap();
match syms.get_mut(&xref).unwrap().unwrap() {
Symbol::Array(array) => array.assign(&[1, 3], Value::Integer(8)).unwrap(),
_ => panic!("Got something that is not the array we asked for"),
}
assert_eq!(
Value::Integer(0),
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("X", VarType::Auto),
args: vec![expr_integer(0), expr_integer(3)],
pos: lc(0, 0),
})
.eval(&mut syms)
)
.unwrap()
);
assert_eq!(
Value::Integer(8),
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("X", VarType::Auto),
args: vec![expr_integer(1), expr_integer(3)],
pos: lc(0, 0),
})
.eval(&mut syms)
)
.unwrap()
);
assert_eq!(
Value::Integer(8),
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("X", VarType::Integer),
args: vec![expr_integer(1), expr_integer(3)],
pos: lc(0, 0),
})
.eval(&mut syms)
)
.unwrap()
);
assert_eq!(
"3:4: Incompatible types in X# reference",
format!(
"{}",
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("X", VarType::Double),
args: vec![expr_integer(1), expr_integer(3)],
pos: lc(3, 4),
})
.eval(&mut syms)
)
.unwrap_err()
)
);
assert_eq!(
"3:4: Cannot index array with 1 subscripts; need 2",
format!(
"{}",
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("X", VarType::Integer),
args: vec![expr_integer(1)],
pos: lc(3, 4),
})
.eval(&mut syms)
)
.unwrap_err()
)
);
assert_eq!(
"3:4: Subscript -1 cannot be negative",
format!(
"{}",
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("X", VarType::Integer),
args: vec![expr_integer(0), expr_integer(-1)],
pos: lc(3, 4),
})
.eval(&mut syms)
)
.unwrap_err()
)
);
assert_eq!(
"3:4: Subscript 10 exceeds limit of 2",
format!(
"{}",
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("X", VarType::Integer),
args: vec![expr_integer(10), expr_integer(0)],
pos: lc(3, 4),
})
.eval(&mut syms)
)
.unwrap_err()
)
);
assert_eq!(
"3:4: Unknown function or array y$",
format!(
"{}",
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("y".to_owned(), VarType::Text),
args: vec![],
pos: lc(3, 4),
})
.eval(&mut syms)
)
.unwrap_err()
)
);
}
#[test]
fn test_expr_function_call_simple() {
let xref = VarRef::new("x", VarType::Integer);
let mut syms = SymbolsBuilder::default().add_function(SumFunction::new()).build();
syms.set_var(&xref, Value::Integer(5)).unwrap();
assert_eq!(
Value::Integer(0),
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("SUM".to_owned(), VarType::Auto),
args: vec![],
pos: lc(0, 0),
})
.eval(&mut syms)
)
.unwrap()
);
assert_eq!(
Value::Integer(5),
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("sum".to_owned(), VarType::Auto),
args: vec![expr_integer(5)],
pos: lc(0, 0),
})
.eval(&mut syms)
)
.unwrap()
);
assert_eq!(
Value::Integer(7),
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("SUM".to_owned(), VarType::Auto),
args: vec![expr_integer(5), expr_integer(2)],
pos: lc(0, 0),
})
.eval(&mut syms)
)
.unwrap()
);
assert_eq!(
Value::Integer(23),
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("SUM".to_owned(), VarType::Integer),
args: vec![
expr_symbol(xref),
expr_integer(8),
Expr::Subtract(Box::from(BinaryOpSpan {
lhs: expr_integer(100),
rhs: expr_integer(90),
pos: lc(0, 0),
}))
],
pos: lc(0, 0),
})
.eval(&mut syms)
)
.unwrap()
);
assert_eq!(
"8:7: Incompatible types in SUM$ reference",
format!(
"{}",
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("SUM".to_owned(), VarType::Text),
args: vec![],
pos: lc(8, 7),
})
.eval(&mut syms)
)
.unwrap_err()
)
);
assert_eq!(
"4:6: Unknown function or array SUMA$",
format!(
"{}",
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("SUMA".to_owned(), VarType::Text),
args: vec![],
pos: lc(4, 6),
})
.eval(&mut syms)
)
.unwrap_err()
)
);
}
#[test]
fn test_expr_function_call_type_check() {
{
let mut syms = SymbolsBuilder::default()
.add_function(TypeCheckFunction::new(Value::Boolean(true)))
.build();
assert_eq!(
Value::Boolean(true),
block_on(
Expr::Symbol(SymbolSpan {
vref: VarRef::new("TYPE_CHECK".to_owned(), VarType::Auto),
pos: lc(0, 0),
})
.eval(&mut syms)
)
.unwrap()
);
}
{
let mut syms = SymbolsBuilder::default()
.add_function(TypeCheckFunction::new(Value::Integer(5)))
.build();
assert_eq!(
"9:2: Value returned by TYPE_CHECK is incompatible with its type definition",
format!(
"{}",
block_on(
Expr::Symbol(SymbolSpan {
vref: VarRef::new("TYPE_CHECK".to_owned(), VarType::Auto),
pos: lc(9, 2),
})
.eval(&mut syms)
)
.unwrap_err()
)
);
}
}
#[test]
fn test_expr_function_error_check() {
let mut syms = SymbolsBuilder::default().add_function(RaiseFunction::new()).build();
assert_eq!(
"1:1: In call to RAISE: 4:5: Bad argument",
format!(
"{}",
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("RAISE".to_owned(), VarType::Auto),
args: vec![Expr::Text(TextSpan {
value: "argument".to_owned(),
pos: lc(4, 5)
})],
pos: lc(1, 1),
})
.eval(&mut syms)
)
.unwrap_err()
)
);
assert_eq!(
"3:2: In call to RAISE: 4:5: Some eval error",
format!(
"{}",
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("RAISE".to_owned(), VarType::Auto),
args: vec![Expr::Text(TextSpan {
value: "eval".to_owned(),
pos: lc(4, 5)
})],
pos: lc(3, 2),
})
.eval(&mut syms)
)
.unwrap_err()
)
);
assert_eq!(
"5:1: In call to RAISE: 4:5: Some internal error",
format!(
"{}",
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("RAISE".to_owned(), VarType::Auto),
args: vec![Expr::Text(TextSpan {
value: "internal".to_owned(),
pos: lc(4, 5)
})],
pos: lc(5, 1),
})
.eval(&mut syms)
)
.unwrap_err()
)
);
assert_eq!(
"5:1: In call to RAISE: Some I/O error",
format!(
"{}",
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("RAISE".to_owned(), VarType::Auto),
args: vec![Expr::Text(TextSpan { value: "io".to_owned(), pos: lc(4, 5) })],
pos: lc(5, 1),
})
.eval(&mut syms)
)
.unwrap_err()
)
);
assert_eq!(
"7:4: In call to RAISE: expected arg1$",
format!(
"{}",
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("RAISE".to_owned(), VarType::Auto),
args: vec![expr_text("syntax")],
pos: lc(7, 4),
})
.eval(&mut syms)
)
.unwrap_err()
)
);
}
#[test]
fn test_expr_call_non_function() {
let mut syms = SymbolsBuilder::default()
.add_var("SOMEVAR", Value::Integer(0))
.add_command(OutCommand::new(Rc::from(RefCell::from(vec![]))))
.build();
assert_eq!(
"3:9: SOMEVAR is not an array or a function",
format!(
"{}",
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("SOMEVAR".to_owned(), VarType::Auto),
args: vec![],
pos: lc(3, 9),
})
.eval(&mut syms)
)
.unwrap_err()
)
);
assert_eq!(
"3:7: OUT is not an array or a function",
format!(
"{}",
block_on(
Expr::Call(FunctionCallSpan {
fref: VarRef::new("OUT".to_owned(), VarType::Auto),
args: vec![],
pos: lc(3, 7),
})
.eval(&mut syms)
)
.unwrap_err()
)
);
}
}