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:
Tijani Lawal 2022-08-26 19:00:32 +00:00
parent 6f00316b43
commit cdf36e8211
4 changed files with 181 additions and 2 deletions

View File

@ -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 {

View File

@ -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)

View File

@ -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
} }

View File

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