Evaluate function literals and function calls.

Signed-off-by: jmug <u.g.a.mariano@gmail.com>
This commit is contained in:
Mariano Uvalle 2025-01-09 16:33:19 -08:00
parent 500a058ff8
commit a76f47a7a3
4 changed files with 137 additions and 1 deletions

View file

@ -59,6 +59,20 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
env.Set(node.Name.Value, val)
case *ast.Identifier:
return evalIdentifier(node, env)
case *ast.FunctionLiteral:
params := node.Parameters
body := node.Body
return &object.Function{Parameters: params, Body: body, Env: env}
case *ast.CallExpression:
fn := Eval(node.Function, env)
if isError(fn) {
return fn
}
args := evalExpressions(node.Arguments, env)
if len(args) == 1 && isError(args[0]) {
return args[0]
}
return applyFunction(fn, args)
}
return nil
}
@ -184,6 +198,46 @@ func evalIdentifier(exp *ast.Identifier, env *object.Environment) object.Object
return val
}
func evalExpressions(
exps []ast.Expression,
env *object.Environment,
) []object.Object {
var res []object.Object
for _, exp := range exps {
ev := Eval(exp, env)
if isError(ev) {
return []object.Object{ev}
}
res = append(res, ev)
}
return res
}
func applyFunction(fnObj object.Object, args []object.Object) object.Object {
fn, ok := fnObj.(*object.Function)
if !ok {
return newError("not a function: %s", fn.Type())
}
env := extendFunctionEnv(fn, args)
ret := Eval(fn.Body, env)
return unwrapReturnValue(ret)
}
func extendFunctionEnv(fn *object.Function, args []object.Object) *object.Environment {
env := object.NewEnclosedEnvironment(fn.Env)
for pi, param := range fn.Parameters {
env.Set(param.Value, args[pi])
}
return env
}
func unwrapReturnValue(obj object.Object) object.Object {
if ret, ok := obj.(*object.ReturnValue); ok {
return ret.Value
}
return obj
}
func isTruthy(obj object.Object) bool {
switch obj {
case _TRUE:

View file

@ -221,6 +221,49 @@ func TestLetStatements(t *testing.T) {
}
}
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 testNullObject(t *testing.T, obj object.Object) bool {
if obj != _NULL {
t.Errorf("object is not NULL. got=%T (%+v)", obj, obj)