Built-in functions:

len() added

git-svn-id: https://svn.tlawal.org/svn/monkey@59 f6afcba9-9ef1-4bdd-9b72-7484f5705bac
This commit is contained in:
Tijani Lawal 2022-11-11 20:30:15 +00:00
parent b028741d75
commit 40de215f70
6 changed files with 107 additions and 34 deletions

19
evaluator/builtins.go Normal file
View File

@ -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())
}
},
},
}

View File

@ -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 { func apply_function(fn object.Object, args []object.Object) object.Object {
function, ok := fn.(*object.Function) switch fn := fn.(type) {
if !ok { 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()) 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 { 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 { func eval_identifier(node *ast.Identifier, env *object.Environment) object.Object {
val, ok := env.Get(node.Value)
if !ok { if val, ok := env.Get(node.Value); ok {
return new_error("identifier not found: " + node.Value) 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 { 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: FEATURES TODO:
- Add support for string comparision '==' and '!='. - Add support for string comparision '==' and '!='.
*/ */
func eval_string_infix_expression(operator string, left object.Object, right object.Object) object.Object { func eval_string_infix_expression(operator string, left object.Object, right object.Object) object.Object {
if operator != "+" { if operator != "+" {

View File

@ -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 // Helpers
func test_eval(input string) object.Object { func test_eval(input string) object.Object {
l_lexer := lexer.New(input) l_lexer := lexer.New(input)

View File

@ -139,9 +139,9 @@ func is_digit(ch byte) bool {
} }
/* /*
Read the current character until it encounters a closing '"' or end of input. 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 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. reaches the end of input without proper termination. Support for character escaping would be really neat.
*/ */
func (l_lexer *Lexer) read_string() string { func (l_lexer *Lexer) read_string() string {
position := l_lexer.position + 1 position := l_lexer.position + 1

View File

@ -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 i = 5;
let print_num = fn(i) { let print_num = fn(i) {
puts(i); puts(i);
} }
print_num(10); print_num(10);
puts(i); puts(i);
``` ```
The ideal result of the above code in the monkey programming language is for 10 and 5 to be the outputs respectively. 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 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 would be overwritten. The ideal situation would be to preserve the previous binding to 'i' while also making a a new
one. one.
This works be creating a new instance of object.Environment with a pointer to the environment it should extend, doing this 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 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 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 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. anymore and it will error out to an unknown identifier.
*/ */
func NewEnclosedEnvironment(outer *Environment) *Environment { func NewEnclosedEnvironment(outer *Environment) *Environment {
env := NewEnvironment() env := NewEnvironment()

View File

@ -17,6 +17,7 @@ const (
ERROR_OBJECT = "ERROR" ERROR_OBJECT = "ERROR"
FUNCTION_OBJECT = "FUNCTION" FUNCTION_OBJECT = "FUNCTION"
STRING_OBJECT = "STRING" STRING_OBJECT = "STRING"
BUILTIN_OBJ = "BUILTIN"
) )
type Object interface { type Object interface {
@ -110,3 +111,18 @@ type String struct {
func (s *String) Type() ObjectType { return STRING_OBJECT } func (s *String) Type() ObjectType { return STRING_OBJECT }
func (s *String) Inspect() string { return s.Value } 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"
}