aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2024-03-29 19:53:00 +0000
committerBobby <[email protected]>2024-03-29 19:53:00 +0000
commitc6a3c37f268ba3189a4902b852121e979c70817f (patch)
treed81011b7f0831f7df6d07bb49b363ebc15bd6a3b
parent87b188bae2c8a2a9f81e872805d072be7ec910b2 (diff)
downloadmana-c6a3c37f268ba3189a4902b852121e979c70817f.tar.xz
mana-c6a3c37f268ba3189a4902b852121e979c70817f.zip
functions and closures
-rw-r--r--evaluator/evaluator.go32
-rw-r--r--evaluator/evaluator_test.go13
-rw-r--r--object/environment.go13
3 files changed, 57 insertions, 1 deletions
diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go
index f765d02..87bfc48 100644
--- a/evaluator/evaluator.go
+++ b/evaluator/evaluator.go
@@ -75,6 +75,8 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
return args[0]
}
+ return applyFunction(function, args)
+
case *ast.FunctionLiteral:
params := node.Parameters
body := node.Body
@@ -92,6 +94,36 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
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)
+ evaulated := Eval(function.Body, extendedEnv)
+ return unwrapReturnValue(evaulated)
+}
+
+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 evalProgram(program *ast.Program, env *object.Environment) object.Object {
var result object.Object
diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go
index f83cf7f..75480dc 100644
--- a/evaluator/evaluator_test.go
+++ b/evaluator/evaluator_test.go
@@ -258,6 +258,19 @@ 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/object/environment.go b/object/environment.go
index bd89056..74b6ed2 100644
--- a/object/environment.go
+++ b/object/environment.go
@@ -1,16 +1,27 @@
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}
+ return &Environment{store: s, outer: nil}
}
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
}