diff --git a/crates/fuse-ast/src/ast.rs b/crates/fuse-ast/src/ast.rs index fc0e315..6ddd406 100644 --- a/crates/fuse-ast/src/ast.rs +++ b/crates/fuse-ast/src/ast.rs @@ -106,6 +106,7 @@ pub enum Expression { ArrayExpression(Box), TupleExpression(Box), ParenthesizedExpression(Box), + CallExpression(Box), } #[serializable] @@ -282,6 +283,7 @@ pub enum BinaryOperatorKind { Modulo(Span), ShiftLeft(Span), ShiftRight(Span), + Member(Span), } #[serializable] @@ -363,3 +365,11 @@ pub struct ParenthesizedExpression { pub span: Span, pub expression: Expression, } + +#[serializable] +#[derive(Debug, PartialEq)] +pub struct CallExpression { + pub span: Span, + pub target: Expression, + pub arguments: Vec, +} diff --git a/crates/fuse-ast/src/ast_factory.rs b/crates/fuse-ast/src/ast_factory.rs index 8affabc..901370b 100644 --- a/crates/fuse-ast/src/ast_factory.rs +++ b/crates/fuse-ast/src/ast_factory.rs @@ -159,4 +159,17 @@ impl AstFactory { pub fn parenthesized_expression(&self, span: Span, expression: Expression) -> Expression { Expression::ParenthesizedExpression(Box::from(ParenthesizedExpression { span, expression })) } + + pub fn call_expression( + &self, + span: Span, + target: Expression, + arguments: Vec, + ) -> Expression { + Expression::CallExpression(Box::from(CallExpression { + span, + target, + arguments, + })) + } } diff --git a/crates/fuse-ast/src/precedence.rs b/crates/fuse-ast/src/precedence.rs index 61bdcd3..64c8798 100644 --- a/crates/fuse-ast/src/precedence.rs +++ b/crates/fuse-ast/src/precedence.rs @@ -7,7 +7,6 @@ pub enum Precedence { /// used as 0 value of enum. Expression, Assignment, - Construction, LogicalOr, LogicalAnd, BitwiseOr, @@ -19,11 +18,12 @@ pub enum Precedence { Add, Multiply, Exponential, + Member, } impl Precedence { pub fn is_right_associative(&self) -> bool { - matches!(self, Self::Assignment | Self::Construction) + matches!(self, Self::Assignment | Self::Member) } pub fn is_left_associative(&self) -> bool { diff --git a/crates/fuse-parser/src/lexer/token_kind.rs b/crates/fuse-parser/src/lexer/token_kind.rs index b22c9dc..01a8e7f 100644 --- a/crates/fuse-parser/src/lexer/token_kind.rs +++ b/crates/fuse-parser/src/lexer/token_kind.rs @@ -211,7 +211,6 @@ impl TokenKind { use TokenKind::*; match self { Eq => Some(Assignment), - LCurly => Some(Construction), Or => Some(LogicalOr), And => Some(LogicalAnd), Pipe => Some(BitwiseOr), @@ -223,6 +222,7 @@ impl TokenKind { Plus | Minus => Some(Add), Star | Slash | Slash2 | Percent => Some(Multiply), Star2 => Some(Exponential), + Dot => Some(Member), _ => None, } } diff --git a/crates/fuse-parser/src/parsers/expressions.rs b/crates/fuse-parser/src/parsers/expressions.rs index 2e283e3..b202e19 100644 --- a/crates/fuse-parser/src/parsers/expressions.rs +++ b/crates/fuse-parser/src/parsers/expressions.rs @@ -7,7 +7,6 @@ use fuse_ast::{ impl<'a> Parser<'a> { pub(crate) fn parse_expression(&mut self) -> ParserResult { let expr = self.parse_primary_expression()?; - let expr = self.parse_expression_with_suffix(expr)?; self.parse_expression_with_precedence(expr, Precedence::Expression) } @@ -21,43 +20,46 @@ impl<'a> Parser<'a> { pub(crate) fn try_parse_primary_expression(&mut self) -> Option> { use TokenKind::*; - match self.cur_kind() { + let expr = match self.cur_kind() { True => { let token = self.consume(); - Some(Ok(self.ast.boolean_expression(BooleanLiteral { + Ok(self.ast.boolean_expression(BooleanLiteral { span: token.span(), value: true, - }))) + })) } False => { let token = self.consume(); - Some(Ok(self.ast.boolean_expression(BooleanLiteral { + Ok(self.ast.boolean_expression(BooleanLiteral { span: token.span(), value: false, - }))) + })) } - NumberLiteral => Some( - self.parse_number_literal() - .map(|expr| self.ast.number_expression(expr)), - ), - StringLiteral | InterpolatedStringHead => Some( - self.parse_string_literal() - .map(|expr| self.ast.string_expression(expr)), - ), - Identifier => Some( - self.parse_identifier() - .map(|id| self.ast.identifier_expression(id)), - ), - - Function | TokenKind::Fn => Some(self.parse_function_expression()), - If => Some(self.parse_if_expression()), - - Not | Plus | Minus => Some(self.parse_unary_operator_expression()), - LBrack => Some(self.parse_array_expression()), - LParen => Some(self.parse_tuple_or_parenthesized_expression()), - - _ => None, - } + NumberLiteral => self + .parse_number_literal() + .map(|expr| self.ast.number_expression(expr)), + StringLiteral | InterpolatedStringHead => self + .parse_string_literal() + .map(|expr| self.ast.string_expression(expr)), + Identifier => self + .parse_identifier() + .map(|id| self.ast.identifier_expression(id)), + + Function | TokenKind::Fn => self.parse_function_expression(), + If => self.parse_if_expression(), + + Not | Plus | Minus => self.parse_unary_operator_expression(), + LBrack => self.parse_array_expression(), + LParen => self.parse_tuple_or_parenthesized_expression(), + + _ => return None, + }; + + let Ok(expr) = expr else { + return Some(expr); + }; + + Some(self.parse_expression_with_suffix(expr)) } pub(crate) fn parse_identifier(&mut self) -> ParserResult { @@ -224,13 +226,41 @@ impl<'a> Parser<'a> { TokenKind::LCurly => { todo!("parse construction") } - TokenKind::LParen => { - todo!("parse call expression") - } + TokenKind::LParen => self.parse_call_expression(expr), _ => Ok(expr), } } + fn parse_call_expression(&mut self, lhs: Expression) -> ParserResult { + let start = self.start_span(); + // consume the open parentheses + self.consume(); + let mut arguments: Vec = Vec::new(); + + // return early for calls with no arguments. + if self.consume_if(TokenKind::RParen).is_some() { + return Ok(self + .ast + .call_expression(self.end_span(start), lhs, arguments)); + } + + loop { + let argument = self.parse_expression()?; + + arguments.push(argument); + + if self.consume_if(TokenKind::Comma).is_none() { + break; + } + } + + self.consume_expect(TokenKind::RParen)?; + + Ok(self + .ast + .call_expression(self.end_span(start), lhs, arguments)) + } + fn parse_expression_with_precedence( &mut self, lhs: Expression, diff --git a/crates/fuse-parser/src/parsers/operators.rs b/crates/fuse-parser/src/parsers/operators.rs index 2001517..cca0562 100644 --- a/crates/fuse-parser/src/parsers/operators.rs +++ b/crates/fuse-parser/src/parsers/operators.rs @@ -81,6 +81,7 @@ impl<'a> Parser<'a> { Percent => Modulo LShift => ShiftLeft RShift => ShiftRight + Dot => Member } } diff --git a/crates/fuse-parser/tests/cases/pass/call-expression-01/ast.snap b/crates/fuse-parser/tests/cases/pass/call-expression-01/ast.snap new file mode 100644 index 0000000..de2c24d --- /dev/null +++ b/crates/fuse-parser/tests/cases/pass/call-expression-01/ast.snap @@ -0,0 +1,30 @@ +--- +source: crates/fuse-parser/tests/cases/mod.rs +description: "test()\n" +expression: parsed.chunk +input_file: crates/fuse-parser/tests/cases/pass/call-expression-01/case.fuse +--- +Some(Chunk( + span: Span( + start: 0, + end: 7, + ), + body: Block( + statements: [ + Expression(CallExpression(CallExpression( + span: Span( + start: 4, + end: 6, + ), + target: Identifier(Identifier( + span: Span( + start: 0, + end: 4, + ), + name: Atom("test"), + )), + arguments: [], + ))), + ], + ), +)) diff --git a/crates/fuse-parser/tests/cases/pass/call-expression-01/case.fuse b/crates/fuse-parser/tests/cases/pass/call-expression-01/case.fuse new file mode 100644 index 0000000..8be6379 --- /dev/null +++ b/crates/fuse-parser/tests/cases/pass/call-expression-01/case.fuse @@ -0,0 +1 @@ +test() diff --git a/crates/fuse-parser/tests/cases/pass/call-expression-01/tokens.snap b/crates/fuse-parser/tests/cases/pass/call-expression-01/tokens.snap new file mode 100644 index 0000000..e7b3795 --- /dev/null +++ b/crates/fuse-parser/tests/cases/pass/call-expression-01/tokens.snap @@ -0,0 +1,49 @@ +--- +source: crates/fuse-parser/tests/cases/mod.rs +description: "test()\n" +expression: tokens +input_file: crates/fuse-parser/tests/cases/pass/call-expression-01/case.fuse +--- +[ + TokenReference( + token: Token( + span: Span( + start: 0, + end: 4, + ), + kind: Identifier, + ), + leading_trivia: [], + trailing_trivia: [], + ), + TokenReference( + token: Token( + span: Span( + start: 4, + end: 5, + ), + kind: LParen, + ), + leading_trivia: [], + trailing_trivia: [], + ), + TokenReference( + token: Token( + span: Span( + start: 5, + end: 6, + ), + kind: RParen, + ), + leading_trivia: [], + trailing_trivia: [ + Token( + span: Span( + start: 6, + end: 7, + ), + kind: Whitespace, + ), + ], + ), +] diff --git a/crates/fuse-parser/tests/cases/pass/call-expression-02/ast.snap b/crates/fuse-parser/tests/cases/pass/call-expression-02/ast.snap new file mode 100644 index 0000000..69435bf --- /dev/null +++ b/crates/fuse-parser/tests/cases/pass/call-expression-02/ast.snap @@ -0,0 +1,52 @@ +--- +source: crates/fuse-parser/tests/cases/mod.rs +description: "test(a, b, c)\n" +expression: parsed.chunk +input_file: crates/fuse-parser/tests/cases/pass/call-expression-02/case.fuse +--- +Some(Chunk( + span: Span( + start: 0, + end: 14, + ), + body: Block( + statements: [ + Expression(CallExpression(CallExpression( + span: Span( + start: 4, + end: 13, + ), + target: Identifier(Identifier( + span: Span( + start: 0, + end: 4, + ), + name: Atom("test"), + )), + arguments: [ + Identifier(Identifier( + span: Span( + start: 5, + end: 6, + ), + name: Atom("a"), + )), + Identifier(Identifier( + span: Span( + start: 8, + end: 9, + ), + name: Atom("b"), + )), + Identifier(Identifier( + span: Span( + start: 11, + end: 12, + ), + name: Atom("c"), + )), + ], + ))), + ], + ), +)) diff --git a/crates/fuse-parser/tests/cases/pass/call-expression-02/case.fuse b/crates/fuse-parser/tests/cases/pass/call-expression-02/case.fuse new file mode 100644 index 0000000..0e31b63 --- /dev/null +++ b/crates/fuse-parser/tests/cases/pass/call-expression-02/case.fuse @@ -0,0 +1 @@ +test(a, b, c) diff --git a/crates/fuse-parser/tests/cases/pass/call-expression-02/tokens.snap b/crates/fuse-parser/tests/cases/pass/call-expression-02/tokens.snap new file mode 100644 index 0000000..60a5867 --- /dev/null +++ b/crates/fuse-parser/tests/cases/pass/call-expression-02/tokens.snap @@ -0,0 +1,120 @@ +--- +source: crates/fuse-parser/tests/cases/mod.rs +description: "test(a, b, c)\n" +expression: tokens +input_file: crates/fuse-parser/tests/cases/pass/call-expression-02/case.fuse +--- +[ + TokenReference( + token: Token( + span: Span( + start: 0, + end: 4, + ), + kind: Identifier, + ), + leading_trivia: [], + trailing_trivia: [], + ), + TokenReference( + token: Token( + span: Span( + start: 4, + end: 5, + ), + kind: LParen, + ), + leading_trivia: [], + trailing_trivia: [], + ), + TokenReference( + token: Token( + span: Span( + start: 5, + end: 6, + ), + kind: Identifier, + ), + leading_trivia: [], + trailing_trivia: [], + ), + TokenReference( + token: Token( + span: Span( + start: 6, + end: 7, + ), + kind: Comma, + ), + leading_trivia: [], + trailing_trivia: [ + Token( + span: Span( + start: 7, + end: 8, + ), + kind: Whitespace, + ), + ], + ), + TokenReference( + token: Token( + span: Span( + start: 8, + end: 9, + ), + kind: Identifier, + ), + leading_trivia: [], + trailing_trivia: [], + ), + TokenReference( + token: Token( + span: Span( + start: 9, + end: 10, + ), + kind: Comma, + ), + leading_trivia: [], + trailing_trivia: [ + Token( + span: Span( + start: 10, + end: 11, + ), + kind: Whitespace, + ), + ], + ), + TokenReference( + token: Token( + span: Span( + start: 11, + end: 12, + ), + kind: Identifier, + ), + leading_trivia: [], + trailing_trivia: [], + ), + TokenReference( + token: Token( + span: Span( + start: 12, + end: 13, + ), + kind: RParen, + ), + leading_trivia: [], + trailing_trivia: [ + Token( + span: Span( + start: 13, + end: 14, + ), + kind: Whitespace, + ), + ], + ), +] diff --git a/crates/fuse-parser/tests/cases/pass/call-expression-03/ast.snap b/crates/fuse-parser/tests/cases/pass/call-expression-03/ast.snap new file mode 100644 index 0000000..b4072cb --- /dev/null +++ b/crates/fuse-parser/tests/cases/pass/call-expression-03/ast.snap @@ -0,0 +1,63 @@ +--- +source: crates/fuse-parser/tests/cases/mod.rs +description: "foo.bar().baz()\n" +expression: parsed.chunk +input_file: crates/fuse-parser/tests/cases/pass/call-expression-03/case.fuse +--- +Some(Chunk( + span: Span( + start: 0, + end: 16, + ), + body: Block( + statements: [ + Expression(BinaryOperator(BinaryOperator( + kind: Member(Span( + start: 3, + end: 4, + )), + lhs: Identifier(Identifier( + span: Span( + start: 0, + end: 3, + ), + name: Atom("foo"), + )), + rhs: BinaryOperator(BinaryOperator( + kind: Member(Span( + start: 9, + end: 10, + )), + lhs: CallExpression(CallExpression( + span: Span( + start: 7, + end: 9, + ), + target: Identifier(Identifier( + span: Span( + start: 4, + end: 7, + ), + name: Atom("bar"), + )), + arguments: [], + )), + rhs: CallExpression(CallExpression( + span: Span( + start: 13, + end: 15, + ), + target: Identifier(Identifier( + span: Span( + start: 10, + end: 13, + ), + name: Atom("baz"), + )), + arguments: [], + )), + )), + ))), + ], + ), +)) diff --git a/crates/fuse-parser/tests/cases/pass/call-expression-03/case.fuse b/crates/fuse-parser/tests/cases/pass/call-expression-03/case.fuse new file mode 100644 index 0000000..1365079 --- /dev/null +++ b/crates/fuse-parser/tests/cases/pass/call-expression-03/case.fuse @@ -0,0 +1 @@ +foo.bar().baz() diff --git a/crates/fuse-parser/tests/cases/pass/call-expression-03/tokens.snap b/crates/fuse-parser/tests/cases/pass/call-expression-03/tokens.snap new file mode 100644 index 0000000..0685520 --- /dev/null +++ b/crates/fuse-parser/tests/cases/pass/call-expression-03/tokens.snap @@ -0,0 +1,115 @@ +--- +source: crates/fuse-parser/tests/cases/mod.rs +description: "foo.bar().baz()\n" +expression: tokens +input_file: crates/fuse-parser/tests/cases/pass/call-expression-03/case.fuse +--- +[ + TokenReference( + token: Token( + span: Span( + start: 0, + end: 3, + ), + kind: Identifier, + ), + leading_trivia: [], + trailing_trivia: [], + ), + TokenReference( + token: Token( + span: Span( + start: 3, + end: 4, + ), + kind: Dot, + ), + leading_trivia: [], + trailing_trivia: [], + ), + TokenReference( + token: Token( + span: Span( + start: 4, + end: 7, + ), + kind: Identifier, + ), + leading_trivia: [], + trailing_trivia: [], + ), + TokenReference( + token: Token( + span: Span( + start: 7, + end: 8, + ), + kind: LParen, + ), + leading_trivia: [], + trailing_trivia: [], + ), + TokenReference( + token: Token( + span: Span( + start: 8, + end: 9, + ), + kind: RParen, + ), + leading_trivia: [], + trailing_trivia: [], + ), + TokenReference( + token: Token( + span: Span( + start: 9, + end: 10, + ), + kind: Dot, + ), + leading_trivia: [], + trailing_trivia: [], + ), + TokenReference( + token: Token( + span: Span( + start: 10, + end: 13, + ), + kind: Identifier, + ), + leading_trivia: [], + trailing_trivia: [], + ), + TokenReference( + token: Token( + span: Span( + start: 13, + end: 14, + ), + kind: LParen, + ), + leading_trivia: [], + trailing_trivia: [], + ), + TokenReference( + token: Token( + span: Span( + start: 14, + end: 15, + ), + kind: RParen, + ), + leading_trivia: [], + trailing_trivia: [ + Token( + span: Span( + start: 15, + end: 16, + ), + kind: Whitespace, + ), + ], + ), +]