1
0
Fork 0
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:
Étienne Fildadut 2021-04-12 17:08:35 +02:00
parent 5c3e9d2c5d
commit f9edaff7e7
8 changed files with 353 additions and 35 deletions

View file

@ -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:

View file

@ -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

View file

@ -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
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)
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

View file

@ -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

View 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()

View 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" }
]]--

View file

@ -0,0 +1,16 @@
$ f
# "a":"a"
a
~ 1 # "b":"b"
§ p
b # "c":"c"
c
d
e
~ f
~ f

View 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" }
]]--