mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-27 16:49:31 +00:00
Restore tags when resuming from a checkpoint; simply resuming code
This commit is contained in:
parent
5c3e9d2c5d
commit
f9edaff7e7
8 changed files with 353 additions and 35 deletions
10
README.md
10
README.md
|
|
@ -237,7 +237,7 @@ Paragraphs always have the following variable defined in its namespace by defaul
|
|||
|
||||
`👁️`: number, number of times the paragraph was reached or executed before
|
||||
|
||||
* `#`: tag line. Can be followed by an [expression](#expressions); otherwise empty expression is assumed. The results of the [expression](#expressions) will be added to the tags send along with any event sent from its children. Can be nested.
|
||||
* `#`: tag line. Can be followed by an [expression](#expressions); otherwise nil expression is assumed. The results of the [expression](#expressions) will be added to the tags send along with any event sent from its children. Can be nested.
|
||||
|
||||
```
|
||||
# "color": "red"
|
||||
|
|
@ -279,7 +279,7 @@ And this is more text, in a different event.
|
|||
|
||||
Every line can also be followed with decorators, which are appended at the end of the line and affect its behaviour.
|
||||
|
||||
* `~`: expression decorator. Same as an expression line, behaving as if this line was it sole child. Typically used to conditionally execute line.
|
||||
* `~`: expression decorator. Same as an expression line, behaving as if this line was it sole child. Typically used to conditionally execute line. Does not affect following else-conditions.
|
||||
|
||||
* `§`: paragraph decorator. Same as a paragraph line, behaving as if this line was it sole child.
|
||||
|
||||
|
|
@ -559,6 +559,12 @@ Method style calling is also possible, like with functions.
|
|||
|
||||
Paragraphs commit variables after a call.
|
||||
|
||||
Please also be aware that when resuming from a paragraph, Anselme will try to restore the interpreter state as if the function was correctly executed from the start up to this paragraph. This includes:
|
||||
|
||||
* if the paragraph is in a expression block, it will assume the expression was true (but will not re-evaluate it)
|
||||
* if the paragraph is in a choice block, it will assume this choice was selected (but will not re-evaluate any of the choices from the same choice group)
|
||||
* will try to re-add every tag from parent lines; this require Anselme to re-evaluate every tag line and decorator that's a parent of the paragraph in the function. Be careful if your tag expressions have side-effects.
|
||||
|
||||
#### Operators
|
||||
|
||||
Built-in operators:
|
||||
|
|
|
|||
|
|
@ -103,25 +103,7 @@ local function eval(state, exp)
|
|||
if fn.value.type == "paragraph" or fn.value.paragraph then
|
||||
local r, e
|
||||
if fn.value.type == "paragraph" then
|
||||
r, e = run_block(state, fn.value.child)
|
||||
if e then return r, e end
|
||||
state.variables[fn.value.namespace.."👁️"] = {
|
||||
type = "number",
|
||||
value = state.variables[fn.value.namespace.."👁️"].value + 1
|
||||
}
|
||||
state.variables[fn.value.parent_function.namespace.."🏁"] = {
|
||||
type = "string",
|
||||
value = fn.value.name
|
||||
}
|
||||
flush_state(state)
|
||||
if r then
|
||||
return r, e
|
||||
-- resume function from paragraph
|
||||
elseif not exp.explicit_call then
|
||||
r, e = run(state, fn.value.parent_block, true, fn.value.parent_position+1)
|
||||
else
|
||||
r = { type = "nil", value = nil }
|
||||
end
|
||||
r, e = run(state, fn.value.child, not exp.explicit_call)
|
||||
-- paragraph decorators: run single line or resume from it.
|
||||
-- checkpoint & seen variables will be updated from the interpreter usual paragraph-reaching code.
|
||||
elseif exp.explicit_call then
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
local eval
|
||||
local truthy, flush_state, to_lua, eval_text
|
||||
local truthy, flush_state, to_lua, eval_text, escape
|
||||
|
||||
local tags = {
|
||||
--- push new tags on top of the stack, from Anselme values
|
||||
push = function(self, state, val)
|
||||
local new = {}
|
||||
-- copy
|
||||
local last = state.interpreter.tags[#state.interpreter.tags] or {}
|
||||
local last = self:current(state)
|
||||
for k,v in pairs(last) do new[k] = v end
|
||||
-- merge with new values
|
||||
if val.type ~= "list" then val = { type = "list", value = { val } } end
|
||||
|
|
@ -13,14 +14,27 @@ local tags = {
|
|||
-- add
|
||||
table.insert(state.interpreter.tags, new)
|
||||
end,
|
||||
--- same but do not merge with last stack item
|
||||
push_lua_no_merge = function(self, state, val)
|
||||
table.insert(state.interpreter.tags, val)
|
||||
end,
|
||||
-- pop tag table on top of the stack
|
||||
pop = function(self, state)
|
||||
table.remove(state.interpreter.tags)
|
||||
end,
|
||||
--- return current lua tags table
|
||||
current = function(self, state)
|
||||
return state.interpreter.tags[#state.interpreter.tags] or {}
|
||||
end,
|
||||
push_ignore_past = function(self, state, tags)
|
||||
table.insert(state.interpreter.tags, tags)
|
||||
--- returns length of tags stack
|
||||
len = function(self, state)
|
||||
return #state.interpreter.tags
|
||||
end,
|
||||
--- pop item until we reached desired stack length
|
||||
trim = function(self, state, len)
|
||||
while #state.interpreter.tags > len do
|
||||
self:pop(state)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
|
|
@ -94,13 +108,11 @@ local function run_line(state, line)
|
|||
})
|
||||
write_event(state, "choice", t)
|
||||
elseif line.type == "tag" then
|
||||
if line.expression then
|
||||
local v, e = eval(state, line.expression)
|
||||
if not v then return v, ("%s; at %s"):format(e, line.source) end
|
||||
tags:push(state, v)
|
||||
end
|
||||
local v, e = run_block(state, line.child)
|
||||
if line.expression then tags:pop(state) end
|
||||
v, e = run_block(state, line.child)
|
||||
tags:pop(state)
|
||||
if e then return v, e end
|
||||
if v then return v end
|
||||
elseif line.type == "return" then
|
||||
|
|
@ -127,7 +139,7 @@ local function run_line(state, line)
|
|||
else
|
||||
local choice = state.interpreter.choice_available[sel]
|
||||
state.interpreter.choice_available = {}
|
||||
tags:push_ignore_past(state, choice.tags)
|
||||
tags:push_lua_no_merge(state, choice.tags)
|
||||
local v, e = run_block(state, choice.block)
|
||||
tags:pop(state)
|
||||
if e then return v, e end
|
||||
|
|
@ -142,7 +154,7 @@ local function run_line(state, line)
|
|||
if line.tag then
|
||||
tags:pop(state)
|
||||
end
|
||||
-- paragraph decorator
|
||||
-- paragraph decorator and line
|
||||
if line.paragraph then
|
||||
state.variables[line.namespace.."👁️"] = {
|
||||
type = "number",
|
||||
|
|
@ -182,10 +194,31 @@ run_block = function(state, block, resume_from_there, i, j)
|
|||
end
|
||||
i = i + 1
|
||||
end
|
||||
-- if we are exiting a paragraph block, mark it as ran and update checkpoint
|
||||
-- (when resuming from a checkpoint, execution is resumed from inside the paragraph, the line.paragraph check in run_line is never called)
|
||||
-- (and we want this to be done after executing the checkpoint block anyway)
|
||||
if block.parent_line and block.parent_line.paragraph then
|
||||
local parent_line = block.parent_line
|
||||
state.variables[parent_line.namespace.."👁️"] = {
|
||||
type = "number",
|
||||
value = state.variables[parent_line.namespace.."👁️"].value + 1
|
||||
}
|
||||
-- don't update checkpoint if an already more precise checkpoint is set
|
||||
-- (since we will go up the whole checkpoint hierarchy when resuming from a nested checkpoint)
|
||||
local current_checkpoint = state.variables[parent_line.parent_function.namespace.."🏁"].value
|
||||
if not current_checkpoint:match("^"..escape(parent_line.name)) then
|
||||
state.variables[parent_line.parent_function.namespace.."🏁"] = {
|
||||
type = "string",
|
||||
value = parent_line.name
|
||||
}
|
||||
end
|
||||
flush_state(state)
|
||||
end
|
||||
-- go up hierarchy if asked to resume
|
||||
-- will stop at function boundary
|
||||
-- if parent is a choice, will ignore choices that belong to the same block (like the whole block was executed naturally from a higher parent)
|
||||
-- if parent if a condition, will mark it as a success (skipping following else-conditions) (for the same reasons as for choices)
|
||||
-- if parent pushed a tag, will pop it (tags from parents are added to the stack in run())
|
||||
if resume_from_there and block.parent_line and block.parent_line.type ~= "function" then
|
||||
local parent_line = block.parent_line
|
||||
if parent_line.type == "choice" then
|
||||
|
|
@ -193,6 +226,9 @@ run_block = function(state, block, resume_from_there, i, j)
|
|||
elseif parent_line.type == "condition" or parent_line.type == "else-condition" then
|
||||
parent_line.parent_block.last_condition_success = true
|
||||
end
|
||||
if parent_line.type == "tag" or parent_line.tag then
|
||||
tags:pop(state)
|
||||
end
|
||||
local v, e = run_block(state, parent_line.parent_block, resume_from_there, parent_line.parent_position+1)
|
||||
if e then return v, e end
|
||||
if v then return v, e end
|
||||
|
|
@ -203,8 +239,39 @@ end
|
|||
-- returns var in case of success
|
||||
-- return nil, err in case of error
|
||||
local function run(state, block, resume_from_there, i, j)
|
||||
-- restore tags from parents when resuming
|
||||
local tags_len = tags:len(state)
|
||||
if resume_from_there then
|
||||
local tags_to_add = {}
|
||||
-- go up in hierarchy in ascending order until function boundary
|
||||
local parent_line = block.parent_line
|
||||
while parent_line and parent_line.type ~= "function" do
|
||||
if parent_line.type == "tag" then
|
||||
local v, e = eval(state, parent_line.expression)
|
||||
if not v then return v, ("%s; at %s"):format(e, parent_line.source) end
|
||||
table.insert(tags_to_add, v)
|
||||
end
|
||||
if parent_line.tag then
|
||||
local v, e = eval(state, parent_line.tag)
|
||||
if not v then return v, ("%s; in tag decorator at %s"):format(e, parent_line.source) end
|
||||
table.insert(tags_to_add, v)
|
||||
end
|
||||
parent_line = parent_line.parent_block.parent_line
|
||||
end
|
||||
-- re-add tag in desceding order
|
||||
for k=#tags_to_add, 1, -1 do
|
||||
tags:push(state, tags_to_add[k])
|
||||
end
|
||||
end
|
||||
-- run
|
||||
local v, e = run_block(state, block, resume_from_there, i, j)
|
||||
-- return to previous tag state
|
||||
-- tag stack pop when resuming is done when exiting the tag block
|
||||
-- stray elements may be left on the stack if there is a return before we exit all the tag block, so we trim them
|
||||
if resume_from_there then
|
||||
tags:trim(state, tags_len)
|
||||
end
|
||||
-- return
|
||||
if e then return v, e end
|
||||
if v then
|
||||
return v
|
||||
|
|
@ -227,5 +294,6 @@ package.loaded[...] = interpreter
|
|||
eval = require((...):gsub("interpreter$", "expression"))
|
||||
local common = require((...):gsub("interpreter$", "common"))
|
||||
truthy, flush_state, to_lua, eval_text = common.truthy, common.flush_state, common.to_lua, common.eval_text
|
||||
escape = require((...):gsub("interpreter%.interpreter$", "parser.common")).escape
|
||||
|
||||
return interpreter
|
||||
|
|
|
|||
|
|
@ -307,7 +307,7 @@ local function parse_line(line, state, namespace)
|
|||
if expr:match("[^%s]") then
|
||||
r.expression = expr
|
||||
else
|
||||
r.expression = nil
|
||||
r.expression = "()"
|
||||
end
|
||||
-- return
|
||||
elseif l:match("^%@") then
|
||||
|
|
|
|||
26
test/tests/resume from nested paragraph.ans
Normal file
26
test/tests/resume from nested paragraph.ans
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
$ f
|
||||
x
|
||||
§ p
|
||||
a
|
||||
|
||||
§ q
|
||||
b
|
||||
|
||||
c
|
||||
|
||||
d
|
||||
|
||||
From start:
|
||||
~ f
|
||||
|
||||
From p checkpoint:
|
||||
~ f
|
||||
|
||||
From q checkpoint:
|
||||
~ f
|
||||
|
||||
From q checkpoint again:
|
||||
~ f
|
||||
|
||||
Force p checkpoint:
|
||||
~ f.p()
|
||||
135
test/tests/resume from nested paragraph.lua
Normal file
135
test/tests/resume from nested paragraph.lua
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
local _={}
|
||||
_[63]={}
|
||||
_[62]={}
|
||||
_[61]={}
|
||||
_[60]={}
|
||||
_[59]={}
|
||||
_[58]={}
|
||||
_[57]={}
|
||||
_[56]={}
|
||||
_[55]={}
|
||||
_[54]={}
|
||||
_[53]={}
|
||||
_[52]={}
|
||||
_[51]={}
|
||||
_[50]={}
|
||||
_[49]={}
|
||||
_[48]={}
|
||||
_[47]={}
|
||||
_[46]={}
|
||||
_[45]={tags=_[63],data="c"}
|
||||
_[44]={tags=_[62],data="a"}
|
||||
_[43]={tags=_[61],data="Force p checkpoint:"}
|
||||
_[42]={tags=_[60],data="d"}
|
||||
_[41]={tags=_[59],data="c"}
|
||||
_[40]={tags=_[58],data="b"}
|
||||
_[39]={tags=_[57],data="From q checkpoint again:"}
|
||||
_[38]={tags=_[56],data="d"}
|
||||
_[37]={tags=_[55],data="c"}
|
||||
_[36]={tags=_[54],data="b"}
|
||||
_[35]={tags=_[53],data="From q checkpoint:"}
|
||||
_[34]={tags=_[52],data="d"}
|
||||
_[33]={tags=_[51],data="c"}
|
||||
_[32]={tags=_[50],data="a"}
|
||||
_[31]={tags=_[49],data="From p checkpoint:"}
|
||||
_[30]={tags=_[48],data="d"}
|
||||
_[29]={tags=_[47],data="x"}
|
||||
_[28]={tags=_[46],data="From start:"}
|
||||
_[27]={_[45]}
|
||||
_[26]={_[43],_[44]}
|
||||
_[25]={_[42]}
|
||||
_[24]={_[41]}
|
||||
_[23]={_[39],_[40]}
|
||||
_[22]={_[38]}
|
||||
_[21]={_[37]}
|
||||
_[20]={_[35],_[36]}
|
||||
_[19]={_[34]}
|
||||
_[18]={_[33]}
|
||||
_[17]={_[31],_[32]}
|
||||
_[16]={_[30]}
|
||||
_[15]={_[28],_[29]}
|
||||
_[14]={"return"}
|
||||
_[13]={"text",_[27]}
|
||||
_[12]={"text",_[26]}
|
||||
_[11]={"text",_[25]}
|
||||
_[10]={"text",_[24]}
|
||||
_[9]={"text",_[23]}
|
||||
_[8]={"text",_[22]}
|
||||
_[7]={"text",_[21]}
|
||||
_[6]={"text",_[20]}
|
||||
_[5]={"text",_[19]}
|
||||
_[4]={"text",_[18]}
|
||||
_[3]={"text",_[17]}
|
||||
_[2]={"text",_[16]}
|
||||
_[1]={"text",_[15]}
|
||||
return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9],_[10],_[11],_[12],_[13],_[14]}
|
||||
--[[
|
||||
{ "text", { {
|
||||
data = "From start:",
|
||||
tags = {}
|
||||
}, {
|
||||
data = "x",
|
||||
tags = {}
|
||||
} } }
|
||||
{ "text", { {
|
||||
data = "d",
|
||||
tags = {}
|
||||
} } }
|
||||
{ "text", { {
|
||||
data = "From p checkpoint:",
|
||||
tags = {}
|
||||
}, {
|
||||
data = "a",
|
||||
tags = {}
|
||||
} } }
|
||||
{ "text", { {
|
||||
data = "c",
|
||||
tags = {}
|
||||
} } }
|
||||
{ "text", { {
|
||||
data = "d",
|
||||
tags = {}
|
||||
} } }
|
||||
{ "text", { {
|
||||
data = "From q checkpoint:",
|
||||
tags = {}
|
||||
}, {
|
||||
data = "b",
|
||||
tags = {}
|
||||
} } }
|
||||
{ "text", { {
|
||||
data = "c",
|
||||
tags = {}
|
||||
} } }
|
||||
{ "text", { {
|
||||
data = "d",
|
||||
tags = {}
|
||||
} } }
|
||||
{ "text", { {
|
||||
data = "From q checkpoint again:",
|
||||
tags = {}
|
||||
}, {
|
||||
data = "b",
|
||||
tags = {}
|
||||
} } }
|
||||
{ "text", { {
|
||||
data = "c",
|
||||
tags = {}
|
||||
} } }
|
||||
{ "text", { {
|
||||
data = "d",
|
||||
tags = {}
|
||||
} } }
|
||||
{ "text", { {
|
||||
data = "Force p checkpoint:",
|
||||
tags = {}
|
||||
}, {
|
||||
data = "a",
|
||||
tags = {}
|
||||
} } }
|
||||
{ "text", { {
|
||||
data = "c",
|
||||
tags = {}
|
||||
} } }
|
||||
{ "return" }
|
||||
]]--
|
||||
16
test/tests/resume from paragraph restore tags.ans
Normal file
16
test/tests/resume from paragraph restore tags.ans
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
$ f
|
||||
# "a":"a"
|
||||
a
|
||||
~ 1 # "b":"b"
|
||||
§ p
|
||||
b # "c":"c"
|
||||
|
||||
c
|
||||
|
||||
d
|
||||
|
||||
e
|
||||
|
||||
~ f
|
||||
|
||||
~ f
|
||||
85
test/tests/resume from paragraph restore tags.lua
Normal file
85
test/tests/resume from paragraph restore tags.lua
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
local _={}
|
||||
_[32]={}
|
||||
_[31]={a="a"}
|
||||
_[30]={a="a",b="b"}
|
||||
_[29]={a="a",c="c",b="b"}
|
||||
_[28]={}
|
||||
_[27]={a="a",b="b"}
|
||||
_[26]={a="a"}
|
||||
_[25]={tags=_[32],data="e"}
|
||||
_[24]={tags=_[31],data="d"}
|
||||
_[23]={tags=_[30],data="c"}
|
||||
_[22]={tags=_[29],data="b"}
|
||||
_[21]={tags=_[28],data="e"}
|
||||
_[20]={tags=_[26],data="d"}
|
||||
_[19]={tags=_[27],data="c"}
|
||||
_[18]={tags=_[26],data="a"}
|
||||
_[17]={_[25]}
|
||||
_[16]={_[24]}
|
||||
_[15]={_[23]}
|
||||
_[14]={_[22]}
|
||||
_[13]={_[21]}
|
||||
_[12]={_[20]}
|
||||
_[11]={_[19]}
|
||||
_[10]={_[18]}
|
||||
_[9]={"return"}
|
||||
_[8]={"text",_[17]}
|
||||
_[7]={"text",_[16]}
|
||||
_[6]={"text",_[15]}
|
||||
_[5]={"text",_[14]}
|
||||
_[4]={"text",_[13]}
|
||||
_[3]={"text",_[12]}
|
||||
_[2]={"text",_[11]}
|
||||
_[1]={"text",_[10]}
|
||||
return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9]}
|
||||
--[[
|
||||
{ "text", { {
|
||||
data = "a",
|
||||
tags = {
|
||||
a = "a"
|
||||
}
|
||||
} } }
|
||||
{ "text", { {
|
||||
data = "c",
|
||||
tags = {
|
||||
a = "a",
|
||||
b = "b"
|
||||
}
|
||||
} } }
|
||||
{ "text", { {
|
||||
data = "d",
|
||||
tags = {
|
||||
a = "a"
|
||||
}
|
||||
} } }
|
||||
{ "text", { {
|
||||
data = "e",
|
||||
tags = {}
|
||||
} } }
|
||||
{ "text", { {
|
||||
data = "b",
|
||||
tags = {
|
||||
a = "a",
|
||||
b = "b",
|
||||
c = "c"
|
||||
}
|
||||
} } }
|
||||
{ "text", { {
|
||||
data = "c",
|
||||
tags = {
|
||||
a = "a",
|
||||
b = "b"
|
||||
}
|
||||
} } }
|
||||
{ "text", { {
|
||||
data = "d",
|
||||
tags = {
|
||||
a = "a"
|
||||
}
|
||||
} } }
|
||||
{ "text", { {
|
||||
data = "e",
|
||||
tags = {}
|
||||
} } }
|
||||
{ "return" }
|
||||
]]--
|
||||
Loading…
Add table
Add a link
Reference in a new issue