1
0
Fork 0
mirror of https://github.com/Reuh/anselme.git synced 2025-10-27 08:39:30 +00:00

Closures are not affected by following redefinitions of upvalues

This commit is contained in:
Étienne Fildadut 2024-01-03 16:44:12 +01:00
parent 3d10b1b15c
commit d1818d10b1
2 changed files with 33 additions and 2 deletions

View file

@ -85,6 +85,14 @@ local Environment = ast.abstract.Runtime {
self._lookup_cache = {}
self._lookup_cache_current = {}
end,
-- precache variable
-- 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())
end,
traverse = function(self, fn, ...)
if self.parent then

View file

@ -8,6 +8,15 @@ local operator_priority = require("anselme.common").operator_priority
local resume_manager, calling_environment_manager
local function list_upvalues(v, l)
if ast.Identifier:is(v) then
table.insert(l, v)
elseif ast.Symbol:is(v) then
table.insert(l, v:to_identifier())
end
v:traverse(list_upvalues, l)
end
local Function
Function = Overloadable {
type = "function",
@ -15,11 +24,13 @@ 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.
init = function(self, parameters, expression, scope)
init = function(self, parameters, expression, scope, upvalues)
self.parameters = parameters
self.expression = expression
self.scope = scope
self.upvalues = upvalues
end,
with_return_boundary = function(self, parameters, expression)
return Function:new(parameters, ReturnBoundary:new(expression))
@ -51,7 +62,19 @@ Function = Overloadable {
local scope = state.scope:capture() -- capture current scope to build closure
state.scope:pop()
return Function:new(self.parameters:eval(state), self.expression, scope)
-- get upvalues
local upvalues = {}
self.expression:traverse(list_upvalues, upvalues)
if scope:defined(state, ast.Identifier:new("_")) then
scope:get(state, ast.Identifier:new("_")):traverse(list_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)
end
return Function:new(self.parameters:eval(state), self.expression, scope, upvalues)
end,
compatible_with_arguments = function(self, state, args)