From a23b0fefdf054454c9d28c52ddfa610925d00208 Mon Sep 17 00:00:00 2001 From: Kamil Chmielewski <45183584+ChmielewskiKamil@users.noreply.github.com> Date: Sat, 7 Sep 2024 15:22:42 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Parse=20call=20expressions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ast/ast.go | 27 ++++++++++++++++++++++ parser/exprparsing.go | 47 ++++++++++++++++++++++++++++++++++++++ parser/exprparsing_test.go | 34 +++++++++++++++++++++++++++ parser/parser.go | 2 ++ 4 files changed, 110 insertions(+) diff --git a/ast/ast.go b/ast/ast.go index 4c59d5f..9824d95 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -80,6 +80,12 @@ type ( Operator token.Token // operator token Right Expression // right operand } + + CallExpression struct { + Pos token.Pos // Position of the identifier being called + Function Expression // Function being called + Args []Expression // Comma-separated list of arguments + } ) // Start() and End() implementations for Expression type Nodes @@ -107,6 +113,13 @@ func (x *InfixExpression) Start() token.Pos { return x.Pos } func (x *InfixExpression) End() token.Pos { return x.Right.End() } +func (x *CallExpression) Start() token.Pos { return x.Pos } +func (x *CallExpression) End() token.Pos { + if len(x.Args) > 0 { + return x.Args[len(x.Args)-1].End() + 1 // @TODO: Shouldnt +2? + } + return x.Pos + 2 // length of "()" +} // expressionNode() implementations to ensure that only expressions can be // assigned to an Expression. This is useful if by mistake we try to use @@ -117,6 +130,7 @@ func (*NumberLiteral) expressionNode() {} func (*BooleanLiteral) expressionNode() {} func (*PrefixExpression) expressionNode() {} func (*InfixExpression) expressionNode() {} +func (*CallExpression) expressionNode() {} // String() implementations for Expressions @@ -147,6 +161,19 @@ func (x *InfixExpression) String() string { out.WriteString(")") return out.String() } +func (x *CallExpression) String() string { + var out bytes.Buffer + out.WriteString(x.Function.String()) + out.WriteString("(") + for i, arg := range x.Args { + if i > 0 { + out.WriteString(", ") + } + out.WriteString(arg.String()) + } + out.WriteString(")") + return out.String() +} /*~*~*~*~*~*~*~*~*~*~*~*~*~* Types ~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*/ // Type nodes are constrains on expressions. They define the kinds of values diff --git a/parser/exprparsing.go b/parser/exprparsing.go index 92ea410..2875c37 100644 --- a/parser/exprparsing.go +++ b/parser/exprparsing.go @@ -114,6 +114,7 @@ var precedences = map[token.TokenType]int{ // 1. // @TODO: The function calls, array subscripting, member access etc. // is harder to implement. + token.LPAREN: HIGHEST, } func (p *Parser) peekPrecedence() int { @@ -307,3 +308,49 @@ func (p *Parser) parseElementaryType() ast.Expression { return et } + +// parseCallExpression is called when LPAREN is encountered in an +// infix position. That's why we have access to the function ident +// and we can pass it as an argument to the function. +func (p *Parser) parseCallExpression(fn ast.Expression) ast.Expression { + if p.trace { + defer un(trace("parseCallExpression")) + } + + callExp := &ast.CallExpression{ + Pos: fn.Start(), // TODO: fn is not passed as ref, will this work? + Function: fn, + } + + callExp.Args = p.parseCallArguments() + return callExp +} + +func (p *Parser) parseCallArguments() []ast.Expression { + if p.trace { + defer un(trace("parseCallArguments")) + } + + args := []ast.Expression{} + if p.peekTkn.Type == token.RPAREN { + p.nextToken() + return args + } + + // Move to the first arg. + p.nextToken() + args = append(args, p.parseExpression(LOWEST)) + + for p.peekTkn.Type == token.COMMA { + p.nextToken() // Sitting on comma + p.nextToken() // Move to the next arg + + args = append(args, p.parseExpression(LOWEST)) + } + + if !p.expectPeek(token.RPAREN) { + return nil + } + + return args +} diff --git a/parser/exprparsing_test.go b/parser/exprparsing_test.go index b2d1091..4d3ea53 100644 --- a/parser/exprparsing_test.go +++ b/parser/exprparsing_test.go @@ -274,6 +274,40 @@ func Test_ParseOperatorPrecedence(t *testing.T) { } } +func Test_ParseCallExpression(t *testing.T) { + src := `function test() public { + foo(a + b, 3 * 5, bar); + }` + + file := test_helper_parseSource(t, src, false) + + fnBody := test_helper_parseFnBody(t, file) + + if len(fnBody.Statements) != 1 { + t.Fatalf("Expected 1 statement, got %d", len(fnBody.Statements)) + } + + exprStmt, ok := fnBody.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("Expected ExpressionStatement, got %T", fnBody.Statements[0]) + } + + callExpr, ok := exprStmt.Expression.(*ast.CallExpression) + if !ok { + t.Fatalf("Expected CallExpression, got %T", exprStmt.Expression) + } + + test_Identifier(t, callExpr.Function, "foo") + + if len(callExpr.Args) != 3 { + t.Fatalf("Expected 3 arguments, got %d", len(callExpr.Args)) + } + + test_InfixExpression(t, callExpr.Args[0], "a", "+", "b") + test_InfixExpression(t, callExpr.Args[1], big.NewInt(3), "*", big.NewInt(5)) + test_LiteralExpression(t, callExpr.Args[2], "bar") +} + /*~*~*~*~*~*~*~*~*~*~*~*~* Helper Functions ~*~*~*~*~*~*~*~*~*~*~*~*~*/ func test_helper_parseSource(t *testing.T, src string, tracing bool) *ast.File { diff --git a/parser/parser.go b/parser/parser.go index 4dd2228..b2898cd 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -87,6 +87,8 @@ func (p *Parser) Init(file *token.File) { p.registerInfix(token.ASSIGN_MUL, p.parseInfixExpression) p.registerInfix(token.ASSIGN_DIV, p.parseInfixExpression) p.registerInfix(token.ASSIGN_MOD, p.parseInfixExpression) + + p.registerInfix(token.LPAREN, p.parseCallExpression) } func registerPrefixElementaryTypes(p *Parser) {