diff --git a/LANGUAGE.md b/LANGUAGE.md index f5e0ce4..8246c2a 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -359,8 +359,8 @@ Functions can return a value using a [return line](#lines-that-can-t-have-childr Functions always have the following variables defined in its namespace by default: -`👁️`: number, number of times the function was executed before -`🔖`: function reference, last reached checkpoint. `nil` if no checkpoint reached. +`👁️`: number, number of times the function was executed/resumed before (incremented when reaching the end of the function or a return line) +`🔖`: function reference, last reached checkpoint. `()` (nil) if no checkpoint reached. (updated when reaching a checkpoint or directly executing a checkpoint) These variables are persistent, unless the function is scoped. @@ -382,8 +382,8 @@ When executing the parent function after this checkpoint has been reached (using Checkpoints always have the following variable defined in its namespace by default: -`👁️`: number, number of times the checkpoint was executed before -`🏁`: number, number of times the checkpoint was reached before (includes times where it was resumed from and executed) +`👁️`: number, number of times the checkpoint was executed/resumed before (incremented when reaching the end of the function or a return line) +`🏁`: number, number of times the checkpoint was reached before (incremented when reaching the checkpoint line; not incremented when resuming from/executing the checkpoint directly) These variables are persistent. @@ -901,7 +901,7 @@ But in the cases when you want to manually set the current checkpoint, you can c b c -Force run the function starting from checkpoint, will write "b" and "c" and set the current checkpoint to "checkpoint": +Set the current checkpoint to "checkpoint" and force run the function starting from this checkpoint, will write "b" and "c": ~ f.checkpoint Will correctly resumes from the last set checkpoint, and write "b" and "c": @@ -921,7 +921,7 @@ You can also only execute the checkpoints' children code only by using a paranth b c -Run the checkpoint only, will only write "b" and set the current checkpoint to "checkpoint": +Set the current checkpoint to "checkpoint" and run this checkpoint only, will only write "b": ~ f.checkpoint() And will resume from the checkpoint like before: diff --git a/anselme.lua b/anselme.lua index b4bac9b..b3bd91a 100644 --- a/anselme.lua +++ b/anselme.lua @@ -247,7 +247,7 @@ local interpreter_methods = { end if not r then coroutine.yield("error", e) end if self.state.interpreter.current_event then -- flush final events - local rf, re = run_line(self.state, { type = "flush_events" }) + local rf, re = run_line(self.state, { type = "flush events" }) if re then coroutine.yield("error", re) end if rf then r = rf end end @@ -541,6 +541,8 @@ local vm_mt = { -- * `signature`: string, full signature of the function -- * `fn`: function (Lua function or table, see examples in `stdlib/functions.lua`) -- + -- Alternatively, can also take a table as a sole argument to load several functions: { ["signature"] = fn, ... } + -- -- Returns this VM. loadfunction = function(self, signature, fn) if type(signature) == "table" then diff --git a/interpreter/common.lua b/interpreter/common.lua index be82235..8ab1a5d 100644 --- a/interpreter/common.lua +++ b/interpreter/common.lua @@ -41,7 +41,7 @@ common = { end end -- scoping: since merging means we will re-copy every variable from global state again, we need to simulate this - -- behavious for scoped variables (to have consistent references for mutables values in particular), including + -- behaviour for scoped variables (to have consistent references for mutables values in particular), including -- scopes that aren't currently active fix_not_modified_references(mt.scoped, copy_cache, modified_tables) -- replace not modified values in scope with original before re-copying to keep consistent references for _, scopes in pairs(mt.scoped) do diff --git a/interpreter/expression.lua b/interpreter/expression.lua index 891f063..9d80d24 100644 --- a/interpreter/expression.lua +++ b/interpreter/expression.lua @@ -397,8 +397,15 @@ local function eval(state, exp) local fn = selected_variant.variant if fn.type ~= "function" then return nil, ("unknown function type %q"):format(fn.type) - -- checkpoint: no args and resume execution + -- checkpoint: no args and can resume execution elseif fn.subtype == "checkpoint" then + -- set current checkpoint + local s, e = set_variable(state, fn.parent_resumable.namespace.."🔖", { + type = "function reference", + value = { fn.name } + }) + if not s then return nil, e end + -- run checkpoint content, eventually resuming local r, e = run(state, fn.child, not paren_call) if not r then return nil, e end return r @@ -423,8 +430,6 @@ local function eval(state, exp) checkpoint, checkpointe = get_variable(state, fn.namespace.."🔖") if not checkpoint then return nil, checkpointe end end - local seen, seene = get_variable(state, fn.namespace.."👁️") - if not seen then return nil, seene end -- execute lua functions -- I guess we could technically skip getting & updating the seen and checkpoints vars since they can't be used from Anselme -- but it's also kinda fun to known how many time a function was ran @@ -501,12 +506,6 @@ local function eval(state, exp) end if not ret then return nil, e end end - -- update function vars - local s, e = set_variable(state, fn.namespace.."👁️", { - type = "number", - value = seen.value + 1 - }) - if not s then return nil, e end -- for classes: build resulting object if fn.subtype == "class" and ret and ret.type == "nil" then ret = { diff --git a/interpreter/interpreter.lua b/interpreter/interpreter.lua index 38b87b8..6f7013e 100644 --- a/interpreter/interpreter.lua +++ b/interpreter/interpreter.lua @@ -108,7 +108,7 @@ run_line = function(state, line) if not iv then return nil, ("%s; at %s"):format(ie, line.source) end end end - elseif line.type == "flush_events" then + elseif line.type == "flush events" then local v, e = events:flush(state) if not v then return nil, ("%s; in event flush at %s"):format(e, line.source) end elseif line.type == "function" and line.subtype == "checkpoint" then @@ -151,36 +151,8 @@ run_block = function(state, block, resume_from_there, i, j) end i = i + 1 end - -- if we are exiting a checkpoint block, mark it as ran and update checkpoint - -- (when resuming from a checkpoint, execution is resumed from inside the checkpoint, the line.subtype=="checkpoint" check in run_line is never called) - -- (and we want this to be done after executing the checkpoint block anyway) + -- if we reach the end of a checkpoint block (we are resuming execution from a checkpoint), merge state if block.parent_line and block.parent_line.type == "function" and block.parent_line.subtype == "checkpoint" then - local parent_line = block.parent_line - local reached, reachede = get_variable(state, parent_line.namespace.."🏁") - if not reached then return nil, reachede end - local seen, seene = get_variable(state, parent_line.namespace.."👁️") - if not seen then return nil, seene end - local checkpoint, checkpointe = get_variable(state, parent_line.parent_resumable.namespace.."🔖") - if not checkpoint then return nil, checkpointe end - local s, e = set_variable(state, parent_line.namespace.."👁️", { - type = "number", - value = seen.value + 1 - }) - if not s then return nil, e end - s, e = set_variable(state, parent_line.namespace.."🏁", { - type = "number", - value = reached.value + 1 - }) - if not s then return nil, e end - -- 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) - if checkpoint.type == "nil" or not checkpoint.value[1]:match("^"..escape(parent_line.name)) then - s, e = set_variable(state, parent_line.parent_resumable.namespace.."🔖", { - type = "function reference", - value = { parent_line.name } - }) - if not s then return nil, e end - end merge_state(state) end -- go up hierarchy if asked to resume diff --git a/notes.txt b/notes.txt index 41b88c6..71006ac 100644 --- a/notes.txt +++ b/notes.txt @@ -83,8 +83,6 @@ TODO: fn/checkpoint/tag: maybe consider them a regular func call that takes chil a ~ tag.pop() -TODO: perform seen/reached/etc default variable not in interpreter but using parse-time macro - TODO: make language simple enough to be able to reimplement it in, say, nim. Especially the AST interpreter (we could precompile a lot of stuff...) TODO: test reacheability of script paths + visualization of different branches the script can take. For one of those overarching story visualization thingy. diff --git a/parser/preparser.lua b/parser/preparser.lua index e4d6e29..051be01 100644 --- a/parser/preparser.lua +++ b/parser/preparser.lua @@ -105,6 +105,7 @@ local function parse_line(line, state, namespace, parent_resumable, in_scoped) allow_params = false allow_assign = false keep_in_ast = true + if not parent_resumable then return nil, ("checkpoint definition line is not in a function; at %s"):format(line.source) end r.parent_resumable = parent_resumable -- store parent resumable function and run checkpoint when line is read else error("unknown function line type") @@ -248,6 +249,8 @@ local function parse_line(line, state, namespace, parent_resumable, in_scoped) -- custom code injection inject(state, r, "start", line.children, 2) inject(state, r, "end", line.children) + -- update 👁️ variable + table.insert(line.children, { content = "~👁️+=1", source = line.source }) -- define args for _, param in ipairs(r.params) do if not state.variables[param.full_name] then @@ -336,6 +339,7 @@ local function parse_line(line, state, namespace, parent_resumable, in_scoped) r.expression = ("{%s}"):format(expr) -- return elseif l:match("^%@") then + if not parent_resumable then return nil, ("return line is not in a function; at %s"):format(line.source) end r.type = "return" r.child = true local expr = l:match("^%@(.*)$") @@ -347,13 +351,15 @@ local function parse_line(line, state, namespace, parent_resumable, in_scoped) -- custom code injection if not line.children then line.children = {} end inject(state, parent_resumable, "return", line.children) + -- update 👁️ variable + table.insert(line.children, { content = "~👁️+=1", source = line.source }) -- text elseif l:match("[^%s]") then r.type = "text" r.text = l -- flush events else - r.type = "flush_events" + r.type = "flush events" end if not r.type then return nil, ("unknown line %s type"):format(line.source) end return r diff --git a/test/run.lua b/test/run.lua index e407cb5..7acb9fc 100644 --- a/test/run.lua +++ b/test/run.lua @@ -152,7 +152,7 @@ else -- simple random to get the same result across lua versions local prev = 0 local function badrandom(a, b) - prev = (15485863 * prev + 11) % 2038074743 + prev = (4241 * prev + 11) % 6997 return a + prev % (b-a+1) end function math.random(a, b) diff --git a/test/tests/checkpoint reached seen.lua b/test/tests/checkpoint reached seen.lua index 04a0afd..6093d12 100644 --- a/test/tests/checkpoint reached seen.lua +++ b/test/tests/checkpoint reached seen.lua @@ -1,18 +1,22 @@ local _={} +_[29]={} +_[28]={} +_[27]={} +_[26]={} _[25]={} _[24]={} _[23]={} _[22]={} _[21]={} -_[20]={tags=_[25],text="2"} -_[19]={tags=_[25],text="Reached: "} -_[18]={tags=_[24],text="1"} -_[17]={tags=_[24],text="Seen: "} -_[16]={tags=_[23],text="seen!"} -_[15]={tags=_[22],text="1"} -_[14]={tags=_[22],text="Reached: "} -_[13]={tags=_[21],text="0"} -_[12]={tags=_[21],text="Seen: "} +_[20]={text="1",tags=_[29]} +_[19]={text="Reached: ",tags=_[28]} +_[18]={text="1",tags=_[27]} +_[17]={text="Seen: ",tags=_[26]} +_[16]={text="seen!",tags=_[25]} +_[15]={text="1",tags=_[24]} +_[14]={text="Reached: ",tags=_[23]} +_[13]={text="0",tags=_[22]} +_[12]={text="Seen: ",tags=_[21]} _[11]={_[19],_[20]} _[10]={_[17],_[18]} _[9]={_[16]} @@ -27,17 +31,17 @@ _[1]={"text",_[7]} return {_[1],_[2],_[3],_[4],_[5],_[6]} --[[ { "text", { { - tags = <1>{}, + tags = {}, text = "Seen: " }, { - tags = , + tags = {}, text = "0" } } } { "text", { { - tags = <1>{}, + tags = {}, text = "Reached: " }, { - tags =
, + tags = {}, text = "1" } } } { "text", { { @@ -45,18 +49,18 @@ return {_[1],_[2],_[3],_[4],_[5],_[6]} text = "seen!" } } } { "text", { { - tags = <1>{}, + tags = {}, text = "Seen: " }, { - tags =
, + tags = {}, text = "1" } } } { "text", { { - tags = <1>{}, + tags = {}, text = "Reached: " }, { - tags =
, - text = "2" + tags = {}, + text = "1" } } } { "return" } ]]-- \ No newline at end of file diff --git a/test/tests/function random.lua b/test/tests/function random.lua index 74a21ca..cce621d 100644 --- a/test/tests/function random.lua +++ b/test/tests/function random.lua @@ -4,10 +4,10 @@ _[20]={} _[19]={} _[18]={} _[17]={} -_[16]={tags=_[21],text="c"} -_[15]={tags=_[20],text="a"} -_[14]={tags=_[19],text="c"} -_[13]={tags=_[18],text="b"} +_[16]={tags=_[21],text="a"} +_[15]={tags=_[20],text="c"} +_[14]={tags=_[19],text="b"} +_[13]={tags=_[18],text="a"} _[12]={tags=_[17],text="c"} _[11]={_[16]} _[10]={_[15]} @@ -26,6 +26,10 @@ return {_[1],_[2],_[3],_[4],_[5],_[6]} tags = {}, text = "c" } } } +{ "text", { { + tags = {}, + text = "a" + } } } { "text", { { tags = {}, text = "b" @@ -38,9 +42,5 @@ return {_[1],_[2],_[3],_[4],_[5],_[6]} tags = {}, text = "a" } } } -{ "text", { { - tags = {}, - text = "c" - } } } { "return" } ]]-- \ No newline at end of file diff --git a/test/tests/seen checkpoint resume.ans b/test/tests/seen checkpoint resume.ans new file mode 100644 index 0000000..5ee996b --- /dev/null +++ b/test/tests/seen checkpoint resume.ans @@ -0,0 +1,12 @@ +:$ fn + {👁️} + + :! a + + a: {👁️} + +~ fn.a + +~ fn.a + +~ fn.a diff --git a/test/tests/seen checkpoint resume.lua b/test/tests/seen checkpoint resume.lua new file mode 100644 index 0000000..5bc9aba --- /dev/null +++ b/test/tests/seen checkpoint resume.lua @@ -0,0 +1,45 @@ +local _={} +_[19]={} +_[18]={} +_[17]={} +_[16]={} +_[15]={} +_[14]={} +_[13]={text="2",tags=_[19]} +_[12]={text="a: ",tags=_[18]} +_[11]={text="1",tags=_[17]} +_[10]={text="a: ",tags=_[16]} +_[9]={text="0",tags=_[15]} +_[8]={text="a: ",tags=_[14]} +_[7]={_[12],_[13]} +_[6]={_[10],_[11]} +_[5]={_[8],_[9]} +_[4]={"return"} +_[3]={"text",_[7]} +_[2]={"text",_[6]} +_[1]={"text",_[5]} +return {_[1],_[2],_[3],_[4]} +--[[ +{ "text", { { + tags = {}, + text = "a: " + }, { + tags = {}, + text = "0" + } } } +{ "text", { { + tags = {}, + text = "a: " + }, { + tags = {}, + text = "1" + } } } +{ "text", { { + tags = {}, + text = "a: " + }, { + tags = {}, + text = "2" + } } } +{ "return" } +]]-- \ No newline at end of file