Просмотр исходного кода

bindings & The environment -- function object

runningwater 3 лет назад
Родитель
Сommit
6e77de5b98
3 измененных файлов с 101 добавлено и 1 удалено
  1. 26 0
      evaluator/evaluator.go
  2. 43 0
      evaluator/evaluator_test.go
  3. 32 1
      object/object.go

+ 26 - 0
evaluator/evaluator.go

@@ -60,6 +60,19 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
 			return val
 		}
 		env.Set(node.Name.Value, val)
+	case *ast.FunctionLiteral:
+		params := node.Parameters
+		body := node.Body
+		return &object.Function{Parameters: params, Body: body, Env: env}
+	case *ast.CallExpression:
+		function := Eval(node.Function, env)
+		if isError(function) {
+			return function
+		}
+		args := evalExpressions(node.Arguments, env)
+		if len(args) == 1 && isError(args[0]) {
+			return args[0]
+		}
 	}
 
 	return nil
@@ -199,6 +212,19 @@ func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object
 	return val
 }
 
+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 isTruthy(obj object.Object) bool {
 	switch obj {
 	case NULL:

+ 43 - 0
evaluator/evaluator_test.go

@@ -196,6 +196,49 @@ func TestLetStatement(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 identify = fn(x){return x;};identify(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)

+ 32 - 1
object/object.go

@@ -1,6 +1,11 @@
 package object
 
-import "fmt"
+import (
+	"bytes"
+	"fmt"
+	"github/runnignwater/monkey/ast"
+	"strings"
+)
 
 // ObjType type of object
 type ObjType string
@@ -11,6 +16,7 @@ const (
 	NullObj        = "NULL"
 	ReturnValueObj = "RETURN_VALUE"
 	ErrorObj       = "ERROR"
+	FunctionObj    = "FUNCTION"
 )
 
 // Object source code as an Object
@@ -51,3 +57,28 @@ type Error struct {
 
 func (e *Error) Type() ObjType   { return ErrorObj }
 func (e *Error) Inspect() string { return "ERROR: " + e.Msg }
+
+type Function struct {
+	Parameters []*ast.Identifier
+	Body       *ast.BlockStatement
+	Env        *Environment
+}
+
+func (f *Function) Type() ObjType { return FunctionObj }
+func (f *Function) Inspect() string {
+	var out bytes.Buffer
+
+	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("\n}")
+
+	return out.String()
+}