Skip to content

Commit

Permalink
✨ Parse call expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
ChmielewskiKamil committed Sep 7, 2024
1 parent 6e09e7c commit a23b0fe
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 0 deletions.
27 changes: 27 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -117,6 +130,7 @@ func (*NumberLiteral) expressionNode() {}
func (*BooleanLiteral) expressionNode() {}
func (*PrefixExpression) expressionNode() {}
func (*InfixExpression) expressionNode() {}
func (*CallExpression) expressionNode() {}

// String() implementations for Expressions

Expand Down Expand Up @@ -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
Expand Down
47 changes: 47 additions & 0 deletions parser/exprparsing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
34 changes: 34 additions & 0 deletions parser/exprparsing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit a23b0fe

Please sign in to comment.