diff --git a/evaluator/builtins.go b/evaluator/builtins.go new file mode 100644 index 0000000..989e7c9 --- /dev/null +++ b/evaluator/builtins.go @@ -0,0 +1,21 @@ +package evaluator + +import "monkey/object" + +var builtins = map[string]*object.Builtin{ + "len": &object.Builtin{ + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", len(args)) + } + + switch arg := args[0].(type) { + case *object.String: + return &object.Integer{Value: int64(len(arg.Value))} + default: + return newError("argument to `len` not supported, got %s", args[0].Type()) + + } + }, + }, +} diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 6969853..b2f9213 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -264,11 +264,15 @@ func newError(format string, a ...interface{}) *object.Error { } func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object { - val, ok := env.Get(node.Value) - if !ok { - return newError("identifier not found: " + node.Value) + if val, ok := env.Get(node.Value); ok { + return val } - return val + + if builtin, ok := builtins[node.Value]; ok { + return builtin + } + + return newError("identifier not found: " + node.Value) } func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Object { @@ -286,14 +290,20 @@ func evalExpressions(exps []ast.Expression, env *object.Environment) []object.Ob } func applyFunction(fn object.Object, args []object.Object) object.Object { - function, ok := fn.(*object.Function) - if !ok { - newError("not a function: %s", fn.Type()) - } + switch fn := fn.(type) { - extendedEnv := extendFunctionEnv(function, args) - evaluated := Eval(function.Body, extendedEnv) - return unwrapReturnValue(evaluated) + case *object.Function: + extendedEnv := extendFunctionEnv(fn, args) + evaluated := Eval(fn.Body, extendedEnv) + return unwrapReturnValue(evaluated) + + case *object.Builtin: + return fn.Fn(args...) + + default: + return newError("not a function: %s", fn.Type()) + + } } func extendFunctionEnv(fn *object.Function, args []object.Object) *object.Environment { diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index dffabef..160258b 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -326,6 +326,37 @@ func TestStringConcatenation(t *testing.T) { } } +func TestBuiltinFunction(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {`len("")`, 0}, + {`len("four")`, 4}, + {`len("hello world")`, 11}, + {`len(1)`, "argument to `len` not supported, got INTEGER"}, + {`len("one", "two")`, "wrong number of arguments. got=2, want=1"}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + switch expected := tt.expected.(type) { + case int: + testIntegerObject(t, evaluated, int64(expected)) + case string: + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("object is not Error. got=%T (%+v)", evaluated, evaluated) + continue + } + if errObj.Message != expected { + t.Errorf("wrong error message. expected=%q, got=%q", expected, errObj.Message) + } + } + } +} + func testEval(input string) object.Object { l := lexer.New(input) p := parser.New(l) diff --git a/object/object.go b/object/object.go index 7d2db1d..6b1aa65 100644 --- a/object/object.go +++ b/object/object.go @@ -17,6 +17,7 @@ const ( ERROR_OBJ = "ERROR" FUNCTION_OBJ = "FUNCTION" STRING_OBJ = "STRING" + BUILTIN_OBJ = "BUILTIN" ) type Object interface { @@ -119,3 +120,16 @@ func (s *String) Type() ObjectType { func (s *String) Inspect() string { return s.Value } + +type BuiltinFunction func(args ...Object) Object + +type Builtin struct { + Fn BuiltinFunction +} + +func (b Builtin) Type() ObjectType { + return BUILTIN_OBJ +} +func (b Builtin) Inspect() string { + return "builtin function" +}