diff --git a/src/lib/syntax/parser.rs b/src/lib/syntax/parser.rs index 8c537d5045..e360332b3f 100644 --- a/src/lib/syntax/parser.rs +++ b/src/lib/syntax/parser.rs @@ -757,3 +757,430 @@ impl Parser { self.expect(TokenData::Punctuator(p), routine) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::syntax::{ + ast::expr::{Expr, ExprDef}, + lexer::Lexer, + }; + + fn check_parser(js: &str, expr: &[Expr]) { + let mut lexer = Lexer::new(js); + lexer.lex().unwrap(); + + assert_eq!( + Parser::new(lexer.tokens).parse_all().unwrap(), + Expr::new(ExprDef::BlockExpr(expr.into())) + ); + } + + fn check_invalid(js: &str) { + let mut lexer = Lexer::new(js); + lexer.lex().unwrap(); + + assert!(Parser::new(lexer.tokens).parse_all().is_err()); + } + + #[test] + fn check_string() { + use crate::syntax::ast::constant::Const; + + // Check empty string + check_parser( + "\"\"", + &[Expr::new(ExprDef::ConstExpr(Const::String(String::new())))], + ); + + // Check non-empty string + check_parser( + "\"hello\"", + &[Expr::new(ExprDef::ConstExpr(Const::String(String::from( + "hello", + ))))], + ); + } + + #[test] + fn check_array() { + use crate::syntax::ast::constant::Const; + + // Check empty array + check_parser("[]", &[Expr::new(ExprDef::ArrayDeclExpr(vec![]))]); + + // Check array with empty slot + // FIXME: This does not work, it should ignore the comma: + // + // check_parser("[,]", &[Expr::new(ExprDef::ArrayDeclExpr(vec![]))]); + + // Check numeric array + check_parser( + "[1, 2, 3]", + &[Expr::new(ExprDef::ArrayDeclExpr(vec![ + Expr::new(ExprDef::ConstExpr(Const::Num(1.0))), + Expr::new(ExprDef::ConstExpr(Const::Num(2.0))), + Expr::new(ExprDef::ConstExpr(Const::Num(3.0))), + ]))], + ); + + // Check numeric array with trailing comma + // FIXME: This does not work, it should ignore the trailing comma: + // + // check_parser("[1, 2, 3,]", &[Expr::new(ExprDef::ArrayDeclExpr(vec![ + // Expr::new(ExprDef::ConstExpr(Const::Num(1.0))), + // Expr::new(ExprDef::ConstExpr(Const::Num(2.0))), + // Expr::new(ExprDef::ConstExpr(Const::Num(3.0))), + // ]))]); + + // Check combined array + check_parser( + "[1, \"a\", 2]", + &[Expr::new(ExprDef::ArrayDeclExpr(vec![ + Expr::new(ExprDef::ConstExpr(Const::Num(1.0))), + Expr::new(ExprDef::ConstExpr(Const::String(String::from("a")))), + Expr::new(ExprDef::ConstExpr(Const::Num(2.0))), + ]))], + ); + + // Check combined array with empty string + check_parser( + "[1, \"\", 2]", + &[Expr::new(ExprDef::ArrayDeclExpr(vec![ + Expr::new(ExprDef::ConstExpr(Const::Num(1.0))), + Expr::new(ExprDef::ConstExpr(Const::String(String::new()))), + Expr::new(ExprDef::ConstExpr(Const::Num(2.0))), + ]))], + ); + } + + #[test] + fn check_declarations() { + use crate::syntax::ast::constant::Const; + + // Check `var` declaration + check_parser( + "var a = 5;", + &[Expr::new(ExprDef::VarDeclExpr(vec![( + String::from("a"), + Some(Expr::new(ExprDef::ConstExpr(Const::Num(5.0)))), + )]))], + ); + + // Check `var` declaration with no spaces + check_parser( + "var a=5;", + &[Expr::new(ExprDef::VarDeclExpr(vec![( + String::from("a"), + Some(Expr::new(ExprDef::ConstExpr(Const::Num(5.0)))), + )]))], + ); + + // Check empty `var` declaration + check_parser( + "var a;", + &[Expr::new(ExprDef::VarDeclExpr(vec![( + String::from("a"), + None, + )]))], + ); + + // Check multiple `var` declaration + check_parser( + "var a = 5, b, c = 6;", + &[Expr::new(ExprDef::VarDeclExpr(vec![ + ( + String::from("a"), + Some(Expr::new(ExprDef::ConstExpr(Const::Num(5.0)))), + ), + (String::from("b"), None), + ( + String::from("c"), + Some(Expr::new(ExprDef::ConstExpr(Const::Num(6.0)))), + ), + ]))], + ); + + // Check `let` declaration + check_parser( + "let a = 5;", + &[Expr::new(ExprDef::LetDeclExpr(vec![( + String::from("a"), + Some(Expr::new(ExprDef::ConstExpr(Const::Num(5.0)))), + )]))], + ); + + // Check `let` declaration with no spaces + check_parser( + "let a=5;", + &[Expr::new(ExprDef::LetDeclExpr(vec![( + String::from("a"), + Some(Expr::new(ExprDef::ConstExpr(Const::Num(5.0)))), + )]))], + ); + + // Check empty `let` declaration + check_parser( + "let a;", + &[Expr::new(ExprDef::LetDeclExpr(vec![( + String::from("a"), + None, + )]))], + ); + + // Check multiple `let` declaration + check_parser( + "let a = 5, b, c = 6;", + &[Expr::new(ExprDef::LetDeclExpr(vec![ + ( + String::from("a"), + Some(Expr::new(ExprDef::ConstExpr(Const::Num(5.0)))), + ), + (String::from("b"), None), + ( + String::from("c"), + Some(Expr::new(ExprDef::ConstExpr(Const::Num(6.0)))), + ), + ]))], + ); + + // Check `const` declaration + check_parser( + "const a = 5;", + &[Expr::new(ExprDef::ConstDeclExpr(vec![( + String::from("a"), + Some(Expr::new(ExprDef::ConstExpr(Const::Num(5.0)))), + )]))], + ); + + // Check `const` declaration with no spaces + check_parser( + "const a=5;", + &[Expr::new(ExprDef::ConstDeclExpr(vec![( + String::from("a"), + Some(Expr::new(ExprDef::ConstExpr(Const::Num(5.0)))), + )]))], + ); + + // Check empty `const` declaration + // FIXME: This does not work, it should fail to parse an unitialized const declaration: + // + // check_invalid("const a;"); + + // Check multiple `const` declaration + check_parser( + "const a = 5, b, c = 6;", + &[Expr::new(ExprDef::ConstDeclExpr(vec![ + ( + String::from("a"), + Some(Expr::new(ExprDef::ConstExpr(Const::Num(5.0)))), + ), + (String::from("b"), None), + ( + String::from("c"), + Some(Expr::new(ExprDef::ConstExpr(Const::Num(6.0)))), + ), + ]))], + ); + } + + #[test] + fn check_operations() { + use crate::syntax::ast::{constant::Const, op::BinOp}; + + fn create_bin_op(op: BinOp, exp1: Expr, exp2: Expr) -> Expr { + Expr::new(ExprDef::BinOpExpr(op, Box::new(exp1), Box::new(exp2))) + } + + // Check numeric operations + check_parser( + "a + b", + &[create_bin_op( + BinOp::Num(NumOp::Add), + Expr::new(ExprDef::LocalExpr(String::from("a"))), + Expr::new(ExprDef::LocalExpr(String::from("b"))), + )], + ); + check_parser( + "a+1", + &[create_bin_op( + BinOp::Num(NumOp::Add), + Expr::new(ExprDef::LocalExpr(String::from("a"))), + Expr::new(ExprDef::ConstExpr(Const::Num(1.0))), + )], + ); + check_parser( + "a - b", + &[create_bin_op( + BinOp::Num(NumOp::Sub), + Expr::new(ExprDef::LocalExpr(String::from("a"))), + Expr::new(ExprDef::LocalExpr(String::from("b"))), + )], + ); + check_parser( + "a-1", + &[create_bin_op( + BinOp::Num(NumOp::Sub), + Expr::new(ExprDef::LocalExpr(String::from("a"))), + Expr::new(ExprDef::ConstExpr(Const::Num(1.0))), + )], + ); + check_parser( + "a / b", + &[create_bin_op( + BinOp::Num(NumOp::Div), + Expr::new(ExprDef::LocalExpr(String::from("a"))), + Expr::new(ExprDef::LocalExpr(String::from("b"))), + )], + ); + check_parser( + "a/2", + &[create_bin_op( + BinOp::Num(NumOp::Div), + Expr::new(ExprDef::LocalExpr(String::from("a"))), + Expr::new(ExprDef::ConstExpr(Const::Num(2.0))), + )], + ); + check_parser( + "a * b", + &[create_bin_op( + BinOp::Num(NumOp::Mul), + Expr::new(ExprDef::LocalExpr(String::from("a"))), + Expr::new(ExprDef::LocalExpr(String::from("b"))), + )], + ); + check_parser( + "a*2", + &[create_bin_op( + BinOp::Num(NumOp::Mul), + Expr::new(ExprDef::LocalExpr(String::from("a"))), + Expr::new(ExprDef::ConstExpr(Const::Num(2.0))), + )], + ); + check_parser( + "a % b", + &[create_bin_op( + BinOp::Num(NumOp::Mod), + Expr::new(ExprDef::LocalExpr(String::from("a"))), + Expr::new(ExprDef::LocalExpr(String::from("b"))), + )], + ); + check_parser( + "a%2", + &[create_bin_op( + BinOp::Num(NumOp::Mod), + Expr::new(ExprDef::LocalExpr(String::from("a"))), + Expr::new(ExprDef::ConstExpr(Const::Num(2.0))), + )], + ); + + // Check complex numeric operations + check_parser( + "a + d*(b-3)+1", + &[create_bin_op( + BinOp::Num(NumOp::Add), + Expr::new(ExprDef::LocalExpr(String::from("a"))), + create_bin_op( + BinOp::Num(NumOp::Add), + // FIXME: shouldn't the last addition be on the right? + Expr::new(ExprDef::ConstExpr(Const::Num(1.0))), + create_bin_op( + BinOp::Num(NumOp::Mul), + Expr::new(ExprDef::LocalExpr(String::from("d"))), + create_bin_op( + BinOp::Num(NumOp::Sub), + Expr::new(ExprDef::LocalExpr(String::from("b"))), + Expr::new(ExprDef::ConstExpr(Const::Num(3.0))), + ), + ), + ), + )], + ); + + // Check bitwise operations + check_parser( + "a & b", + &[create_bin_op( + BinOp::Bit(BitOp::And), + Expr::new(ExprDef::LocalExpr(String::from("a"))), + Expr::new(ExprDef::LocalExpr(String::from("b"))), + )], + ); + check_parser( + "a&b", + &[create_bin_op( + BinOp::Bit(BitOp::And), + Expr::new(ExprDef::LocalExpr(String::from("a"))), + Expr::new(ExprDef::LocalExpr(String::from("b"))), + )], + ); + + check_parser( + "a | b", + &[create_bin_op( + BinOp::Bit(BitOp::Or), + Expr::new(ExprDef::LocalExpr(String::from("a"))), + Expr::new(ExprDef::LocalExpr(String::from("b"))), + )], + ); + check_parser( + "a|b", + &[create_bin_op( + BinOp::Bit(BitOp::Or), + Expr::new(ExprDef::LocalExpr(String::from("a"))), + Expr::new(ExprDef::LocalExpr(String::from("b"))), + )], + ); + + check_parser( + "a ^ b", + &[create_bin_op( + BinOp::Bit(BitOp::Xor), + Expr::new(ExprDef::LocalExpr(String::from("a"))), + Expr::new(ExprDef::LocalExpr(String::from("b"))), + )], + ); + check_parser( + "a^b", + &[create_bin_op( + BinOp::Bit(BitOp::Xor), + Expr::new(ExprDef::LocalExpr(String::from("a"))), + Expr::new(ExprDef::LocalExpr(String::from("b"))), + )], + ); + + check_parser( + "a << b", + &[create_bin_op( + BinOp::Bit(BitOp::Shl), + Expr::new(ExprDef::LocalExpr(String::from("a"))), + Expr::new(ExprDef::LocalExpr(String::from("b"))), + )], + ); + check_parser( + "a<> b", + &[create_bin_op( + BinOp::Bit(BitOp::Shr), + Expr::new(ExprDef::LocalExpr(String::from("a"))), + Expr::new(ExprDef::LocalExpr(String::from("b"))), + )], + ); + check_parser( + "a>>b", + &[create_bin_op( + BinOp::Bit(BitOp::Shr), + Expr::new(ExprDef::LocalExpr(String::from("a"))), + Expr::new(ExprDef::LocalExpr(String::from("b"))), + )], + ); + } +} diff --git a/tests/js/test.js b/tests/js/test.js index 3fbc177066..aeb6aea8de 100644 --- a/tests/js/test.js +++ b/tests/js/test.js @@ -1,2 +1,7 @@ -let a = "hello world"; -a; +let a = 5; +let b = 6; +let c = 8; +let d = 5; + +let res = a + d * (b - 3) + 1; +res;