Monk now fully supports Functions, Function calls and closures.
git-svn-id: https://svn.tlawal.org/svn/monkey@54 f6afcba9-9ef1-4bdd-9b72-7484f5705bac
This commit is contained in:
parent
6f00316b43
commit
cdf36e8211
@ -69,6 +69,22 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
|
|||||||
|
|
||||||
case *ast.Identifier:
|
case *ast.Identifier:
|
||||||
return eval_identifier(node, env)
|
return eval_identifier(node, env)
|
||||||
|
|
||||||
|
case *ast.FunctionLiteral:
|
||||||
|
params := node.Parameters
|
||||||
|
body := node.Body
|
||||||
|
return &object.Function{Parameters: params, Env: env, Body: body}
|
||||||
|
|
||||||
|
case *ast.CallExpression:
|
||||||
|
function := Eval(node.Function, env)
|
||||||
|
if is_error(function) {
|
||||||
|
return function
|
||||||
|
}
|
||||||
|
args := eval_expression(node.Arguments, env)
|
||||||
|
if len(args) == 1 && is_error(args[0]) {
|
||||||
|
return args[0]
|
||||||
|
}
|
||||||
|
return apply_function(function, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -91,6 +107,46 @@ func eval_program(program *ast.Program, env *object.Environment) object.Object {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
env := object.NewEnclosedEnvironment(fn.Env)
|
||||||
|
|
||||||
|
for param_index, param := range fn.Parameters {
|
||||||
|
env.Set(param.Value, args[param_index])
|
||||||
|
}
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
|
||||||
|
func unwrap_return_value(obj object.Object) object.Object {
|
||||||
|
if return_value, ok := obj.(*object.ReturnValue); ok {
|
||||||
|
return return_value.Value
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
func eval_expression(expressions []ast.Expression, env *object.Environment) []object.Object {
|
||||||
|
var result []object.Object
|
||||||
|
|
||||||
|
for _, e := range expressions {
|
||||||
|
evaluated := Eval(e, env)
|
||||||
|
if is_error(evaluated) {
|
||||||
|
return []object.Object{evaluated}
|
||||||
|
}
|
||||||
|
result = append(result, evaluated)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
val, ok := env.Get(node.Value)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -184,6 +184,59 @@ func TestLetStatements(l_test *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFunctionObject(l_test *testing.T) {
|
||||||
|
input := "fn(x) { x + 2;};"
|
||||||
|
evaluated := test_eval(input)
|
||||||
|
|
||||||
|
fn, ok := evaluated.(*object.Function)
|
||||||
|
if !ok {
|
||||||
|
l_test.Fatalf("object is not Function, got=%T (%+v)", evaluated, evaluated)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fn.Parameters) != 1 {
|
||||||
|
l_test.Fatalf("function has wrong parameters, Parameters=%+v", fn.Parameters)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fn.Parameters[0].String() != "x" {
|
||||||
|
l_test.Fatalf("parameter is not 'x', got=%q", fn.Parameters[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
expected_body := "(x + 2)"
|
||||||
|
|
||||||
|
if fn.Body.String() != expected_body {
|
||||||
|
l_test.Fatalf("body is not %q, got=%q", expected_body, fn.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFunctionApplication(l_test *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
expected int64
|
||||||
|
}{
|
||||||
|
{"let identity = fn(x) { x; }; identity(5);", 5},
|
||||||
|
{"let identity = fn(x) { return x; }; identity(5);", 5},
|
||||||
|
{"let double = fn(x) { x * 2; }; double(5);", 10},
|
||||||
|
{"let add = fn(x, y) { x + y; }; add(5, 5);", 10},
|
||||||
|
{"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20},
|
||||||
|
{"fn(x) { x; }(5)", 5},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
test_integer_object(l_test, test_eval(tt.input), tt.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClosures(l_test *testing.T) {
|
||||||
|
input := `
|
||||||
|
let newAdder = fn(x) {
|
||||||
|
fn(y) { x + y };
|
||||||
|
};
|
||||||
|
let addTwo = newAdder(2);
|
||||||
|
addTwo(2);
|
||||||
|
`
|
||||||
|
test_integer_object(l_test, test_eval(input), 4)
|
||||||
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
|
@ -9,15 +9,53 @@ package object
|
|||||||
|
|
||||||
type Environment struct {
|
type Environment struct {
|
||||||
store map[string]Object
|
store map[string]Object
|
||||||
|
outer *Environment
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEnvironment() *Environment {
|
func NewEnvironment() *Environment {
|
||||||
s := make(map[string]Object)
|
s := make(map[string]Object)
|
||||||
return &Environment{store: s}
|
return &Environment{store: s, outer: nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Enclosing Environments
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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()
|
||||||
|
env.outer = outer
|
||||||
|
return env
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l_environment *Environment) Get(name string) (Object, bool) {
|
func (l_environment *Environment) Get(name string) (Object, bool) {
|
||||||
obj, ok := l_environment.store[name]
|
obj, ok := l_environment.store[name]
|
||||||
|
|
||||||
|
if !ok && l_environment.outer != nil {
|
||||||
|
obj, ok = l_environment.outer.Get(name)
|
||||||
|
}
|
||||||
|
|
||||||
return obj, ok
|
return obj, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"monkey/ast"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
type ObjectType string
|
type ObjectType string
|
||||||
|
|
||||||
@ -10,6 +15,7 @@ const (
|
|||||||
NULL_OBJECT = "NULL"
|
NULL_OBJECT = "NULL"
|
||||||
RETURN_VALUE_OBJECT = "RETURN_VALUE"
|
RETURN_VALUE_OBJECT = "RETURN_VALUE"
|
||||||
ERROR_OBJECT = "ERROR"
|
ERROR_OBJECT = "ERROR"
|
||||||
|
FUNCTION_OBJECT = "FUNCTION"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Object interface {
|
type Object interface {
|
||||||
@ -69,3 +75,29 @@ type Error struct {
|
|||||||
|
|
||||||
func (err *Error) Type() ObjectType { return ERROR_OBJECT }
|
func (err *Error) Type() ObjectType { return ERROR_OBJECT }
|
||||||
func (err *Error) Inspect() string { return "ERROR: " + err.Message }
|
func (err *Error) Inspect() string { return "ERROR: " + err.Message }
|
||||||
|
|
||||||
|
// Function
|
||||||
|
type Function struct {
|
||||||
|
Parameters []*ast.Identifier
|
||||||
|
Body *ast.BlockStatement
|
||||||
|
Env *Environment
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Function) Type() ObjectType { return FUNCTION_OBJECT }
|
||||||
|
func (f *Function) Inspect() string {
|
||||||
|
var out bytes.Buffer
|
||||||
|
|
||||||
|
params := []string{}
|
||||||
|
for _, p := range f.Parameters {
|
||||||
|
params = append(params, p.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
out.WriteString("fn")
|
||||||
|
out.WriteString("(")
|
||||||
|
out.WriteString(strings.Join(params, ", "))
|
||||||
|
out.WriteString(") {\n")
|
||||||
|
out.WriteString(f.Body.String())
|
||||||
|
out.WriteString("\n}")
|
||||||
|
|
||||||
|
return out.String()
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user