diff --git a/lykiadb-lang/src/ast/expr.rs b/lykiadb-lang/src/ast/expr.rs index f772f82..8532830 100644 --- a/lykiadb-lang/src/ast/expr.rs +++ b/lykiadb-lang/src/ast/expr.rs @@ -491,39 +491,6 @@ impl Expr { } } } - - pub fn collect(&self, predicate: &impl Fn(&Expr) -> bool, collected: &mut Vec) { - if predicate(self) { - collected.push(self.clone()); - return; - } - match &self { - Expr::Grouping { expr, .. } => expr.collect(predicate, collected), - Expr::Between { - lower, - upper, - subject, - .. - } => { - lower.collect(predicate, collected); - upper.collect(predicate, collected); - subject.collect(predicate, collected); - } - Expr::Binary { left, right, .. } => { - left.collect(predicate, collected); - right.collect(predicate, collected); - } - Expr::Unary { expr, .. } => expr.collect(predicate, collected), - Expr::Logical { left, right, .. } => { - left.collect(predicate, collected); - right.collect(predicate, collected); - } - Expr::Call { args, .. } => { - args.iter().for_each(|x| x.collect(predicate, collected)); - } - _ => (), - } - } } #[cfg(test)] @@ -532,6 +499,647 @@ pub mod test { use crate::{ast::expr::Expr, Literal, Span}; + use super::*; + + #[test] + fn test_expr_walk() { + // Test simple expressions that don't traverse children + { + let simple_exprs = vec![ + Expr::Variable { + name: Identifier::new("x", false), + span: Span::default(), + id: 1, + }, + Expr::Literal { + value: Literal::Num(42.0), + raw: "42".to_string(), + span: Span::default(), + id: 2, + }, + Expr::FieldPath { + head: Identifier::new("user", false), + tail: vec![], + span: Span::default(), + id: 3, + }, + Expr::Function { + name: Some(Identifier::new("test", false)), + parameters: vec![], + body: Arc::new(vec![]), + span: Span::default(), + id: 4, + }, + ]; + + for expr in simple_exprs { + let mut visited = Vec::new(); + expr.walk(&mut |e| { + visited.push(e.get_id()); + Some(Ok::<(), ()>(())) + }); + assert_eq!(visited, vec![expr.get_id()]); + } + } + + // Test Binary and Logical expressions + { + let binary_expr = Expr::Binary { + left: Box::new(Expr::Literal { + value: Literal::Num(1.0), + raw: "1".to_string(), + span: Span::default(), + id: 1, + }), + operation: Operation::Add, + right: Box::new(Expr::Literal { + value: Literal::Num(2.0), + raw: "2".to_string(), + span: Span::default(), + id: 2, + }), + span: Span::default(), + id: 3, + }; + + let mut visited = Vec::new(); + binary_expr.walk(&mut |e| { + visited.push(e.get_id()); + Some(Ok::<(), ()>(())) + }); + assert_eq!(visited, vec![3, 1, 2]); + } + + // Test Grouping, Unary, and Assignment expressions + { + let unary_expr = Expr::Unary { + operation: Operation::Not, + expr: Box::new(Expr::Literal { + value: Literal::Bool(true), + raw: "true".to_string(), + span: Span::default(), + id: 1, + }), + span: Span::default(), + id: 2, + }; + + let mut visited = Vec::new(); + unary_expr.walk(&mut |e| { + visited.push(e.get_id()); + Some(Ok::<(), ()>(())) + }); + assert_eq!(visited, vec![2, 1]); + } + + // Test Call expression + { + let call_expr = Expr::Call { + callee: Box::new(Expr::Variable { + name: Identifier::new("test_func", false), + span: Span::default(), + id: 1, + }), + args: vec![ + Expr::Literal { + value: Literal::Num(1.0), + raw: "1".to_string(), + span: Span::default(), + id: 2, + }, + Expr::Literal { + value: Literal::Num(2.0), + raw: "2".to_string(), + span: Span::default(), + id: 3, + }, + ], + span: Span::default(), + id: 4, + }; + + let mut visited = Vec::new(); + call_expr.walk(&mut |e| { + visited.push(e.get_id()); + Some(Ok::<(), ()>(())) + }); + assert_eq!(visited, vec![4, 1, 2, 3]); + } + + // Test Between expression + { + let between_expr = Expr::Between { + lower: Box::new(Expr::Literal { + value: Literal::Num(1.0), + raw: "1".to_string(), + span: Span::default(), + id: 1, + }), + upper: Box::new(Expr::Literal { + value: Literal::Num(10.0), + raw: "10".to_string(), + span: Span::default(), + id: 2, + }), + subject: Box::new(Expr::Variable { + name: Identifier::new("x", false), + span: Span::default(), + id: 3, + }), + kind: RangeKind::Between, + span: Span::default(), + id: 4, + }; + + let mut visited = Vec::new(); + between_expr.walk(&mut |e| { + visited.push(e.get_id()); + Some(Ok::<(), ()>(())) + }); + assert_eq!(visited, vec![4, 1, 2, 3]); + } + + // Test Get and Set expressions + { + let get_expr = Expr::Get { + object: Box::new(Expr::Variable { + name: Identifier::new("obj", false), + span: Span::default(), + id: 1, + }), + name: Identifier::new("prop", false), + span: Span::default(), + id: 2, + }; + + let mut visited = Vec::new(); + get_expr.walk(&mut |e| { + visited.push(e.get_id()); + Some(Ok::<(), ()>(())) + }); + assert_eq!(visited, vec![2, 1]); + + let set_expr = Expr::Set { + object: Box::new(Expr::Variable { + name: Identifier::new("obj", false), + span: Span::default(), + id: 1, + }), + name: Identifier::new("prop", false), + value: Box::new(Expr::Literal { + value: Literal::Num(42.0), + raw: "42".to_string(), + span: Span::default(), + id: 2, + }), + span: Span::default(), + id: 3, + }; + + let mut visited = Vec::new(); + set_expr.walk(&mut |e| { + visited.push(e.get_id()); + Some(Ok::<(), ()>(())) + }); + assert_eq!(visited, vec![3, 1, 2]); + } + + } + + #[test] + fn test_expr_get_id() { + + // Test Variable + let var_expr = Expr::Variable { + name: Identifier::new("test_var", false), + span: Span::default(), + id: 2, + }; + assert_eq!(var_expr.get_id(), 2); + + // Test Grouping + let group_expr = Expr::Grouping { + expr: Box::new(Expr::Literal { + value: Literal::Num(42.0), + raw: "42".to_string(), + span: Span::default(), + id: 3, + }), + span: Span::default(), + id: 4, + }; + assert_eq!(group_expr.get_id(), 4); + + // Test Between + let between_expr = Expr::Between { + lower: Box::new(Expr::Literal { + value: Literal::Num(1.0), + raw: "1".to_string(), + span: Span::default(), + id: 5, + }), + upper: Box::new(Expr::Literal { + value: Literal::Num(10.0), + raw: "10".to_string(), + span: Span::default(), + id: 6, + }), + subject: Box::new(Expr::Variable { + name: Identifier::new("x", false), + span: Span::default(), + id: 7, + }), + kind: RangeKind::Between, + span: Span::default(), + id: 8, + }; + assert_eq!(between_expr.get_id(), 8); + + // Test Binary + let binary_expr = Expr::Binary { + left: Box::new(Expr::Literal { + value: Literal::Num(1.0), + raw: "1".to_string(), + span: Span::default(), + id: 9, + }), + operation: Operation::Add, + right: Box::new(Expr::Literal { + value: Literal::Num(2.0), + raw: "2".to_string(), + span: Span::default(), + id: 10, + }), + span: Span::default(), + id: 11, + }; + assert_eq!(binary_expr.get_id(), 11); + + // Test Logical + let logical_expr = Expr::Logical { + left: Box::new(Expr::Literal { + value: Literal::Bool(true), + raw: "true".to_string(), + span: Span::default(), + id: 12, + }), + operation: Operation::And, + right: Box::new(Expr::Literal { + value: Literal::Bool(false), + raw: "false".to_string(), + span: Span::default(), + id: 13, + }), + span: Span::default(), + id: 14, + }; + assert_eq!(logical_expr.get_id(), 14); + + // Test Call + let call_expr = Expr::Call { + callee: Box::new(Expr::Variable { + name: Identifier::new("test_func", false), + span: Span::default(), + id: 15, + }), + args: vec![], + span: Span::default(), + id: 16, + }; + assert_eq!(call_expr.get_id(), 16); + + // Test FieldPath + let field_path_expr = Expr::FieldPath { + head: Identifier::new("user", false), + tail: vec![], + span: Span::default(), + id: 17, + }; + assert_eq!(field_path_expr.get_id(), 17); + } + + #[test] + fn test_expr_get_span() { + let test_span = Span::default(); + + + // Test Variable + let var_expr = Expr::Variable { + name: Identifier::new("test_var", false), + span: test_span, + id: 5, + }; + assert_eq!(var_expr.get_span(), test_span); + + // Test Grouping + let group_expr = Expr::Grouping { + expr: Box::new(Expr::Literal { + value: Literal::Num(42.0), + raw: "42".to_string(), + span: Span::default(), + id: 6, + }), + span: test_span, + id: 7, + }; + assert_eq!(group_expr.get_span(), test_span); + + // Test Literal + let literal_expr = Expr::Literal { + value: Literal::Num(42.0), + raw: "42".to_string(), + span: test_span, + id: 8, + }; + assert_eq!(literal_expr.get_span(), test_span); + + // Test Function + let func_expr = Expr::Function { + name: Some(Identifier::new("test_func", false)), + parameters: vec![], + body: Arc::new(vec![]), + span: test_span, + id: 9, + }; + assert_eq!(func_expr.get_span(), test_span); + + // Test Between + let between_expr = Expr::Between { + lower: Box::new(Expr::Literal { + value: Literal::Num(1.0), + raw: "1".to_string(), + span: Span::default(), + id: 10, + }), + upper: Box::new(Expr::Literal { + value: Literal::Num(10.0), + raw: "10".to_string(), + span: Span::default(), + id: 11, + }), + subject: Box::new(Expr::Variable { + name: Identifier::new("x", false), + span: Span::default(), + id: 12, + }), + kind: RangeKind::Between, + span: test_span, + id: 13, + }; + assert_eq!(between_expr.get_span(), test_span); + + // Test Binary + let binary_expr = Expr::Binary { + left: Box::new(Expr::Literal { + value: Literal::Num(1.0), + raw: "1".to_string(), + span: Span::default(), + id: 14, + }), + operation: Operation::Add, + right: Box::new(Expr::Literal { + value: Literal::Num(2.0), + raw: "2".to_string(), + span: Span::default(), + id: 15, + }), + span: test_span, + id: 16, + }; + assert_eq!(binary_expr.get_span(), test_span); + + // Test Unary + let unary_expr = Expr::Unary { + operation: Operation::Not, + expr: Box::new(Expr::Literal { + value: Literal::Bool(true), + raw: "true".to_string(), + span: Span::default(), + id: 17, + }), + span: test_span, + id: 18, + }; + assert_eq!(unary_expr.get_span(), test_span); + + // Test Assignment + let assignment_expr = Expr::Assignment { + dst: Identifier::new("x", false), + expr: Box::new(Expr::Literal { + value: Literal::Num(42.0), + raw: "42".to_string(), + span: Span::default(), + id: 19, + }), + span: test_span, + id: 20, + }; + assert_eq!(assignment_expr.get_span(), test_span); + + // Test Logical + let logical_expr = Expr::Logical { + left: Box::new(Expr::Literal { + value: Literal::Bool(true), + raw: "true".to_string(), + span: Span::default(), + id: 21, + }), + operation: Operation::And, + right: Box::new(Expr::Literal { + value: Literal::Bool(false), + raw: "false".to_string(), + span: Span::default(), + id: 22, + }), + span: test_span, + id: 23, + }; + assert_eq!(logical_expr.get_span(), test_span); + + // Test Call + let call_expr = Expr::Call { + callee: Box::new(Expr::Variable { + name: Identifier::new("test_func", false), + span: Span::default(), + id: 24, + }), + args: vec![], + span: test_span, + id: 25, + }; + assert_eq!(call_expr.get_span(), test_span); + + // Test Get + let get_expr = Expr::Get { + object: Box::new(Expr::Variable { + name: Identifier::new("obj", false), + span: Span::default(), + id: 26, + }), + name: Identifier::new("prop", false), + span: test_span, + id: 27, + }; + assert_eq!(get_expr.get_span(), test_span); + + // Test FieldPath + let field_path_expr = Expr::FieldPath { + head: Identifier::new("user", false), + tail: vec![Identifier::new("name", false)], + span: test_span, + id: 28, + }; + assert_eq!(field_path_expr.get_span(), test_span); + + // Test Set + let set_expr = Expr::Set { + object: Box::new(Expr::Variable { + name: Identifier::new("obj", false), + span: Span::default(), + id: 29, + }), + name: Identifier::new("prop", false), + value: Box::new(Expr::Literal { + value: Literal::Num(42.0), + raw: "42".to_string(), + span: Span::default(), + id: 30, + }), + span: test_span, + id: 31, + }; + assert_eq!(set_expr.get_span(), test_span); + } + + #[test] + fn test_expr_display() { + // Test Variable display + let var_expr = Expr::Variable { + name: Identifier::new("test_var", false), + span: Span::default(), + id: 1, + }; + assert_eq!(var_expr.to_string(), "test_var"); + + // Test Grouping display + let group_expr = Expr::Grouping { + expr: Box::new(Expr::Literal { + value: Literal::Num(42.0), + raw: "42".to_string(), + span: Span::default(), + id: 2, + }), + span: Span::default(), + id: 3, + }; + assert_eq!(group_expr.to_string(), "(Num(42.0))"); + + // Test Binary display + let binary_expr = Expr::Binary { + left: Box::new(Expr::Literal { + value: Literal::Num(1.0), + raw: "1".to_string(), + span: Span::default(), + id: 4, + }), + operation: Operation::Add, + right: Box::new(Expr::Literal { + value: Literal::Num(2.0), + raw: "2".to_string(), + span: Span::default(), + id: 5, + }), + span: Span::default(), + id: 6, + }; + assert_eq!(binary_expr.to_string(), "(Num(1.0) Add Num(2.0))"); + } + + #[test] + fn test_field_path_display() { + let field_path = Expr::FieldPath { + head: Identifier::new("user", false), + tail: vec![ + Identifier::new("address", false), + Identifier::new("city", false), + ], + span: Span::default(), + id: 1, + }; + assert_eq!(field_path.to_string(), "user.address.city"); + } + + #[test] + fn test_function_display() { + let func = Expr::Function { + name: Some(Identifier::new("test_func", false)), + parameters: vec![ + Identifier::new("a", false), + Identifier::new("b", false), + ], + body: Arc::new(vec![]), + span: Span::default(), + id: 1, + }; + assert_eq!(func.to_string(), "fn test_func(a, b)"); + } + + #[test] + fn test_between_display() { + let between = Expr::Between { + lower: Box::new(Expr::Literal { + value: Literal::Num(1.0), + raw: "1".to_string(), + span: Span::default(), + id: 1, + }), + upper: Box::new(Expr::Literal { + value: Literal::Num(10.0), + raw: "10".to_string(), + span: Span::default(), + id: 2, + }), + subject: Box::new(Expr::Variable { + name: Identifier::new("x", false), + span: Span::default(), + id: 3, + }), + kind: RangeKind::Between, + span: Span::default(), + id: 4, + }; + assert_eq!(between.to_string(), "(x Between Num(1.0) And Num(10.0))"); + } + + #[test] + fn test_call_display() { + let call = Expr::Call { + callee: Box::new(Expr::Variable { + name: Identifier::new("test_func", false), + span: Span::default(), + id: 1, + }), + args: vec![ + Expr::Literal { + value: Literal::Num(1.0), + raw: "1".to_string(), + span: Span::default(), + id: 2, + }, + Expr::Literal { + value: Literal::Num(2.0), + raw: "2".to_string(), + span: Span::default(), + id: 3, + }, + ], + span: Span::default(), + id: 4, + }; + assert_eq!(call.to_string(), "test_func(Num(1.0), Num(2.0))"); + } + + pub fn create_simple_add_expr(id: usize, left: f64, right: f64) -> Expr { Expr::Binary { left: Box::new(Expr::Literal { diff --git a/lykiadb-lang/src/lib.rs b/lykiadb-lang/src/lib.rs index 879cd85..978af0b 100644 --- a/lykiadb-lang/src/lib.rs +++ b/lykiadb-lang/src/lib.rs @@ -96,6 +96,16 @@ pub struct Identifier { pub span: Span, } +impl Identifier { + pub fn new(name: &str, dollar: bool) -> Self { + Identifier { + name: name.to_string(), + dollar, + span: Span::default(), + } + } +} + impl Display for Identifier { fn fmt(&self, f: &mut Formatter) -> Result { write!(f, "{}", self.name) diff --git a/lykiadb-server/tests/interpreter/eval b/lykiadb-server/tests/interpreter/eval deleted file mode 100644 index 706a7c6..0000000 --- a/lykiadb-server/tests/interpreter/eval +++ /dev/null @@ -1,51 +0,0 @@ -#[name=unary_evaluation, run=interpreter]> - -test_utils::out(-2); -test_utils::out(-(-2)); -test_utils::out(!3); -test_utils::out(!!3); -test_utils::out(!!!3); - ---- - --2 -2 -false -true -false - -#[name=binary_evaluation, run=interpreter]> - -test_utils::out(5-(-2)); -test_utils::out((5 + 2) * 4); -test_utils::out(5 + 2 * 4); -test_utils::out((13 + 4) * (7 + 3)); -test_utils::out(-5-2); - ---- - -7 -28 -13 -170 --7 - -#[name=logical_evaluation, run=interpreter]> - -test_utils::out(5 && 1); -test_utils::out(5 || 1); -test_utils::out(5 && 0); -test_utils::out(5 || 0); -test_utils::out(!(5 || 0)); -test_utils::out(!(5 || 0) || 1); -test_utils::out(!(5 || 0) || (1 && 0)); - ---- - -true -true -false -true -false -true -false \ No newline at end of file diff --git a/lykiadb-server/tests/interpreter/expr b/lykiadb-server/tests/interpreter/expr new file mode 100644 index 0000000..1db7e55 --- /dev/null +++ b/lykiadb-server/tests/interpreter/expr @@ -0,0 +1,271 @@ +#[name=unary_evaluation, run=interpreter]> + +test_utils::out(-2); +test_utils::out(-(-2)); +test_utils::out(!3); +test_utils::out(!!3); +test_utils::out(!!!3); + +--- + +-2 +2 +false +true +false + +#[name=binary_evaluation, run=interpreter]> + +test_utils::out(5-(-2)); +test_utils::out((5 + 2) * 4); +test_utils::out(5 + 2 * 4); +test_utils::out((13 + 4) * (7 + 3)); +test_utils::out(-5-2); + +--- + +7 +28 +13 +170 +-7 + +#[name=logical_evaluation, run=interpreter]> + +test_utils::out(5 && 1); +test_utils::out(5 || 1); +test_utils::out(5 && 0); +test_utils::out(5 || 0); +test_utils::out(!(5 || 0)); +test_utils::out(!(5 || 0) || 1); +test_utils::out(!(5 || 0) || (1 && 0)); + +--- + +true +true +false +true +false +true +false + + +#[name=get_evaluation, run=interpreter]> + +var $obj = { + name: "John", + age: 30, + address: { + city: "New York", + zip: 10001 + } +}; + +test_utils::out($obj.name); +test_utils::out($obj.age); +test_utils::out($obj.address.city); +test_utils::out($obj.address.zip); + +--- + +John +30 +New York +10001 + + +#[name=get_errors, run=interpreter]> + +var $obj = { + name: "John" +}; + +test_utils::out($obj.age); + +---err + +Interpret(PropertyNotFound { span: Span { start: 50, end: 58, line: 4, line_end: 4 }, property: "age" }) + +---> + +test_utils::out($obj.address.city); + +---err + +Interpret(PropertyNotFound { span: Span { start: 50, end: 58, line: 4, line_end: 4 }, property: "age" }) +Interpret(PropertyNotFound { span: Span { start: 16, end: 28, line: 0, line_end: 0 }, property: "address" }) + +---> + +test_utils::out(null.prop); + +---err + +Interpret(PropertyNotFound { span: Span { start: 50, end: 58, line: 4, line_end: 4 }, property: "age" }) +Interpret(PropertyNotFound { span: Span { start: 16, end: 28, line: 0, line_end: 0 }, property: "address" }) +Interpret(Other { message: "Only objects have properties. Null is not an object" }) + +---> + +test_utils::out(5.prop); + +---err + +Interpret(PropertyNotFound { span: Span { start: 50, end: 58, line: 4, line_end: 4 }, property: "age" }) +Interpret(PropertyNotFound { span: Span { start: 16, end: 28, line: 0, line_end: 0 }, property: "address" }) +Interpret(Other { message: "Only objects have properties. Null is not an object" }) +Interpret(Other { message: "Only objects have properties. Num(5.0) is not an object" }) + +---> + +test_utils::out("string".length); + +---err + +Interpret(PropertyNotFound { span: Span { start: 50, end: 58, line: 4, line_end: 4 }, property: "age" }) +Interpret(PropertyNotFound { span: Span { start: 16, end: 28, line: 0, line_end: 0 }, property: "address" }) +Interpret(Other { message: "Only objects have properties. Null is not an object" }) +Interpret(Other { message: "Only objects have properties. Num(5.0) is not an object" }) +Interpret(Other { message: "Only objects have properties. Str(\"string\") is not an object" }) + +#[name=set_evaluation, run=interpreter]> + +var $obj = { + name: "John", + age: 30, + address: { + city: "New York", + zip: 10001 + } +}; + +$obj.name = "Jane"; +test_utils::out($obj.name); + +$obj.age = 31; +test_utils::out($obj.age); + +$obj.address.city = "Boston"; +test_utils::out($obj.address.city); + +$obj.newProp = "test"; +test_utils::out($obj.newProp); + +--- + +Jane +31 +Boston +test + + +#[name=set_errors, run=interpreter]> + +var $obj = { + name: "John" +}; + +$obj.address.city = "Boston"; + +---err + +Interpret(PropertyNotFound { span: Span { start: 34, end: 46, line: 4, line_end: 4 }, property: "address" }) + +---> + +null.prop = "test"; + +---err + +Interpret(PropertyNotFound { span: Span { start: 34, end: 46, line: 4, line_end: 4 }, property: "address" }) +Interpret(Other { message: "Only objects have properties. Null is not an object" }) + +---> + +5.prop = "test"; + +---err + +Interpret(PropertyNotFound { span: Span { start: 34, end: 46, line: 4, line_end: 4 }, property: "address" }) +Interpret(Other { message: "Only objects have properties. Null is not an object" }) +Interpret(Other { message: "Only objects have properties. Num(5.0) is not an object" }) + +---> + +"string".prop = "test"; + +---err + +Interpret(PropertyNotFound { span: Span { start: 34, end: 46, line: 4, line_end: 4 }, property: "address" }) +Interpret(Other { message: "Only objects have properties. Null is not an object" }) +Interpret(Other { message: "Only objects have properties. Num(5.0) is not an object" }) +Interpret(Other { message: "Only objects have properties. Str(\"string\") is not an object" }) + +---> + +undefined.prop = "test"; + +---err + +Interpret(PropertyNotFound { span: Span { start: 34, end: 46, line: 4, line_end: 4 }, property: "address" }) +Interpret(Other { message: "Only objects have properties. Null is not an object" }) +Interpret(Other { message: "Only objects have properties. Num(5.0) is not an object" }) +Interpret(Other { message: "Only objects have properties. Str(\"string\") is not an object" }) +Interpret(Other { message: "Only objects have properties. Undefined is not an object" }) + + +#[name=between_evaluation, run=interpreter]> + +var $x = 5; +test_utils::out($x between 1 and 10); +test_utils::out($x between 5 and 10); +test_utils::out($x between 1 and 5); +test_utils::out($x between 6 and 10); +test_utils::out($x between 0 and 4); + +test_utils::out($x not between 1 and 10); +test_utils::out($x not between 5 and 10); +test_utils::out($x not between 1 and 5); +test_utils::out($x not between 6 and 10); +test_utils::out($x not between 0 and 4); + +--- + +true +true +true +false +false +false +false +false +true +true + +#[name=between_errors, run=interpreter]> + +test_utils::out(5 between "1" and 10); + +---err + +Interpret(Other { message: "Range can only be created with numbers. Str(\"1\") Num(10.0) Num(5.0)" }) + +---> + +test_utils::out(5 between 1 and "10"); + +---err + +Interpret(Other { message: "Range can only be created with numbers. Str(\"1\") Num(10.0) Num(5.0)" }) +Interpret(Other { message: "Range can only be created with numbers. Num(1.0) Str(\"10\") Num(5.0)" }) + +---> + +test_utils::out("5" between 1 and 10); + +---err + +Interpret(Other { message: "Range can only be created with numbers. Str(\"1\") Num(10.0) Num(5.0)" }) +Interpret(Other { message: "Range can only be created with numbers. Num(1.0) Str(\"10\") Num(5.0)" }) +Interpret(Other { message: "Range can only be created with numbers. Num(1.0) Num(10.0) Str(\"5\")" }) \ No newline at end of file