commit 4b22811f7acc064528a19ddf71c11c337afcd2d6
parent 8c8be1578f07ada04e5b7e54fe3150b52c4b18c2
Author: bsandro <email@bsandro.tech>
Date: Tue, 21 Jun 2022 01:07:50 +0300
function literals parsing
Diffstat:
3 files changed, 119 insertions(+), 0 deletions(-)
diff --git a/ast/ast.go b/ast/ast.go
@@ -3,6 +3,7 @@ package ast
import (
"bytes"
"interp/token"
+ "strings"
)
type Node interface {
@@ -217,3 +218,27 @@ func (bs *BlockStatement) String() string {
}
return out.String()
}
+
+type FunctionLiteral struct {
+ Token token.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()
+}
diff --git a/parser/parser.go b/parser/parser.go
@@ -74,6 +74,8 @@ func New(l *lexer.Lexer) *Parser {
p.registerPrefix(token.IF, p.parseIfExpression)
+ p.registerPrefix(token.FUNCTION, p.parseFunctionLiteral)
+
return p
}
@@ -304,3 +306,37 @@ func (p *Parser) parseBlockStatement() *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.LCURLY) {
+ 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
+}
diff --git a/parser/parser_test.go b/parser/parser_test.go
@@ -480,3 +480,61 @@ func TestIfElseExpression(t *testing.T) {
return
}
}
+
+func TestFunctionLiteralParsing(t *testing.T) {
+ input := "fn(x,y) { x+y; }"
+ l := lexer.New(input)
+ p := New(l)
+ prg := p.ParseProgram()
+ checkParserErrors(t, p)
+ if len(prg.Statements) != 1 {
+ t.Fatalf("prg.Statements count is wrong")
+ }
+ statement, ok := prg.Statements[0].(*ast.ExpressionStatement)
+ if !ok {
+ t.Fatalf("prg.Statement[0] is not expressionstatement but %T", prg.Statements[0])
+ }
+ function, ok := statement.Expression.(*ast.FunctionLiteral)
+ if !ok {
+ t.Fatalf("statement.Expression is not a function but %T", statement.Expression)
+ }
+ if len(function.Parameters) != 2 {
+ t.Fatalf("function parameters count is wrong")
+ }
+ testLiteralExpression(t, function.Parameters[0], "x")
+ testLiteralExpression(t, function.Parameters[1], "y")
+ if len(function.Body.Statements) != 1 {
+ t.Fatalf("function body statements count is wrong")
+ }
+ bodyStatement, ok := function.Body.Statements[0].(*ast.ExpressionStatement)
+ if !ok {
+ t.Fatalf("body statement is not an expression")
+ }
+ testInfixExpression(t, bodyStatement.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)
+ prg := p.ParseProgram()
+ checkParserErrors(t, p)
+ statement := prg.Statements[0].(*ast.ExpressionStatement)
+ function := statement.Expression.(*ast.FunctionLiteral)
+ if len(function.Parameters) != len(tt.expectedParams) {
+ t.Errorf("parameters count is wrong (%d instead of %d)", len(function.Parameters), len(tt.expectedParams))
+ }
+ for i, ident := range tt.expectedParams {
+ testLiteralExpression(t, function.Parameters[i], ident)
+ }
+ }
+}