use super::*; use crate::lexer::Lexer; /// Helper: lex + parse, return (stmts, errors). fn parse(input: &str) -> (Vec, Vec) { let (tokens, _) = Lexer::new(input).tokenize(); Parser::new(tokens).parse() } /// Helper: lex + parse single expression (wraps in `let _ = ;`). fn parse_expr(input: &str) -> Expr { let (stmts, errors) = parse(&format!("let __x = {};", input)); assert!(errors.is_empty(), "Parse errors: {:?}", errors); match &stmts[0] { Stmt::Let { initializer, .. } => initializer.clone(), _ => panic!("Expected Let statement"), } } /// Helper: lex + parse single statement. fn parse_stmt(input: &str) -> Stmt { let (stmts, errors) = parse(input); assert!(errors.is_empty(), "Parse errors: {:?}", errors); assert_eq!(stmts.len(), 1); stmts[0].clone() } // ----- literals ----- #[test] fn parse_number() { match parse_expr("42") { Expr::Literal(Literal::Number(42.0)) => {} e => panic!("Expected Number(42.0), got {:?}", e), } } #[test] fn parse_float() { match parse_expr("3.14") { Expr::Literal(Literal::Number(n)) => assert!((n - 3.14).abs() < 0.001), e => panic!("Expected Number, got {:?}", e), } } #[test] fn parse_string() { match parse_expr("\"hello\"") { Expr::Literal(Literal::String(s)) => assert_eq!(s, "hello"), e => panic!("Expected String, got {:?}", e), } } #[test] fn parse_bool_true() { match parse_expr("true") { Expr::Literal(Literal::Bool(true)) => {} e => panic!("Expected Bool(true), got {:?}", e), } } #[test] fn parse_bool_false() { match parse_expr("false") { Expr::Literal(Literal::Bool(false)) => {} e => panic!("Expected Bool(false), got {:?}", e), } } #[test] fn parse_nil() { match parse_expr("nil") { Expr::Literal(Literal::Nil) => {} e => panic!("Expected Nil, got {:?}", e), } } // ----- variables ----- #[test] fn parse_variable() { match parse_expr("x") { Expr::Variable(name) => assert_eq!(name, "x"), e => panic!("Expected Variable, got {:?}", e), } } // ----- unary ----- #[test] fn parse_negation() { match parse_expr("-5") { Expr::Unary { op: UnaryOp::Negate, right } => { match *right { Expr::Literal(Literal::Number(5.0)) => {} e => panic!("Expected Number(5), got {:?}", e), } } e => panic!("Expected Unary(Negate, Number), got {:?}", e), } } #[test] fn parse_not() { match parse_expr("!true") { Expr::Unary { op: UnaryOp::Not, right } => { match *right { Expr::Literal(Literal::Bool(true)) => {} e => panic!("Expected Bool(true), got {:?}", e), } } e => panic!("Expected Unary(Not, Bool), got {:?}", e), } } #[test] fn parse_double_negation() { match parse_expr("!!true") { Expr::Unary { op: UnaryOp::Not, right } => { match *right { Expr::Unary { op: UnaryOp::Not, .. } => {} e => panic!("Expected nested Unary, got {:?}", e), } } e => panic!("Expected Unary(Not, ...), got {:?}", e), } } // ----- binary precedence ----- #[test] fn parse_addition() { match parse_expr("1 + 2") { Expr::Binary { op: BinaryOp::Add, left, right } => { match (*left, *right) { (Expr::Literal(Literal::Number(1.0)), Expr::Literal(Literal::Number(2.0))) => {} e => panic!("Expected (1, 2), got {:?}", e), } } e => panic!("Expected Binary(Add), got {:?}", e), } } #[test] fn parse_multiplication_precedence() { // 1 + 2 * 3 => 1 + (2 * 3) match parse_expr("1 + 2 * 3") { Expr::Binary { op: BinaryOp::Add, left, right } => { match *left { Expr::Literal(Literal::Number(1.0)) => {} e => panic!("Expected left=1, got {:?}", e), } match *right { Expr::Binary { op: BinaryOp::Mul, .. } => {} e => panic!("Expected right=Binary(Mul), got {:?}", e), } } e => panic!("Expected Binary(Add), got {:?}", e), } } #[test] fn parse_modulo() { match parse_expr("7 % 3") { Expr::Binary { op: BinaryOp::Mod, .. } => {} e => panic!("Expected Binary(Mod), got {:?}", e), } } // ----- comparison ----- #[test] fn parse_comparison_chain() { // a < b == c match parse_expr("a < b == c") { Expr::Binary { op: BinaryOp::Equal, left, right } => { match *left { Expr::Binary { op: BinaryOp::Less, .. } => {} e => panic!("Expected left=Binary(Less), got {:?}", e), } match *right { Expr::Variable(name) => assert_eq!(name, "c"), e => panic!("Expected right=Variable(c), got {:?}", e), } } e => panic!("Expected Binary(Equal), got {:?}", e), } } // ----- logical ----- #[test] fn parse_logical_and() { match parse_expr("true && false") { Expr::Logical { op: LogicalOp::And, .. } => {} e => panic!("Expected Logical(And), got {:?}", e), } } #[test] fn parse_logical_or() { match parse_expr("true || false") { Expr::Logical { op: LogicalOp::Or, .. } => {} e => panic!("Expected Logical(Or), got {:?}", e), } } #[test] fn parse_logical_precedence() { // a || b && c => a || (b && c) match parse_expr("a || b && c") { Expr::Logical { op: LogicalOp::Or, left, right } => { match *left { Expr::Variable(name) => assert_eq!(name, "a"), e => panic!("Expected left=a, got {:?}", e), } match *right { Expr::Logical { op: LogicalOp::And, .. } => {} e => panic!("Expected right=Logical(And), got {:?}", e), } } e => panic!("Expected Logical(Or), got {:?}", e), } } // ----- ternary ----- #[test] fn parse_ternary_simple() { match parse_expr("a ? 1 : 2") { Expr::Ternary { condition, then_branch, else_branch } => { assert!(matches!(*condition, Expr::Variable(_))); assert!(matches!(*then_branch, Expr::Literal(Literal::Number(1.0)))); assert!(matches!(*else_branch, Expr::Literal(Literal::Number(2.0)))); } e => panic!("Expected Ternary, got {:?}", e), } } #[test] fn parse_ternary_nested_right_assoc() { // a ? b : c ? d : e → a ? b : (c ? d : e) match parse_expr("a ? b : c ? d : e") { Expr::Ternary { condition, then_branch, else_branch } => { assert!(matches!(*condition, Expr::Variable(_))); assert!(matches!(*then_branch, Expr::Variable(_))); assert!(matches!(*else_branch, Expr::Ternary { .. })); } e => panic!("Expected Ternary with nested Ternary else, got {:?}", e), } } #[test] fn parse_ternary_with_assignment_in_else() { // a ? b : c = d → a ? b : (c = d) match parse_expr("a ? b : c = 5") { Expr::Ternary { else_branch, .. } => { assert!(matches!(*else_branch, Expr::Assign { .. })); } e => panic!("Expected Ternary with Assign else, got {:?}", e), } } // ----- assignment ----- #[test] fn parse_simple_assignment() { match parse_expr("x = 5") { Expr::Assign { name, op: AssignOp::Equal, value } => { assert_eq!(name, "x"); match *value { Expr::Literal(Literal::Number(5.0)) => {} e => panic!("Expected Number(5), got {:?}", e), } } e => panic!("Expected Assign, got {:?}", e), } } #[test] fn parse_compound_assignment() { match parse_expr("x += 2") { Expr::Assign { name, op: AssignOp::PlusEqual, value } => { assert_eq!(name, "x"); match *value { Expr::Literal(Literal::Number(2.0)) => {} e => panic!("Expected Number(2), got {:?}", e), } } e => panic!("Expected Assign(PlusEqual), got {:?}", e), } } #[test] fn parse_all_compound_ops() { let ops = [ ("+=", AssignOp::PlusEqual), ("-=", AssignOp::MinusEqual), ("*=", AssignOp::StarEqual), ("/=", AssignOp::SlashEqual), ("%=", AssignOp::PercentEqual), ]; for (op_str, expected_op) in &ops { let expr = parse_expr(&format!("x {} 1", op_str)); match expr { Expr::Assign { op, .. } => { assert_eq!(std::mem::discriminant(&op), std::mem::discriminant(expected_op)); } e => panic!("Expected Assign for '{}', got {:?}", op_str, e), } } } // ----- property access ----- #[test] fn parse_property_get() { match parse_expr("obj.name") { Expr::Get { object, name } => { match *object { Expr::Variable(n) => assert_eq!(n, "obj"), e => panic!("Expected Variable(obj), got {:?}", e), } assert_eq!(name, "name"); } e => panic!("Expected Get, got {:?}", e), } } #[test] fn parse_property_set() { match parse_expr("obj.name = 5") { Expr::Set { object, name, op: AssignOp::Equal, value } => { match *object { Expr::Variable(n) => assert_eq!(n, "obj"), e => panic!("Expected Variable(obj), got {:?}", e), } assert_eq!(name, "name"); match *value { Expr::Literal(Literal::Number(5.0)) => {} e => panic!("Expected Number(5), got {:?}", e), } } e => panic!("Expected Set, got {:?}", e), } } #[test] fn parse_chained_property_access() { match parse_expr("a.b.c") { Expr::Get { object, name } => { assert_eq!(name, "c"); match *object { Expr::Get { name: ref inner, .. } => assert_eq!(inner, "b"), e => panic!("Expected chained Get, got {:?}", e), } } e => panic!("Expected Get, got {:?}", e), } } // ----- index access ----- #[test] fn parse_index_get() { match parse_expr("arr[0]") { Expr::IndexGet { array, index } => { match *array { Expr::Variable(n) => assert_eq!(n, "arr"), e => panic!("Expected Variable(arr), got {:?}", e), } match *index { Expr::Literal(Literal::Number(0.0)) => {} e => panic!("Expected Number(0), got {:?}", e), } } e => panic!("Expected IndexGet, got {:?}", e), } } #[test] fn parse_index_set() { match parse_expr("arr[i] = 42") { Expr::IndexSet { array, index, op: AssignOp::Equal, value } => { match *array { Expr::Variable(n) => assert_eq!(n, "arr"), e => panic!("Expected Variable(arr), got {:?}", e), } match *index { Expr::Variable(n) => assert_eq!(n, "i"), e => panic!("Expected Variable(i), got {:?}", e), } match *value { Expr::Literal(Literal::Number(42.0)) => {} e => panic!("Expected Number(42), got {:?}", e), } } e => panic!("Expected IndexSet, got {:?}", e), } } #[test] fn parse_nested_index() { match parse_expr("a[1][2]") { Expr::IndexGet { array, .. } => { match *array { Expr::IndexGet { .. } => {} e => panic!("Expected nested IndexGet, got {:?}", e), } } e => panic!("Expected IndexGet, got {:?}", e), } } // ----- function calls ----- #[test] fn parse_call_no_args() { match parse_expr("f()") { Expr::Call { callee, arguments } => { match *callee { Expr::Variable(n) => assert_eq!(n, "f"), e => panic!("Expected Variable(f), got {:?}", e), } assert!(arguments.is_empty()); } e => panic!("Expected Call, got {:?}", e), } } #[test] fn parse_call_with_args() { match parse_expr("add(1, 2, 3)") { Expr::Call { callee, arguments } => { match *callee { Expr::Variable(n) => assert_eq!(n, "add"), e => panic!("Expected Variable(add), got {:?}", e), } assert_eq!(arguments.len(), 3); } e => panic!("Expected Call, got {:?}", e), } } #[test] fn parse_chained_calls() { match parse_expr("f()()") { Expr::Call { callee, .. } => { match *callee { Expr::Call { callee, .. } => { match *callee { Expr::Variable(n) => assert_eq!(n, "f"), e => panic!("Expected Variable(f), got {:?}", e), } } e => panic!("Expected inner Call, got {:?}", e), } } e => panic!("Expected Call, got {:?}", e), } } #[test] fn parse_method_style_call() { // obj.method() match parse_expr("obj.method()") { Expr::Call { callee, .. } => { match *callee { Expr::Get { .. } => {} e => panic!("Expected Get, got {:?}", e), } } e => panic!("Expected Call, got {:?}", e), } } // ----- lambda ----- #[test] fn parse_simple_lambda() { match parse_expr("fn(x, y) { return x + y; }") { Expr::Lambda { params, body } => { assert_eq!(params, vec!["x", "y"]); assert_eq!(body.len(), 1); } e => panic!("Expected Lambda, got {:?}", e), } } #[test] fn parse_lambda_no_params() { match parse_expr("fn() { return 42; }") { Expr::Lambda { params, body } => { assert!(params.is_empty()); assert_eq!(body.len(), 1); } e => panic!("Expected Lambda, got {:?}", e), } } // ----- object literal ----- #[test] fn parse_empty_object() { match parse_expr("{}") { Expr::ObjectLiteral { properties } => assert!(properties.is_empty()), e => panic!("Expected ObjectLiteral, got {:?}", e), } } #[test] fn parse_object_with_properties() { match parse_expr("{ name: \"aster\", version: 1 }") { Expr::ObjectLiteral { properties } => { assert_eq!(properties.len(), 2); assert_eq!(properties[0].0, "name"); assert_eq!(properties[1].0, "version"); } e => panic!("Expected ObjectLiteral, got {:?}", e), } } // ----- array literal ----- #[test] fn parse_empty_array() { match parse_expr("[]") { Expr::ArrayLiteral { elements } => assert!(elements.is_empty()), e => panic!("Expected ArrayLiteral, got {:?}", e), } } #[test] fn parse_array_with_elements() { match parse_expr("[1, 2, 3]") { Expr::ArrayLiteral { elements } => assert_eq!(elements.len(), 3), e => panic!("Expected ArrayLiteral, got {:?}", e), } } #[test] fn parse_nested_arrays() { match parse_expr("[[1, 2], [3, 4]]") { Expr::ArrayLiteral { elements } => { assert_eq!(elements.len(), 2); for e in &elements { assert!(matches!(e, Expr::ArrayLiteral { .. })); } } e => panic!("Expected ArrayLiteral, got {:?}", e), } } #[test] fn parse_array_trailing_comma() { match parse_expr("[1, 2, 3,]") { Expr::ArrayLiteral { elements } => assert_eq!(elements.len(), 3), e => panic!("Expected ArrayLiteral, got {:?}", e), } } #[test] fn parse_object_trailing_comma() { match parse_expr("{a: 1, b: 2,}") { Expr::ObjectLiteral { properties } => assert_eq!(properties.len(), 2), e => panic!("Expected ObjectLiteral, got {:?}", e), } } // ----- statements ----- #[test] fn parse_let_statement() { match parse_stmt("let x = 5;") { Stmt::Let { name, initializer, .. } => { assert_eq!(name, "x"); assert!(matches!(initializer, Expr::Literal(Literal::Number(5.0)))); } e => panic!("Expected Let, got {:?}", e), } } #[test] fn parse_let_without_semicolon() { // Semicolons are optional match parse_stmt("let x = 5") { Stmt::Let { name, .. } => assert_eq!(name, "x"), e => panic!("Expected Let, got {:?}", e), } } #[test] fn parse_const_statement() { match parse_stmt("const x = 5;") { Stmt::Let { name, mutable, .. } => { assert_eq!(name, "x"); assert!(!mutable, "const should set mutable=false"); } e => panic!("Expected Let (const), got {:?}", e), } } #[test] fn parse_expression_statement() { match parse_stmt("x;") { Stmt::ExprStmt(Expr::Variable(name)) => assert_eq!(name, "x"), e => panic!("Expected ExprStmt(Variable), got {:?}", e), } } #[test] fn parse_if_statement() { match parse_stmt("if (x) { 1; }") { Stmt::If { condition, then_branch, else_branch } => { assert!(matches!(condition, Expr::Variable(_))); assert!(matches!(*then_branch, Stmt::Block(_))); assert!(else_branch.is_none()); } e => panic!("Expected If, got {:?}", e), } } #[test] fn parse_if_else_statement() { match parse_stmt("if (x) { 1; } else { 2; }") { Stmt::If { else_branch, .. } => { assert!(else_branch.is_some()); } e => panic!("Expected If with else, got {:?}", e), } } #[test] fn parse_while_statement() { match parse_stmt("while (x < 10) { x = x + 1; }") { Stmt::While { condition, body } => { assert!(matches!(condition, Expr::Binary { .. })); assert!(matches!(*body, Stmt::Block(_))); } e => panic!("Expected While, got {:?}", e), } } #[test] fn parse_for_statement() { match parse_stmt("for (let i = 0; i < 10; i = i + 1) { }") { Stmt::For { initializer, condition, step, body } => { assert!(initializer.is_some()); assert!(condition.is_some()); assert!(step.is_some()); assert!(matches!(*body, Stmt::Block(_))); } e => panic!("Expected For, got {:?}", e), } } #[test] fn parse_for_empty_clauses() { match parse_stmt("for (;;) { }") { Stmt::For { initializer, condition, step, .. } => { assert!(initializer.is_none()); assert!(condition.is_none()); assert!(step.is_none()); } e => panic!("Expected For, got {:?}", e), } } #[test] fn parse_for_in_array() { match parse_stmt("for (x in arr) { print(x); }") { Stmt::ForIn { var_name, iterable, body } => { assert_eq!(var_name, "x"); assert!(matches!(iterable, Expr::Variable(_))); assert!(matches!(*body, Stmt::Block(_))); } e => panic!("Expected ForIn, got {:?}", e), } } #[test] fn parse_for_in_with_object_literal() { match parse_stmt("for (k in {a:1,b:2}) { print(k); }") { Stmt::ForIn { var_name, iterable, .. } => { assert_eq!(var_name, "k"); assert!(matches!(iterable, Expr::ObjectLiteral { .. })); } e => panic!("Expected ForIn, got {:?}", e), } } #[test] fn parse_for_in_with_expression() { match parse_stmt("for (v in get_list()) { }") { Stmt::ForIn { var_name, .. } => { assert_eq!(var_name, "v"); } e => panic!("Expected ForIn, got {:?}", e), } } #[test] fn parse_for_in_distinct_from_c_style() { // "for (x = 0; ...)" is C-style for, not for-in match parse_stmt("for (x = 0; x < 10; x = x + 1) { }") { Stmt::For { .. } => {} e => panic!("Expected C-style For, got {:?}", e), } } #[test] fn parse_block() { match parse_stmt("{ let x = 1; x; }") { Stmt::Block(stmts) => assert_eq!(stmts.len(), 2), e => panic!("Expected Block, got {:?}", e), } } #[test] fn parse_return_statement() { match parse_stmt("return 42;") { Stmt::Return(Some(expr)) => { assert!(matches!(expr, Expr::Literal(Literal::Number(42.0)))); } e => panic!("Expected Return(42), got {:?}", e), } } #[test] fn parse_return_nil() { match parse_stmt("return;") { Stmt::Return(None) => {} e => panic!("Expected Return(None), got {:?}", e), } } #[test] fn parse_break_statement() { match parse_stmt("break;") { Stmt::Break => {} e => panic!("Expected Break, got {:?}", e), } } #[test] fn parse_continue_statement() { match parse_stmt("continue;") { Stmt::Continue => {} e => panic!("Expected Continue, got {:?}", e), } } #[test] fn parse_function_declaration() { match parse_stmt("fn add(a, b) { return a + b; }") { Stmt::Function { name, params, body } => { assert_eq!(name, "add"); assert_eq!(params, vec!["a", "b"]); assert_eq!(body.len(), 1); } e => panic!("Expected Function, got {:?}", e), } } #[test] fn parse_function_no_params() { match parse_stmt("fn greet() { print(\"hi\"); }") { Stmt::Function { name, params, .. } => { assert_eq!(name, "greet"); assert!(params.is_empty()); } e => panic!("Expected Function, got {:?}", e), } } // ----- error recovery ----- #[test] fn error_recovery_skips_past_semicolon() { // First statement is invalid, second should parse fine let (stmts, errors) = parse("let = 5; let y = 10;"); assert!(!errors.is_empty(), "Should have parse errors"); // Should have at least the second statement assert!(stmts.len() >= 1); // The second statement should be a valid Let for y match &stmts.last().unwrap() { Stmt::Let { name, .. } => assert_eq!(name, "y"), e => panic!("Expected Let(y), got {:?}", e), } } #[test] fn error_recovery_at_statement_boundary() { let (stmts, errors) = parse("let x = @@@; let y = 10;"); assert!(!errors.is_empty()); assert!(stmts.len() >= 1); } // ----- parse errors ----- #[test] fn error_invalid_assignment_target() { let (_stmts, errors) = parse("5 = 10;"); assert!(!errors.is_empty()); } #[test] fn error_missing_closing_paren() { let (_stmts, errors) = parse("if (x { 1; }"); assert!(!errors.is_empty()); } #[test] fn error_missing_semicolon_after_for_condition() { // for(;;){} is valid — test something actually invalid let (_stmts, _errors) = parse("for (let i = 0 i < 10) { }"); assert!(!_errors.is_empty()); } // ----- parenthesized expression ----- #[test] fn parse_parenthesized_expression() { match parse_expr("(1 + 2)") { Expr::Binary { op: BinaryOp::Add, .. } => {} e => panic!("Expected Binary(Add), got {:?}", e), } } #[test] fn parse_nested_parentheses() { match parse_expr("((42))") { Expr::Literal(Literal::Number(42.0)) => {} e => panic!("Expected Number(42), got {:?}", e), } }