mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-27 16:49:31 +00:00
Only serialize referenced upvalues in function serialization
This commit is contained in:
parent
289f10b3c5
commit
a46ac380e8
4 changed files with 41 additions and 27 deletions
|
|
@ -85,13 +85,14 @@ local Environment = ast.abstract.Runtime {
|
||||||
self._lookup_cache = {}
|
self._lookup_cache = {}
|
||||||
self._lookup_cache_current = {}
|
self._lookup_cache_current = {}
|
||||||
end,
|
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
|
-- 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
|
-- 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
|
-- 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)
|
precache = function(self, state, identifier)
|
||||||
self:_lookup(state, identifier)
|
self:_lookup(state, identifier)
|
||||||
self:_lookup_in_current(state, identifier:to_symbol())
|
self:_lookup_in_current(state, identifier:to_symbol())
|
||||||
|
return self:_lookup(state, identifier)
|
||||||
end,
|
end,
|
||||||
|
|
||||||
traverse = function(self, fn, ...)
|
traverse = function(self, fn, ...)
|
||||||
|
|
@ -194,10 +195,6 @@ local Environment = ast.abstract.Runtime {
|
||||||
defined_in_current = function(self, state, symbol)
|
defined_in_current = function(self, state, symbol)
|
||||||
return self:_lookup_in_current(state, symbol) ~= nil
|
return self:_lookup_in_current(state, symbol) ~= nil
|
||||||
end,
|
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 in current or parent scope, with metadata
|
||||||
_get_variable = function(self, state, identifier)
|
_get_variable = function(self, state, identifier)
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,19 @@
|
||||||
|
|
||||||
local ast = require("anselme.ast")
|
local ast = require("anselme.ast")
|
||||||
local Overloadable = ast.abstract.Overloadable
|
local Overloadable = ast.abstract.Overloadable
|
||||||
local ReturnBoundary
|
local ReturnBoundary, Environment
|
||||||
|
|
||||||
local operator_priority = require("anselme.common").operator_priority
|
local operator_priority = require("anselme.common").operator_priority
|
||||||
|
|
||||||
local resume_manager, calling_environment_manager
|
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
|
if ast.Identifier:is(v) then
|
||||||
table.insert(l, v)
|
list[v.name] = scope:precache(state, v)
|
||||||
elseif ast.Symbol:is(v) then
|
elseif ast.Symbol:is(v) then
|
||||||
table.insert(l, v:to_identifier())
|
list[v.string] = scope:precache(state, v:to_identifier())
|
||||||
end
|
end
|
||||||
v:traverse(list_upvalues, l)
|
v:traverse(list_cache_upvalues, state, list, scope)
|
||||||
end
|
end
|
||||||
|
|
||||||
local Function
|
local Function
|
||||||
|
|
@ -24,13 +24,14 @@ Function = Overloadable {
|
||||||
parameters = nil, -- ParameterTuple
|
parameters = nil, -- ParameterTuple
|
||||||
expression = nil, -- function content
|
expression = nil, -- function content
|
||||||
scope = nil, -- Environment; captured scope for closure (evaluated functions); not set when not evaluated
|
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)
|
init = function(self, parameters, expression, scope, upvalues)
|
||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
self.expression = expression
|
self.expression = expression
|
||||||
self.scope = scope
|
self.scope = scope
|
||||||
self.upvalues = upvalues
|
self.upvalues = upvalues
|
||||||
|
if self.scope then self._evaluated = true end
|
||||||
end,
|
end,
|
||||||
with_return_boundary = function(self, parameters, expression)
|
with_return_boundary = function(self, parameters, expression)
|
||||||
return Function:new(parameters, ReturnBoundary:new(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
|
local scope = state.scope:capture() -- capture current scope to build closure
|
||||||
state.scope:pop()
|
state.scope:pop()
|
||||||
|
|
||||||
-- get upvalues
|
-- list & cache upvalues so they aren't affected by future redefinition in a parent scope
|
||||||
local upvalues = {}
|
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
|
if scope:defined(state, ast.Identifier:new("_")) then
|
||||||
scope:get(state, ast.Identifier:new("_")):traverse(list_upvalues, upvalues)
|
scope:get(state, ast.Identifier:new("_")):traverse(list_cache_upvalues, state, upvalues, scope)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return Function:new(self.parameters:eval(state), self.expression, scope, upvalues)
|
return Function:new(self.parameters:eval(state), self.expression, scope, upvalues)
|
||||||
|
|
@ -124,10 +120,30 @@ Function = Overloadable {
|
||||||
state.scope:pop()
|
state.scope:pop()
|
||||||
return exp
|
return exp
|
||||||
end,
|
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
|
package.loaded[...] = Function
|
||||||
ReturnBoundary = ast.ReturnBoundary
|
ReturnBoundary, Environment = ast.ReturnBoundary, ast.Environment
|
||||||
|
|
||||||
resume_manager = require("anselme.state.resume_manager")
|
resume_manager = require("anselme.state.resume_manager")
|
||||||
calling_environment_manager = require("anselme.state.calling_environment_manager")
|
calling_environment_manager = require("anselme.state.calling_environment_manager")
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,14 @@ LuaFunction = ast.abstract.Runtime(Overloadable) {
|
||||||
to_lua = function(self, state)
|
to_lua = function(self, state)
|
||||||
return self.func
|
return self.func
|
||||||
end,
|
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
|
package.loaded[...] = LuaFunction
|
||||||
|
|
|
||||||
7
ideas.md
7
ideas.md
|
|
@ -22,13 +22,6 @@ Do some more fancy scope work to allow the translation to access variables defin
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Persistence "issue": Storing a closure stores it whole environment, which includes all the stdlib. Technically it works, but that's a lot of useless information. Would need to track which variable is used (should be doable in prepare) and prune the closure (list identifiers and symbols used in children and regroup in a single exported+normal layer). The closure would also captures things like _translations that should not be persisted and prevent any update to it or its upvalues (the captured scope in the closure will not be able to be linked with the real scope in the reloaded script)...
|
|
||||||
Or register all functions as ressources in binser - that makes kinda sense, they're immutable, and their signature should be unique. Would need to track which functions are safe to skip / can be reloaded from somewhere on load. Would need to distinguish anonymous from non anonymous functions...
|
|
||||||
|
|
||||||
Or just say closures probably shouldn't be persisted. Yeah, probably easier.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Standard library.
|
Standard library.
|
||||||
|
|
||||||
* Text manipulation would make sense, but that would require a full UTF-8/Unicode support library like https://github.com/starwing/luautf8.
|
* Text manipulation would make sense, but that would require a full UTF-8/Unicode support library like https://github.com/starwing/luautf8.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue