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

152 lines
5.5 KiB
Lua

-- note: functions only appear in non-evaluated nodes! once evaluated, they always become closures
local ast = require("anselme.ast")
local Overloadable = ast.abstract.Overloadable
local ReturnBoundary, Environment, Identifier, Symbol
local operator_priority = require("anselme.common").operator_priority
local resume_manager, calling_environment_manager
local function list_cache_upvalues(v, state, list, scope)
if Identifier:is(v) then
list[v.name] = scope:precache(state, v)
elseif Symbol:is(v) then
list[v.string] = scope:precache(state, v:to_identifier())
end
v:traverse(list_cache_upvalues, state, list, scope)
end
local Function
Function = Overloadable {
type = "function",
parameters = nil, -- ParameterTuple
expression = nil, -- function content
scope = nil, -- Environment; captured scope for closure (evaluated functions); not set when not evaluated
upvalues = nil, -- {[name]=variable metadata}; not set when not evaluated. Contain _at least_ all the upvalues explicitely defined in the function code.
init = function(self, parameters, expression, scope, upvalues)
self.parameters = parameters
self.expression = expression
self.scope = scope
self.upvalues = upvalues
if self.scope then self._evaluated = true end
end,
with_return_boundary = function(self, parameters, expression)
return Function:new(parameters, ReturnBoundary:new(expression))
end,
_format = function(self, ...)
if self.parameters.assignment then
return "$"..self.parameters:format(...).."; "..self.expression:format_right(...)
else
return "$"..self.parameters:format(...).." "..self.expression:format_right(...)
end
end,
_format_priority = function(self)
return operator_priority["$_"]
end,
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()
-- list & cache upvalues so they aren't affected by future redefinition in a parent scope
local upvalues = {}
self.expression:traverse(list_cache_upvalues, state, upvalues, scope)
if scope:defined(state, Identifier:new("_")) then
scope:get(state, Identifier:new("_")):traverse(list_cache_upvalues, state, upvalues, scope)
end
return Function:new(self.parameters:eval(state), self.expression, scope, upvalues)
end,
compatible_with_arguments = function(self, state, args)
return args:match_parameter_tuple(state, self.parameters)
end,
format_signature = function(self, state)
return self.parameters:format(state)
end,
hash_signature = function(self)
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()
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")
-- 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()
resume_manager:push(state, target)
local exp = self.expression:eval(state)
resume_manager:pop(state)
state.scope:pop()
calling_environment_manager:pop(state)
state.scope:pop()
return exp
end,
-- Note: when serializing and reloading a function, its upvalues will not be linked anymore to their original definition.
-- The reloaded function will not be able to affect variables defined outside its body.
-- Only the upvalues that explicitely appear in the function body will be saved, so we don't have to keep a copy of the whole environment.
-- TODO: we should also store variables that have been defined in the function scope, even if they are not referred directly in the body
_serialize = function(self)
return { parameters = self.parameters, expression = self.expression, upvalues = self.upvalues }
end,
_deserialize = function(self)
local state = require("anselme.serializer_state")
local scope
if self.upvalues then
-- rebuild scope: exported + normal layer so any upvalue that happen to be exported stay there
-- (and link again to current scope to allow internal vars that are not considered explicit upvalues to still work, like _translations)
scope = Environment:new(state, Environment:new(state, state.scope:capture(), nil, true))
for _, var in pairs(self.upvalues) do
scope:define(state, var:get_symbol(), var:get(state))
end
end
return Function:new(self.parameters, self.expression, Environment:new(state, scope), self.upvalues)
end
}
package.loaded[...] = Function
ReturnBoundary, Environment, Identifier, Symbol = ast.ReturnBoundary, ast.Environment, ast.Identifier, ast.Symbol
resume_manager = require("anselme.state.resume_manager")
calling_environment_manager = require("anselme.state.calling_environment_manager")
return Function