Skip to content

Commit

Permalink
3.9: Implement function call
Browse files Browse the repository at this point in the history
  • Loading branch information
nibral committed Jan 28, 2019
1 parent 0548f28 commit f09f2fd
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 3 deletions.
61 changes: 60 additions & 1 deletion evaluator/evaluator.go
Expand Up @@ -67,12 +67,57 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
return evalInfixExpression(node.Operator, left, right)
case *ast.IfExpression:
return evalIfExpression(node, env)

case *ast.FunctionLiteral:
params := node.Parameters
body := node.Body
return &object.Function{Parameters: params, Body: body, Env: env}
case *ast.CallExpression:
// eval function body
function := Eval(node.Function, env)
if isError(function) {
return function
}
// eval parameters
args := evalExpressions(node.Arguments, env)
if len(args) == 1 && isError(args[0]) {
return args[0]
}
// execute function
return applyFunction(function, args)
}

return nil
}

func applyFunction(fn object.Object, args []object.Object) object.Object {
function, ok := fn.(*object.Function)
if !ok {
return newError("not a function: %s", fn.Type())
}

extendedEnv := extendFunctionEnv(function, args)
evaluated := Eval(function.Body, extendedEnv)

return unwrapReturnValue(evaluated)
}

func extendFunctionEnv(fn *object.Function, args []object.Object) *object.Environment {
env := object.NewEnclosedEnvironment(fn.Env)

for paramIdx, param := range fn.Parameters {
env.Set(param.Value, args[paramIdx])
}

return env
}

func unwrapReturnValue(obj object.Object) object.Object {
if returnValue, ok := obj.(*object.ReturnValue); ok {
return returnValue.Value
}
return obj
}

func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object {
val, ok := env.Get(node.Value)
if !ok {
Expand Down Expand Up @@ -115,6 +160,20 @@ func evalBlockStatement(block *ast.BlockStatement, env *object.Environment) obje
return result
}

func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Object {
var result []object.Object

for _, e := range exps {
evaluated := Eval(e, env)
if isError(evaluated) {
return []object.Object{evaluated}
}
result = append(result, evaluated)
}

return result
}

func evalPrefixExpression(operator string, right object.Object) object.Object {
switch operator {
case "!":
Expand Down
55 changes: 55 additions & 0 deletions evaluator/evaluator_test.go
Expand Up @@ -263,3 +263,58 @@ func TestLetStatements(t *testing.T) {
testIntegerObject(t, testEval(tt.input), tt.expected)
}
}

func TestFunctionObject(t *testing.T) {
input := "fn(x) { x + 2; };"

evaluated := testEval(input)
fn, ok := evaluated.(*object.Function)
if !ok {
t.Fatalf("object is not Function. got=%T (%+v)", evaluated, evaluated)
}

if len(fn.Parameters) != 1 {
t.Fatalf("function has wrong parameters. Parameters=%+v",
fn.Parameters)
}

if fn.Parameters[0].String() != "x" {
t.Fatalf("parameter is not 'x'. got=%q", fn.Parameters[0])
}

expectedBody := "(x + 2)"

if fn.Body.String() != expectedBody {
t.Fatalf("body is not %q. got=%q", expectedBody, fn.Body.String())
}
}

func TestFunctionApplication(t *testing.T) {
tests := []struct {
input string
expected int64
}{
{"let identity = fn(x) { x; }; identity(5);", 5},
{"let identity = fn(x) { return x; }; identity(5);", 5},
{"let double = fn(x) { x * 2; }; double(5);", 10},
{"let add = fn(x, y) { x + y; }; add(5, 5);", 10},
{"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20},
{"fn(x) { x; }(5)", 5},
}

for _, tt := range tests {
testIntegerObject(t, testEval(tt.input), tt.expected)
}
}

func TestClosures(t *testing.T) {
input := `
let newAdder = fn(x) {
fn(y) { x + y };
};
let addTwo = newAdder(2);
addTwo(2);`

testIntegerObject(t, testEval(input), 4)
}
12 changes: 11 additions & 1 deletion object/environment.go
Expand Up @@ -2,15 +2,25 @@ package object

func NewEnvironment() *Environment {
s := make(map[string]Object)
return &Environment{store: s}
return &Environment{store: s, outer: nil}
}

func NewEnclosedEnvironment(outer *Environment) *Environment {
env := NewEnvironment()
env.outer = outer
return env
}

type Environment struct {
store map[string]Object
outer *Environment
}

func (e *Environment) Get(name string) (Object, bool) {
obj, ok := e.store[name]
if !ok && e.outer != nil {
obj, ok = e.outer.Get(name)
}
return obj, ok
}

Expand Down
33 changes: 32 additions & 1 deletion object/object.go
@@ -1,6 +1,11 @@
package object

import "fmt"
import (
"bytes"
"fmt"
"monkey_interpreter/ast"
"strings"
)

type ObjectType string

Expand All @@ -10,6 +15,7 @@ const (
NULL_OBJ = "NULL"
RETURN_VALUE_OBJ = "RETURN_VALUE"
ERROR_OBJ = "ERROR"
FUNCTION_OBJ = "FUNCTION"
)

type Object interface {
Expand Down Expand Up @@ -49,3 +55,28 @@ type Error struct {

func (e *Error) Type() ObjectType { return ERROR_OBJ }
func (e *Error) Inspect() string { return "ERROR: " + e.Message }

type Function struct {
Parameters []*ast.Identifier
Body *ast.BlockStatement
Env *Environment
}

func (f *Function) Type() ObjectType { return FUNCTION_OBJ }
func (f *Function) Inspect() string {
var out bytes.Buffer

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

out.WriteString("fn")
out.WriteString("(")
out.WriteString(strings.Join(params, ", "))
out.WriteString(") {\n")
out.WriteString(f.Body.String())
out.WriteString("}")

return out.String()
}

0 comments on commit f09f2fd

Please sign in to comment.