mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-28 09:09:31 +00:00
Anselme v2.0.0-alpha rewrite
Woke up and felt like changing a couple things. It's actually been worked on for a while, little at a time... The goal was to make the language and implementation much simpler. Well I don't know if it really ended up being simpler but it sure is more robust. Main changes: * proper first class functions and closures supports! proper scoping rules! no more namespace shenanigans! * everything is an expression, no more statements! make the implementation both simpler and more complex, but it's much more consistent now! the syntax has massively changed as a result though. * much more organized and easy to modify codebase: one file for each AST node, no more random fields or behavior set by some random node exceptionally, everything should now follow the same API defined in ast.abstract.Node Every foundational feature should be implemented right now. The vast majority of things that were possible in v2 are possible now; some things aren't, but that's usually because v2 is a bit more sane. The main missing things before a proper release are tests and documentation. There's a few other things that might be implemented later, see the ideas.md file.
This commit is contained in:
parent
2ff494d108
commit
fe351b5ca4
484 changed files with 7099 additions and 18084 deletions
159
state/ScopeStack.lua
Normal file
159
state/ScopeStack.lua
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
-- 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,
|
||||
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,
|
||||
|
||||
-- return a table { [symbol] = value } of persistent variables defined on the root scope on this branch
|
||||
list_persistent_global = function(self)
|
||||
local env = self.stack[1]
|
||||
return env:list_persistent(self.state)
|
||||
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
|
||||
229
state/State.lua
Normal file
229
state/State.lua
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
--- Contains all state relative to an Anselme interpreter. Each State is fully independant from each other.
|
||||
-- Each State can run a single script at a time, and variable changes are isolated between each State (see [branching](#branching-and-merging)).
|
||||
|
||||
local class = require("class")
|
||||
local ScopeStack = require("state.ScopeStack")
|
||||
local tag_manager = require("state.tag_manager")
|
||||
local event_manager = require("state.event_manager")
|
||||
local resumable_manager = require("state.resumable_manager")
|
||||
local uuid = require("common").uuid
|
||||
local parser = require("parser")
|
||||
local binser = require("lib.binser")
|
||||
local anselme
|
||||
|
||||
local State
|
||||
State = class {
|
||||
type = "anselme state",
|
||||
|
||||
init = function(self, branch_from)
|
||||
-- create a new branch from an existing state
|
||||
-- note: the existing state must not currently have an active script
|
||||
if branch_from then
|
||||
self.branch_id = uuid()
|
||||
self.source_branch_id = branch_from.branch_id
|
||||
self.scope = ScopeStack:new(self, branch_from)
|
||||
|
||||
event_manager:reset(self) -- events are isolated per branch
|
||||
resumable_manager:reset(self) -- resumable stack is isolated per branch
|
||||
-- create new empty state
|
||||
else
|
||||
self.scope = ScopeStack:new(self)
|
||||
|
||||
event_manager:setup(self)
|
||||
tag_manager:setup(self)
|
||||
resumable_manager:setup(self)
|
||||
end
|
||||
end,
|
||||
|
||||
--- Load standard library.
|
||||
-- You will probably want to call this on every State right after creation.
|
||||
load_stdlib = function(self)
|
||||
require("stdlib")(self)
|
||||
end,
|
||||
|
||||
---## Branching and merging
|
||||
|
||||
--- Name of the branch associated to this State.
|
||||
branch_id = "main",
|
||||
--- Name of the branch this State was branched from.
|
||||
source_branch_id = "main",
|
||||
|
||||
--- Return a new branch of this State.
|
||||
--
|
||||
-- Branches act as indepent copies of this State where any change will not be reflected in the source State until it is merged back into the source branch.
|
||||
-- Note: probably makes the most sense to create branches from the main State only.
|
||||
branch = function(self)
|
||||
assert(not self:active(), "can't branch while a script is active")
|
||||
return State:new(self)
|
||||
end,
|
||||
--- Merge everything that was changed in this branch back into the main State branch.
|
||||
--
|
||||
-- Recommendation: only merge if you know that the state of the variables is consistent, for example at the end of the script, checkpoints, ...
|
||||
-- If your script errored or was interrupted at an unknown point in the script, you might be in the middle of a calculation and variables won't contain
|
||||
-- values you want to merge.
|
||||
merge = function(self)
|
||||
self.scope:merge()
|
||||
end,
|
||||
|
||||
---## Variable definition
|
||||
|
||||
scope = nil, -- ScopeStack associated with the State. Contains *all* scopes related to this State.
|
||||
|
||||
--- Define a value in the global scope, converting it from Lua to Anselme if needed.
|
||||
--
|
||||
-- * for lua functions: `define("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("name", value)`
|
||||
-- * for anselme AST: `define("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 = function(self, name, value, func, raw_mode)
|
||||
self.scope:push_global()
|
||||
self:define_local(name, value, func, raw_mode)
|
||||
self.scope:pop()
|
||||
end,
|
||||
--- Same as `:define`, but define the expression in the current scope.
|
||||
define_local = function(self, name, value, func, raw_mode)
|
||||
self.scope:define_lua(name, value, func, raw_mode)
|
||||
end,
|
||||
|
||||
--- For anything more advanced, you can directly access the current scope stack stored in `state.scope`.
|
||||
-- See [state/ScopeStack.lua](../state/ScopeStack.lua) for details; the documentation is not as polished as this file but you should still be able to find your way around.
|
||||
|
||||
---## Saving and loading persistent variables
|
||||
|
||||
--- Return a serialized (string) representation of all global persistent variables in this State.
|
||||
--
|
||||
-- This can be loaded back later using `:load`.
|
||||
save = function(self)
|
||||
local list = self.scope:list_persistent_global()
|
||||
return binser.serialize(anselme.versions.save, list)
|
||||
end,
|
||||
--- Load a string generated by `:save`.
|
||||
--
|
||||
-- Variables that do not exist currently in the global scope will be defined, those that do will be overwritten with the loaded data.
|
||||
load = function(self, save)
|
||||
local version, list = binser.deserializeN(save, 2)
|
||||
if version ~= anselme.versions.save then print("Loading a save file generated by a different Anselme version, things may break!") end
|
||||
self.scope:push_global()
|
||||
for sym, val in pairs(list) do
|
||||
if self.scope:defined_in_current(sym) then
|
||||
self.scope:set(sym:to_identifier(), val)
|
||||
else
|
||||
self.scope:define(sym, val)
|
||||
end
|
||||
end
|
||||
self.scope:pop()
|
||||
end,
|
||||
|
||||
---## Current script state
|
||||
|
||||
-- Currently active script
|
||||
_coroutine = nil,
|
||||
|
||||
--- Indicate if a script is currently loaded in this branch.
|
||||
active = function(self)
|
||||
return not not self._coroutine
|
||||
end,
|
||||
--- Returns `"running`" if a script is currently loaded and running (i.e. this was called from the script).
|
||||
--
|
||||
-- Returns `"active"` if a script is loaded but not currently running (i.e. the script has not started or is waiting on an event).
|
||||
--
|
||||
-- Returns `"inactive"` if no script is loaded.
|
||||
state = function(self)
|
||||
if self:active() then
|
||||
return coroutine.status(self._coroutine) == "running" and "running" or "active"
|
||||
else
|
||||
return "inactive"
|
||||
end
|
||||
end,
|
||||
--- Load a script in this branch. It will become the active script.
|
||||
--
|
||||
-- `code` is the code string or AST to run, `source` is the source name string to show in errors (optional).
|
||||
--
|
||||
-- Note that this will only load the script; execution will only start by using the `:step` method. Will error if a script is already active in this State.
|
||||
run = function(self, code, source)
|
||||
assert(not self:active(), "a script is already active")
|
||||
self._coroutine = coroutine.create(function()
|
||||
local r = assert(self:eval_local(code, source))
|
||||
event_manager:final_flush(self)
|
||||
return "return", r
|
||||
end)
|
||||
end,
|
||||
--- When a script is active, will resume running it until the next event.
|
||||
--
|
||||
-- Will error if no script is active.
|
||||
--
|
||||
-- Returns `event type string, event data`.
|
||||
step = function(self)
|
||||
assert(self:active(), "trying to step but no script is currently active")
|
||||
local success, type, data = coroutine.resume(self._coroutine)
|
||||
if not success then
|
||||
self.scope:reset()
|
||||
type, data = "error", type
|
||||
end
|
||||
if coroutine.status(self._coroutine) == "dead" then
|
||||
self._coroutine = nil
|
||||
end
|
||||
return type, data
|
||||
end,
|
||||
--- Stops the currently active script.
|
||||
--
|
||||
-- Will error if no script is active.
|
||||
--
|
||||
-- If `code` is given, the script will not be disabled but instead will be immediately replaced with this new script.
|
||||
-- The new script will then be started on the next `:step` and will preserve the current scope. This can be used to trigger an exit function or similar in the active script.
|
||||
interrupt = function(self, code, source)
|
||||
assert(self:active(), "trying to interrupt but no script is currently active")
|
||||
if code then
|
||||
self._coroutine = coroutine.create(function()
|
||||
local r = assert(self:eval_local(code, source))
|
||||
event_manager:final_flush(self)
|
||||
self.scope:reset() -- scope stack is probably messed up after the switch
|
||||
return "return", r
|
||||
end)
|
||||
else
|
||||
self.scope:reset()
|
||||
self._coroutine = nil
|
||||
end
|
||||
end,
|
||||
|
||||
--- Evaluate an expression in the global scope.
|
||||
--
|
||||
-- This can be called from outside a running script, but an error will be triggered the expression raise any event other than return.
|
||||
--
|
||||
-- * returns AST in case of success. Run `:to_lua(state)` on it to convert to a Lua value.
|
||||
-- * returns `nil, error message` in case of error.
|
||||
eval = function(self, code, source)
|
||||
self.scope:push_global()
|
||||
local r, e = self:eval_local(code, source)
|
||||
self.scope:pop()
|
||||
return r, e
|
||||
end,
|
||||
--- Same as `:eval`, but evaluate the expression in the current scope.
|
||||
eval_local = function(self, code, source)
|
||||
if type(code) == "string" then code = parser(code, source) end
|
||||
local stack_size = self.scope:size()
|
||||
local s, e = pcall(code.eval, code, self)
|
||||
if not s then
|
||||
self.scope:reset(stack_size)
|
||||
return nil, e
|
||||
else
|
||||
return e
|
||||
end
|
||||
end,
|
||||
--- If you want to perform more advanced manipulation of the resulting AST nodes, look at the `ast` modules.
|
||||
-- In particular, every Node inherits the methods from [ast.abstract.Node](../ast/abstract/Node.lua).
|
||||
-- Otherwise, each Node has its own module file defined in the [ast/](../ast) directory.
|
||||
|
||||
__tostring = function(self)
|
||||
return ("anselme state, branch %s, %s"):format(self.branch_id, self:state())
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = State
|
||||
anselme = require("anselme")
|
||||
|
||||
return State
|
||||
53
state/event_manager.lua
Normal file
53
state/event_manager.lua
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
local class = require("class")
|
||||
|
||||
local ast = require("ast")
|
||||
local Nil, String, List, Identifier = ast.Nil, ast.String, ast.List, ast.Identifier
|
||||
|
||||
-- list of event data
|
||||
local event_buffer_identifier = Identifier:new("_event_buffer")
|
||||
local event_buffer_symbol = event_buffer_identifier:to_symbol{ confined_to_branch = true } -- per-branch, global variables
|
||||
|
||||
-- type of currently buffered event
|
||||
local last_event_type_identifier = Identifier:new("_last_event_type")
|
||||
local last_event_type_symbol = last_event_type_identifier:to_symbol{ confined_to_branch = true }
|
||||
|
||||
return class {
|
||||
init = false,
|
||||
|
||||
setup = function(self, state)
|
||||
state.scope:define(event_buffer_symbol, List:new(state))
|
||||
state.scope:define(last_event_type_symbol, Nil:new())
|
||||
end,
|
||||
reset = function(self, state)
|
||||
state.scope:set(event_buffer_identifier, List:new(state))
|
||||
state.scope:set(last_event_type_identifier, Nil:new())
|
||||
end,
|
||||
|
||||
write = function(self, state, event)
|
||||
local current_type = state.scope:get(last_event_type_identifier):to_lua(state)
|
||||
if current_type ~= nil and current_type ~= event.type then
|
||||
self:flush(state)
|
||||
end
|
||||
state.scope:set(last_event_type_identifier, String:new(event.type))
|
||||
state.scope:get(event_buffer_identifier):insert(state, event)
|
||||
end,
|
||||
|
||||
flush = function(self, state)
|
||||
local last_type = state.scope:get(last_event_type_identifier):to_lua(state)
|
||||
if last_type then
|
||||
local last_buffer = state.scope:get(event_buffer_identifier)
|
||||
local event_president = last_buffer:get(state, 1) -- elected representative of all concerned events
|
||||
-- yield event data
|
||||
local data = event_president:build_event_data(state, last_buffer)
|
||||
coroutine.yield(last_type, data)
|
||||
-- clear room for the future
|
||||
state.scope:set(last_event_type_identifier, Nil:new())
|
||||
state.scope:set(event_buffer_identifier, List:new(state))
|
||||
-- post callback
|
||||
if event_president.post_flush_callback then event_president:post_flush_callback(state, last_buffer, data) end
|
||||
end
|
||||
end,
|
||||
final_flush = function(self, state)
|
||||
while state.scope:get(last_event_type_identifier):to_lua(state) do self:flush(state) end
|
||||
end
|
||||
}
|
||||
70
state/resumable_manager.lua
Normal file
70
state/resumable_manager.lua
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
local class = require("class")
|
||||
|
||||
local ast = require("ast")
|
||||
local Resumable, Nil, List, Identifier
|
||||
|
||||
-- stack of resumable contexts
|
||||
local resumable_stack_identifier, resumable_stack_symbol
|
||||
|
||||
local resumable_manager = class {
|
||||
init = false,
|
||||
|
||||
setup = function(self, state)
|
||||
state.scope:define(resumable_stack_symbol, List:new(state))
|
||||
self:push(state, Resumable:new(state, Nil:new(), state.scope:capture()))
|
||||
end,
|
||||
reset = function(self, state)
|
||||
state.scope:set(resumable_stack_identifier, List:new(state))
|
||||
self:push(state, Resumable:new(state, Nil:new(), state.scope:capture()))
|
||||
end,
|
||||
|
||||
push = function(self, state, resumable)
|
||||
local stack = state.scope:get(resumable_stack_identifier)
|
||||
stack:insert(state, resumable)
|
||||
end,
|
||||
pop = function(self, state)
|
||||
local stack = state.scope:get(resumable_stack_identifier)
|
||||
stack:remove(state)
|
||||
end,
|
||||
_get = function(self, state)
|
||||
return state.scope:get(resumable_stack_identifier):get(state, -1)
|
||||
end,
|
||||
|
||||
-- returns the Resumable object that resumes from this point
|
||||
-- level indicate which function to resume: level=0 means resume the current function, level=1 the parent function (resume from the call to the current function in the parent function), etc.
|
||||
capture = function(self, state, level)
|
||||
level = level or 0
|
||||
return state.scope:get(resumable_stack_identifier):get(state, -1-level):capture(state)
|
||||
end,
|
||||
|
||||
eval = function(self, state, exp)
|
||||
self:push(state, Resumable:new(state, exp, state.scope:capture()))
|
||||
local r = exp:eval(state)
|
||||
self:pop(state)
|
||||
return r
|
||||
end,
|
||||
|
||||
set_data = function(self, state, node, data)
|
||||
self:_get(state).data:set(state, node, data)
|
||||
end,
|
||||
get_data = function(self, state, node)
|
||||
return self:_get(state).data:get(state, node)
|
||||
end,
|
||||
resuming = function(self, state, node)
|
||||
local resumable = self:_get(state)
|
||||
if node then
|
||||
return resumable.resuming and resumable.data:has(state, node)
|
||||
else
|
||||
return resumable.resuming
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = resumable_manager
|
||||
|
||||
Resumable, Nil, List, Identifier = ast.Resumable, ast.Nil, ast.List, ast.Identifier
|
||||
|
||||
resumable_stack_identifier = Identifier:new("_resumable_stack")
|
||||
resumable_stack_symbol = resumable_stack_identifier:to_symbol{ confined_to_branch = true } -- per-branch, global variables
|
||||
|
||||
return resumable_manager
|
||||
38
state/tag_manager.lua
Normal file
38
state/tag_manager.lua
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
local class = require("class")
|
||||
|
||||
local ast = require("ast")
|
||||
local Struct, Identifier
|
||||
|
||||
local tag_identifier, tag_symbol
|
||||
|
||||
local tag_manager = class {
|
||||
init = false,
|
||||
|
||||
setup = function(self, state)
|
||||
state.scope:define(tag_symbol, Struct:new())
|
||||
end,
|
||||
|
||||
push = function(self, state, tbl)
|
||||
local new_strct = Struct:new()
|
||||
new_strct:include(self:get(state))
|
||||
new_strct:include(tbl)
|
||||
|
||||
state.scope:push_partial(tag_identifier)
|
||||
state.scope:define(tag_symbol, new_strct)
|
||||
end,
|
||||
pop = function(self, state)
|
||||
state.scope:pop()
|
||||
end,
|
||||
|
||||
get = function(self, state)
|
||||
return state.scope:get(tag_identifier)
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = tag_manager
|
||||
Struct, Identifier = ast.Struct, ast.Identifier
|
||||
|
||||
tag_identifier = Identifier:new("_tags")
|
||||
tag_symbol = tag_identifier:to_symbol()
|
||||
|
||||
return tag_manager
|
||||
Loading…
Add table
Add a link
Reference in a new issue