diff --git a/evaluator/builtins.go b/evaluator/builtins.go new file mode 100644 index 0000000..064c580 --- /dev/null +++ b/evaluator/builtins.go @@ -0,0 +1,20 @@ +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 new_error("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 new_error("argument to `len` not supported, got %s", args[0].Type()) + } + }, + }, +} diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index fef25ea..8a8d3c9 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -111,14 +111,19 @@ func eval_program(program *ast.Program, env *object.Environment) object.Object { } func apply_function(fn object.Object, args []object.Object) object.Object { - function, ok := fn.(*object.Function) - if !ok { - return new_error("not a function: %s", fn.Type()) + switch fn := fn.(type) { + case *object.Function: + extended_env := extend_function_env(fn, args) + evaluated := Eval(fn.Body, extended_env) + return unwrap_return_value(evaluated) + + case *object.Builtin: + return fn.Fn(args...) + + default: + return new_error("not a funciton: %s", fn.Type()) } - extended_env := extend_function_env(function, args) - evaluated := Eval(function.Body, extended_env) - return unwrap_return_value(evaluated) } func extend_function_env(fn *object.Function, args []object.Object) *object.Environment { @@ -151,11 +156,14 @@ func eval_expression(expressions []ast.Expression, env *object.Environment) []ob } func eval_identifier(node *ast.Identifier, env *object.Environment) object.Object { - val, ok := env.Get(node.Value) - if !ok { - return new_error("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 new_error("identifier not found: " + node.Value) } func eval_block_statement(block *ast.BlockStatement, env *object.Environment) object.Object { diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index 058ae42..abd5b01 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -270,6 +270,37 @@ func TestStringConcatenation(l_test *testing.T) { } } +func TestBuiltinFunction(l_test *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 := test_eval(tt.input) + + switch expected := tt.expected.(type) { + case int: + test_integer_object(l_test, evaluated, int64(expected)) + case string: + error_object, ok := evaluated.(*object.Error) + if !ok { + l_test.Errorf("object is not Error, got=%T (%+v)", evaluated, evaluated) + continue + } + if error_object.Message != expected { + l_test.Errorf("wrong error message, expected=%q, got=%q", expected, error_object.Message) + } + } + } +} + // Helpers func test_eval(input string) object.Object { l_lexer := lexer.New(input) diff --git a/object/object.go b/object/object.go index b4091a4..319b141 100644 --- a/object/object.go +++ b/object/object.go @@ -17,6 +17,7 @@ const ( ERROR_OBJECT = "ERROR" FUNCTION_OBJECT = "FUNCTION" STRING_OBJECT = "STRING" + BUILTIN_OBJ = "BUILTIN" ) type Object interface { @@ -109,4 +110,14 @@ type String struct { } func (s *String) Type() ObjectType { return STRING_OBJECT } -func (s *String) Inspect() string { return s.Value } +func (s *String) Inspect() string { return s.Value } + +// Built-in functions +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" }