1
0
Fork 0
mirror of https://github.com/Reuh/anselme.git synced 2025-10-27 16:49:31 +00:00
anselme/state/ScopeStack.lua
Étienne Reuh Fildadut e71bff9562 Replace persistent variable system
Previous system linked the variable name with the saved value, meaning the variable could not be renamed or moved outside the global scope.

Instead we propose to store all persistent values in a global table, identifying each by a key. To still allow nice manipulation with identifiers, the alias syntax replace the persistent syntax for symbols - an aliases symbol will act as if a function call was used in place of the identifier when it appear.
2023-12-27 21:25:14 +01:00

154 lines
5.8 KiB
Lua

-- The current scope stack. One scope stack per State branch.
-- Only the scope currently on top of the stack will be used by the running script.
local class = require("class")
local ast = require("ast")
local to_anselme = require("common.to_anselme")
local LuaFunction, Environment, Node
local parameter_tuple = require("parser.expression.contextual.parameter_tuple")
local symbol = require("parser.expression.primary.symbol")
local ScopeStack = class {
state = nil,
stack = nil, -- stack of Environment
current = nil, -- Environment
initial_size = nil, -- number, size of the stack at creation (i.e. the "global" scopes only)
init = function(self, state, branch_from_state)
self.state = state
self.stack = {}
if branch_from_state then
-- load existing environments from branched from state instead of creating new ones
for _, env in ipairs(branch_from_state.scope.stack) do
self:push(env)
end
else
self:push_export() -- root scope is the global scope, stuff can be exported there
self:push() -- for non-exported variables
end
self.initial_size = #self.stack
end,
-- store all changed variables back into the main branch
merge = function(self)
local cache = {}
for _, env in ipairs(self.stack) do
env:merge(self.state, cache)
end
end,
-- helper to define stuff from lua easily in the current scope
-- for lua functions: define_lua("name", "(x, y, z=5)", function(x, y, z) ... end), where arguments and return values of the function are automatically converted between anselme and lua values
-- for other lua values: define_lua("name", value)
-- for anselme AST: define_lua("name", value)
-- name can be prefixed with symbol modifiers, for example ":name" for a constant variable
-- if `raw_mode` is true, no anselme-to/from-lua conversion will be performed in the function
-- the function will receive the state followed by AST nodes as arguments, and is expected to return an AST node
define_lua = function(self, name, value, func, raw_mode)
local source = require("parser.Source"):new()
local sym = symbol:parse(source, (":%s"):format(name))
if func then
local parameters = parameter_tuple:parse(source, value)
if not raw_mode then
local original_func = func
func = function(state, ...)
local lua_args = {}
for _, arg in ipairs{...} do
table.insert(lua_args, arg:to_lua(state))
end
return to_anselme(original_func(table.unpack(lua_args)))
end
end
self:define_overloadable(sym, LuaFunction:new(parameters, func):eval(self.state))
elseif Node:is(value) then
self:define(sym, value)
else
self:define(sym, to_anselme(value))
end
end,
-- methods that call the associated method from the current scope, see ast.Environment for details
define = function(self, symbol, exp) self.current:define(self.state, symbol, exp) end,
define_overloadable = function(self, symbol, exp) return self.current:define_overloadable(self.state, symbol, exp) end,
define_alias = function(self, symbol, exp) return self.current:define_alias(self.state, symbol, exp) end,
defined = function(self, identifier) return self.current:defined(self.state, identifier) end,
defined_in_current = function(self, symbol) return self.current:defined_in_current(self.state, symbol) end,
set = function(self, identifier, exp) self.current:set(self.state, identifier, exp) end,
get = function(self, identifier) return self.current:get(self.state, identifier) end,
depth = function(self) return self.current:depth() end,
-- push new scope
-- if environment is given, it will be used instead of creating a new children of the current environment
push = function(self, environment)
local env
if environment then
env = environment
else
env = Environment:new(self.state, self.current)
end
table.insert(self.stack, env)
self.current = env
end,
-- push a partial layer on the current scope
-- this is used to shadow or temporarly define specific variable in the current scope
-- a partial layer is considered to be part of the current scope when looking up and defining variables, and any
-- other variable will still be defined in the current scope
-- ... is a list of identifiers
-- (still use :pop to pop it though)
push_partial = function(self, ...)
local is_partial = {}
for _, id in ipairs{...} do is_partial[id.name] = true end
local env = Environment:new(self.state, self.current, is_partial)
self:push(env)
end,
-- push an export layer on the current scope
-- this is where any exported variable defined on this or children scope will end up being defined
-- note: non-exported variables will not be defined in such a layer, make sure to add a normal layer on top as needed
-- still need to be :pop'd
push_export = function(self)
local env = Environment:new(self.state, self.current, nil, true)
self:push(env)
end,
-- push the global scope on top of the stack
push_global = function(self)
self:push(self.stack[self.initial_size])
end,
-- pop current scope
pop = function(self)
table.remove(self.stack)
self.current = self.stack[#self.stack]
assert(self.current, "popped the root scope!")
end,
-- get the size of the stack
size = function(self)
return #self.stack
end,
-- pop all the scopes until only n are left (by default, only keep the global scopes)
reset = function(self, n)
n = n or self.initial_size
while self.stack[n+1] do
self:pop()
end
end,
-- return current environment, to use with :push later (mostly for closures)
-- reminder: scopes are mutable
capture = function(self)
return self.current
end,
_debug_state = function(self, filter)
filter = filter or ""
local s = "current branch id: "..self.state.branch_id.."\n"
return s .. table.concat(self.current:_debug_state(self.state, filter), "\n")
end
}
package.loaded[...] = ScopeStack
LuaFunction, Environment, Node = ast.LuaFunction, ast.Environment, ast.abstract.Node
return ScopeStack