diff --git a/ast/ast.go b/ast/ast.go index 09fd6bf..07695d5 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -50,6 +50,30 @@ func (b *Boolean) expressionNode() {} func (b *Boolean) TokenLiteral() string { return b.Token.Literal } func (b *Boolean) String() string { return b.Token.Literal } +type CallExpression struct { + Token token.Token + Function Expression + Arguments []Expression +} + +func (ce *CallExpression) expressionNode() {} +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() +} + type FunctionLiteral struct { Token token.Token Parameters []*Identifier diff --git a/parser/parser.go b/parser/parser.go index fa3fca9..fe499f4 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, } type Parser struct { @@ -74,6 +75,7 @@ func New(l *lexer.Lexer) *Parser { p.registerInfix(token.NOT_EQ, p.parseInfixExpression) p.registerInfix(token.LT, p.parseInfixExpression) p.registerInfix(token.GT, p.parseInfixExpression) + p.registerInfix(token.LPAREN, p.parseCallExpression) // read two tokens to set both curToken and peekToken p.nextToken() @@ -116,6 +118,36 @@ func (p *Parser) nextToken() { p.peekToken = p.l.NextToken() } +func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { + exp := &ast.CallExpression{Token: p.curToken, Function: function} + exp.Arguments = p.parseCallArguments() + return exp +} + +func (p *Parser) parseCallArguments() []ast.Expression { + args := []ast.Expression{} + + if p.peekTokenIs(token.RPAREN) { + p.nextToken() + return args + } + + p.nextToken() + args = append(args, p.parseExpression(LOWEST)) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + args = append(args, p.parseExpression(LOWEST)) + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return args +} + func (p *Parser) ParseProgram() *ast.Program { program := &ast.Program{} program.Statements = []ast.Statement{} @@ -316,8 +348,12 @@ func (p *Parser) parseLetStatement() *ast.LetStatement { if !p.expectPeek(token.ASSIGN) { return nil } - // skip expression parsing for now - for !p.curTokenIs(token.SEMICOLON) { + + p.nextToken() + + stmt.Value = p.parseExpression(LOWEST) + + for !p.curTokenIs(token.SEMICOLON) && !p.curTokenIs(token.EOF) { p.nextToken() } return stmt @@ -327,8 +363,10 @@ func (p *Parser) parseReturnStatement() *ast.ReturnStatement { stmt := &ast.ReturnStatement{Token: p.curToken} p.nextToken() - // skip expression parsing for now - for !p.curTokenIs(token.SEMICOLON) { + + stmt.ReturnValue = p.parseExpression(LOWEST) + + for !p.curTokenIs(token.SEMICOLON) && !p.curTokenIs(token.EOF) { p.nextToken() } diff --git a/parser/parser_test.go b/parser/parser_test.go index a9426a6..b8495a1 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -45,6 +45,41 @@ func TestBooleanExpression(t *testing.T) { } } +func TestCallExpressionParsing(t *testing.T) { + input := "add(1, 2 * 3, 4 + 5);" + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program does not contain %d statements. got=%d", 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("stmt is not ast.ExpressionStatement. got=%T", program.Statements[0]) + } + + exp, ok := stmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("stmt.Expression is not ast.CallExpression. got=%T", stmt.Expression) + } + + if !testIdentifier(t, exp.Function, "add") { + return + } + + if len(exp.Arguments) != 3 { + t.Fatalf("wrong number of arguments. got=%d", len(exp.Arguments)) + } + + testLiteralExpression(t, exp.Arguments[0], 1) + testInfixExpression(t, exp.Arguments[1], 2, "*", 3) + testInfixExpression(t, exp.Arguments[2], 4, "+", 5) +} + func TestIdentifierExpression(t *testing.T) { input := "foobar;" @@ -544,6 +579,18 @@ func TestOperatorPrecedenceParsing(t *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.New(tt.input) @@ -559,35 +606,38 @@ func TestOperatorPrecedenceParsing(t *testing.T) { } func TestLetStatements(t *testing.T) { - input := ` -let x = 5; -let y = 10; -let foobar = 838383; -` - - l := lexer.New(input) - p := New(l) - - program := p.ParseProgram() - checkParserErrors(t, p) - if program == nil { - t.Fatalf("ParseProgram() returned nil") - } - tests := []struct { + input string expectedIdentifier string + expectedValue interface{} }{ - {"x"}, - {"y"}, - {"foobar"}, + {"let x = 5;", "x", 5}, + {"let y = true;", "y", true}, + {"let foobar = y;", "foobar", "y"}, } - for i, tt := range tests { - stmt := program.Statements[i] + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain 1 statement. got=%d", + len(program.Statements)) + } + + stmt := program.Statements[0] if !testLetStatement(t, stmt, tt.expectedIdentifier) { return } + + val := stmt.(*ast.LetStatement).Value + if !testLiteralExpression(t, val, tt.expectedValue) { + return + } } + } func testLetStatement(t *testing.T, s ast.Statement, name string) bool {