mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-28 17:19:31 +00:00
233 lines
8.8 KiB
Lua
233 lines
8.8 KiB
Lua
--- 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 translation_manager = require("state.translation_manager")
|
|
local persistent_manager = require("state.persistent_manager")
|
|
local uuid = require("common").uuid
|
|
local parser = require("parser")
|
|
local binser = require("lib.binser")
|
|
local anselme
|
|
|
|
-- same as assert, but do not add position information
|
|
-- useful for errors raised from anselme (don't care about Lua error position)
|
|
local function assert0(v, message, ...)
|
|
if not v then
|
|
error(message, 0)
|
|
end
|
|
return v, message, ...
|
|
end
|
|
|
|
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
|
|
-- create new empty state
|
|
else
|
|
self.scope = ScopeStack:new(self)
|
|
|
|
event_manager:setup(self)
|
|
tag_manager:setup(self)
|
|
persistent_manager:setup(self)
|
|
translation_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 persistent variables in this State.
|
|
--
|
|
-- This can be loaded back later using `:load`.
|
|
save = function(self)
|
|
local struct = persistent_manager:capture(self)
|
|
return binser.serialize(anselme.versions.save, struct)
|
|
end,
|
|
--- Load a string generated by `:save`.
|
|
--
|
|
-- Variables that already exist will be overwritten with the loaded data.
|
|
load = function(self, save)
|
|
local version, struct = 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
|
|
for key, val in struct:iter() do
|
|
persistent_manager:set(self, key, val)
|
|
end
|
|
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 = assert0(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 = assert0(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
|