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

155 lines
5.9 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,
get_symbol = function(self, identifier) return self.current:get_symbol(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