Skip to content

Commit

Permalink
2.8: Add call expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
nibral committed Jan 13, 2019
1 parent 25a7d0b commit 05202be
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 0 deletions.
24 changes: 24 additions & 0 deletions ast/ast.go
Expand Up @@ -227,3 +227,27 @@ func (fl *FunctionLiteral) String() string {

return out.String()
}

type CallExpression struct {
Token token.Token // then '(' 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

var 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()
}
32 changes: 32 additions & 0 deletions parser/parser.go
Expand Up @@ -28,6 +28,7 @@ var precedences = map[token.TokenType]int{
token.MINUS: SUM,
token.SLASH: PRODUCT,
token.ASTERISK: PRODUCT,
token.LPAREN: CALL,
}

type (
Expand Down Expand Up @@ -77,6 +78,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)

return p
}
Expand Down Expand Up @@ -399,3 +401,33 @@ func (p *Parser) parseFunctionLiteral() ast.Expression {

return lit
}

func (p *Parser) parseCallArguments() []ast.Expression {
var 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) parseCallExpression(function ast.Expression) ast.Expression {
exp := &ast.CallExpression{Token: p.curToken, Function: function}
exp.Arguments = p.parseCallArguments()
return exp
}
50 changes: 50 additions & 0 deletions parser/parser_test.go
Expand Up @@ -435,6 +435,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 {
Expand Down Expand Up @@ -684,3 +696,41 @@ func TestFunctionParameterParsing(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.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)
}

0 comments on commit 05202be

Please sign in to comment.