umx_compiler

UMX virtual machine "Monkey" interpreter / bytecode compiler
git clone git://bsandro.tech/umx_compiler
Log | Files | Refs

commit 4b22811f7acc064528a19ddf71c11c337afcd2d6
parent 8c8be1578f07ada04e5b7e54fe3150b52c4b18c2
Author: bsandro <email@bsandro.tech>
Date:   Tue, 21 Jun 2022 01:07:50 +0300

function literals parsing

Diffstat:
Mast/ast.go | 25+++++++++++++++++++++++++
Mparser/parser.go | 36++++++++++++++++++++++++++++++++++++
Mparser/parser_test.go | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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) + } + } +}