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:
parent
b028741d75
commit
40de215f70
19
evaluator/builtins.go
Normal file
19
evaluator/builtins.go
Normal 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())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
@ -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 != "+" {
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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"
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user