mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-28 00:59:31 +00:00
Replace checkpoint system
The previous system needed to store of the scope and full AST to build a Resumable object, which means that if persisted, updating the resumable script will have no effect. The new system instead uses an anchor token and does not require any information besides the anchor name.
This commit is contained in:
parent
c4636343b4
commit
56ed6c912b
21 changed files with 217 additions and 234 deletions
35
ast/Anchor.lua
Normal file
35
ast/Anchor.lua
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
local ast = require("ast")
|
||||
|
||||
local resume_manager
|
||||
|
||||
local Anchor
|
||||
Anchor = ast.abstract.Node {
|
||||
type = "anchor",
|
||||
|
||||
name = nil,
|
||||
|
||||
init = function(self, name)
|
||||
self.name = name
|
||||
self._list_anchors_cache = { [name] = true }
|
||||
end,
|
||||
|
||||
_hash = function(self)
|
||||
return ("anchor<%q>"):format(self.name)
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
return "#"..self.name
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
if self:contains_resume_target(state) then
|
||||
resume_manager:set_reached(state)
|
||||
end
|
||||
return Anchor:new(self.name)
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = Anchor
|
||||
resume_manager = require("state.resume_manager")
|
||||
|
||||
return Anchor
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
local ast = require("ast")
|
||||
local Nil, Return, AutoCall, ArgumentTuple, Flush
|
||||
|
||||
local resume_manager = require("state.resume_manager")
|
||||
|
||||
local Block = ast.abstract.Node {
|
||||
type = "block",
|
||||
|
||||
|
|
@ -34,11 +36,11 @@ local Block = ast.abstract.Node {
|
|||
_eval = function(self, state)
|
||||
local r
|
||||
state.scope:push()
|
||||
if self:resuming(state) then
|
||||
local resuming = self:get_resume_data(state)
|
||||
if self:contains_resume_target(state) then
|
||||
local anchor = resume_manager:get(state)
|
||||
local resumed = false
|
||||
for _, e in ipairs(self.expressions) do
|
||||
if e == resuming then resumed = true end
|
||||
if e:contains_anchor(anchor) then resumed = true end
|
||||
if resumed then
|
||||
r = e:eval(state)
|
||||
if AutoCall:issub(r) then
|
||||
|
|
@ -51,7 +53,6 @@ local Block = ast.abstract.Node {
|
|||
end
|
||||
else
|
||||
for _, e in ipairs(self.expressions) do
|
||||
self:set_resume_data(state, e)
|
||||
r = e:eval(state)
|
||||
if AutoCall:issub(r) then
|
||||
r = r:call(state, ArgumentTuple:new())
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ Function = Overloadable {
|
|||
state.scope:push()
|
||||
args:bind_parameter_tuple(state, self.parameters)
|
||||
|
||||
local exp = self.expression:eval_resumable(state)
|
||||
local exp = self.expression:eval(state)
|
||||
|
||||
state.scope:pop()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
local ast = require("ast")
|
||||
local Table
|
||||
|
||||
local resumable_manager
|
||||
|
||||
local Resumable
|
||||
Resumable = ast.abstract.Runtime {
|
||||
type = "resumable",
|
||||
|
||||
resuming = false,
|
||||
|
||||
expression = nil,
|
||||
scope = nil,
|
||||
data = nil,
|
||||
|
||||
init = function(self, state, expression, scope, data)
|
||||
self.expression = expression
|
||||
self.scope = scope
|
||||
self.data = data or Table:new(state)
|
||||
end,
|
||||
|
||||
_format = function(self)
|
||||
return "<resumable>"
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.expression, ...)
|
||||
fn(self.data, ...)
|
||||
fn(self.scope, ...)
|
||||
end,
|
||||
|
||||
-- returns a copy with the data copied
|
||||
capture = function(self, state)
|
||||
return Resumable:new(state, self.expression, self.scope, self.data:copy(state))
|
||||
end,
|
||||
|
||||
-- resume from this resumable
|
||||
call = function(self, state, args)
|
||||
assert(args.arity == 0, "Resumable! does not accept arguments")
|
||||
|
||||
state.scope:push(self.scope)
|
||||
|
||||
local resuming = self:capture(state)
|
||||
resuming.resuming = true
|
||||
resumable_manager:push(state, resuming)
|
||||
local r = self.expression:eval(state)
|
||||
resumable_manager:pop(state)
|
||||
|
||||
state.scope:pop()
|
||||
|
||||
return r
|
||||
end,
|
||||
}
|
||||
|
||||
package.loaded[...] = Resumable
|
||||
Table = ast.Table
|
||||
|
||||
resumable_manager = require("state.resumable_manager")
|
||||
|
||||
return Resumable
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
-- intended to be wrapped in a Function, so that when resuming from the function, will keep resuming to where the function was called from
|
||||
-- used in Choices to resume back from where the event was flushed
|
||||
-- note: when resuming, the return value will be discarded, instead returning what the parent function will return
|
||||
|
||||
local ast = require("ast")
|
||||
local ArgumentTuple
|
||||
|
||||
local resumable_manager
|
||||
|
||||
local ResumeParentFunction = ast.abstract.Node {
|
||||
type = "resume parent function",
|
||||
|
||||
expression = nil,
|
||||
|
||||
init = function(self, expression)
|
||||
self.expression = expression
|
||||
self.format_priority = expression.format_priority
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
return self.expression:format(...)
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.expression, ...)
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
if self:resuming(state) then
|
||||
self.expression:eval(state)
|
||||
return self:get_data(state):call(state, ArgumentTuple:new())
|
||||
else
|
||||
self:set_data(state, resumable_manager:capture(state, 1))
|
||||
return self.expression:eval(state)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = ResumeParentFunction
|
||||
ArgumentTuple = ast.ArgumentTuple
|
||||
|
||||
resumable_manager = require("state.resumable_manager")
|
||||
|
||||
return ResumeParentFunction
|
||||
|
|
@ -12,8 +12,8 @@ local binser = require("lib.binser")
|
|||
|
||||
local uuid = require("common").uuid
|
||||
|
||||
local State, Runtime
|
||||
local resumable_manager
|
||||
local State, Runtime, Call, Identifier, ArgumentTuple
|
||||
local resume_manager
|
||||
|
||||
local custom_call_identifier
|
||||
|
||||
|
|
@ -99,8 +99,8 @@ Node = class {
|
|||
end,
|
||||
|
||||
-- prepare the AST after parsing and before evaluation
|
||||
-- this behave like a cached :traverse through the AST, except this keeps track of the scope stack
|
||||
-- i.e. when :prepare is called on a node, it should be in a similar scope stack context as will be when it will be evaluated
|
||||
-- this behave like a cached :traverse through the AST, except this keeps track of the scope stack and preserve evaluation order
|
||||
-- i.e. when :prepare is called on a node, it should be in a similar scope stack context and order as will be when it will be evaluated
|
||||
-- used to predefine exported variables and other compile-time variable handling
|
||||
-- note: the state here is a temporary state only used during the prepare step
|
||||
-- the actual preparation is done in _prepare
|
||||
|
|
@ -122,6 +122,30 @@ Node = class {
|
|||
self:traverse(traverse.prepare, state)
|
||||
end,
|
||||
|
||||
-- returns a reversed list { [anchor] = true, ... } of the anchors contained in this node and its children
|
||||
_list_anchors = function(self)
|
||||
if not self._list_anchors_cache then
|
||||
self._list_anchors_cache = {}
|
||||
self:traverse(function(v)
|
||||
for name in pairs(v:_list_anchors()) do
|
||||
self._list_anchors_cache[name] = true
|
||||
end
|
||||
end)
|
||||
end
|
||||
return self._list_anchors_cache
|
||||
end,
|
||||
_list_anchors_cache = nil,
|
||||
|
||||
-- returns true if the node or its children contains the anchor
|
||||
contains_anchor = function(self, anchor)
|
||||
return not not self:_list_anchors()[anchor.name]
|
||||
end,
|
||||
|
||||
-- returns true if we are currently trying to resume to an anchor target contained in the current node
|
||||
contains_resume_target = function(self, state)
|
||||
return resume_manager:resuming(state) and self:contains_anchor(resume_manager:get(state))
|
||||
end,
|
||||
|
||||
-- generate a list of translatable nodes that appear in this node
|
||||
-- should only be called on non-runtime nodes
|
||||
-- if a node is translatable, redefine this to add it to the table - note that it shouldn't call :traverse or :list_translatable on its children, as nested translations should not be needed
|
||||
|
|
@ -131,26 +155,6 @@ Node = class {
|
|||
return t
|
||||
end,
|
||||
|
||||
-- same as eval, but make the evaluated expression as a resume boundary
|
||||
-- i.e. if a checkpoint is defined somewhere in this eval, it will start back from this node eval when resuming
|
||||
eval_resumable = function(self, state)
|
||||
return resumable_manager:eval(state, self)
|
||||
end,
|
||||
-- set the current resume data for this node
|
||||
-- (relevant inside :eval)
|
||||
set_resume_data = function(self, state, data)
|
||||
resumable_manager:set_data(state, self, data)
|
||||
end,
|
||||
-- get the current resume data for this node
|
||||
get_resume_data = function(self, state)
|
||||
return resumable_manager:get_data(state, self)
|
||||
end,
|
||||
-- returns true if the current node is in a resuming state
|
||||
-- (relevant inside :eval)
|
||||
resuming = function(self, state)
|
||||
return resumable_manager:resuming(state, self)
|
||||
end,
|
||||
|
||||
-- return result AST
|
||||
-- arg is a ArgumentTuple node (already evaluated)
|
||||
-- redefine if relevant
|
||||
|
|
@ -276,11 +280,11 @@ Node = class {
|
|||
-- Thus, any require here that may require other Nodes shall be done here. This method is called in anselme.lua after everything else is required.
|
||||
_i_hate_cycles = function(self)
|
||||
local ast = require("ast")
|
||||
custom_call_identifier = ast.Identifier:new("_!")
|
||||
Runtime = ast.abstract.Runtime
|
||||
Runtime, Call, Identifier, ArgumentTuple = ast.abstract.Runtime, ast.Call, ast.Identifier, ast.ArgumentTuple
|
||||
custom_call_identifier = Identifier:new("_!")
|
||||
|
||||
State = require("state.State")
|
||||
resumable_manager = require("state.resumable_manager")
|
||||
resume_manager = require("state.resume_manager")
|
||||
end,
|
||||
|
||||
_debug_traverse = function(self, level)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue