From 9413094bac72b7fbc118430a764c23efec257588 Mon Sep 17 00:00:00 2001 From: Chuck Smith Date: Thu, 18 Jan 2024 15:06:46 -0500 Subject: [PATCH] parse calls, returns, and expressions --- ast/ast.go | 243 +++++++++++++++++++++------------------ parser/parser.go | 261 +++++++++++++++++++++++++++--------------- parser/parser_test.go | 248 +++++++++++++++++++++++++++++++++++---- 3 files changed, 529 insertions(+), 223 deletions(-) diff --git a/ast/ast.go b/ast/ast.go index 7852327..9ab4a76 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -3,18 +3,22 @@ package ast import ( "bytes" "monkey/token" + "strings" ) +// The base Node interface type Node interface { TokenLiteral() string String() string } +// All statement nodes implement this type Statement interface { Node statementNode() } +// All expression nodes implement this type Expression interface { Node expressionNode() @@ -42,67 +46,15 @@ func (p *Program) String() string { return out.String() } -type Identifier struct { - Token token.Token // the token.Ident token - Value string -} - +// Statements type LetStatement struct { - Token token.Token // the token.Let token + Token token.Token // the token.LET token Name *Identifier Value Expression } -type ReturnStatement struct { - Token token.Token // the 'return token - ReturnValue Expression -} - -type ExpressionStatement struct { - Token token.Token // the first token of the expression - Expression Expression -} - -type IntegerLiteral struct { - Token token.Token - Value int64 -} - -type PrefixExpression struct { - Token token.Token - Operator string - Right Expression -} - -type InfixExpression struct { - Token token.Token - Left Expression - Operator string - Right Expression -} - -type Boolean struct { - Token token.Token - Value bool -} - -type BlockStatement struct { - Token token.Token // the { token - Statements []Statement -} - -type IfExpression struct { - Token token.Token // The 'if' token - Condition Expression - Consequence *BlockStatement - Alternative *BlockStatement -} - -func (ls *LetStatement) statementNode() { -} -func (ls *LetStatement) TokenLiteral() string { - return ls.Token.Literal -} +func (ls *LetStatement) statementNode() {} +func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal } func (ls *LetStatement) String() string { var out bytes.Buffer @@ -119,18 +71,13 @@ func (ls *LetStatement) String() string { return out.String() } -func (i *Identifier) expressionNode() { +type ReturnStatement struct { + Token token.Token // the 'return' token + ReturnValue Expression } -func (i *Identifier) TokenLiteral() string { - return i.Token.Literal -} -func (i *Identifier) String() string { return i.Value } -func (rs *ReturnStatement) statementNode() { -} -func (rs *ReturnStatement) TokenLiteral() string { - return rs.Token.Literal -} +func (rs *ReturnStatement) statementNode() {} +func (rs *ReturnStatement) TokenLiteral() string { return rs.Token.Literal } func (rs *ReturnStatement) String() string { var out bytes.Buffer @@ -145,32 +92,73 @@ func (rs *ReturnStatement) String() string { return out.String() } -func (es *ExpressionStatement) statementNode() { -} -func (es *ExpressionStatement) TokenLiteral() string { - return es.Token.Literal +type ExpressionStatement struct { + Token token.Token // the first token of the expression + Expression Expression } + +func (es *ExpressionStatement) statementNode() {} +func (es *ExpressionStatement) TokenLiteral() string { return es.Token.Literal } func (es *ExpressionStatement) String() string { if es.Expression != nil { return es.Expression.String() } - return "" } -func (il *IntegerLiteral) expressionNode() { -} -func (il *IntegerLiteral) TokenLiteral() string { - return il.Token.Literal -} -func (il *IntegerLiteral) String() string { - return il.Token.Literal +type BlockStatement struct { + Token token.Token // the { token + Statements []Statement } -func (pe *PrefixExpression) expressionNode() {} -func (pe *PrefixExpression) TokenLiteral() string { - return pe.Token.Literal +func (bs *BlockStatement) statementNode() {} +func (bs *BlockStatement) TokenLiteral() string { return bs.Token.Literal } +func (bs *BlockStatement) String() string { + var out bytes.Buffer + + for _, s := range bs.Statements { + out.WriteString(s.String()) + } + + return out.String() } + +// Expressions +type Identifier struct { + Token token.Token // the token.IDENT token + Value string +} + +func (i *Identifier) expressionNode() {} +func (i *Identifier) TokenLiteral() string { return i.Token.Literal } +func (i *Identifier) String() string { return i.Value } + +type Boolean struct { + Token token.Token + Value bool +} + +func (b *Boolean) expressionNode() {} +func (b *Boolean) TokenLiteral() string { return b.Token.Literal } +func (b *Boolean) String() string { return b.Token.Literal } + +type IntegerLiteral struct { + Token token.Token + Value int64 +} + +func (il *IntegerLiteral) expressionNode() {} +func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal } +func (il *IntegerLiteral) String() string { return il.Token.Literal } + +type PrefixExpression struct { + Token token.Token // The prefix token, e.g. ! + Operator string + Right Expression +} + +func (pe *PrefixExpression) expressionNode() {} +func (pe *PrefixExpression) TokenLiteral() string { return pe.Token.Literal } func (pe *PrefixExpression) String() string { var out bytes.Buffer @@ -182,10 +170,15 @@ func (pe *PrefixExpression) String() string { return out.String() } -func (ie *InfixExpression) expressionNode() {} -func (ie *InfixExpression) TokenLiteral() string { - return ie.Token.Literal +type InfixExpression struct { + Token token.Token // The operator token, e.g. + + Left Expression + Operator string + Right Expression } + +func (ie *InfixExpression) expressionNode() {} +func (ie *InfixExpression) TokenLiteral() string { return ie.Token.Literal } func (ie *InfixExpression) String() string { var out bytes.Buffer @@ -198,32 +191,16 @@ func (ie *InfixExpression) String() string { return out.String() } -func (b Boolean) TokenLiteral() string { - return b.Token.Literal +type IfExpression struct { + Token token.Token // The 'if' token + Condition Expression + Consequence *BlockStatement + Alternative *BlockStatement } -func (b Boolean) String() string { - return b.Token.Literal -} -func (b Boolean) expressionNode() {} -func (bs BlockStatement) TokenLiteral() string { - return bs.Token.Literal -} -func (bs BlockStatement) String() string { - var out bytes.Buffer - - for _, s := range bs.Statements { - out.WriteString(s.String()) - } - - return out.String() -} -func (bs BlockStatement) statementNode() {} - -func (ie IfExpression) TokenLiteral() string { - return ie.Token.Literal -} -func (ie IfExpression) String() string { +func (ie *IfExpression) expressionNode() {} +func (ie *IfExpression) TokenLiteral() string { return ie.Token.Literal } +func (ie *IfExpression) String() string { var out bytes.Buffer out.WriteString("if") @@ -238,4 +215,52 @@ func (ie IfExpression) String() string { return out.String() } -func (ie IfExpression) expressionNode() {} + +type FunctionLiteral struct { + Token token.Token // The 'fn' token + Parameters []*Identifier + Body *BlockStatement +} + +func (fl *FunctionLiteral) expressionNode() {} +func (fl *FunctionLiteral) TokenLiteral() string { return fl.Token.Literal } +func (fl *FunctionLiteral) String() string { + var out bytes.Buffer + + params := []string{} + for _, p := range fl.Parameters { + params = append(params, p.String()) + } + + out.WriteString(fl.TokenLiteral()) + out.WriteString("(") + out.WriteString(strings.Join(params, ", ")) + out.WriteString(") ") + out.WriteString(fl.Body.String()) + + return out.String() +} + +type CallExpression struct { + Token token.Token // The '(' token + Function Expression // Identifier or FunctionLiteral + 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() +} diff --git a/parser/parser.go b/parser/parser.go index de7e971..c632951 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -17,6 +17,7 @@ const ( PRODUCT // * PREFIX // -X or !X CALL // myFunction(X) + INDEX // array[index] ) var precedences = map[token.TokenType]int{ @@ -28,8 +29,14 @@ var precedences = map[token.TokenType]int{ token.MINUS: SUM, token.SLASH: PRODUCT, token.ASTERISK: PRODUCT, + token.LPAREN: CALL, } +type ( + prefixParseFn func() ast.Expression + infixParseFn func(ast.Expression) ast.Expression +) + type Parser struct { l *lexer.Lexer errors []string @@ -41,16 +48,6 @@ type Parser struct { infixParseFns map[token.TokenType]infixParseFn } -type ( - prefixParseFn func() ast.Expression - infixParseFn func(expression ast.Expression) ast.Expression -) - -func (p *Parser) nextToken() { - p.curToken = p.peekToken - p.peekToken = p.l.NextToken() -} - func New(l *lexer.Lexer) *Parser { p := &Parser{ l: l, @@ -66,6 +63,7 @@ func New(l *lexer.Lexer) *Parser { p.registerPrefix(token.FALSE, p.parseBoolean) p.registerPrefix(token.LPAREN, p.parseGroupedExpression) p.registerPrefix(token.IF, p.parseIfExpression) + p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral) p.infixParseFns = make(map[token.TokenType]infixParseFn) p.registerInfix(token.PLUS, p.parseInfixExpression) @@ -77,13 +75,53 @@ func New(l *lexer.Lexer) *Parser { p.registerInfix(token.LT, p.parseInfixExpression) p.registerInfix(token.GT, p.parseInfixExpression) - // Read two token, so curToken and peekToken are both set + p.registerInfix(token.LPAREN, p.parseCallExpression) + + // Read two tokens, so curToken and peekToken are both set p.nextToken() p.nextToken() return p } +func (p *Parser) nextToken() { + p.curToken = p.peekToken + p.peekToken = p.l.NextToken() +} + +func (p *Parser) curTokenIs(t token.TokenType) bool { + return p.curToken.Type == t +} + +func (p *Parser) peekTokenIs(t token.TokenType) bool { + return p.peekToken.Type == t +} + +func (p *Parser) expectPeek(t token.TokenType) bool { + if p.peekTokenIs(t) { + p.nextToken() + return true + } else { + p.peekError(t) + return false + } +} + +func (p *Parser) Errors() []string { + return p.errors +} + +func (p *Parser) peekError(t token.TokenType) { + msg := fmt.Sprintf("expected next token to be %s, got %s instead", + t, p.peekToken.Type) + p.errors = append(p.errors, msg) +} + +func (p *Parser) noPrefixParseFnError(t token.TokenType) { + msg := fmt.Sprintf("no prefix parse function for %s found", t) + p.errors = append(p.errors, msg) +} + func (p *Parser) ParseProgram() *ast.Program { program := &ast.Program{} program.Statements = []ast.Statement{} @@ -106,7 +144,7 @@ func (p *Parser) parseStatement() ast.Statement { case token.RETURN: return p.parseReturnStatement() default: - return p.parseExpressionsStatement() + return p.parseExpressionStatement() } } @@ -117,65 +155,38 @@ func (p *Parser) parseLetStatement() *ast.LetStatement { return nil } - stmt.Name = &ast.Identifier{ - Token: p.curToken, - Value: p.curToken.Literal, - } + stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} if !p.expectPeek(token.ASSIGN) { return nil } - // TODO: We're skipping expressions until we - // encounter a semicolon - for !p.curTokenIs(token.SEMICOLON) { + p.nextToken() + + stmt.Value = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { p.nextToken() } return stmt } -func (p *Parser) expectPeek(t token.TokenType) bool { - if p.peekTokenIs(t) { - p.nextToken() - return true - } else { - p.peekError(t) - return false - } -} - -func (p *Parser) curTokenIs(t token.TokenType) bool { - return p.curToken.Type == t -} - -func (p *Parser) peekTokenIs(t token.TokenType) bool { - return p.peekToken.Type == t -} - -func (p *Parser) Errors() []string { - return p.errors -} - -func (p *Parser) peekError(t token.TokenType) { - msg := fmt.Sprintf("expected next token to be %s, got %s instead", t, p.peekToken.Type) - p.errors = append(p.errors, msg) -} - func (p *Parser) parseReturnStatement() *ast.ReturnStatement { stmt := &ast.ReturnStatement{Token: p.curToken} p.nextToken() - // TODO: We're skipping the expression until we encounter a semicolon - for !p.curTokenIs(token.SEMICOLON) { + stmt.ReturnValue = p.parseExpression(LOWEST) + + if p.peekTokenIs(token.SEMICOLON) { p.nextToken() } return stmt } -func (p *Parser) parseExpressionsStatement() *ast.ExpressionStatement { +func (p *Parser) parseExpressionStatement() *ast.ExpressionStatement { stmt := &ast.ExpressionStatement{Token: p.curToken} stmt.Expression = p.parseExpression(LOWEST) @@ -209,6 +220,26 @@ func (p *Parser) parseExpression(precedence int) ast.Expression { return leftExp } +func (p *Parser) peekPrecedence() int { + if p, ok := precedences[p.peekToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) curPrecedence() int { + if p, ok := precedences[p.curToken.Type]; ok { + return p + } + + return LOWEST +} + +func (p *Parser) parseIdentifier() ast.Expression { + return &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} +} + func (p *Parser) parseIntegerLiteral() ast.Expression { lit := &ast.IntegerLiteral{Token: p.curToken} @@ -224,26 +255,6 @@ func (p *Parser) parseIntegerLiteral() ast.Expression { return lit } -func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) { - p.prefixParseFns[tokenType] = fn -} - -func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { - p.infixParseFns[tokenType] = fn -} - -func (p *Parser) parseIdentifier() ast.Expression { - return &ast.Identifier{ - Token: p.curToken, - Value: p.curToken.Literal, - } -} - -func (p *Parser) noPrefixParseFnError(t token.TokenType) { - msg := fmt.Sprintf("no prefix parse function for %s found", t) - p.errors = append(p.errors, msg) -} - func (p *Parser) parsePrefixExpression() ast.Expression { expression := &ast.PrefixExpression{ Token: p.curToken, @@ -257,22 +268,6 @@ func (p *Parser) parsePrefixExpression() ast.Expression { return expression } -func (p *Parser) peekPrecedence() int { - if p, ok := precedences[p.peekToken.Type]; ok { - return p - } - - return LOWEST -} - -func (p *Parser) curPrecedence() int { - if p, ok := precedences[p.curToken.Type]; ok { - return p - } - - return LOWEST -} - func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { expression := &ast.InfixExpression{ Token: p.curToken, @@ -288,10 +283,7 @@ func (p *Parser) parseInfixExpression(left ast.Expression) ast.Expression { } func (p *Parser) parseBoolean() ast.Expression { - return &ast.Boolean{ - Token: p.curToken, - Value: p.curTokenIs(token.TRUE), - } + return &ast.Boolean{Token: p.curToken, Value: p.curTokenIs(token.TRUE)} } func (p *Parser) parseGroupedExpression() ast.Expression { @@ -299,7 +291,7 @@ func (p *Parser) parseGroupedExpression() ast.Expression { exp := p.parseExpression(LOWEST) - if p.expectPeek(token.RPAREN) { + if !p.expectPeek(token.RPAREN) { return nil } @@ -324,7 +316,7 @@ func (p *Parser) parseIfExpression() ast.Expression { return nil } - expression.Consequence = p.parseBlockExpression() + expression.Consequence = p.parseBlockStatement() if p.peekTokenIs(token.ELSE) { p.nextToken() @@ -333,13 +325,13 @@ func (p *Parser) parseIfExpression() ast.Expression { return nil } - expression.Alternative = p.parseBlockExpression() + expression.Alternative = p.parseBlockStatement() } return expression } -func (p *Parser) parseBlockExpression() *ast.BlockStatement { +func (p *Parser) parseBlockStatement() *ast.BlockStatement { block := &ast.BlockStatement{Token: p.curToken} block.Statements = []ast.Statement{} @@ -355,3 +347,86 @@ func (p *Parser) parseBlockExpression() *ast.BlockStatement { return block } + +func (p *Parser) parseFunctionLiteral() ast.Expression { + lit := &ast.FunctionLiteral{Token: p.curToken} + + if !p.expectPeek(token.LPAREN) { + return nil + } + + lit.Parameters = p.parseFunctionParameters() + + if !p.expectPeek(token.LBRACE) { + return nil + } + + lit.Body = p.parseBlockStatement() + + return lit +} + +func (p *Parser) parseFunctionParameters() []*ast.Identifier { + identifiers := []*ast.Identifier{} + + if p.peekTokenIs(token.RPAREN) { + p.nextToken() + return identifiers + } + + p.nextToken() + + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + ident := &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} + identifiers = append(identifiers, ident) + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return identifiers +} + +func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression { + exp := &ast.CallExpression{Token: p.curToken, Function: function} + exp.Arguments = p.parseExpressionList(token.RPAREN) + return exp +} + +func (p *Parser) parseExpressionList(end token.TokenType) []ast.Expression { + list := []ast.Expression{} + + if p.peekTokenIs(end) { + p.nextToken() + return list + } + + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + + for p.peekTokenIs(token.COMMA) { + p.nextToken() + p.nextToken() + list = append(list, p.parseExpression(LOWEST)) + } + + if !p.expectPeek(end) { + return nil + } + + return list +} + +func (p *Parser) registerPrefix(tokenType token.TokenType, fn prefixParseFn) { + p.prefixParseFns[tokenType] = fn +} + +func (p *Parser) registerInfix(tokenType token.TokenType, fn infixParseFn) { + p.infixParseFns[tokenType] = fn +} diff --git a/parser/parser_test.go b/parser/parser_test.go index acdac3f..52ba231 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -356,6 +356,43 @@ func TestOperatorPrecedenceParsing(t *testing.T) { } } +func TestBooleanExpression(t *testing.T) { + tests := []struct { + input string + expectedBoolean bool + }{ + {"true;", true}, + {"false;", false}, + } + + 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 has not enough statements. got=%d", + len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + boolean, ok := stmt.Expression.(*ast.Boolean) + if !ok { + t.Fatalf("exp not *ast.Boolean. got=%T", stmt.Expression) + } + if boolean.Value != tt.expectedBoolean { + t.Errorf("boolean.Value not %t. got=%t", tt.expectedBoolean, + boolean.Value) + } + } +} + func TestIfExpression(t *testing.T) { input := `if (x < y) { x }` @@ -464,6 +501,175 @@ func TestIfElseExpression(t *testing.T) { } } +func TestFunctionLiteralParsing(t *testing.T) { + input := `fn(x, y) { x + y; }` + + l := lexer.New(input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program.Statements does not contain %d statements. got=%d\n", + 1, len(program.Statements)) + } + + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + function, ok := stmt.Expression.(*ast.FunctionLiteral) + if !ok { + t.Fatalf("stmt.Expression is not ast.FunctionLiteral. got=%T", + stmt.Expression) + } + + if len(function.Parameters) != 2 { + t.Fatalf("function literal parameters wrong. want 2, got=%d\n", + len(function.Parameters)) + } + + testLiteralExpression(t, function.Parameters[0], "x") + testLiteralExpression(t, function.Parameters[1], "y") + + if len(function.Body.Statements) != 1 { + t.Fatalf("function.Body.Statements has not 1 statements. got=%d\n", + len(function.Body.Statements)) + } + + bodyStmt, ok := function.Body.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("function body stmt is not ast.ExpressionStatement. got=%T", + function.Body.Statements[0]) + } + + testInfixExpression(t, bodyStmt.Expression, "x", "+", "y") +} + +func TestFunctionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedParams []string + }{ + {input: "fn() {};", expectedParams: []string{}}, + {input: "fn(x) {};", expectedParams: []string{"x"}}, + {input: "fn(x, y, z) {};", expectedParams: []string{"x", "y", "z"}}, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + function := stmt.Expression.(*ast.FunctionLiteral) + + if len(function.Parameters) != len(tt.expectedParams) { + t.Errorf("length parameters wrong. want %d, got=%d\n", + len(tt.expectedParams), len(function.Parameters)) + } + + for i, ident := range tt.expectedParams { + testLiteralExpression(t, function.Parameters[i], ident) + } + } +} + +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.Statements does not contain %d statements. got=%d\n", + 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 length 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 TestCallExpressionParameterParsing(t *testing.T) { + tests := []struct { + input string + expectedIdent string + expectedArgs []string + }{ + { + input: "add();", + expectedIdent: "add", + expectedArgs: []string{}, + }, + { + input: "add(1);", + expectedIdent: "add", + expectedArgs: []string{"1"}, + }, + { + input: "add(1, 2 * 3, 4 + 5);", + expectedIdent: "add", + expectedArgs: []string{"1", "(2 * 3)", "(4 + 5)"}, + }, + } + + for _, tt := range tests { + l := lexer.New(tt.input) + p := New(l) + program := p.ParseProgram() + checkParserErrors(t, p) + + stmt := program.Statements[0].(*ast.ExpressionStatement) + 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, tt.expectedIdent) { + return + } + + if len(exp.Arguments) != len(tt.expectedArgs) { + t.Fatalf("wrong number of arguments. want=%d, got=%d", + len(tt.expectedArgs), len(exp.Arguments)) + } + + for i, arg := range tt.expectedArgs { + if exp.Arguments[i].String() != arg { + t.Errorf("argument %d wrong. want=%q, got=%q", i, + arg, exp.Arguments[i].String()) + } + } + } +} + func testLetStatement(t *testing.T, s ast.Statement, name string) bool { if s.TokenLiteral() != "let" { t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral()) @@ -534,27 +740,6 @@ func testLiteralExpression( return false } -func testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool { - bo, ok := exp.(*ast.Boolean) - if !ok { - t.Errorf("exp *ast.Boolean. got=%T", exp) - return false - } - - if bo.Value != value { - t.Errorf("bo.Value not %t. got=%t", value, bo.Value) - return false - } - - if bo.TokenLiteral() != fmt.Sprintf("%t", value) { - t.Errorf("intboeg.TokenLiteral not %t. got=%s", value, - bo.TokenLiteral()) - return false - } - - return true -} - func testIntegerLiteral(t *testing.T, il ast.Expression, value int64) bool { integ, ok := il.(*ast.IntegerLiteral) if !ok { @@ -597,6 +782,27 @@ func testIdentifier(t *testing.T, exp ast.Expression, value string) bool { return true } +func testBooleanLiteral(t *testing.T, exp ast.Expression, value bool) bool { + bo, ok := exp.(*ast.Boolean) + if !ok { + t.Errorf("exp not *ast.Boolean. got=%T", exp) + return false + } + + if bo.Value != value { + t.Errorf("bo.Value not %t. got=%t", value, bo.Value) + return false + } + + if bo.TokenLiteral() != fmt.Sprintf("%t", value) { + t.Errorf("bo.TokenLiteral not %t. got=%s", + value, bo.TokenLiteral()) + return false + } + + return true +} + func checkParserErrors(t *testing.T, p *Parser) { errors := p.Errors() if len(errors) == 0 {