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

Only serialize referenced upvalues in function serialization

This commit is contained in:
Étienne Fildadut 2024-01-03 17:59:44 +01:00
parent 289f10b3c5
commit a46ac380e8
4 changed files with 41 additions and 27 deletions

View file

@ -85,13 +85,14 @@ local Environment = ast.abstract.Runtime {
self._lookup_cache = {}
self._lookup_cache_current = {}
end,
-- precache variable
-- precache variable and return its variable metadata
-- when cached, if a variable is defined in a parent scope after it has been cached here from a higher parent, it will not be used in this env
-- most of the time scopes are discarded after a pop so there's no possibility for this anyway, except for closures as they restore an old environment
-- in which case we may want to precache variables that appear in the function so future definitions don't affect the closure
precache = function(self, state, identifier)
self:_lookup(state, identifier)
self:_lookup_in_current(state, identifier:to_symbol())
return self:_lookup(state, identifier)
end,
traverse = function(self, fn, ...)
@ -194,10 +195,6 @@ local Environment = ast.abstract.Runtime {
defined_in_current = function(self, state, symbol)
return self:_lookup_in_current(state, symbol) ~= nil
end,
-- return bool if variable is defined in the current environment only - won't search in parent env for exported & partial names
defined_in_current_strict = function(self, state, identifier)
return self.variables:has(state, identifier) and not self.variables:get(state, identifier):undefined(state)
end,
-- get variable in current or parent scope, with metadata
_get_variable = function(self, state, identifier)

View file

@ -2,19 +2,19 @@
local ast = require("anselme.ast")
local Overloadable = ast.abstract.Overloadable
local ReturnBoundary
local ReturnBoundary, Environment
local operator_priority = require("anselme.common").operator_priority
local resume_manager, calling_environment_manager
local function list_upvalues(v, l)
local function list_cache_upvalues(v, state, list, scope)
if ast.Identifier:is(v) then
table.insert(l, v)
list[v.name] = scope:precache(state, v)
elseif ast.Symbol:is(v) then
table.insert(l, v:to_identifier())
list[v.string] = scope:precache(state, v:to_identifier())
end
v:traverse(list_upvalues, l)
v:traverse(list_cache_upvalues, state, list, scope)
end
local Function
@ -24,13 +24,14 @@ Function = Overloadable {
parameters = nil, -- ParameterTuple
expression = nil, -- function content
scope = nil, -- Environment; captured scope for closure (evaluated functions); not set when not evaluated
upvalues = nil, -- list of identifiers; not set when not evaluated. Contain _at least_ all the upvalues explicitely defined in the function code.
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))
@ -62,16 +63,11 @@ Function = Overloadable {
local scope = state.scope:capture() -- capture current scope to build closure
state.scope:pop()
-- get upvalues
-- list & cache upvalues so they aren't affected by future redefinition in a parent scope
local upvalues = {}
self.expression:traverse(list_upvalues, upvalues)
self.expression:traverse(list_cache_upvalues, state, upvalues, scope)
if scope:defined(state, ast.Identifier:new("_")) then
scope:get(state, ast.Identifier:new("_")):traverse(list_upvalues, upvalues)
end
-- cache upvalues so they aren't affected by future redefinition in a parent scope
for _, ident in ipairs(upvalues) do
scope:precache(state, ident)
scope:get(state, ast.Identifier:new("_")):traverse(list_cache_upvalues, state, upvalues, scope)
end
return Function:new(self.parameters:eval(state), self.expression, scope, upvalues)
@ -124,10 +120,30 @@ Function = Overloadable {
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.
_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 = ast.ReturnBoundary
ReturnBoundary, Environment = ast.ReturnBoundary, ast.Environment
resume_manager = require("anselme.state.resume_manager")
calling_environment_manager = require("anselme.state.calling_environment_manager")

View file

@ -75,6 +75,14 @@ LuaFunction = ast.abstract.Runtime(Overloadable) {
to_lua = function(self, state)
return self.func
end,
-- TODO: binser does not serialize lua function upvalues!
_serialize = function(self)
error("LuaFunction can not be serialized")
end,
_deserialize = function(self)
error("LuaFunction can not be serialized")
end
}
package.loaded[...] = LuaFunction