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
|
`👁️`: 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"
|
# "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.
|
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.
|
* `§`: 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.
|
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
|
#### Operators
|
||||||
|
|
||||||
Built-in operators:
|
Built-in operators:
|
||||||
|
|
|
||||||
|
|
@ -103,25 +103,7 @@ local function eval(state, exp)
|
||||||
if fn.value.type == "paragraph" or fn.value.paragraph then
|
if fn.value.type == "paragraph" or fn.value.paragraph then
|
||||||
local r, e
|
local r, e
|
||||||
if fn.value.type == "paragraph" then
|
if fn.value.type == "paragraph" then
|
||||||
r, e = run_block(state, fn.value.child)
|
r, e = run(state, fn.value.child, not exp.explicit_call)
|
||||||
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
|
|
||||||
-- paragraph decorators: run single line or resume from it.
|
-- paragraph decorators: run single line or resume from it.
|
||||||
-- checkpoint & seen variables will be updated from the interpreter usual paragraph-reaching code.
|
-- checkpoint & seen variables will be updated from the interpreter usual paragraph-reaching code.
|
||||||
elseif exp.explicit_call then
|
elseif exp.explicit_call then
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
local eval
|
local eval
|
||||||
local truthy, flush_state, to_lua, eval_text
|
local truthy, flush_state, to_lua, eval_text, escape
|
||||||
|
|
||||||
local tags = {
|
local tags = {
|
||||||
|
--- push new tags on top of the stack, from Anselme values
|
||||||
push = function(self, state, val)
|
push = function(self, state, val)
|
||||||
local new = {}
|
local new = {}
|
||||||
-- copy
|
-- 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
|
for k,v in pairs(last) do new[k] = v end
|
||||||
-- merge with new values
|
-- merge with new values
|
||||||
if val.type ~= "list" then val = { type = "list", value = { val } } end
|
if val.type ~= "list" then val = { type = "list", value = { val } } end
|
||||||
|
|
@ -13,14 +14,27 @@ local tags = {
|
||||||
-- add
|
-- add
|
||||||
table.insert(state.interpreter.tags, new)
|
table.insert(state.interpreter.tags, new)
|
||||||
end,
|
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)
|
pop = function(self, state)
|
||||||
table.remove(state.interpreter.tags)
|
table.remove(state.interpreter.tags)
|
||||||
end,
|
end,
|
||||||
|
--- return current lua tags table
|
||||||
current = function(self, state)
|
current = function(self, state)
|
||||||
return state.interpreter.tags[#state.interpreter.tags] or {}
|
return state.interpreter.tags[#state.interpreter.tags] or {}
|
||||||
end,
|
end,
|
||||||
push_ignore_past = function(self, state, tags)
|
--- returns length of tags stack
|
||||||
table.insert(state.interpreter.tags, tags)
|
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
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,13 +108,11 @@ local function run_line(state, line)
|
||||||
})
|
})
|
||||||
write_event(state, "choice", t)
|
write_event(state, "choice", t)
|
||||||
elseif line.type == "tag" then
|
elseif line.type == "tag" then
|
||||||
if line.expression then
|
local v, e = eval(state, line.expression)
|
||||||
local v, e = eval(state, line.expression)
|
if not v then return v, ("%s; at %s"):format(e, line.source) end
|
||||||
if not v then return v, ("%s; at %s"):format(e, line.source) end
|
tags:push(state, v)
|
||||||
tags:push(state, v)
|
v, e = run_block(state, line.child)
|
||||||
end
|
tags:pop(state)
|
||||||
local v, e = run_block(state, line.child)
|
|
||||||
if line.expression then tags:pop(state) end
|
|
||||||
if e then return v, e end
|
if e then return v, e end
|
||||||
if v then return v end
|
if v then return v end
|
||||||
elseif line.type == "return" then
|
elseif line.type == "return" then
|
||||||
|
|
@ -127,7 +139,7 @@ local function run_line(state, line)
|
||||||
else
|
else
|
||||||
local choice = state.interpreter.choice_available[sel]
|
local choice = state.interpreter.choice_available[sel]
|
||||||
state.interpreter.choice_available = {}
|
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)
|
local v, e = run_block(state, choice.block)
|
||||||
tags:pop(state)
|
tags:pop(state)
|
||||||
if e then return v, e end
|
if e then return v, e end
|
||||||
|
|
@ -142,7 +154,7 @@ local function run_line(state, line)
|
||||||
if line.tag then
|
if line.tag then
|
||||||
tags:pop(state)
|
tags:pop(state)
|
||||||
end
|
end
|
||||||
-- paragraph decorator
|
-- paragraph decorator and line
|
||||||
if line.paragraph then
|
if line.paragraph then
|
||||||
state.variables[line.namespace.."👁️"] = {
|
state.variables[line.namespace.."👁️"] = {
|
||||||
type = "number",
|
type = "number",
|
||||||
|
|
@ -182,10 +194,31 @@ run_block = function(state, block, resume_from_there, i, j)
|
||||||
end
|
end
|
||||||
i = i + 1
|
i = i + 1
|
||||||
end
|
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
|
-- go up hierarchy if asked to resume
|
||||||
-- will stop at function boundary
|
-- 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 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 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
|
if resume_from_there and block.parent_line and block.parent_line.type ~= "function" then
|
||||||
local parent_line = block.parent_line
|
local parent_line = block.parent_line
|
||||||
if parent_line.type == "choice" then
|
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
|
elseif parent_line.type == "condition" or parent_line.type == "else-condition" then
|
||||||
parent_line.parent_block.last_condition_success = true
|
parent_line.parent_block.last_condition_success = true
|
||||||
end
|
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)
|
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 e then return v, e end
|
||||||
if v then return v, e end
|
if v then return v, e end
|
||||||
|
|
@ -203,8 +239,39 @@ end
|
||||||
-- returns var in case of success
|
-- returns var in case of success
|
||||||
-- return nil, err in case of error
|
-- return nil, err in case of error
|
||||||
local function run(state, block, resume_from_there, i, j)
|
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
|
-- run
|
||||||
local v, e = run_block(state, block, resume_from_there, i, j)
|
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 e then return v, e end
|
||||||
if v then
|
if v then
|
||||||
return v
|
return v
|
||||||
|
|
@ -227,5 +294,6 @@ package.loaded[...] = interpreter
|
||||||
eval = require((...):gsub("interpreter$", "expression"))
|
eval = require((...):gsub("interpreter$", "expression"))
|
||||||
local common = require((...):gsub("interpreter$", "common"))
|
local common = require((...):gsub("interpreter$", "common"))
|
||||||
truthy, flush_state, to_lua, eval_text = common.truthy, common.flush_state, common.to_lua, common.eval_text
|
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
|
return interpreter
|
||||||
|
|
|
||||||
|
|
@ -307,7 +307,7 @@ local function parse_line(line, state, namespace)
|
||||||
if expr:match("[^%s]") then
|
if expr:match("[^%s]") then
|
||||||
r.expression = expr
|
r.expression = expr
|
||||||
else
|
else
|
||||||
r.expression = nil
|
r.expression = "()"
|
||||||
end
|
end
|
||||||
-- return
|
-- return
|
||||||
elseif l:match("^%@") then
|
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