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
|
|
@ -76,7 +76,7 @@ local ScopeStack = class {
|
|||
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,
|
||||
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,
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ 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 translation_manager = require("state.translation_manager")
|
||||
local uuid = require("common").uuid
|
||||
local parser = require("parser")
|
||||
|
|
@ -25,14 +24,12 @@ State = class {
|
|||
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)
|
||||
translation_manager:setup(self)
|
||||
end
|
||||
end,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
local class = require("class")
|
||||
|
||||
local ast = require("ast")
|
||||
local Nil, String, List, Identifier = ast.Nil, ast.String, ast.List, ast.Identifier
|
||||
local Nil, String, List, Identifier, Boolean = ast.Nil, ast.String, ast.List, ast.Identifier, ast.Boolean
|
||||
|
||||
-- list of event data
|
||||
local event_buffer_identifier = Identifier:new("_event_buffer")
|
||||
|
|
@ -11,18 +11,26 @@ local event_buffer_symbol = event_buffer_identifier:to_symbol{ confined_to_branc
|
|||
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 }
|
||||
|
||||
-- indicate if the next flush should be ignored for the current buffered event
|
||||
local discard_next_flush_identifier = Identifier:new("_discard_next_flush")
|
||||
local discard_next_flush_symbol = discard_next_flush_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())
|
||||
state.scope:define(discard_next_flush_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())
|
||||
state.scope:set(discard_next_flush_identifier, Nil:new())
|
||||
end,
|
||||
|
||||
-- write an event into the event buffer
|
||||
-- will flush if an event of a different type is present in the buffer
|
||||
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
|
||||
|
|
@ -31,22 +39,33 @@ return class {
|
|||
state.scope:set(last_event_type_identifier, String:new(event.type))
|
||||
state.scope:get(event_buffer_identifier):insert(state, event)
|
||||
end,
|
||||
-- same as :write, but the buffer will be discarded instead of yielded on the next flush
|
||||
write_and_discard_on_flush = function(self, state, event)
|
||||
self:write(state, event)
|
||||
state.scope:set(discard_next_flush_identifier, Boolean:new(true))
|
||||
end,
|
||||
|
||||
-- flush the event buffer: build the event data and yield it
|
||||
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
|
||||
local discard_next_flush = state.scope:get(discard_next_flush_identifier):truthy()
|
||||
if discard_next_flush then
|
||||
self:reset(state)
|
||||
else
|
||||
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
|
||||
self:reset(state)
|
||||
-- post callback
|
||||
if event_president.post_flush_callback then event_president:post_flush_callback(state, last_buffer, data) end
|
||||
end
|
||||
end
|
||||
end,
|
||||
-- keep flushing until nothing is left (a flush may re-fill the buffer during its execution)
|
||||
final_flush = function(self, state)
|
||||
while state.scope:get(last_event_type_identifier):to_lua(state) do self:flush(state) end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,70 +0,0 @@
|
|||
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
|
||||
44
state/resume_manager.lua
Normal file
44
state/resume_manager.lua
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
local class = require("class")
|
||||
|
||||
local ast = require("ast")
|
||||
local Nil, Identifier, Anchor
|
||||
|
||||
-- stack of resumable contexts
|
||||
local resume_anchor_identifier, resume_anchor_symbol
|
||||
|
||||
local resume_manager = class {
|
||||
init = false,
|
||||
|
||||
-- push a new resume context: all run code between this and the next push will try to resume to anchor
|
||||
push = function(self, state, anchor)
|
||||
assert(Anchor:is(anchor), "can only resume to an anchor target") -- well technically it wouldn't be hard to allow to resume to any node, but I feel like there's already enough stuff in Anselme that was done just because it could be done
|
||||
state.scope:push_partial(resume_anchor_identifier)
|
||||
state.scope:define(resume_anchor_symbol, anchor)
|
||||
end,
|
||||
-- pop the current resume context
|
||||
pop = function(self, state)
|
||||
state.scope:pop()
|
||||
end,
|
||||
|
||||
-- returns true if we are currently trying to resume to an anchor
|
||||
resuming = function(self, state)
|
||||
return state.scope:defined(resume_anchor_identifier) and not Nil:is(state.scope:get(resume_anchor_identifier))
|
||||
end,
|
||||
-- returns the anchor we are trying to resume to
|
||||
get = function(self, state)
|
||||
return state.scope:get(resume_anchor_identifier)
|
||||
end,
|
||||
-- mark the anchor as reached and stop the resume
|
||||
set_reached = function(self, state)
|
||||
state.scope:set(resume_anchor_identifier, Nil:new())
|
||||
end,
|
||||
}
|
||||
|
||||
package.loaded[...] = resume_manager
|
||||
|
||||
Nil, Identifier, Anchor = ast.Nil, ast.Identifier, ast.Anchor
|
||||
|
||||
resume_anchor_identifier = Identifier:new("_resume_anchor")
|
||||
resume_anchor_symbol = resume_anchor_identifier:to_symbol()
|
||||
|
||||
return resume_manager
|
||||
Loading…
Add table
Add a link
Reference in a new issue