diff --git a/lykiadb-lang/src/ast/expr.rs b/lykiadb-lang/src/ast/expr.rs index f772f824..1b848e18 100644 --- a/lykiadb-lang/src/ast/expr.rs +++ b/lykiadb-lang/src/ast/expr.rs @@ -532,6 +532,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 879cd859..978af0b0 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)