From 7e3416401923fe1d7b8ef70b2f19419bd7fc43fc Mon Sep 17 00:00:00 2001 From: tijani Date: Mon, 1 Aug 2022 04:58:28 +0000 Subject: [PATCH] CallExpression ast, parser and tests completed. git-svn-id: https://svn.tlawal.org/svn/monkey@39 f6afcba9-9ef1-4bdd-9b72-7484f5705bac --- ast/ast.go | 25 ++++++++++ parser/parser.go | 36 +++++++++++++- parser/parser_test.go | 113 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 167 insertions(+), 7 deletions(-) diff --git a/ast/ast.go b/ast/ast.go index 9322908..7e94417 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -261,3 +261,28 @@ func (fl *FunctionLiteral) String() string { return out.String() } + +// Call Expression +type CallExpression struct { + Token token.Token // The '(' token + Function Expression // Identifier or FunctionLiteral + Arguments []Expression +} + +func (ce *CallExpression) expression_node() {} +func (ce *CallExpression) TokenLiteral() string { return ce.Token.Literal } +func (ce *CallExpression) String() string { + var out bytes.Buffer + + args := []string{} + for _, a := range ce.Arguments { + args = append(args, a.String()) + } + + out.WriteString(ce.Function.String()) + out.WriteString("(") + out.WriteString(strings.Join(args, ", ")) + out.WriteString(")") + + return out.String() +} diff --git a/parser/parser.go b/parser/parser.go index 428bd3d..44d324e 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -30,6 +30,7 @@ var precedences = map[token.TokenType]int{ token.MINUS: SUM, token.SLASH: PRODUCT, token.ASTERISK: PRODUCT, + token.LPAREN: CALL, } func (l_parser *Parser) peek_precedence() int { @@ -109,6 +110,9 @@ func New(l_lexer *lexer.Lexer) *Parser { // Function Literals l_parser.register_prefix(token.FUNCTION, l_parser.parse_function_literal) + // Call Expression + l_parser.register_infix(token.LPAREN, l_parser.parse_call_expression) + return l_parser } @@ -349,7 +353,7 @@ func (l_parser *Parser) parse_block_statement() *ast.BlockStatement { } func (l_parser *Parser) parse_function_literal() ast.Expression { - literal := &ast.FunctionLiteral{ Token: l_parser.current_token } + literal := &ast.FunctionLiteral{Token: l_parser.current_token} if !l_parser.expect_peek(token.LPAREN) { return nil } @@ -358,6 +362,7 @@ func (l_parser *Parser) parse_function_literal() ast.Expression { if !l_parser.expect_peek(token.LBRACE) { return nil } + literal.Body = l_parser.parse_block_statement() return literal } @@ -386,3 +391,32 @@ func (l_parser *Parser) parse_function_parameters() []*ast.Identifier { } return identifiers } + +func (l_parser *Parser) parse_call_expression(function ast.Expression) ast.Expression { + expression := &ast.CallExpression{Token: l_parser.current_token, Function: function} + expression.Arguments = l_parser.parse_call_arguments() + return expression +} + +func (l_parser *Parser) parse_call_arguments() []ast.Expression { + args := []ast.Expression{} + + if l_parser.peek_token_is(token.RPAREN) { + l_parser.next_token() + return args + } + + l_parser.next_token() + args = append(args, l_parser.parse_expression(LOWEST)) + + for l_parser.peek_token_is(token.COMMA) { + l_parser.next_token() + l_parser.next_token() + args = append(args, l_parser.parse_expression(LOWEST)) + } + + if !l_parser.expect_peek(token.RPAREN) { + return nil + } + return args +} diff --git a/parser/parser_test.go b/parser/parser_test.go index f448fab..8ce4eb7 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -310,6 +310,18 @@ func TestOperatorPrecedenceParsing(l_test *testing.T) { "!(true == true)", "(!(true == true))", }, + { + "a + add(b * c) + d", + "((a + add((b * c))) + d)", + }, + { + "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", + "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", + }, + { + "add(a + b + c * d / f + g)", + "add((((a + b) + ((c * d) / f)) + g))", + }, } for _, tt := range tests { l_lexer := lexer.New(tt.input) @@ -493,14 +505,14 @@ func TestFunctionLiteralParsing(l_test *testing.T) { } -func TestFunctionParameterParsing(l_test *testing.T){ +func TestFunctionParameterParsing(l_test *testing.T) { tests := []struct { - input string + input string expected_params []string }{ - { input: "fn() {};", expected_params: []string{}}, - { input: "fn(x) {};", expected_params: []string{"x"}}, - { input: "fn(x, y, z) {};", expected_params: []string{"x", "y", "z"}}, + {input: "fn() {};", expected_params: []string{}}, + {input: "fn(x) {};", expected_params: []string{"x"}}, + {input: "fn(x, y, z) {};", expected_params: []string{"x", "y", "z"}}, } for _, tt := range tests { @@ -512,7 +524,7 @@ func TestFunctionParameterParsing(l_test *testing.T){ statement := program.Statements[0].(*ast.ExpressionStatement) function := statement.Expression.(*ast.FunctionLiteral) - if len(function.Parameters) != len(tt.expected_params){ + if len(function.Parameters) != len(tt.expected_params) { l_test.Errorf("length of parameters is wrong, want %d, got=%d\n", len(tt.expected_params), len(function.Parameters)) } @@ -523,6 +535,95 @@ func TestFunctionParameterParsing(l_test *testing.T){ } } +func TestCallExpressionParsing(l_test *testing.T) { + input := "add(1, 2 * 3, 4 + 5);" + + l_lexer := lexer.New(input) + l_parser := New(l_lexer) + program := l_parser.ParseProgram() + check_parser_errors(l_test, l_parser) + + if len(program.Statements) != 1 { + l_test.Fatalf("program.Statements does not contain %d statements, got=%d\n", 1, len(program.Statements)) + } + + statement, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + l_test.Fatalf("statement is not ast.ExpressionStatement, got=%T", program.Statements[0]) + } + + expression, ok := statement.Expression.(*ast.CallExpression) + if !ok { + l_test.Fatalf("statemnt.Expression is not ast.CallExpression, got=%T", statement.Expression) + } + + if !testIdentifier(l_test, expression.Function, "add") { + return + } + + if len(expression.Arguments) != 3 { + l_test.Fatalf("wrong length of arguments, got=%d", len(expression.Arguments)) + } + + testLiteralExpression(l_test, expression.Arguments[0], 1) + testInfixExpression(l_test, expression.Arguments[1], 2, "*", 3) + testInfixExpression(l_test, expression.Arguments[2], 4, "+", 5) +} + +func TestCallExpressionParameterParsing(l_test *testing.T) { + tests := []struct { + input string + expected_ident string + expected_args []string + }{ + { + input: "add();", + expected_ident: "add", + expected_args: []string{}, + }, + { + input: "add(1);", + expected_ident: "add", + expected_args: []string{"1"}, + }, + { + input: "add(1, 2 * 3, 4 + 5);", + expected_ident: "add", + expected_args: []string{"1", "(2 * 3)", "(4 + 5)"}, + }, + } + + for _, tt := range tests { + l_lexer := lexer.New(tt.input) + l_parser := New(l_lexer) + program := l_parser.ParseProgram() + check_parser_errors(l_test, l_parser) + + statement := program.Statements[0].(*ast.ExpressionStatement) + expression, ok := statement.Expression.(*ast.CallExpression) + if !ok { + l_test.Fatalf("statement.Expression is not ast.CallExpression, got=%T", + statement.Expression) + } + + if !testIdentifier(l_test, expression.Function, tt.expected_ident) { + return + } + + if len(expression.Arguments) != len(tt.expected_args) { + l_test.Fatalf("wrong number of arguments, want=%d, got=%d", + len(tt.expected_args), len(expression.Arguments)) + } + + for i, arg := range tt.expected_args { + if expression.Arguments[i].String() != arg { + l_test.Errorf("argument %d wrong. want=%q, got=%q", i, + arg, expression.Arguments[i].String()) + } + } + } +} + // Helpers func check_parser_errors(l_test *testing.T, l_parser *Parser) {