From 40de215f70de74e9073f15c507d306fd8058c17e Mon Sep 17 00:00:00 2001 From: tijani Date: Fri, 11 Nov 2022 20:30:15 +0000 Subject: [PATCH] Built-in functions: len() added git-svn-id: https://svn.tlawal.org/svn/monkey@59 f6afcba9-9ef1-4bdd-9b72-7484f5705bac --- evaluator/builtins.go | 19 +++++++++++++++++++ evaluator/evaluator.go | 31 ++++++++++++++++++------------ evaluator/evaluator_test.go | 31 ++++++++++++++++++++++++++++++ lexer/lexer.go | 6 +++--- object/environment.go | 38 ++++++++++++++++++------------------- object/object.go | 16 ++++++++++++++++ 6 files changed, 107 insertions(+), 34 deletions(-) create mode 100644 evaluator/builtins.go diff --git a/evaluator/builtins.go b/evaluator/builtins.go new file mode 100644 index 0000000..a6c7dfd --- /dev/null +++ b/evaluator/builtins.go @@ -0,0 +1,19 @@ +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 1ff45b3..790c960 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -111,14 +111,18 @@ 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 { + 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 function: %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 +155,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 { @@ -267,8 +274,8 @@ func eval_integer_infix_expression(operator string, left object.Object, right ob } /* - FEATURES TODO: - - Add support for string comparision '==' and '!='. +FEATURES TODO: +- Add support for string comparision '==' and '!='. */ func eval_string_infix_expression(operator string, left object.Object, right object.Object) object.Object { if operator != "+" { diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index 6147690..eb6e0e1 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -268,6 +268,37 @@ func TestStringConcatenation(l_test *testing.T) { } } +func TestBuiltinFunctions(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/lexer/lexer.go b/lexer/lexer.go index f16f7f3..0f472a7 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -139,9 +139,9 @@ func is_digit(ch byte) bool { } /* - Read the current character until it encounters a closing '"' or end of input. - TODO: some additional thing that can be done at the lexer level with strings is to report an error when it - reaches the end of input without proper termination. Support for character escaping would be really neat. +Read the current character until it encounters a closing '"' or end of input. +TODO: some additional thing that can be done at the lexer level with strings is to report an error when it +reaches the end of input without proper termination. Support for character escaping would be really neat. */ func (l_lexer *Lexer) read_string() string { position := l_lexer.position + 1 diff --git a/object/environment.go b/object/environment.go index 851ba5b..c73a8b2 100644 --- a/object/environment.go +++ b/object/environment.go @@ -18,30 +18,30 @@ func NewEnvironment() *Environment { } /* - Enclosing Environments + Enclosing Environments - Here is a problem case, lets say in monkey I would want to type this: + Here is a problem case, lets say in monkey I would want to type this: - ``` - let i = 5; - let print_num = fn(i) { - puts(i); - } + ``` + let i = 5; + let print_num = fn(i) { + puts(i); + } - print_num(10); - puts(i); - ``` + print_num(10); + puts(i); + ``` - The ideal result of the above code in the monkey programming language is for 10 and 5 to be the outputs respectively. - In a situation where enclosed environment does not exists, both outputs will be 10 because the current value of i - would be overwritten. The ideal situation would be to preserve the previous binding to 'i' while also making a a new - one. +The ideal result of the above code in the monkey programming language is for 10 and 5 to be the outputs respectively. +In a situation where enclosed environment does not exists, both outputs will be 10 because the current value of i +would be overwritten. The ideal situation would be to preserve the previous binding to 'i' while also making a a new +one. - This works be creating a new instance of object.Environment with a pointer to the environment it should extend, doing this - encloses a fresh and empty environment with an existing one. When the Get method is called and it itself doesn't have the value - associated with the given name, it calls the Get of the enclosing environment. That's the environment it's extending. If that - enclosing environment can't find the value, it calls its own enclosing environment and so on until there is no enclosing environment - anymore and it will error out to an unknown identifier. +This works be creating a new instance of object.Environment with a pointer to the environment it should extend, doing this +encloses a fresh and empty environment with an existing one. When the Get method is called and it itself doesn't have the value +associated with the given name, it calls the Get of the enclosing environment. That's the environment it's extending. If that +enclosing environment can't find the value, it calls its own enclosing environment and so on until there is no enclosing environment +anymore and it will error out to an unknown identifier. */ func NewEnclosedEnvironment(outer *Environment) *Environment { env := NewEnvironment() diff --git a/object/object.go b/object/object.go index a4a8f88..47ed104 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 { @@ -110,3 +111,18 @@ type String struct { func (s *String) Type() ObjectType { return STRING_OBJECT } func (s *String) Inspect() string { return s.Value } + +// Builtin 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" +}