From bb45cc8fdde1c5dd1ff2b2f763b19b10a179cfa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Reuh=20Fildadut?= Date: Sun, 12 Dec 2021 15:38:27 +0100 Subject: [PATCH] Fix scoping with mutable variables --- anselme.lua | 5 +- common.lua | 26 ++- interpreter/common.lua | 117 +++++++--- stdlib/functions.lua | 2 +- .../scope checkpoint mutable bis error.ans | 49 +++++ .../scope checkpoint mutable bis error.lua | 120 ++++++++++ test/tests/scope checkpoint mutable bis.ans | 40 ++++ test/tests/scope checkpoint mutable bis.lua | 206 ++++++++++++++++++ test/tests/scope checkpoint mutable error.ans | 38 ++++ test/tests/scope checkpoint mutable error.lua | 99 +++++++++ .../scope checkpoint mutable ter error.ans | 51 +++++ .../scope checkpoint mutable ter error.lua | 120 ++++++++++ test/tests/scope checkpoint mutable ter.ans | 42 ++++ test/tests/scope checkpoint mutable ter.lua | 206 ++++++++++++++++++ test/tests/scope checkpoint mutable.ans | 29 +++ test/tests/scope checkpoint mutable.lua | 131 +++++++++++ 16 files changed, 1243 insertions(+), 38 deletions(-) create mode 100644 test/tests/scope checkpoint mutable bis error.ans create mode 100644 test/tests/scope checkpoint mutable bis error.lua create mode 100644 test/tests/scope checkpoint mutable bis.ans create mode 100644 test/tests/scope checkpoint mutable bis.lua create mode 100644 test/tests/scope checkpoint mutable error.ans create mode 100644 test/tests/scope checkpoint mutable error.lua create mode 100644 test/tests/scope checkpoint mutable ter error.ans create mode 100644 test/tests/scope checkpoint mutable ter error.lua create mode 100644 test/tests/scope checkpoint mutable ter.ans create mode 100644 test/tests/scope checkpoint mutable ter.lua create mode 100644 test/tests/scope checkpoint mutable.ans create mode 100644 test/tests/scope checkpoint mutable.lua diff --git a/anselme.lua b/anselme.lua index 95a3628..3d74275 100644 --- a/anselme.lua +++ b/anselme.lua @@ -487,11 +487,12 @@ local vm_mt = { return cache[k] end, -- variables that keep current state and should be cleared at each checkpoint - copy_cache = {}, -- table of [original table] = copied table - modified_tables = {}, -- list of modified tables (copies) that should be merged with global state on next checkpoint cache = {}, -- cache of previously read values (copies), to get repeatable reads & handle mutable types without changing global state + modified_tables = {}, -- list of modified tables (copies) that should be merged with global state on next checkpoint + copy_cache = {}, -- table of [original table] = copied table. Automatically filled by copy(). -- keep track of scoped variables in scoped functions [fn line] = {{scoped variables}, next scope, ...} -- (scoped variables aren't merged on checkpoint, shouldn't be cleared at checkpoints) + -- (only stores scoped variables that have been reassigned at some point (i.e. every accessed one since they start as undefined)) scoped = {} }), interpreter = { diff --git a/common.lua b/common.lua index b0d054c..cfb3a25 100644 --- a/common.lua +++ b/common.lua @@ -1,3 +1,5 @@ +local common + --- replace values recursively in table t according to to_replace ([old table] = new table) -- already_replaced is a temporary table to avoid infinite loop & duplicate processing, no need to give it local function replace_in_table(t, to_replace, already_replaced) @@ -12,7 +14,6 @@ local function replace_in_table(t, to_replace, already_replaced) end end -local common common = { --- recursively copy a table, handle cyclic references, no metatable, don't copy keys -- cache is table with copied tables [original table] = copied value, will use temporary table is omitted @@ -59,6 +60,29 @@ common = { end -- replace in t replace_in_table(t, to_replace) + end, + --- given a table t issued from some copy, the copy cache, and a list of tables from the copied version, + -- put the original tables that are not in the list in t in place of their copied values + fix_not_modified_references = function(t, cache, copied_to_replace) + -- reverse copy cache + local ehcac = {} + for k, v in pairs(cache) do ehcac[v] = k end + -- build table of [original table] = replacement copied table + local to_replace = {} + for _, v in ipairs(copied_to_replace) do + local original = ehcac[v] + if original then -- table doesn't have an original value if it's a new table... + to_replace[original] = v + end + end + -- fix references to not-modified tables in t + local not_modified = {} + for original, modified in pairs(cache) do + if not to_replace[original] then + not_modified[modified] = original + end + end + replace_in_table(t, not_modified) end } diff --git a/interpreter/common.lua b/interpreter/common.lua index d16f8be..0a3a261 100644 --- a/interpreter/common.lua +++ b/interpreter/common.lua @@ -1,6 +1,6 @@ local atypes, ltypes local eval, run_block -local replace_with_copied_values +local replace_with_copied_values, fix_not_modified_references local common local identifier_pattern @@ -44,6 +44,11 @@ end common = { --- merge interpreter state with global state merge_state = function(state) + local mt = getmetatable(state.variables) + -- store current scoped variables before merging them + for fn in pairs(mt.scoped) do + common.scope:store_last_scope(state, fn) + end -- merge alias state local global = state.interpreter.global_state for alias, fqm in pairs(state.aliases) do @@ -51,15 +56,38 @@ common = { state.aliases[alias] = nil end -- merge modified mutable varables - local mt = getmetatable(state.variables) - replace_with_copied_values(global.variables, mt.copy_cache, mt.modified_tables) + local copy_cache, modified_tables = mt.copy_cache, mt.modified_tables + replace_with_copied_values(global.variables, copy_cache, modified_tables) mt.copy_cache = {} - mt.modified = {} + mt.modified_tables = {} mt.cache = {} -- merge modified re-assigned variables for var, value in pairs(state.variables) do - global.variables[var] = value - state.variables[var] = nil + if var:match("^"..identifier_pattern.."$") then -- skip scoped variables + global.variables[var] = value + state.variables[var] = nil + 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 + -- 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 + for _, scope in ipairs(scopes) do + for var, value in pairs(scope) do + -- pretend the value for this scope is the global value so the cache system perform the new copy from it + local old_var = global.variables[var] + global.variables[var] = value + state.variables[var] = nil + scope[var] = state.variables[var] + mt.cache[var] = nil + global.variables[var] = old_var + end + end + end + -- restore last scopes + for fn in pairs(mt.scoped) do + common.scope:set_last_scope(state, fn) end end, --- returns a variable's value, evaluating a pending expression if neccessary @@ -85,46 +113,66 @@ common = { end, --- handle scoped function scope = { - push = function(self, state, fn) + init_scope = function(self, state, fn) local scoped = getmetatable(state.variables).scoped - if not fn.scoped then error("trying to push a scope for a non-scoped function") end - -- add scope - if not scoped[fn] then - scoped[fn] = {} - end - local last_scope = scoped[fn][#scoped[fn]] - local fn_scope = {} - table.insert(scoped[fn], fn_scope) - -- add scoped variables to scope + if not fn.scoped then error("trying to initialize the scope stack for a non-scoped function") end + if not scoped[fn] then scoped[fn] = {} end + -- check scoped variables for _, name in ipairs(fn.scoped) do - -- preserve current values in last scope - if last_scope then - last_scope[name] = state.variables[name] + -- put fresh variable from global state in scope + local val = state.interpreter.global_state.variables[name] + if val.type ~= "undefined argument" and val.type ~= "pending definition" then -- only possibilities for scoped variable, and they're immutable + error("invalid scoped variable") end - -- set last value to nil to force to copy again from global variables in new scope - state.variables[name] = nil - local value = state.variables[name] - fn_scope[name] = value end end, + --- push a new scope for this function + push = function(self, state, fn) + local scoped = getmetatable(state.variables).scoped + self:init_scope(state, fn) + -- preserve current values in last scope + self:store_last_scope(state, fn) + -- add scope + local fn_scope = {} + table.insert(scoped[fn], fn_scope) + self:set_last_scope(state, fn) + end, + --- pop the last scope for this function pop = function(self, state, fn) local scoped = getmetatable(state.variables).scoped if not scoped[fn] then error("trying to pop a scope without any pushed scope") end -- remove current scope table.remove(scoped[fn]) - -- set scopped variables to previous scope - local last_scope = scoped[fn][#scoped[fn]] + -- restore last scope + self:set_last_scope(state, fn) + -- if the stack is empty, + -- we could remove mt.scoped[fn] I guess, but I don't think there's going to be a million different functions in a single game so should be ok + -- (anselme's performance is already bad enough, let's not create tables at each function call...) + end, + --- store the current values of the scoped variables in the last scope of this function + store_last_scope = function(self, state, fn) + local scopes = getmetatable(state.variables).scoped[fn] + local last_scope = scopes[#scopes] if last_scope then - for _, name in ipairs(fn.scoped) do - state.variables[name] = last_scope[name] + for _, name in pairs(fn.scoped) do + local val = rawget(state.variables, name) + if val then + last_scope[name] = val + end end - else -- no previous scope - for _, name in ipairs(fn.scoped) do - state.variables[name] = nil + end + end, + --- set scopped variables to previous scope + set_last_scope = function(self, state, fn) + local scopes = getmetatable(state.variables).scoped[fn] + for _, name in ipairs(fn.scoped) do + state.variables[name] = nil + end + local last_scope = scopes[#scopes] + if last_scope then + for name, val in pairs(last_scope) do + state.variables[name] = val end - -- no need to remove this I think, there's not going to be a million different functions in a single game so we can keep the tables - -- (anselme's performance is already bad enough, let's not create tables at each function call...) - -- scoped[fn] = nil end end }, @@ -468,7 +516,8 @@ local types = require((...):gsub("interpreter%.common$", "stdlib.types")) atypes, ltypes = types.anselme, types.lua eval = require((...):gsub("common$", "expression")) run_block = require((...):gsub("common$", "interpreter")).run_block -replace_with_copied_values = require((...):gsub("interpreter%.common$", "common")).replace_with_copied_values +local acommon = require((...):gsub("interpreter%.common$", "common")) +replace_with_copied_values, fix_not_modified_references = acommon.replace_with_copied_values, acommon.fix_not_modified_references identifier_pattern = require((...):gsub("interpreter%.common$", "parser.common")).identifier_pattern return common diff --git a/stdlib/functions.lua b/stdlib/functions.lua index 4b16fa4..b408c48 100644 --- a/stdlib/functions.lua +++ b/stdlib/functions.lua @@ -77,7 +77,7 @@ lua_functions = { local rval = r.value local name = n.value for _, ffqm in ipairs(rval) do - local var, vfqm = find(state.aliases, state.variables, ffqm..".", name) + local var, vfqm = find(state.aliases, state.interpreter.global_state.variables, ffqm..".", name) if var then return get_variable(state, vfqm) end diff --git a/test/tests/scope checkpoint mutable bis error.ans b/test/tests/scope checkpoint mutable bis error.ans new file mode 100644 index 0000000..fca65ae --- /dev/null +++ b/test/tests/scope checkpoint mutable bis error.ans @@ -0,0 +1,49 @@ +:post run = "check" + +:x = [99] + +:l = [1,x] + +:n = 0 + +$ f(t) + ~ t!insert(len(l)+1) + + f1: {l} {t} + + CHECK + § c + + ~ n < 1 + REC + + ~ n += 1 + ~ f(t) + + END REC + + f2: {l} + + CHECK 2 + § d + + ~ t!insert(len(t)+1) + + ~ t(2)!insert(len(l)+1) + + ~ error("t") + + f3: {l} {t} + +~ f(l) + +FINAL + +l: {l} + +x: {x} + +$ check + AFTER ERROR + + l: {l} \ No newline at end of file diff --git a/test/tests/scope checkpoint mutable bis error.lua b/test/tests/scope checkpoint mutable bis error.lua new file mode 100644 index 0000000..6353c8f --- /dev/null +++ b/test/tests/scope checkpoint mutable bis error.lua @@ -0,0 +1,120 @@ +local _={} +_[54]={} +_[53]={} +_[52]={} +_[51]={} +_[50]={} +_[49]={} +_[48]={} +_[47]={} +_[46]={} +_[45]={} +_[44]={} +_[43]={} +_[42]={} +_[41]={} +_[40]={} +_[39]={} +_[38]={} +_[37]={text="[1, [99], 3, 4]",tags=_[54]} +_[36]={text="l: ",tags=_[53]} +_[35]={text="AFTER ERROR",tags=_[52]} +_[34]={text="CHECK 2",tags=_[51]} +_[33]={text="[1, [99], 3, 4]",tags=_[50]} +_[32]={text="f2: ",tags=_[49]} +_[31]={text="CHECK",tags=_[48]} +_[30]={text="[1, [99], 3, 4]",tags=_[47]} +_[29]={text=" ",tags=_[46]} +_[28]={text="[1, [99], 3, 4]",tags=_[45]} +_[27]={text="f1: ",tags=_[44]} +_[26]={text="REC",tags=_[43]} +_[25]={text="CHECK",tags=_[42]} +_[24]={text="[1, [99], 3]",tags=_[41]} +_[23]={text=" ",tags=_[40]} +_[22]={text="[1, [99], 3]",tags=_[39]} +_[21]={text="f1: ",tags=_[38]} +_[20]={_[36],_[37]} +_[19]={_[35]} +_[18]={_[34]} +_[17]={_[32],_[33]} +_[16]={_[31]} +_[15]={_[27],_[28],_[29],_[30]} +_[14]={_[26]} +_[13]={_[25]} +_[12]={_[21],_[22],_[23],_[24]} +_[11]={"return"} +_[10]={"text",_[20]} +_[9]={"text",_[19]} +_[8]={"error","t; in Lua function \"error\"; at test/tests/scope checkpoint mutable bis error.ans:34; at test/tests/scope checkpoint mutable bis error.ans:21; at test/tests/scope checkpoint mutable bis error.ans:38"} +_[7]={"text",_[18]} +_[6]={"text",_[17]} +_[5]={"text",_[16]} +_[4]={"text",_[15]} +_[3]={"text",_[14]} +_[2]={"text",_[13]} +_[1]={"text",_[12]} +return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9],_[10],_[11]} +--[[ +{ "text", { { + tags = {}, + text = "f1: " + }, { + tags = {}, + text = "[1, [99], 3]" + }, { + tags = {}, + text = " " + }, { + tags = {}, + text = "[1, [99], 3]" + } } } +{ "text", { { + tags = {}, + text = "CHECK" + } } } +{ "text", { { + tags = {}, + text = "REC" + } } } +{ "text", { { + tags = {}, + text = "f1: " + }, { + tags = {}, + text = "[1, [99], 3, 4]" + }, { + tags = {}, + text = " " + }, { + tags = {}, + text = "[1, [99], 3, 4]" + } } } +{ "text", { { + tags = {}, + text = "CHECK" + } } } +{ "text", { { + tags = {}, + text = "f2: " + }, { + tags = {}, + text = "[1, [99], 3, 4]" + } } } +{ "text", { { + tags = {}, + text = "CHECK 2" + } } } +{ "error", 't; in Lua function "error"; at test/tests/scope checkpoint mutable bis error.ans:34; at test/tests/scope checkpoint mutable bis error.ans:21; at test/tests/scope checkpoint mutable bis error.ans:38' } +{ "text", { { + tags = {}, + text = "AFTER ERROR" + } } } +{ "text", { { + tags = {}, + text = "l: " + }, { + tags = {}, + text = "[1, [99], 3, 4]" + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/scope checkpoint mutable bis.ans b/test/tests/scope checkpoint mutable bis.ans new file mode 100644 index 0000000..d4af43b --- /dev/null +++ b/test/tests/scope checkpoint mutable bis.ans @@ -0,0 +1,40 @@ +:x = [99] + +:l = [1,x] + +:n = 0 + +$ f(t) + ~ t!insert(len(l)+1) + + f1: {l} {t} + + CHECK + § c + + ~ n < 1 + REC + + ~ n += 1 + ~ f(t) + + END REC + + f2: {l} + + CHECK 2 + § d + + ~ t!insert(len(t)+1) + + ~ t(2)!insert(len(l)+1) + + f3: {l} {t} + +~ f(l) + +FINAL + +l: {l} + +x: {x} diff --git a/test/tests/scope checkpoint mutable bis.lua b/test/tests/scope checkpoint mutable bis.lua new file mode 100644 index 0000000..7391703 --- /dev/null +++ b/test/tests/scope checkpoint mutable bis.lua @@ -0,0 +1,206 @@ +local _={} +_[93]={} +_[92]={} +_[91]={} +_[90]={} +_[89]={} +_[88]={} +_[87]={} +_[86]={} +_[85]={} +_[84]={} +_[83]={} +_[82]={} +_[81]={} +_[80]={} +_[79]={} +_[78]={} +_[77]={} +_[76]={} +_[75]={} +_[74]={} +_[73]={} +_[72]={} +_[71]={} +_[70]={} +_[69]={} +_[68]={} +_[67]={} +_[66]={} +_[65]={} +_[64]={} +_[63]={} +_[62]={tags=_[93],text="[99, 6, 7]"} +_[61]={tags=_[92],text="x: "} +_[60]={tags=_[91],text="[1, [99, 6, 7], 3, 4, 5, 6]"} +_[59]={tags=_[90],text="l: "} +_[58]={tags=_[89],text="FINAL"} +_[57]={tags=_[88],text="[1, [99, 6, 7], 3, 4, 5, 6]"} +_[56]={tags=_[87],text=" "} +_[55]={tags=_[86],text="[1, [99, 6, 7], 3, 4, 5, 6]"} +_[54]={tags=_[85],text="f3: "} +_[53]={tags=_[84],text="CHECK 2"} +_[52]={tags=_[83],text="[1, [99, 6], 3, 4, 5]"} +_[51]={tags=_[82],text="f2: "} +_[50]={tags=_[81],text="END REC"} +_[49]={tags=_[80],text="[1, [99, 6], 3, 4, 5]"} +_[48]={tags=_[79],text=" "} +_[47]={tags=_[78],text="[1, [99, 6], 3, 4, 5]"} +_[46]={tags=_[77],text="f3: "} +_[45]={tags=_[76],text="CHECK 2"} +_[44]={tags=_[75],text="[1, [99], 3, 4]"} +_[43]={tags=_[74],text="f2: "} +_[42]={tags=_[73],text="CHECK"} +_[41]={tags=_[72],text="[1, [99], 3, 4]"} +_[40]={tags=_[71],text=" "} +_[39]={tags=_[70],text="[1, [99], 3, 4]"} +_[38]={tags=_[69],text="f1: "} +_[37]={tags=_[68],text="REC"} +_[36]={tags=_[67],text="CHECK"} +_[35]={tags=_[66],text="[1, [99], 3]"} +_[34]={tags=_[65],text=" "} +_[33]={tags=_[64],text="[1, [99], 3]"} +_[32]={tags=_[63],text="f1: "} +_[31]={_[61],_[62]} +_[30]={_[59],_[60]} +_[29]={_[58]} +_[28]={_[54],_[55],_[56],_[57]} +_[27]={_[53]} +_[26]={_[51],_[52]} +_[25]={_[50]} +_[24]={_[46],_[47],_[48],_[49]} +_[23]={_[45]} +_[22]={_[43],_[44]} +_[21]={_[42]} +_[20]={_[38],_[39],_[40],_[41]} +_[19]={_[37]} +_[18]={_[36]} +_[17]={_[32],_[33],_[34],_[35]} +_[16]={"return"} +_[15]={"text",_[31]} +_[14]={"text",_[30]} +_[13]={"text",_[29]} +_[12]={"text",_[28]} +_[11]={"text",_[27]} +_[10]={"text",_[26]} +_[9]={"text",_[25]} +_[8]={"text",_[24]} +_[7]={"text",_[23]} +_[6]={"text",_[22]} +_[5]={"text",_[21]} +_[4]={"text",_[20]} +_[3]={"text",_[19]} +_[2]={"text",_[18]} +_[1]={"text",_[17]} +return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9],_[10],_[11],_[12],_[13],_[14],_[15],_[16]} +--[[ +{ "text", { { + tags = {}, + text = "f1: " + }, { + tags = {}, + text = "[1, [99], 3]" + }, { + tags = {}, + text = " " + }, { + tags = {}, + text = "[1, [99], 3]" + } } } +{ "text", { { + tags = {}, + text = "CHECK" + } } } +{ "text", { { + tags = {}, + text = "REC" + } } } +{ "text", { { + tags = {}, + text = "f1: " + }, { + tags = {}, + text = "[1, [99], 3, 4]" + }, { + tags = {}, + text = " " + }, { + tags = {}, + text = "[1, [99], 3, 4]" + } } } +{ "text", { { + tags = {}, + text = "CHECK" + } } } +{ "text", { { + tags = {}, + text = "f2: " + }, { + tags = {}, + text = "[1, [99], 3, 4]" + } } } +{ "text", { { + tags = {}, + text = "CHECK 2" + } } } +{ "text", { { + tags = {}, + text = "f3: " + }, { + tags = {}, + text = "[1, [99, 6], 3, 4, 5]" + }, { + tags = {}, + text = " " + }, { + tags = {}, + text = "[1, [99, 6], 3, 4, 5]" + } } } +{ "text", { { + tags = {}, + text = "END REC" + } } } +{ "text", { { + tags = {}, + text = "f2: " + }, { + tags = {}, + text = "[1, [99, 6], 3, 4, 5]" + } } } +{ "text", { { + tags = {}, + text = "CHECK 2" + } } } +{ "text", { { + tags = {}, + text = "f3: " + }, { + tags = {}, + text = "[1, [99, 6, 7], 3, 4, 5, 6]" + }, { + tags = {}, + text = " " + }, { + tags = {}, + text = "[1, [99, 6, 7], 3, 4, 5, 6]" + } } } +{ "text", { { + tags = {}, + text = "FINAL" + } } } +{ "text", { { + tags = {}, + text = "l: " + }, { + tags = {}, + text = "[1, [99, 6, 7], 3, 4, 5, 6]" + } } } +{ "text", { { + tags = {}, + text = "x: " + }, { + tags = {}, + text = "[99, 6, 7]" + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/scope checkpoint mutable error.ans b/test/tests/scope checkpoint mutable error.ans new file mode 100644 index 0000000..3fbdcfa --- /dev/null +++ b/test/tests/scope checkpoint mutable error.ans @@ -0,0 +1,38 @@ +:post run = "check" + +:l = [1] + +:n = 0 + +$ f(t) + ~ t!insert(len(l)+1) + + f1: {l} {t} + + CHECK + § c + + ~ n < 1 + REC + + ~ n += 1 + ~ f(t) + + END REC + + ~ t!insert(len(t)+1) + + ~ error("t") + + f2: {l} + +~ f(l) + +FINAL + +l: {l} + +$ check + AFTER ERROR + + l: {l} \ No newline at end of file diff --git a/test/tests/scope checkpoint mutable error.lua b/test/tests/scope checkpoint mutable error.lua new file mode 100644 index 0000000..577ea2f --- /dev/null +++ b/test/tests/scope checkpoint mutable error.lua @@ -0,0 +1,99 @@ +local _={} +_[44]={} +_[43]={} +_[42]={} +_[41]={} +_[40]={} +_[39]={} +_[38]={} +_[37]={} +_[36]={} +_[35]={} +_[34]={} +_[33]={} +_[32]={} +_[31]={} +_[30]={tags=_[44],text="[1, 2, 3]"} +_[29]={tags=_[43],text="l: "} +_[28]={tags=_[42],text="AFTER ERROR"} +_[27]={tags=_[41],text="CHECK"} +_[26]={tags=_[40],text="[1, 2, 3]"} +_[25]={tags=_[39],text=" "} +_[24]={tags=_[38],text="[1, 2, 3]"} +_[23]={tags=_[37],text="f1: "} +_[22]={tags=_[36],text="REC"} +_[21]={tags=_[35],text="CHECK"} +_[20]={tags=_[34],text="[1, 2]"} +_[19]={tags=_[33],text=" "} +_[18]={tags=_[32],text="[1, 2]"} +_[17]={tags=_[31],text="f1: "} +_[16]={_[29],_[30]} +_[15]={_[28]} +_[14]={_[27]} +_[13]={_[23],_[24],_[25],_[26]} +_[12]={_[22]} +_[11]={_[21]} +_[10]={_[17],_[18],_[19],_[20]} +_[9]={"return"} +_[8]={"text",_[16]} +_[7]={"text",_[15]} +_[6]={"error","t; in Lua function \"error\"; at test/tests/scope checkpoint mutable error.ans:25; at test/tests/scope checkpoint mutable error.ans:19; at test/tests/scope checkpoint mutable error.ans:29"} +_[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", { { + tags = {}, + text = "f1: " + }, { + tags = {}, + text = "[1, 2]" + }, { + tags = {}, + text = " " + }, { + tags = {}, + text = "[1, 2]" + } } } +{ "text", { { + tags = {}, + text = "CHECK" + } } } +{ "text", { { + tags = {}, + text = "REC" + } } } +{ "text", { { + tags = {}, + text = "f1: " + }, { + tags = {}, + text = "[1, 2, 3]" + }, { + tags = {}, + text = " " + }, { + tags = {}, + text = "[1, 2, 3]" + } } } +{ "text", { { + tags = {}, + text = "CHECK" + } } } +{ "error", 't; in Lua function "error"; at test/tests/scope checkpoint mutable error.ans:25; at test/tests/scope checkpoint mutable error.ans:19; at test/tests/scope checkpoint mutable error.ans:29' } +{ "text", { { + tags = {}, + text = "AFTER ERROR" + } } } +{ "text", { { + tags = {}, + text = "l: " + }, { + tags = {}, + text = "[1, 2, 3]" + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/scope checkpoint mutable ter error.ans b/test/tests/scope checkpoint mutable ter error.ans new file mode 100644 index 0000000..2c70ade --- /dev/null +++ b/test/tests/scope checkpoint mutable ter error.ans @@ -0,0 +1,51 @@ +:post run = "check" + +:x = [99] + +:l = [1,x] + +:n = 0 + +$ f(t) + ~ t!insert(len(l)+1) + + f1: {l} {t} + + CHECK + § c + + ~ n < 1 + REC + + ~ n += 1 + ~ f(t) + + END REC + + ~ x!insert(12) + + f2: {l} + + CHECK 2 + § d + + ~ t!insert(len(t)+1) + + ~ t(2)!insert(len(l)+1) + + ~ error("t") + + f3: {l} {t} + +~ f(l) + +FINAL + +l: {l} + +x: {x} + +$ check + AFTER ERROR + + l: {l} \ No newline at end of file diff --git a/test/tests/scope checkpoint mutable ter error.lua b/test/tests/scope checkpoint mutable ter error.lua new file mode 100644 index 0000000..fe49431 --- /dev/null +++ b/test/tests/scope checkpoint mutable ter error.lua @@ -0,0 +1,120 @@ +local _={} +_[54]={} +_[53]={} +_[52]={} +_[51]={} +_[50]={} +_[49]={} +_[48]={} +_[47]={} +_[46]={} +_[45]={} +_[44]={} +_[43]={} +_[42]={} +_[41]={} +_[40]={} +_[39]={} +_[38]={} +_[37]={text="[1, [99, 12], 3, 4]",tags=_[54]} +_[36]={text="l: ",tags=_[53]} +_[35]={text="AFTER ERROR",tags=_[52]} +_[34]={text="CHECK 2",tags=_[51]} +_[33]={text="[1, [99, 12], 3, 4]",tags=_[50]} +_[32]={text="f2: ",tags=_[49]} +_[31]={text="CHECK",tags=_[48]} +_[30]={text="[1, [99], 3, 4]",tags=_[47]} +_[29]={text=" ",tags=_[46]} +_[28]={text="[1, [99], 3, 4]",tags=_[45]} +_[27]={text="f1: ",tags=_[44]} +_[26]={text="REC",tags=_[43]} +_[25]={text="CHECK",tags=_[42]} +_[24]={text="[1, [99], 3]",tags=_[41]} +_[23]={text=" ",tags=_[40]} +_[22]={text="[1, [99], 3]",tags=_[39]} +_[21]={text="f1: ",tags=_[38]} +_[20]={_[36],_[37]} +_[19]={_[35]} +_[18]={_[34]} +_[17]={_[32],_[33]} +_[16]={_[31]} +_[15]={_[27],_[28],_[29],_[30]} +_[14]={_[26]} +_[13]={_[25]} +_[12]={_[21],_[22],_[23],_[24]} +_[11]={"return"} +_[10]={"text",_[20]} +_[9]={"text",_[19]} +_[8]={"error","t; in Lua function \"error\"; at test/tests/scope checkpoint mutable ter error.ans:36; at test/tests/scope checkpoint mutable ter error.ans:21; at test/tests/scope checkpoint mutable ter error.ans:40"} +_[7]={"text",_[18]} +_[6]={"text",_[17]} +_[5]={"text",_[16]} +_[4]={"text",_[15]} +_[3]={"text",_[14]} +_[2]={"text",_[13]} +_[1]={"text",_[12]} +return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9],_[10],_[11]} +--[[ +{ "text", { { + tags = {}, + text = "f1: " + }, { + tags = {}, + text = "[1, [99], 3]" + }, { + tags = {}, + text = " " + }, { + tags = {}, + text = "[1, [99], 3]" + } } } +{ "text", { { + tags = {}, + text = "CHECK" + } } } +{ "text", { { + tags = {}, + text = "REC" + } } } +{ "text", { { + tags = {}, + text = "f1: " + }, { + tags = {}, + text = "[1, [99], 3, 4]" + }, { + tags = {}, + text = " " + }, { + tags = {}, + text = "[1, [99], 3, 4]" + } } } +{ "text", { { + tags = {}, + text = "CHECK" + } } } +{ "text", { { + tags = {}, + text = "f2: " + }, { + tags = {}, + text = "[1, [99, 12], 3, 4]" + } } } +{ "text", { { + tags = {}, + text = "CHECK 2" + } } } +{ "error", 't; in Lua function "error"; at test/tests/scope checkpoint mutable ter error.ans:36; at test/tests/scope checkpoint mutable ter error.ans:21; at test/tests/scope checkpoint mutable ter error.ans:40' } +{ "text", { { + tags = {}, + text = "AFTER ERROR" + } } } +{ "text", { { + tags = {}, + text = "l: " + }, { + tags = {}, + text = "[1, [99, 12], 3, 4]" + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/scope checkpoint mutable ter.ans b/test/tests/scope checkpoint mutable ter.ans new file mode 100644 index 0000000..de9db86 --- /dev/null +++ b/test/tests/scope checkpoint mutable ter.ans @@ -0,0 +1,42 @@ +:x = [99] + +:l = [1,x] + +:n = 0 + +$ f(t) + ~ t!insert(len(l)+1) + + f1: {l} {t} + + CHECK + § c + + ~ n < 1 + REC + + ~ n += 1 + ~ f(t) + + END REC + + ~ x!insert(12) + + f2: {l} + + CHECK 2 + § d + + ~ t!insert(len(t)+1) + + ~ t(2)!insert(len(l)+1) + + f3: {l} {t} + +~ f(l) + +FINAL + +l: {l} + +x: {x} diff --git a/test/tests/scope checkpoint mutable ter.lua b/test/tests/scope checkpoint mutable ter.lua new file mode 100644 index 0000000..9ea5706 --- /dev/null +++ b/test/tests/scope checkpoint mutable ter.lua @@ -0,0 +1,206 @@ +local _={} +_[93]={} +_[92]={} +_[91]={} +_[90]={} +_[89]={} +_[88]={} +_[87]={} +_[86]={} +_[85]={} +_[84]={} +_[83]={} +_[82]={} +_[81]={} +_[80]={} +_[79]={} +_[78]={} +_[77]={} +_[76]={} +_[75]={} +_[74]={} +_[73]={} +_[72]={} +_[71]={} +_[70]={} +_[69]={} +_[68]={} +_[67]={} +_[66]={} +_[65]={} +_[64]={} +_[63]={} +_[62]={tags=_[93],text="[99, 12, 6, 12, 7]"} +_[61]={tags=_[92],text="x: "} +_[60]={tags=_[91],text="[1, [99, 12, 6, 12, 7], 3, 4, 5, 6]"} +_[59]={tags=_[90],text="l: "} +_[58]={tags=_[89],text="FINAL"} +_[57]={tags=_[88],text="[1, [99, 12, 6, 12, 7], 3, 4, 5, 6]"} +_[56]={tags=_[87],text=" "} +_[55]={tags=_[86],text="[1, [99, 12, 6, 12, 7], 3, 4, 5, 6]"} +_[54]={tags=_[85],text="f3: "} +_[53]={tags=_[84],text="CHECK 2"} +_[52]={tags=_[83],text="[1, [99, 12, 6, 12], 3, 4, 5]"} +_[51]={tags=_[82],text="f2: "} +_[50]={tags=_[81],text="END REC"} +_[49]={tags=_[80],text="[1, [99, 12, 6], 3, 4, 5]"} +_[48]={tags=_[79],text=" "} +_[47]={tags=_[78],text="[1, [99, 12, 6], 3, 4, 5]"} +_[46]={tags=_[77],text="f3: "} +_[45]={tags=_[76],text="CHECK 2"} +_[44]={tags=_[75],text="[1, [99, 12], 3, 4]"} +_[43]={tags=_[74],text="f2: "} +_[42]={tags=_[73],text="CHECK"} +_[41]={tags=_[72],text="[1, [99], 3, 4]"} +_[40]={tags=_[71],text=" "} +_[39]={tags=_[70],text="[1, [99], 3, 4]"} +_[38]={tags=_[69],text="f1: "} +_[37]={tags=_[68],text="REC"} +_[36]={tags=_[67],text="CHECK"} +_[35]={tags=_[66],text="[1, [99], 3]"} +_[34]={tags=_[65],text=" "} +_[33]={tags=_[64],text="[1, [99], 3]"} +_[32]={tags=_[63],text="f1: "} +_[31]={_[61],_[62]} +_[30]={_[59],_[60]} +_[29]={_[58]} +_[28]={_[54],_[55],_[56],_[57]} +_[27]={_[53]} +_[26]={_[51],_[52]} +_[25]={_[50]} +_[24]={_[46],_[47],_[48],_[49]} +_[23]={_[45]} +_[22]={_[43],_[44]} +_[21]={_[42]} +_[20]={_[38],_[39],_[40],_[41]} +_[19]={_[37]} +_[18]={_[36]} +_[17]={_[32],_[33],_[34],_[35]} +_[16]={"return"} +_[15]={"text",_[31]} +_[14]={"text",_[30]} +_[13]={"text",_[29]} +_[12]={"text",_[28]} +_[11]={"text",_[27]} +_[10]={"text",_[26]} +_[9]={"text",_[25]} +_[8]={"text",_[24]} +_[7]={"text",_[23]} +_[6]={"text",_[22]} +_[5]={"text",_[21]} +_[4]={"text",_[20]} +_[3]={"text",_[19]} +_[2]={"text",_[18]} +_[1]={"text",_[17]} +return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9],_[10],_[11],_[12],_[13],_[14],_[15],_[16]} +--[[ +{ "text", { { + tags = {}, + text = "f1: " + }, { + tags = {}, + text = "[1, [99], 3]" + }, { + tags = {}, + text = " " + }, { + tags = {}, + text = "[1, [99], 3]" + } } } +{ "text", { { + tags = {}, + text = "CHECK" + } } } +{ "text", { { + tags = {}, + text = "REC" + } } } +{ "text", { { + tags = {}, + text = "f1: " + }, { + tags = {}, + text = "[1, [99], 3, 4]" + }, { + tags = {}, + text = " " + }, { + tags = {}, + text = "[1, [99], 3, 4]" + } } } +{ "text", { { + tags = {}, + text = "CHECK" + } } } +{ "text", { { + tags = {}, + text = "f2: " + }, { + tags = {}, + text = "[1, [99, 12], 3, 4]" + } } } +{ "text", { { + tags = {}, + text = "CHECK 2" + } } } +{ "text", { { + tags = {}, + text = "f3: " + }, { + tags = {}, + text = "[1, [99, 12, 6], 3, 4, 5]" + }, { + tags = {}, + text = " " + }, { + tags = {}, + text = "[1, [99, 12, 6], 3, 4, 5]" + } } } +{ "text", { { + tags = {}, + text = "END REC" + } } } +{ "text", { { + tags = {}, + text = "f2: " + }, { + tags = {}, + text = "[1, [99, 12, 6, 12], 3, 4, 5]" + } } } +{ "text", { { + tags = {}, + text = "CHECK 2" + } } } +{ "text", { { + tags = {}, + text = "f3: " + }, { + tags = {}, + text = "[1, [99, 12, 6, 12, 7], 3, 4, 5, 6]" + }, { + tags = {}, + text = " " + }, { + tags = {}, + text = "[1, [99, 12, 6, 12, 7], 3, 4, 5, 6]" + } } } +{ "text", { { + tags = {}, + text = "FINAL" + } } } +{ "text", { { + tags = {}, + text = "l: " + }, { + tags = {}, + text = "[1, [99, 12, 6, 12, 7], 3, 4, 5, 6]" + } } } +{ "text", { { + tags = {}, + text = "x: " + }, { + tags = {}, + text = "[99, 12, 6, 12, 7]" + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/scope checkpoint mutable.ans b/test/tests/scope checkpoint mutable.ans new file mode 100644 index 0000000..29f459b --- /dev/null +++ b/test/tests/scope checkpoint mutable.ans @@ -0,0 +1,29 @@ +:l = [1] + +:n = 0 + +$ f(t) + ~ t!insert(len(l)+1) + + f1: {l} {t} + + CHECK + § c + + ~ n < 1 + REC + + ~ n += 1 + ~ f(t) + + END REC + + ~ t!insert(len(t)+1) + + f2: {l} + +~ f(l) + +FINAL + +l: {l} \ No newline at end of file diff --git a/test/tests/scope checkpoint mutable.lua b/test/tests/scope checkpoint mutable.lua new file mode 100644 index 0000000..687ac03 --- /dev/null +++ b/test/tests/scope checkpoint mutable.lua @@ -0,0 +1,131 @@ +local _={} +_[59]={} +_[58]={} +_[57]={} +_[56]={} +_[55]={} +_[54]={} +_[53]={} +_[52]={} +_[51]={} +_[50]={} +_[49]={} +_[48]={} +_[47]={} +_[46]={} +_[45]={} +_[44]={} +_[43]={} +_[42]={} +_[41]={} +_[40]={text="[1, 2, 3, 4, 5]",tags=_[59]} +_[39]={text="l: ",tags=_[58]} +_[38]={text="FINAL",tags=_[57]} +_[37]={text="[1, 2, 3, 4, 5]",tags=_[56]} +_[36]={text="f2: ",tags=_[55]} +_[35]={text="END REC",tags=_[54]} +_[34]={text="[1, 2, 3, 4]",tags=_[53]} +_[33]={text="f2: ",tags=_[52]} +_[32]={text="CHECK",tags=_[51]} +_[31]={text="[1, 2, 3]",tags=_[50]} +_[30]={text=" ",tags=_[49]} +_[29]={text="[1, 2, 3]",tags=_[48]} +_[28]={text="f1: ",tags=_[47]} +_[27]={text="REC",tags=_[46]} +_[26]={text="CHECK",tags=_[45]} +_[25]={text="[1, 2]",tags=_[44]} +_[24]={text=" ",tags=_[43]} +_[23]={text="[1, 2]",tags=_[42]} +_[22]={text="f1: ",tags=_[41]} +_[21]={_[39],_[40]} +_[20]={_[38]} +_[19]={_[36],_[37]} +_[18]={_[35]} +_[17]={_[33],_[34]} +_[16]={_[32]} +_[15]={_[28],_[29],_[30],_[31]} +_[14]={_[27]} +_[13]={_[26]} +_[12]={_[22],_[23],_[24],_[25]} +_[11]={"return"} +_[10]={"text",_[21]} +_[9]={"text",_[20]} +_[8]={"text",_[19]} +_[7]={"text",_[18]} +_[6]={"text",_[17]} +_[5]={"text",_[16]} +_[4]={"text",_[15]} +_[3]={"text",_[14]} +_[2]={"text",_[13]} +_[1]={"text",_[12]} +return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9],_[10],_[11]} +--[[ +{ "text", { { + tags = {}, + text = "f1: " + }, { + tags = {}, + text = "[1, 2]" + }, { + tags = {}, + text = " " + }, { + tags = {}, + text = "[1, 2]" + } } } +{ "text", { { + tags = {}, + text = "CHECK" + } } } +{ "text", { { + tags = {}, + text = "REC" + } } } +{ "text", { { + tags = {}, + text = "f1: " + }, { + tags = {}, + text = "[1, 2, 3]" + }, { + tags = {}, + text = " " + }, { + tags = {}, + text = "[1, 2, 3]" + } } } +{ "text", { { + tags = {}, + text = "CHECK" + } } } +{ "text", { { + tags = {}, + text = "f2: " + }, { + tags = {}, + text = "[1, 2, 3, 4]" + } } } +{ "text", { { + tags = {}, + text = "END REC" + } } } +{ "text", { { + tags = {}, + text = "f2: " + }, { + tags = {}, + text = "[1, 2, 3, 4, 5]" + } } } +{ "text", { { + tags = {}, + text = "FINAL" + } } } +{ "text", { { + tags = {}, + text = "l: " + }, { + tags = {}, + text = "[1, 2, 3, 4, 5]" + } } } +{ "return" } +]]-- \ No newline at end of file