From 1117f2c104bf03480da5836ce9b0ca12ff8bf887 Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 12 Oct 2024 00:09:25 +0300 Subject: [PATCH] closures --- callable.go | 56 ++++++++++++++++++++++++++------------------- globals.go | 18 +++++++++------ interpreter.go | 8 +++---- tests/functions.lox | 19 ++++++++++++++- 4 files changed, 65 insertions(+), 36 deletions(-) diff --git a/callable.go b/callable.go index eddffc9..be3cc74 100644 --- a/callable.go +++ b/callable.go @@ -1,36 +1,44 @@ package main -type Callable struct { - arity int - call func(*Interpreter, ...any) any +type Callable interface { + arity() int + call(i *Interpreter, args ...any) (ret any) } -func newCallable(f *FunStmt) *Callable { - return &Callable{ - arity: len(f.args), - call: func(i *Interpreter, args ...any) (ret any) { +type Function struct { + definition *FunStmt + closure *Environment +} - defer func() { - if err := recover(); err != nil { - re, ok := err.(Return) +func (f *Function) call(i *Interpreter, args ...any) (ret any) { - if !ok { - panic(err) - } + defer func() { + if err := recover(); err != nil { + re, ok := err.(Return) - ret = re.val - } - }() - - env := newEnvironment(i.globals) - - for idx, arg := range f.args { - env.define(arg.lexeme, args[idx]) + if !ok { + panic(err) } - i.executeBlock(f.body, env) + ret = re.val + } + }() - return nil - }, + env := newEnvironment(f.closure) + + for idx, arg := range f.definition.args { + env.define(arg.lexeme, args[idx]) } + + i.executeBlock(f.definition.body, env) + + return nil +} + +func (f *Function) arity() int { + return len(f.definition.args) +} + +func newFunction(fun *FunStmt, env *Environment) Callable { + return &Function{fun, env} } diff --git a/globals.go b/globals.go index 437ce88..458dac9 100644 --- a/globals.go +++ b/globals.go @@ -2,12 +2,16 @@ package main import "time" -func defineGlobals(env *Environment) { +type ClockFun struct{} - env.define("clock", &Callable{ - arity: 0, - call: func(i *Interpreter, arg ...any) any { - return time.Now().Unix() - }, - }) +func (cf *ClockFun) call(i *Interpreter, args ...any) any { + return time.Now().Unix() +} + +func (cf *ClockFun) arity() int { + return 0 +} + +func defineGlobals(env *Environment) { + env.define("clock", &ClockFun{}) } diff --git a/interpreter.go b/interpreter.go index 62d9945..debd954 100644 --- a/interpreter.go +++ b/interpreter.go @@ -171,18 +171,18 @@ func (i *Interpreter) visitCall(c *Call) any { args = append(args, i.evaluate(arg)) } - callable, ok := callee.(*Callable) + callable, ok := callee.(Callable) if !ok { i.panic(&RuntimeError{c.paren, "Can only call function and classes."}) } - if callable.arity != len(args) { + if callable.arity() != len(args) { i.panic(&RuntimeError{ c.paren, fmt.Sprintf( "Expected %d arguments but got %d", - callable.arity, + callable.arity(), len(args), ), }) @@ -192,7 +192,7 @@ func (i *Interpreter) visitCall(c *Call) any { } func (i *Interpreter) visitFunStmt(f *FunStmt) { - i.env.define(f.name.lexeme, newCallable(f)) + i.env.define(f.name.lexeme, newFunction(f, i.env)) } func (i *Interpreter) visitReturnStmt(r *ReturnStmt) { diff --git a/tests/functions.lox b/tests/functions.lox index f8ccf07..0865723 100644 --- a/tests/functions.lox +++ b/tests/functions.lox @@ -41,4 +41,21 @@ fun fib(n) { for (var i = 1; i <= 10; i = i + 1) { print fib(i); -} \ No newline at end of file +} + + +fun makeCounter() { + var i = 0; + + fun incr() { + i = i + 1; + print i; + } + + return incr; +} + +var counter = makeCounter(); + +counter(); +counter(); \ No newline at end of file