diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 0f8a1562..d6a417f6 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -832,6 +832,7 @@ impl Expr { #[cfg(not(feature = "no_index"))] Token::LeftBracket => true, Token::LeftParen => true, + Token::Unit => true, Token::Bang => true, Token::DoubleColon => true, _ => false, diff --git a/src/parser.rs b/src/parser.rs index 6abb4586..a4b6fa60 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -426,10 +426,6 @@ impl Engine { let mut settings = settings; settings.pos = eat_token(input, Token::LeftParen); - if match_token(input, Token::RightParen).0 { - return Ok(Expr::Unit(settings.pos)); - } - let expr = self.parse_expr(input, state, lib, settings.level_up())?; match input.next().expect(NEVER_ENDS) { @@ -453,6 +449,7 @@ impl Engine { state: &mut ParseState, lib: &mut FnLib, id: Identifier, + no_args: bool, capture_parent_scope: bool, #[cfg(not(feature = "no_module"))] namespace: crate::ast::Namespace, settings: ParseSettings, @@ -460,7 +457,11 @@ impl Engine { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let (token, token_pos) = input.peek().expect(NEVER_ENDS); + let (token, token_pos) = if no_args { + &(Token::RightParen, Position::NONE) + } else { + input.peek().expect(NEVER_ENDS) + }; #[cfg(not(feature = "no_module"))] let mut namespace = namespace; @@ -479,7 +480,9 @@ impl Engine { Token::LexError(err) => return Err(err.clone().into_err(*token_pos)), // id() Token::RightParen => { - eat_token(input, Token::RightParen); + if !no_args { + eat_token(input, Token::RightParen); + } #[cfg(not(feature = "no_module"))] let hash = if !namespace.is_empty() { @@ -897,7 +900,7 @@ impl Engine { } let (name, pos) = match input.next().expect(NEVER_ENDS) { - (Token::Identifier(s), pos) | (Token::StringConstant(s), pos) => { + (Token::Identifier(s) | Token::StringConstant(s), pos) => { if map.iter().any(|(p, ..)| **p == s) { return Err(PERR::DuplicatedProperty(s.to_string()).into_err(pos)); } @@ -1201,6 +1204,11 @@ impl Engine { let root_expr = match token { Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)), + Token::Unit => { + input.next(); + Expr::Unit(settings.pos) + } + Token::IntegerConstant(..) | Token::CharConstant(..) | Token::StringConstant(..) @@ -1218,13 +1226,13 @@ impl Engine { #[cfg(not(feature = "no_float"))] Token::FloatConstant(x) => { let x = *x; - input.next().expect(NEVER_ENDS); + input.next(); Expr::FloatConstant(x, settings.pos) } #[cfg(feature = "decimal")] Token::DecimalConstant(x) => { let x = (*x).into(); - input.next().expect(NEVER_ENDS); + input.next(); Expr::DynamicConstant(Box::new(x), settings.pos) } @@ -1235,6 +1243,7 @@ impl Engine { stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt), } } + // ( - grouped expression Token::LeftParen => self.parse_paren_expr(input, state, lib, settings.level_up())?, @@ -1401,7 +1410,7 @@ impl Engine { match input.peek().expect(NEVER_ENDS).0 { // Function call - Token::LeftParen | Token::Bang => { + Token::LeftParen | Token::Bang | Token::Unit => { #[cfg(not(feature = "no_closure"))] { // Once the identifier consumed we must enable next variables capturing @@ -1467,11 +1476,13 @@ impl Engine { match input.peek().expect(NEVER_ENDS).0 { // Function call is allowed to have reserved keyword - Token::LeftParen | Token::Bang if is_keyword_function(&s) => Expr::Variable( - (None, ns, 0, state.get_identifier("", s)).into(), - None, - settings.pos, - ), + Token::LeftParen | Token::Bang | Token::Unit if is_keyword_function(&s) => { + Expr::Variable( + (None, ns, 0, state.get_identifier("", s)).into(), + None, + settings.pos, + ) + } // Access to `this` as a variable is OK within a function scope #[cfg(not(feature = "no_function"))] _ if &*s == KEYWORD_THIS && settings.is_function_scope => Expr::Variable( @@ -1531,30 +1542,33 @@ impl Engine { // Qualified function call with ! #[cfg(not(feature = "no_module"))] (Expr::Variable(x, ..), Token::Bang) if !x.1.is_empty() => { - return if !match_token(input, Token::LeftParen).0 { - Err(LexError::UnexpectedInput(Token::Bang.syntax().to_string()) - .into_err(tail_pos)) - } else { - Err(LexError::ImproperSymbol( + return match input.peek().expect(NEVER_ENDS) { + (Token::LeftParen | Token::Unit, ..) => { + Err(LexError::UnexpectedInput(Token::Bang.syntax().to_string()) + .into_err(tail_pos)) + } + _ => Err(LexError::ImproperSymbol( "!".to_string(), "'!' cannot be used to call module functions".to_string(), ) - .into_err(tail_pos)) + .into_err(tail_pos)), }; } // Function call with ! (Expr::Variable(x, .., pos), Token::Bang) => { - match match_token(input, Token::LeftParen) { - (false, pos) => { + match input.peek().expect(NEVER_ENDS) { + (Token::LeftParen | Token::Unit, ..) => (), + (_, pos) => { return Err(PERR::MissingToken( Token::LeftParen.syntax().into(), "to start arguments list of function call".into(), ) - .into_err(pos)) + .into_err(*pos)) } - _ => (), } + let no_args = input.next().expect(NEVER_ENDS).0 == Token::Unit; + let (.., _ns, _, name) = *x; settings.pos = pos; self.parse_fn_call( @@ -1562,6 +1576,7 @@ impl Engine { state, lib, name, + no_args, true, #[cfg(not(feature = "no_module"))] _ns, @@ -1569,7 +1584,7 @@ impl Engine { )? } // Function call - (Expr::Variable(x, .., pos), Token::LeftParen) => { + (Expr::Variable(x, .., pos), t @ (Token::LeftParen | Token::Unit)) => { let (.., _ns, _, name) = *x; settings.pos = pos; self.parse_fn_call( @@ -1577,6 +1592,7 @@ impl Engine { state, lib, name, + t == Token::Unit, false, #[cfg(not(feature = "no_module"))] _ns, @@ -1992,7 +2008,7 @@ impl Engine { )) } // lhs.dot_lhs.dot_rhs or lhs.dot_lhs[idx_rhs] - (lhs, rhs @ Expr::Dot(..)) | (lhs, rhs @ Expr::Index(..)) => { + (lhs, rhs @ (Expr::Dot(..) | Expr::Index(..))) => { let (x, term, pos, is_dot) = match rhs { Expr::Dot(x, term, pos) => (x, term, pos, true), Expr::Index(x, term, pos) => (x, term, pos, false), @@ -2324,7 +2340,7 @@ impl Engine { } } CUSTOM_SYNTAX_MARKER_BOOL => match input.next().expect(NEVER_ENDS) { - (b @ Token::True, pos) | (b @ Token::False, pos) => { + (b @ (Token::True | Token::False), pos) => { inputs.push(Expr::BoolConstant(b == Token::True, pos)); segments.push(state.get_interned_string("", b.literal_syntax())); tokens.push(state.get_identifier("", CUSTOM_SYNTAX_MARKER_BOOL)); @@ -2999,7 +3015,7 @@ impl Engine { comments.push(comment); match input.peek().expect(NEVER_ENDS) { - (Token::Fn, ..) | (Token::Private, ..) => break, + (Token::Fn | Token::Private, ..) => break, (Token::Comment(..), ..) => (), _ => return Err(PERR::WrongDocComment.into_err(comments_pos)), } @@ -3269,14 +3285,21 @@ impl Engine { Err(_) => return Err(PERR::FnMissingName.into_err(pos)), }; - match input.peek().expect(NEVER_ENDS) { - (Token::LeftParen, ..) => eat_token(input, Token::LeftParen), + let no_params = match input.peek().expect(NEVER_ENDS) { + (Token::LeftParen, ..) => { + eat_token(input, Token::LeftParen); + match_token(input, Token::RightParen).0 + } + (Token::Unit, ..) => { + eat_token(input, Token::Unit); + true + } (.., pos) => return Err(PERR::FnMissingParams(name.to_string()).into_err(*pos)), }; let mut params = StaticVec::new_const(); - if !match_token(input, Token::RightParen).0 { + if !no_params { let sep_err = format!("to separate the parameters of function '{}'", name); loop { diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 98456c47..0a3d7dee 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -382,6 +382,8 @@ pub enum Token { LeftBracket, /// `]` RightBracket, + /// `()` + Unit, /// `+` Plus, /// `+` (unary) @@ -558,6 +560,7 @@ impl Token { RightParen => ")", LeftBracket => "[", RightBracket => "]", + Unit => "()", Plus => "+", UnaryPlus => "+", Minus => "-", @@ -754,6 +757,7 @@ impl Token { ")" => RightParen, "[" => LeftBracket, "]" => RightBracket, + "()" => Unit, "+" => Plus, "-" => Minus, "*" => Multiply, @@ -1702,6 +1706,12 @@ fn get_next_token_inner( ('{', ..) => return Some((Token::LeftBrace, start_pos)), ('}', ..) => return Some((Token::RightBrace, start_pos)), + // Unit + ('(', ')') => { + eat_next(stream, pos); + return Some((Token::Unit, start_pos)); + } + // Parentheses ('(', '*') => { eat_next(stream, pos); diff --git a/src/types/parse_error.rs b/src/types/parse_error.rs index 0a86d8e9..266ddc34 100644 --- a/src/types/parse_error.rs +++ b/src/types/parse_error.rs @@ -51,7 +51,8 @@ impl fmt::Display for LexError { Self::ImproperSymbol(s, d) if d.is_empty() => { write!(f, "Invalid symbol encountered: '{}'", s) } - Self::ImproperSymbol(.., d) => f.write_str(d), + Self::ImproperSymbol(s, d) if s.is_empty() => f.write_str(d), + Self::ImproperSymbol(s, d) => write!(f, "{}: '{}'", d, s), } } } diff --git a/tests/unit.rs b/tests/unit.rs index edfcf61c..bb9d1496 100644 --- a/tests/unit.rs +++ b/tests/unit.rs @@ -17,6 +17,6 @@ fn test_unit_eq() -> Result<(), Box> { #[test] fn test_unit_with_spaces() -> Result<(), Box> { let engine = Engine::new(); - engine.run("let x = ( ); x")?; + engine.run("let x = ( ); x").expect_err("should error"); Ok(()) }