1
0
Fork 0
mirror of https://github.com/Reuh/anselme.git synced 2025-10-27 16:49:31 +00:00

Merge Closure into Function and add calling_environment_manager

This commit is contained in:
Étienne Fildadut 2024-01-02 00:31:32 +01:00
parent 18dd3ad6bd
commit c2bec6f5d2
8 changed files with 105 additions and 106 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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