diff --git a/parser/parser.go b/parser/parser.go index f3e554f..428bd3d 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -106,6 +106,9 @@ func New(l_lexer *lexer.Lexer) *Parser { // IF Expression l_parser.register_prefix(token.IF, l_parser.parse_if_expression) + // Function Literals + l_parser.register_prefix(token.FUNCTION, l_parser.parse_function_literal) + return l_parser } @@ -344,3 +347,42 @@ func (l_parser *Parser) parse_block_statement() *ast.BlockStatement { } return block } + +func (l_parser *Parser) parse_function_literal() ast.Expression { + literal := &ast.FunctionLiteral{ Token: l_parser.current_token } + if !l_parser.expect_peek(token.LPAREN) { + return nil + } + + literal.Parameters = l_parser.parse_function_parameters() + if !l_parser.expect_peek(token.LBRACE) { + return nil + } + literal.Body = l_parser.parse_block_statement() + return literal +} + +func (l_parser *Parser) parse_function_parameters() []*ast.Identifier { + identifiers := []*ast.Identifier{} + + if l_parser.peek_token_is(token.RPAREN) { + l_parser.next_token() + return identifiers + } + + l_parser.next_token() + + ident := &ast.Identifier{Token: l_parser.current_token, Value: l_parser.current_token.Literal} + identifiers = append(identifiers, ident) + + for l_parser.peek_token_is(token.COMMA) { + l_parser.next_token() + l_parser.next_token() + ident := &ast.Identifier{Token: l_parser.current_token, Value: l_parser.current_token.Literal} + identifiers = append(identifiers, ident) + } + if !l_parser.expect_peek(token.RPAREN) { + return nil + } + return identifiers +} diff --git a/parser/parser_test.go b/parser/parser_test.go index d03b72e..f448fab 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -453,6 +453,76 @@ func TestIfElseExpression(l_test *testing.T) { } } +func TestFunctionLiteralParsing(l_test *testing.T) { + input := `fn(x, y) { x + y; }` + + 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("program.Statements[0] is not ast.ExpressionStatement, got=%T", program.Statements[0]) + } + + function, ok := statement.Expression.(*ast.FunctionLiteral) + if !ok { + l_test.Fatalf("statement.Expression is not ast.FunctionLiteral, got=%T", statement.Expression) + } + + if len(function.Parameters) != 2 { + l_test.Fatalf("function literal parameters wrong, want 2, got=%d\n", len(function.Parameters)) + } + testLiteralExpression(l_test, function.Parameters[0], "x") + testLiteralExpression(l_test, function.Parameters[1], "y") + + if len(function.Body.Statements) != 1 { + l_test.Fatalf("function.Body.Statements does not have 1 statement, got=%d\n", len(function.Body.Statements)) + } + + body_statement, ok := function.Body.Statements[0].(*ast.ExpressionStatement) + if !ok { + l_test.Fatalf("function body statement is not ast.ExpressionStatemes, got=%T", function.Body.Statements[0]) + } + testInfixExpression(l_test, body_statement.Expression, "x", "+", "y") + +} + +func TestFunctionParameterParsing(l_test *testing.T){ + tests := []struct { + 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"}}, + } + + 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) + function := statement.Expression.(*ast.FunctionLiteral) + + 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)) + } + + for i, identifier := range tt.expected_params { + testLiteralExpression(l_test, function.Parameters[i], identifier) + } + } +} + // Helpers func check_parser_errors(l_test *testing.T, l_parser *Parser) {