diff --git a/anselme/ast/Closure.lua b/anselme/ast/Closure.lua deleted file mode 100644 index 8e40926..0000000 --- a/anselme/ast/Closure.lua +++ /dev/null @@ -1,77 +0,0 @@ --- note: functions only appear in non-evaluated nodes! once evaluated, they always become closures - -local ast = require("anselme.ast") -local Overloadable, Runtime = ast.abstract.Overloadable, ast.abstract.Runtime - -local resume_manager - -local Closure -Closure = Runtime(Overloadable) { - type = "closure", - - func = nil, -- Function - scope = nil, -- Environment - - init = function(self, func, state) - self.func = func - - -- layer a new scope layer on top of captured/current scope - -- to allow future define in the function (fn.:var = "foo") - state.scope:push() - self.scope = state.scope:capture() - state.scope:define(ast.Symbol:new("_calling_environment"), self.scope) - state.scope:pop() - end, - - _format = function(self, ...) - return self.func:format(...) - end, - - traverse = function(self, fn, ...) - fn(self.func, ...) - fn(self.scope, ...) - end, - - compatible_with_arguments = function(self, state, args) - return args:match_parameter_tuple(state, self.func.parameters) - end, - format_parameters = function(self, state) - return self.func.parameters:format(state) - end, - hash_parameters = function(self) - return self.func.parameters:hash() - end, - call_dispatched = function(self, state, args) - local calling_environment = state.scope:capture() - state.scope:push(self.scope) - state.scope:set(ast.Identifier:new("_calling_environment"), calling_environment) - local exp = self.func:call_dispatched(state, args) - state.scope:pop() - return exp - end, - resume = function(self, state, target) - if self.func.parameters.min_arity > 0 then error("can't resume function with parameters") end - local calling_environment = state.scope:capture() - state.scope:push(self.scope) - state.scope:set(ast.Identifier:new("_calling_environment"), calling_environment) - resume_manager:push(state, target) - local exp = self.func:call(state, ast.ArgumentTuple:new()) - resume_manager:pop(state) - state.scope:pop() - return exp - end, - get_level = function(self, state, level) -- TODO make generic to all calls? only closures? - local env = state.scope:capture() - while level > 0 do - assert(env:defined(state, ast.Identifier:new("_calling_environment"))) - env = env:get(state, ast.Identifier:new("_calling_environment")) - level = level - 1 - end - return env - end -} - -package.loaded[...] = Closure -resume_manager = require("anselme.state.resume_manager") - -return Closure diff --git a/anselme/ast/Function.lua b/anselme/ast/Function.lua index 1bb1dea..e69d4d2 100644 --- a/anselme/ast/Function.lua +++ b/anselme/ast/Function.lua @@ -2,20 +2,27 @@ local ast = require("anselme.ast") local Overloadable = ast.abstract.Overloadable -local Closure, ReturnBoundary +local ReturnBoundary local operator_priority = require("anselme.common").operator_priority +local resume_manager, calling_environment_manager + local Function Function = Overloadable { type = "function", parameters = nil, -- ParameterTuple - expression = nil, + expression = nil, -- function content + scope = nil, -- Environment; captured scope for closure (evaluated functions); not set when not evaluated - init = function(self, parameters, expression) + init = function(self, parameters, expression, scope) self.parameters = parameters - self.expression = ReturnBoundary:new(expression) + self.expression = expression + self.scope = scope + end, + with_return_boundary = function(self, parameters, expression) + return Function:new(parameters, ReturnBoundary:new(expression)) end, _format = function(self, ...) @@ -32,6 +39,19 @@ Function = Overloadable { traverse = function(self, fn, ...) fn(self.parameters, ...) fn(self.expression, ...) + if self.scope then + fn(self.scope, ...) + end + end, + + _eval = function(self, state) + -- layer a new scope layer on top of captured/current scope + -- to allow future define in the function (fn.:var = "foo") + state.scope:push() + local scope = state.scope:capture() -- capture current scope to build closure + state.scope:pop() + + return Function:new(self.parameters:eval(state), self.expression, scope) end, compatible_with_arguments = function(self, state, args) @@ -44,26 +64,51 @@ Function = Overloadable { return self.parameters:hash() end, call_dispatched = function(self, state, args) + assert(self.scope, "can't call unevaluated function") + + -- push captured closure scope + local calling_environment = state.scope:capture() + state.scope:push(self.scope) + calling_environment_manager:push(state, calling_environment) + + -- push function scope state.scope:push() args:bind_parameter_tuple(state, self.parameters) - local exp = self.expression:eval(state) - state.scope:pop() - -- reminder: don't do any additionnal processing here as that won't be executed when resuming self.expression directly - -- which is done in a few places, notably to predefine exports in Closure - -- instead wrap it in some additional node, like our friend ReturnBoundary - + calling_environment_manager:pop(state) + state.scope:pop() return exp end, + resume = function(self, state, target) + if self.parameters.min_arity > 0 then error("can't resume function with parameters") end + assert(self.scope, "can't resume unevaluated function") - _eval = function(self, state) - return Closure:new(Function:new(self.parameters:eval(state), self.expression), state) + -- push captured closure scope + local calling_environment = state.scope:capture() + state.scope:push(self.scope) + calling_environment_manager:push(state, calling_environment) + + resume_manager:push(state, target) + + -- push function scope + state.scope:push() + local exp = self.expression:eval(state) + state.scope:pop() + + resume_manager:pop(state) + + calling_environment_manager:pop(state) + state.scope:pop() + return exp end, } package.loaded[...] = Function -Closure, ReturnBoundary = ast.Closure, ast.ReturnBoundary +ReturnBoundary = ast.ReturnBoundary + +resume_manager = require("anselme.state.resume_manager") +calling_environment_manager = require("anselme.state.calling_environment_manager") return Function diff --git a/anselme/ast/LuaFunction.lua b/anselme/ast/LuaFunction.lua index a31fab1..9d87f7c 100644 --- a/anselme/ast/LuaFunction.lua +++ b/anselme/ast/LuaFunction.lua @@ -4,6 +4,8 @@ local Overloadable = ast.abstract.Overloadable local operator_priority = require("anselme.common").operator_priority local unpack = table.unpack or unpack +local calling_environment_manager + local LuaFunction LuaFunction = ast.abstract.Runtime(Overloadable) { type = "lua function", @@ -45,6 +47,9 @@ LuaFunction = ast.abstract.Runtime(Overloadable) { return self.parameters:hash() end, call_dispatched = function(self, state, args) + local calling_environment = state.scope:capture() + calling_environment_manager:push(state, calling_environment) + local lua_args = { state } state.scope:push() @@ -58,6 +63,8 @@ LuaFunction = ast.abstract.Runtime(Overloadable) { local r = self.func(unpack(lua_args)) assert(r, "lua function returned no value") + + calling_environment_manager:pop(state) return r end, @@ -70,4 +77,7 @@ LuaFunction = ast.abstract.Runtime(Overloadable) { end, } +package.loaded[...] = LuaFunction +calling_environment_manager = require("anselme.state.calling_environment_manager") + return LuaFunction diff --git a/anselme/parser/expression/primary/function_definition.lua b/anselme/parser/expression/primary/function_definition.lua index 1a545a1..9e17084 100644 --- a/anselme/parser/expression/primary/function_definition.lua +++ b/anselme/parser/expression/primary/function_definition.lua @@ -197,7 +197,7 @@ return primary { if not s then error(("invalid expression in function definition: %s"):format(right), 0) end -- return function - local fn = Function:new(parameters, right):set_source(source_start) + local fn = Function:with_return_boundary(parameters, right):set_source(source_start) return Definition:new(symbol, fn):set_source(source_start), rem end end diff --git a/anselme/parser/expression/primary/prefix/function.lua b/anselme/parser/expression/primary/prefix/function.lua index a6eaa05..2984c63 100644 --- a/anselme/parser/expression/primary/prefix/function.lua +++ b/anselme/parser/expression/primary/prefix/function.lua @@ -30,6 +30,6 @@ return prefix { s, right, rem = pcall(expression_to_ast, source, rem, limit_pattern, self.priority) if not s then error(("invalid expression after unop %q: %s"):format(self.operator, right), 0) end - return Function:new(parameters, right):set_source(source_start), rem + return Function:with_return_boundary(parameters, right):set_source(source_start), rem end } diff --git a/anselme/stdlib/checkpoint.lua b/anselme/stdlib/checkpoint.lua index dcb63b6..2abad0f 100644 --- a/anselme/stdlib/checkpoint.lua +++ b/anselme/stdlib/checkpoint.lua @@ -2,16 +2,17 @@ local ast = require("anselme.ast") local ArgumentTuple, Boolean, Nil = ast.ArgumentTuple, ast.Boolean, ast.Nil local resume_manager = require("anselme.state.resume_manager") +local calling_environment_manager = require("anselme.state.calling_environment_manager") return { { - "resume", "(function::closure, anchor::anchor)", + "resume", "(function::function, anchor::anchor)", function(state, func, anchor) return func:resume(state, anchor) end }, { - "resume", "(function::closure, anchor::nil)", + "resume", "(function::function, anchor::nil)", function(state, func) return func:call(state, ArgumentTuple:new()) end @@ -23,9 +24,9 @@ return { end }, { - "resuming", "(level::number)", + "resuming", "(level::number=0)", function(state, level) - local env = ast.Closure:get_level(state, level:to_lua(state)) + local env = calling_environment_manager:get_level(state, level:to_lua(state)+1) state.scope:push(env) local r = Boolean:new(resume_manager:resuming(state)) state.scope:pop() diff --git a/anselme/stdlib/closure.lua b/anselme/stdlib/closure.lua index 45ff5cb..702e26c 100644 --- a/anselme/stdlib/closure.lua +++ b/anselme/stdlib/closure.lua @@ -1,22 +1,24 @@ local ast = require("anselme.ast") -local Nil, Boolean, Definition, Call, Function, ParameterTuple, FunctionParameter, Identifier, Overload, Assignment = ast.Nil, ast.Boolean, ast.Definition, ast.Call, ast.Function, ast.ParameterTuple, ast.FunctionParameter, ast.Identifier, ast.Overload, ast.Assignment +local Nil, Boolean, Definition, Call, Function, ParameterTuple, FunctionParameter, Identifier, Overload, Assignment, Return = ast.Nil, ast.Boolean, ast.Definition, ast.Call, ast.Function, ast.ParameterTuple, ast.FunctionParameter, ast.Identifier, ast.Overload, ast.Assignment, ast.Return local assert0 = require("anselme.common").assert0 +local calling_environment_manager = require("anselme.state.calling_environment_manager") + return { { - "defined", "(c::closure, s::string)", + "defined", "(c::function, s::string)", function(state, c, s) return Boolean:new(c.scope:defined_in_current_strict(state, s:to_identifier())) end }, { - "has upvalue", "(c::closure, s::string)", + "has upvalue", "(c::function, s::string)", function(state, c, s) return Boolean:new(c.scope:defined(state, s:to_identifier())) end }, { - "_._", "(c::closure, s::string)", + "_._", "(c::function, s::string)", function(state, c, s) local identifier = s:to_identifier() assert0(c.scope:defined(state, identifier), ("no variable %q defined in closure"):format(s.string)) @@ -24,7 +26,7 @@ return { end }, { - "_._", "(c::closure, s::string) = v", + "_._", "(c::function, s::string) = v", function(state, c, s, v) local identifier = s:to_identifier() assert0(c.scope:defined(state, identifier), ("no variable %q defined in closure"):format(s.string)) @@ -33,7 +35,7 @@ return { end }, { - "_._", "(c::closure, s::symbol) = v", + "_._", "(c::function, s::symbol) = v", function(state, c, s, v) state.scope:push(c.scope) local r = Definition:new(s, v):eval(state) @@ -45,7 +47,7 @@ return { ">_", "(q::is(\"quote\"))", function(state, q) local exp = q.expression - local get = Function:new(ParameterTuple:new(), exp):eval(state) + local get = Function:with_return_boundary(ParameterTuple:new(), exp):eval(state) local set_exp if Call:is(exp) then @@ -57,11 +59,29 @@ return { if set_exp then local set_param = ParameterTuple:new() set_param:insert_assignment(FunctionParameter:new(Identifier:new("value"))) - local set = Function:new(set_param, set_exp):eval(state) + local set = Function:with_return_boundary(set_param, set_exp):eval(state) return Overload:new(get, set) else return get end end } + { + "attached block", "(level::number=1)", + function(state, level) + -- level 2: env of the function that called the function that called attached block + local env = calling_environment_manager:get_level(state, level:to_lua(state)+1) + local r = env:get(state, Identifier:new("_")) + return Function:with_return_boundary(ParameterTuple:new(), r.expression):eval(state) + end + }, + { + "attached block keep return", "(level::number=1)", + function(state, level) + -- level 2: env of the function that called the function that called attached block + local env = calling_environment_manager:get_level(state, level:to_lua(state)+1) + local r = env:get(state, Identifier:new("_")) + return Function:new(ParameterTuple:new(), r.expression):eval(state) + end + }, } diff --git a/anselme/stdlib/type_check.lua b/anselme/stdlib/type_check.lua index 0ff79bd..32bf086 100644 --- a/anselme/stdlib/type_check.lua +++ b/anselme/stdlib/type_check.lua @@ -16,7 +16,7 @@ return { { "struct", "(x)", function(state, x) return Boolean:new(x.type == "struct") end }, { "table", "(x)", function(state, x) return Boolean:new(x.type == "table") end }, - { "closure", "(x)", function(state, x) return Boolean:new(x.type == "closure") end }, + { "function", "(x)", function(state, x) return Boolean:new(x.type == "function") end }, { "overload", "(x)", function(state, x) return Boolean:new(x.type == "overload") end }, - { "function", "(x)", function(state, x) return Boolean:new(x.type == "overload" or x.type == "closure" or x.type == "funciton" or x.type == "lua function") end }, + { "callable", "(x)", function(state, x) return Boolean:new(x.type == "overload" or x.type == "function" or x.type == "lua function" or x.type == "quote") end }, }