From f09f2fda86033ed5bb179fa2a19bbfe7357d2a8f Mon Sep 17 00:00:00 2001 From: nibral Date: Mon, 28 Jan 2019 22:06:13 +0900 Subject: [PATCH] 3.9: Implement function call --- evaluator/evaluator.go | 61 ++++++++++++++++++++++++++++++++++++- evaluator/evaluator_test.go | 55 +++++++++++++++++++++++++++++++++ object/environment.go | 12 +++++++- object/object.go | 33 +++++++++++++++++++- 4 files changed, 158 insertions(+), 3 deletions(-) diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 9caaae1..7bc40df 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -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 { @@ -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 "!": diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index d0a6396..53b83ba 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -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) +} diff --git a/object/environment.go b/object/environment.go index bd89056..dbf1fd7 100644 --- a/object/environment.go +++ b/object/environment.go @@ -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 } diff --git a/object/object.go b/object/object.go index f7de5c9..3beefc3 100644 --- a/object/object.go +++ b/object/object.go @@ -1,6 +1,11 @@ package object -import "fmt" +import ( + "bytes" + "fmt" + "monkey_interpreter/ast" + "strings" +) type ObjectType string @@ -10,6 +15,7 @@ const ( NULL_OBJ = "NULL" RETURN_VALUE_OBJ = "RETURN_VALUE" ERROR_OBJ = "ERROR" + FUNCTION_OBJ = "FUNCTION" ) type Object interface { @@ -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() +}