Skip to content

Commit

Permalink
Add function literal declaration statement (#65)
Browse files Browse the repository at this point in the history
  • Loading branch information
DustTheory authored Nov 13, 2023
1 parent a326faa commit 480b297
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 0 deletions.
24 changes: 24 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,3 +494,27 @@ func (is *ImportStatement) String() string {

return out.String()
}

func (fd *FunctionDeclarationStatement) statementNode() {}
func (fd *FunctionDeclarationStatement) TokenLiteral() string { return fd.Token.Literal }
func (fd *FunctionDeclarationStatement) TokenValue() token.Token { return fd.Token }
func (fd *FunctionDeclarationStatement) String() string {
var out bytes.Buffer

params := []string{}
for _, p := range fd.Parameters {
params = append(params, p.String())
}

out.WriteString("fn ")
out.WriteString(fd.Name.String())
out.WriteString("(")
out.WriteString(strings.Join(params, ", "))
out.WriteString(")->")
out.WriteString(fd.Type.ReturnType.String())
out.WriteString("{")
out.WriteString(fd.Body.String())
out.WriteString("}")

return out.String()
}
8 changes: 8 additions & 0 deletions ast/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,11 @@ type ExportStatement struct {
Token token.Token
Statement
}

type FunctionDeclarationStatement struct {
Token token.Token
Name *Identifier
Parameters []*Identifier
Body *BlockStatement
Type FunctionType
}
38 changes: 38 additions & 0 deletions parser/parse_function_declaration_statement.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package parser

import (
"github.com/0xM-D/interpreter/ast"
"github.com/0xM-D/interpreter/token"
)

func (p *Parser) parseFunctionDeclarationStatement() *ast.FunctionDeclarationStatement {
stmt := &ast.FunctionDeclarationStatement{Token: p.curToken}

p.nextToken() // "fn"

stmt.Name = p.parseIdentifier().(*ast.Identifier)

if !p.expectPeek(token.LPAREN) {
return nil
}

stmt.Parameters, stmt.Type.ParameterTypes = p.parseFunctionParameters()

if !p.expectPeek(token.DASH_ARROW) {
return nil
}
p.nextToken()

stmt.Type.ReturnType = p.parseType()
if stmt.Type.ReturnType == nil {
return nil
}

if !p.expectPeek(token.LBRACE) {
return nil
}

stmt.Body = p.parseBlockStatement()

return stmt
}
51 changes: 51 additions & 0 deletions parser/parse_function_declaration_statement_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package parser

import (
"testing"

"github.com/0xM-D/interpreter/ast"
"github.com/0xM-D/interpreter/lexer"
)

func TestFunctionDeclarationStatement(t *testing.T) {
input := `fn functionName(param1: int, param2: string) -> int { return param1 + param2 }`

l := lexer.New(input)
p := New(l)
program := p.ParseProgram()
checkParserErrors(t, p)

if len(program.Statements) != 1 {
t.Fatalf("program.Body does not contain %d statements. got=%d", 1, len(program.Statements))
}

functionDeclaration, ok := program.Statements[0].(*ast.FunctionDeclarationStatement)
if !ok {
t.Fatalf("program.Statements[0] is not ast.FunctionDeclarationStatement. got=%T", program.Statements[0])
}

if functionDeclaration.Name.Value != "functionName" {
t.Fatalf("function name wrong. want=%s, got=%s", "functionName", functionDeclaration.Name.Value)
}

if len(functionDeclaration.Parameters) != 2 {
t.Fatalf("function literal parameters wrong. want 2, got=%d\n", len(functionDeclaration.Parameters))
}

testLiteralExpression(t, functionDeclaration.Parameters[0], TestIdentifier{"param1"})
testLiteralExpression(t, functionDeclaration.Parameters[1], TestIdentifier{"param2"})

if len(functionDeclaration.Body.Statements) != 1 {
t.Fatalf("function.Body.Statements has not 1 statements. got=%d\n",
len(functionDeclaration.Body.Statements))
}

bodyStmt, ok := functionDeclaration.Body.Statements[0].(*ast.ReturnStatement)
if !ok {
t.Fatalf("function body stmt is not ast.ReturnStatement. got=%T",
functionDeclaration.Body.Statements[0])
}

testInfixExpression(t, bodyStmt.ReturnValue, TestIdentifier{"param1"}, "+", TestIdentifier{"param2"})

}
6 changes: 6 additions & 0 deletions parser/parse_statement.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ func (p *Parser) parseStatement() ast.Statement {
return p.parseImportStatement()
case token.EXPORT:
return p.parseExportStatement()
case token.FUNCTION:
switch p.peekToken.Type {
case token.IDENT:
return p.parseFunctionDeclarationStatement()
}
fallthrough
case token.IDENT:
switch p.peekToken.Type {
case token.DECL_ASSIGN:
Expand Down
2 changes: 2 additions & 0 deletions runtime/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ func (r *Runtime) Eval(node ast.Node, env *object.Environment) (object.Object, e
return r.evalImportStatement(node, env)
case *ast.ExportStatement:
return r.evalExportStatement(node, env)
case *ast.FunctionDeclarationStatement:
return r.evalFunctionDeclarationStatement(node, env)
}

return nil, nil
Expand Down
22 changes: 22 additions & 0 deletions runtime/eval_function_declaration_statement.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package runtime

import (
"github.com/0xM-D/interpreter/ast"
"github.com/0xM-D/interpreter/object"
)

func (r *Runtime) evalFunctionDeclarationStatement(node *ast.FunctionDeclarationStatement, env *object.Environment) (object.Object, error) {
functionType, err := r.evalType(node.Type, env)
if err != nil {
return nil, err
}

function := &object.Function{
Parameters: node.Parameters,
Env: env,
Body: node.Body,
FunctionObjectType: *functionType.(*object.FunctionObjectType),
}

return env.Declare(node.Name.Value, true, function)
}
22 changes: 22 additions & 0 deletions runtime/eval_function_declaration_statement_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package runtime

import (
"math/big"
"testing"
)

func TestFunctionDeclarationStatement(t *testing.T) {
input := `
fn functionName(x: int64)->int64 {
return x * 2;
}
functionName(50)
`

evaluated, err := testEval(input)
if err != nil {
t.Fatal(err)
}

testIntegerObject(t, evaluated, big.NewInt(100))
}

0 comments on commit 480b297

Please sign in to comment.