From dd53c3fcf6730fc5207d6116f809f3a7004a8d5d Mon Sep 17 00:00:00 2001 From: Maciej Kaczkowski Date: Tue, 8 Oct 2024 17:09:13 +0200 Subject: [PATCH] FEAT: functions' evaluation works and closures work too; 149/208 @ pdf --- monkey/README.md | 1 + monkey/interpreter/evaluator/evaluator.go | 34 +++++++++++++++++++ .../interpreter/evaluator/evaluator_test.go | 10 ++++++ monkey/interpreter/object/environment.go | 16 +++++++-- 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/monkey/README.md b/monkey/README.md index 70f6313..417a0bf 100644 --- a/monkey/README.md +++ b/monkey/README.md @@ -88,6 +88,7 @@ is represented as: * implementing internal error-handling is kinda similar to handling return statements, in the sense, that they both stop further evaluation * `environment` is used to keep track of values of variables, which are stored in `hash map` * function also carry their own `environment`, which allows using `closures` +* extendint `environment` is done by creating new `environment` with reference to the old one (weird, but alows each `function` to have its own `scope`) ### repl * `REPL` - `Read-Eval-Print Loop` diff --git a/monkey/interpreter/evaluator/evaluator.go b/monkey/interpreter/evaluator/evaluator.go index 406d5ab..9a04270 100644 --- a/monkey/interpreter/evaluator/evaluator.go +++ b/monkey/interpreter/evaluator/evaluator.go @@ -84,12 +84,46 @@ func Eval(node ast.Node, env *object.Environment) object.Object { if len(args) == 1 && isError(args[0]) { return args[0] } + + 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 evalExpressions( exps []ast.Expression, env *object.Environment, diff --git a/monkey/interpreter/evaluator/evaluator_test.go b/monkey/interpreter/evaluator/evaluator_test.go index 7cc2e3a..f7d3229 100644 --- a/monkey/interpreter/evaluator/evaluator_test.go +++ b/monkey/interpreter/evaluator/evaluator_test.go @@ -251,6 +251,16 @@ func TestFunctionApplication(t *testing.T) { } } +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) +} + func testEval(input string) object.Object { l := lexer.New(input) p := parser.New(l) diff --git a/monkey/interpreter/object/environment.go b/monkey/interpreter/object/environment.go index bd89056..40022fb 100644 --- a/monkey/interpreter/object/environment.go +++ b/monkey/interpreter/object/environment.go @@ -1,16 +1,26 @@ package object +func NewEnclosedEnvironment(outer *Environment) *Environment { + env := NewEnvironment() + env.outer = outer + return env +} + func NewEnvironment() *Environment { - s := make(map[string]Object) - return &Environment{store: s} + s := make(map[string]Object) // map symbol to it's meaning (value) + return &Environment{store: s, outer: nil} } type Environment struct { - store map[string]Object + store map[string]Object // map symbol to it's meaning (value) + 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 }