diff --git a/REFERENCE.md b/REFERENCE.md index 8c06832..726bd7c 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -153,6 +153,33 @@ $ f(a, b...) ~ f("discarded") ``` +When a parameter list is given (or just empty parentheses `()`), the function is considered `scoped` - this means that any variable defined in it will only be defined in a call to the function and can only be accessed from this specific call: + +``` +(Non-scoped function: usual behaviour, variables are accessible from everywhere and always.) +$ f + :a = 1 + ~ a += 1 + +{f.a} is 1 + +~ f +{f.a} is 2 + +(Scoped function: can't access g.a from outside the function) +$ g() + :a = 1 + {a} + ~ a += 1 + +(Each time the function is called, it has access to its own version of g.a, and don't share it - so this display 1 both times:) +~ g + +~ g +``` + +This is basically the behaviour you'd expect from functions in most other programming languages, and what you would use in Anselme any time you don't care about storing the function variables or want the exact same initial function variables each time you call the function (e.g. recursion). Scoped variables are not kept in save files, and are not affected by checkpointing. + Functions with the same name can be defined, as long as they have a different arguments. Functions will be selected based on the number of arguments given, their name and their type annotation: ``` @@ -891,7 +918,7 @@ This only works on strings: ##### Sequential execution -`cycle(...)`: given function/checkpoint identifiers as string as arguments, will execute them in the order given each time the function is ran; e.g., `cycle("a", "b")` will execute a on the first execution, then b, then a again, etc. +`cycle(...)`: given function/checkpoint references as arguments, will execute them in the order given each time the function is ran; e.g., `cycle(&a, &b)` will execute a on the first execution, then b, then a again, etc. `next(...)`: same as cycle, but will not cycle; once the end of sequence is reached, will keep executing the last element. diff --git a/anselme.lua b/anselme.lua index 0b8747b..95a3628 100644 --- a/anselme.lua +++ b/anselme.lua @@ -6,11 +6,11 @@ local anselme = { -- api is incremented a each update which may break Lua API compatibility versions = { save = 1, - language = 20, + language = 21, api = 4 }, -- version is incremented at each update - version = 21, + version = 22, --- currently running interpreter running = nil } @@ -198,6 +198,7 @@ local interpreter_methods = { if not success then return nil, event elseif event == "error" then + self.end_event = "error" return nil, data elseif event ~= "return" then return nil, ("evaluated expression generated an %q event; at %s"):format(event, self.state.interpreter.running_line.source) @@ -479,8 +480,7 @@ local vm_mt = { functions = self.state.functions, -- no need for a cache as we can't define or modify any function from the interpreter for now variables = setmetatable({}, { __index = function(variables, k) - local mt = getmetatable(variables) - local cache = mt.cache + local cache = getmetatable(variables).cache if cache[k] == nil then cache[k] = copy(self.state.variables[k], getmetatable(variables).copy_cache) end @@ -490,6 +490,9 @@ local vm_mt = { 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 + -- 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) + scoped = {} }), interpreter = { -- constant diff --git a/common.lua b/common.lua index 381f5d0..b0d054c 100644 --- a/common.lua +++ b/common.lua @@ -1,5 +1,3 @@ -local identifier_pattern - --- 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) @@ -65,6 +63,5 @@ common = { } package.loaded[...] = common -identifier_pattern = require((...):gsub("common$", "parser.common")).identifier_pattern return common diff --git a/interpreter/common.lua b/interpreter/common.lua index 40a746e..d16f8be 100644 --- a/interpreter/common.lua +++ b/interpreter/common.lua @@ -58,9 +58,7 @@ common = { mt.cache = {} -- merge modified re-assigned variables for var, value in pairs(state.variables) do - if common.should_keep_variable(state, var) then - global.variables[var] = value - end + global.variables[var] = value state.variables[var] = nil end end, @@ -81,16 +79,62 @@ common = { return var end end, + --- set the value of a variable set_variable = function(state, name, val) state.variables[name] = val end, + --- handle scoped function + scope = { + push = 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 + for _, name in ipairs(fn.scoped) do + -- preserve current values in last scope + if last_scope then + last_scope[name] = state.variables[name] + 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, + 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]] + if last_scope then + for _, name in ipairs(fn.scoped) do + state.variables[name] = last_scope[name] + end + else -- no previous scope + for _, name in ipairs(fn.scoped) do + state.variables[name] = nil + 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 + }, --- mark a table as modified, so it will be merged on the next checkpoint if it appears somewhere in a value mark_as_modified = function(state, v) local modified = getmetatable(state.variables).modified_tables table.insert(modified, v) end, - --- returns true if a variable should be persisted on save/merge - -- will exclude: undefined variables, variables in functions defined with parentheses, internal anselme variables + --- returns true if a variable should be persisted on save + -- will exclude: undefined variables, variables in scoped functions, internal anselme variables should_keep_variable = function(state, name) local v = state.variables[name] return v.type ~= "undefined argument" and v.type ~= "pending definition" and name:match("^"..identifier_pattern.."$") and not name:match("^anselme%.") diff --git a/interpreter/expression.lua b/interpreter/expression.lua index 5fe49b0..05ad756 100644 --- a/interpreter/expression.lua +++ b/interpreter/expression.lua @@ -1,5 +1,5 @@ local expression -local to_lua, from_lua, eval_text, is_of_type, truthy, format, pretty_type, get_variable, tags, eval_text_callback, events, flatten_list, set_variable +local to_lua, from_lua, eval_text, is_of_type, truthy, format, pretty_type, get_variable, tags, eval_text_callback, events, flatten_list, set_variable, scope local run @@ -65,20 +65,6 @@ local function eval(state, exp) type = "list", value = l } - -- event buffer (internal type, only issued from a text or choice line) - elseif exp.type == "text" then - local l = {} - events:push_buffer(state, l) - local current_tags = tags:current(state) - local v, e = eval_text_callback(state, exp.text, function(text) - events:append(state, "text", { text = text, tags = current_tags }) - end) - events:pop_buffer(state) - if not v then return v, e end - return { - type = "event buffer", - value = l - } -- assignment elseif exp.type == ":=" then if exp.left.type == "variable" then @@ -203,7 +189,7 @@ local function eval(state, exp) end -- try to select a function local tried_function_error_messages = {} - local selected_variant = { depths = { assignment = nil }, variant = nil } + local selected_variant = { depths = { assignment = nil }, variant = nil, args_to_set = nil } for _, fn in ipairs(variants) do -- checkpoint: no args, nothing to select on if fn.type == "checkpoint" then @@ -218,6 +204,7 @@ local function eval(state, exp) if not fn.assignment or exp.assignment then local ok = true -- get and set args + local variant_args = {} local used_args = {} local depths = { assignment = nil } for j, param in ipairs(fn.params) do @@ -257,11 +244,11 @@ local function eval(state, exp) depths[j] = math.huge end -- set - set_variable(state, param.full_name, val) + variant_args[param.full_name] = val -- default: evaluate once function is selected -- there's no need to type check because the type annotation is already the default value's type, because of syntax elseif param.default then - set_variable(state, param.full_name, { type = "pending definition", value = { expression = param.default, source = fn.source } }) + variant_args[param.full_name] = { type = "pending definition", value = { expression = param.default, source = fn.source } } else ok = false table.insert(tried_function_error_messages, ("%s: missing mandatory argument %q in function %q call"):format(fn.pretty_signature, param.name, fn.name)) @@ -300,12 +287,13 @@ local function eval(state, exp) depths.assignment = math.huge end -- set - set_variable(state, param.full_name, assignment) + variant_args[param.full_name] = assignment end if ok then if not selected_variant.variant then selected_variant.depths = depths selected_variant.variant = fn + selected_variant.args_to_set = variant_args else -- check specificity order local lower @@ -330,6 +318,7 @@ local function eval(state, exp) if lower then selected_variant.depths = depths selected_variant.variant = fn + selected_variant.args_to_set = variant_args elseif lower == nil then -- equal, ambigous dispatch return nil, ("function call %q is ambigous; may be at least either:\n\t%s\n\t%s"):format(exp.called_name, fn.pretty_signature, selected_variant.variant.pretty_signature) end @@ -349,6 +338,17 @@ local function eval(state, exp) return r elseif fn.type == "function" then local ret + -- push scope + -- NOTE: if error happens between here and scope:pop, will leave the stack a mess + -- should not be an issue since an interpreter is supposed to be discarded after an error, but should change this if we ever + -- add some excepetion handling in anselme at some point + if fn.scoped then + scope:push(state, fn) + end + -- set arguments + for name, val in pairs(selected_variant.args_to_set) do + set_variable(state, name, val) + end -- get function vars local checkpoint, checkpointe = get_variable(state, fn.namespace.."๐Ÿ”–") if not checkpoint then return nil, checkpointe end @@ -435,6 +435,10 @@ local function eval(state, exp) type = "number", value = seen.value + 1 }) + -- pop scope + if fn.scoped then + scope:pop(state, fn) + end -- return value if not ret then return nil, ("function %q didn't return a value"):format(exp.called_name) end return ret @@ -456,6 +460,20 @@ local function eval(state, exp) called_name = called_name .. " := " .. pretty_type(assignment) end return nil, ("no compatible function found for call to %s; potential candidates were:\n\t%s"):format(called_name, table.concat(tried_function_error_messages, "\n\t")) + -- event buffer (internal type, only issued from a text or choice line) + elseif exp.type == "text" then + local l = {} + events:push_buffer(state, l) + local current_tags = tags:current(state) + local v, e = eval_text_callback(state, exp.text, function(text) + events:append(state, "text", { text = text, tags = current_tags }) + end) + events:pop_buffer(state) + if not v then return v, e end + return { + type = "event buffer", + value = l + } else return nil, ("unknown expression %q"):format(tostring(exp.type)) end @@ -466,6 +484,6 @@ run = require((...):gsub("expression$", "interpreter")).run expression = require((...):gsub("interpreter%.expression$", "parser.expression")) flatten_list = require((...):gsub("interpreter%.expression$", "parser.common")).flatten_list local common = require((...):gsub("expression$", "common")) -to_lua, from_lua, eval_text, is_of_type, truthy, format, pretty_type, get_variable, tags, eval_text_callback, events, set_variable = common.to_lua, common.from_lua, common.eval_text, common.is_of_type, common.truthy, common.format, common.pretty_type, common.get_variable, common.tags, common.eval_text_callback, common.events, common.set_variable +to_lua, from_lua, eval_text, is_of_type, truthy, format, pretty_type, get_variable, tags, eval_text_callback, events, set_variable, scope = common.to_lua, common.from_lua, common.eval_text, common.is_of_type, common.truthy, common.format, common.pretty_type, common.get_variable, common.tags, common.eval_text_callback, common.events, common.set_variable, common.scope return eval diff --git a/notes.txt b/notes.txt index 55a733a..e08548c 100644 --- a/notes.txt +++ b/notes.txt @@ -43,7 +43,7 @@ Reserved symbols that are still not used as a line type: `^+-= 1 + ~ find first smaller(2) + ~ f! ]] local functions = { diff --git a/test/tests/function cycle.ans b/test/tests/function cycle.ans index d70e4d8..132f48f 100644 --- a/test/tests/function cycle.ans +++ b/test/tests/function cycle.ans @@ -5,10 +5,14 @@ $ f b $ c c - ~ cycle("a","b","c") + ~ cycle(&a,&b,&c) ~ f + ~ f + ~ f + +~ f + ~ f -~ f \ No newline at end of file diff --git a/test/tests/function scoped mutable.ans b/test/tests/function scoped mutable.ans new file mode 100644 index 0000000..382ce8a --- /dev/null +++ b/test/tests/function scoped mutable.ans @@ -0,0 +1,40 @@ +:n = 0 + +$ f(c=1) + :a = [] + + start: {a} + + ~ a!insert(c) + + ~ n += 1 + + before recursion {c}: {a} + + ~ n < 5 + ~ f(c+1) + + after recursion {c}: {a} + +new list each time: + +~ f + +$ g(c=1, a=[]) + start: {a} + + ~ a!insert(c) + + ~ n += 1 + + before recursion {c}: {a} + + ~ n < 5 + ~ g(c+1, a) + + after recursion {c}: {a} + +pass list: + +~ n := 0 +~ g diff --git a/test/tests/function scoped mutable.lua b/test/tests/function scoped mutable.lua new file mode 100644 index 0000000..8503a69 --- /dev/null +++ b/test/tests/function scoped mutable.lua @@ -0,0 +1,566 @@ +local _={} +_[249]={} +_[248]={} +_[247]={} +_[246]={} +_[245]={} +_[244]={} +_[243]={} +_[242]={} +_[241]={} +_[240]={} +_[239]={} +_[238]={} +_[237]={} +_[236]={} +_[235]={} +_[234]={} +_[233]={} +_[232]={} +_[231]={} +_[230]={} +_[229]={} +_[228]={} +_[227]={} +_[226]={} +_[225]={} +_[224]={} +_[223]={} +_[222]={} +_[221]={} +_[220]={} +_[219]={} +_[218]={} +_[217]={} +_[216]={} +_[215]={} +_[214]={} +_[213]={} +_[212]={} +_[211]={} +_[210]={} +_[209]={} +_[208]={} +_[207]={} +_[206]={} +_[205]={} +_[204]={} +_[203]={} +_[202]={} +_[201]={} +_[200]={} +_[199]={} +_[198]={} +_[197]={} +_[196]={} +_[195]={} +_[194]={} +_[193]={} +_[192]={} +_[191]={} +_[190]={} +_[189]={} +_[188]={} +_[187]={} +_[186]={} +_[185]={} +_[184]={} +_[183]={} +_[182]={} +_[181]={} +_[180]={} +_[179]={} +_[178]={} +_[177]={} +_[176]={} +_[175]={} +_[174]={} +_[173]={} +_[172]={} +_[171]={} +_[170]={} +_[169]={} +_[168]={} +_[167]={} +_[166]={} +_[165]={} +_[164]={} +_[163]={} +_[162]={} +_[161]={} +_[160]={} +_[159]={} +_[158]={} +_[157]={} +_[156]={} +_[155]={tags=_[249],text="[1, 2, 3, 4, 5]"} +_[154]={tags=_[248],text=": "} +_[153]={tags=_[247],text="1"} +_[152]={tags=_[246],text="after recursion "} +_[151]={tags=_[245],text="[1, 2, 3, 4, 5]"} +_[150]={tags=_[244],text=": "} +_[149]={tags=_[243],text="2"} +_[148]={tags=_[242],text="after recursion "} +_[147]={tags=_[241],text="[1, 2, 3, 4, 5]"} +_[146]={tags=_[240],text=": "} +_[145]={tags=_[239],text="3"} +_[144]={tags=_[238],text="after recursion "} +_[143]={tags=_[237],text="[1, 2, 3, 4, 5]"} +_[142]={tags=_[236],text=": "} +_[141]={tags=_[235],text="4"} +_[140]={tags=_[234],text="after recursion "} +_[139]={tags=_[233],text="[1, 2, 3, 4, 5]"} +_[138]={tags=_[232],text=": "} +_[137]={tags=_[231],text="5"} +_[136]={tags=_[230],text="before recursion "} +_[135]={tags=_[229],text="[1, 2, 3, 4]"} +_[134]={tags=_[228],text="start: "} +_[133]={tags=_[227],text="[1, 2, 3, 4]"} +_[132]={tags=_[226],text=": "} +_[131]={tags=_[225],text="4"} +_[130]={tags=_[224],text="before recursion "} +_[129]={tags=_[223],text="[1, 2, 3]"} +_[128]={tags=_[222],text="start: "} +_[127]={tags=_[221],text="[1, 2, 3]"} +_[126]={tags=_[220],text=": "} +_[125]={tags=_[219],text="3"} +_[124]={tags=_[218],text="before recursion "} +_[123]={tags=_[217],text="[1, 2]"} +_[122]={tags=_[216],text="start: "} +_[121]={tags=_[215],text="[1, 2]"} +_[120]={tags=_[214],text=": "} +_[119]={tags=_[213],text="2"} +_[118]={tags=_[212],text="before recursion "} +_[117]={tags=_[211],text="[1]"} +_[116]={tags=_[210],text="start: "} +_[115]={tags=_[209],text="[1]"} +_[114]={tags=_[208],text=": "} +_[113]={tags=_[207],text="1"} +_[112]={tags=_[206],text="before recursion "} +_[111]={tags=_[205],text="[]"} +_[110]={tags=_[204],text="start: "} +_[109]={tags=_[203],text="pass list:"} +_[108]={tags=_[202],text="[1]"} +_[107]={tags=_[201],text=": "} +_[106]={tags=_[200],text="1"} +_[105]={tags=_[199],text="after recursion "} +_[104]={tags=_[198],text="[2]"} +_[103]={tags=_[197],text=": "} +_[102]={tags=_[196],text="2"} +_[101]={tags=_[195],text="after recursion "} +_[100]={tags=_[194],text="[3]"} +_[99]={tags=_[193],text=": "} +_[98]={tags=_[192],text="3"} +_[97]={tags=_[191],text="after recursion "} +_[96]={tags=_[190],text="[4]"} +_[95]={tags=_[189],text=": "} +_[94]={tags=_[188],text="4"} +_[93]={tags=_[187],text="after recursion "} +_[92]={tags=_[186],text="[5]"} +_[91]={tags=_[185],text=": "} +_[90]={tags=_[184],text="5"} +_[89]={tags=_[183],text="before recursion "} +_[88]={tags=_[182],text="[]"} +_[87]={tags=_[181],text="start: "} +_[86]={tags=_[180],text="[4]"} +_[85]={tags=_[179],text=": "} +_[84]={tags=_[178],text="4"} +_[83]={tags=_[177],text="before recursion "} +_[82]={tags=_[176],text="[]"} +_[81]={tags=_[175],text="start: "} +_[80]={tags=_[174],text="[3]"} +_[79]={tags=_[173],text=": "} +_[78]={tags=_[172],text="3"} +_[77]={tags=_[171],text="before recursion "} +_[76]={tags=_[170],text="[]"} +_[75]={tags=_[169],text="start: "} +_[74]={tags=_[168],text="[2]"} +_[73]={tags=_[167],text=": "} +_[72]={tags=_[166],text="2"} +_[71]={tags=_[165],text="before recursion "} +_[70]={tags=_[164],text="[]"} +_[69]={tags=_[163],text="start: "} +_[68]={tags=_[162],text="[1]"} +_[67]={tags=_[161],text=": "} +_[66]={tags=_[160],text="1"} +_[65]={tags=_[159],text="before recursion "} +_[64]={tags=_[158],text="[]"} +_[63]={tags=_[157],text="start: "} +_[62]={tags=_[156],text="new list each time:"} +_[61]={_[152],_[153],_[154],_[155]} +_[60]={_[148],_[149],_[150],_[151]} +_[59]={_[144],_[145],_[146],_[147]} +_[58]={_[140],_[141],_[142],_[143]} +_[57]={_[136],_[137],_[138],_[139]} +_[56]={_[134],_[135]} +_[55]={_[130],_[131],_[132],_[133]} +_[54]={_[128],_[129]} +_[53]={_[124],_[125],_[126],_[127]} +_[52]={_[122],_[123]} +_[51]={_[118],_[119],_[120],_[121]} +_[50]={_[116],_[117]} +_[49]={_[112],_[113],_[114],_[115]} +_[48]={_[110],_[111]} +_[47]={_[109]} +_[46]={_[105],_[106],_[107],_[108]} +_[45]={_[101],_[102],_[103],_[104]} +_[44]={_[97],_[98],_[99],_[100]} +_[43]={_[93],_[94],_[95],_[96]} +_[42]={_[89],_[90],_[91],_[92]} +_[41]={_[87],_[88]} +_[40]={_[83],_[84],_[85],_[86]} +_[39]={_[81],_[82]} +_[38]={_[77],_[78],_[79],_[80]} +_[37]={_[75],_[76]} +_[36]={_[71],_[72],_[73],_[74]} +_[35]={_[69],_[70]} +_[34]={_[65],_[66],_[67],_[68]} +_[33]={_[63],_[64]} +_[32]={_[62]} +_[31]={"return"} +_[30]={"text",_[61]} +_[29]={"text",_[60]} +_[28]={"text",_[59]} +_[27]={"text",_[58]} +_[26]={"text",_[57]} +_[25]={"text",_[56]} +_[24]={"text",_[55]} +_[23]={"text",_[54]} +_[22]={"text",_[53]} +_[21]={"text",_[52]} +_[20]={"text",_[51]} +_[19]={"text",_[50]} +_[18]={"text",_[49]} +_[17]={"text",_[48]} +_[16]={"text",_[47]} +_[15]={"text",_[46]} +_[14]={"text",_[45]} +_[13]={"text",_[44]} +_[12]={"text",_[43]} +_[11]={"text",_[42]} +_[10]={"text",_[41]} +_[9]={"text",_[40]} +_[8]={"text",_[39]} +_[7]={"text",_[38]} +_[6]={"text",_[37]} +_[5]={"text",_[36]} +_[4]={"text",_[35]} +_[3]={"text",_[34]} +_[2]={"text",_[33]} +_[1]={"text",_[32]} +return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9],_[10],_[11],_[12],_[13],_[14],_[15],_[16],_[17],_[18],_[19],_[20],_[21],_[22],_[23],_[24],_[25],_[26],_[27],_[28],_[29],_[30],_[31]} +--[[ +{ "text", { { + tags = {}, + text = "new list each time:" + } } } +{ "text", { { + tags = {}, + text = "start: " + }, { + tags = {}, + text = "[]" + } } } +{ "text", { { + tags = {}, + text = "before recursion " + }, { + tags = {}, + text = "1" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "[1]" + } } } +{ "text", { { + tags = {}, + text = "start: " + }, { + tags = {}, + text = "[]" + } } } +{ "text", { { + tags = {}, + text = "before recursion " + }, { + tags = {}, + text = "2" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "[2]" + } } } +{ "text", { { + tags = {}, + text = "start: " + }, { + tags = {}, + text = "[]" + } } } +{ "text", { { + tags = {}, + text = "before recursion " + }, { + tags = {}, + text = "3" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "[3]" + } } } +{ "text", { { + tags = {}, + text = "start: " + }, { + tags = {}, + text = "[]" + } } } +{ "text", { { + tags = {}, + text = "before recursion " + }, { + tags = {}, + text = "4" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "[4]" + } } } +{ "text", { { + tags = {}, + text = "start: " + }, { + tags = {}, + text = "[]" + } } } +{ "text", { { + tags = {}, + text = "before recursion " + }, { + tags = {}, + text = "5" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "[5]" + } } } +{ "text", { { + tags = {}, + text = "after recursion " + }, { + tags = {}, + text = "4" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "[4]" + } } } +{ "text", { { + tags = {}, + text = "after recursion " + }, { + tags = {}, + text = "3" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "[3]" + } } } +{ "text", { { + tags = {}, + text = "after recursion " + }, { + tags = {}, + text = "2" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "[2]" + } } } +{ "text", { { + tags = {}, + text = "after recursion " + }, { + tags = {}, + text = "1" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "[1]" + } } } +{ "text", { { + tags = {}, + text = "pass list:" + } } } +{ "text", { { + tags = {}, + text = "start: " + }, { + tags = {}, + text = "[]" + } } } +{ "text", { { + tags = {}, + text = "before recursion " + }, { + tags = {}, + text = "1" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "[1]" + } } } +{ "text", { { + tags = {}, + text = "start: " + }, { + tags = {}, + text = "[1]" + } } } +{ "text", { { + tags = {}, + text = "before recursion " + }, { + tags = {}, + text = "2" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "[1, 2]" + } } } +{ "text", { { + tags = {}, + text = "start: " + }, { + tags = {}, + text = "[1, 2]" + } } } +{ "text", { { + tags = {}, + text = "before recursion " + }, { + tags = {}, + text = "3" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "[1, 2, 3]" + } } } +{ "text", { { + tags = {}, + text = "start: " + }, { + tags = {}, + text = "[1, 2, 3]" + } } } +{ "text", { { + tags = {}, + text = "before recursion " + }, { + tags = {}, + text = "4" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "[1, 2, 3, 4]" + } } } +{ "text", { { + tags = {}, + text = "start: " + }, { + tags = {}, + text = "[1, 2, 3, 4]" + } } } +{ "text", { { + tags = {}, + text = "before recursion " + }, { + tags = {}, + text = "5" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "[1, 2, 3, 4, 5]" + } } } +{ "text", { { + tags = {}, + text = "after recursion " + }, { + tags = {}, + text = "4" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "[1, 2, 3, 4, 5]" + } } } +{ "text", { { + tags = {}, + text = "after recursion " + }, { + tags = {}, + text = "3" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "[1, 2, 3, 4, 5]" + } } } +{ "text", { { + tags = {}, + text = "after recursion " + }, { + tags = {}, + text = "2" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "[1, 2, 3, 4, 5]" + } } } +{ "text", { { + tags = {}, + text = "after recursion " + }, { + tags = {}, + text = "1" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "[1, 2, 3, 4, 5]" + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/function scoped recursive.ans b/test/tests/function scoped recursive.ans new file mode 100644 index 0000000..c4f9e00 --- /dev/null +++ b/test/tests/function scoped recursive.ans @@ -0,0 +1,19 @@ +:n = 0 + +$ f(c=1) + :a = 1 + + start: {a} + + ~ a := a + 1 + + ~ n += 1 + + before recursion {c}: {a} + + ~ n < 5 + ~ f(c+1) + + after recursion {c}: {a} + +~ f diff --git a/test/tests/function scoped recursive.lua b/test/tests/function scoped recursive.lua new file mode 100644 index 0000000..8887049 --- /dev/null +++ b/test/tests/function scoped recursive.lua @@ -0,0 +1,278 @@ +local _={} +_[121]={} +_[120]={} +_[119]={} +_[118]={} +_[117]={} +_[116]={} +_[115]={} +_[114]={} +_[113]={} +_[112]={} +_[111]={} +_[110]={} +_[109]={} +_[108]={} +_[107]={} +_[106]={} +_[105]={} +_[104]={} +_[103]={} +_[102]={} +_[101]={} +_[100]={} +_[99]={} +_[98]={} +_[97]={} +_[96]={} +_[95]={} +_[94]={} +_[93]={} +_[92]={} +_[91]={} +_[90]={} +_[89]={} +_[88]={} +_[87]={} +_[86]={} +_[85]={} +_[84]={} +_[83]={} +_[82]={} +_[81]={} +_[80]={} +_[79]={} +_[78]={} +_[77]={} +_[76]={} +_[75]={tags=_[121],text="2"} +_[74]={tags=_[120],text=": "} +_[73]={tags=_[119],text="1"} +_[72]={tags=_[118],text="after recursion "} +_[71]={tags=_[117],text="2"} +_[70]={tags=_[116],text=": "} +_[69]={tags=_[115],text="2"} +_[68]={tags=_[114],text="after recursion "} +_[67]={tags=_[113],text="2"} +_[66]={tags=_[112],text=": "} +_[65]={tags=_[111],text="3"} +_[64]={tags=_[110],text="after recursion "} +_[63]={tags=_[109],text="2"} +_[62]={tags=_[108],text=": "} +_[61]={tags=_[107],text="4"} +_[60]={tags=_[106],text="after recursion "} +_[59]={tags=_[105],text="2"} +_[58]={tags=_[104],text=": "} +_[57]={tags=_[103],text="5"} +_[56]={tags=_[102],text="before recursion "} +_[55]={tags=_[101],text="1"} +_[54]={tags=_[100],text="start: "} +_[53]={tags=_[99],text="2"} +_[52]={tags=_[98],text=": "} +_[51]={tags=_[97],text="4"} +_[50]={tags=_[96],text="before recursion "} +_[49]={tags=_[95],text="1"} +_[48]={tags=_[94],text="start: "} +_[47]={tags=_[93],text="2"} +_[46]={tags=_[92],text=": "} +_[45]={tags=_[91],text="3"} +_[44]={tags=_[90],text="before recursion "} +_[43]={tags=_[89],text="1"} +_[42]={tags=_[88],text="start: "} +_[41]={tags=_[87],text="2"} +_[40]={tags=_[86],text=": "} +_[39]={tags=_[85],text="2"} +_[38]={tags=_[84],text="before recursion "} +_[37]={tags=_[83],text="1"} +_[36]={tags=_[82],text="start: "} +_[35]={tags=_[81],text="2"} +_[34]={tags=_[80],text=": "} +_[33]={tags=_[79],text="1"} +_[32]={tags=_[78],text="before recursion "} +_[31]={tags=_[77],text="1"} +_[30]={tags=_[76],text="start: "} +_[29]={_[72],_[73],_[74],_[75]} +_[28]={_[68],_[69],_[70],_[71]} +_[27]={_[64],_[65],_[66],_[67]} +_[26]={_[60],_[61],_[62],_[63]} +_[25]={_[56],_[57],_[58],_[59]} +_[24]={_[54],_[55]} +_[23]={_[50],_[51],_[52],_[53]} +_[22]={_[48],_[49]} +_[21]={_[44],_[45],_[46],_[47]} +_[20]={_[42],_[43]} +_[19]={_[38],_[39],_[40],_[41]} +_[18]={_[36],_[37]} +_[17]={_[32],_[33],_[34],_[35]} +_[16]={_[30],_[31]} +_[15]={"return"} +_[14]={"text",_[29]} +_[13]={"text",_[28]} +_[12]={"text",_[27]} +_[11]={"text",_[26]} +_[10]={"text",_[25]} +_[9]={"text",_[24]} +_[8]={"text",_[23]} +_[7]={"text",_[22]} +_[6]={"text",_[21]} +_[5]={"text",_[20]} +_[4]={"text",_[19]} +_[3]={"text",_[18]} +_[2]={"text",_[17]} +_[1]={"text",_[16]} +return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9],_[10],_[11],_[12],_[13],_[14],_[15]} +--[[ +{ "text", { { + tags = {}, + text = "start: " + }, { + tags = {}, + text = "1" + } } } +{ "text", { { + tags = {}, + text = "before recursion " + }, { + tags = {}, + text = "1" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "2" + } } } +{ "text", { { + tags = {}, + text = "start: " + }, { + tags = {}, + text = "1" + } } } +{ "text", { { + tags = {}, + text = "before recursion " + }, { + tags = {}, + text = "2" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "2" + } } } +{ "text", { { + tags = {}, + text = "start: " + }, { + tags = {}, + text = "1" + } } } +{ "text", { { + tags = {}, + text = "before recursion " + }, { + tags = {}, + text = "3" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "2" + } } } +{ "text", { { + tags = {}, + text = "start: " + }, { + tags = {}, + text = "1" + } } } +{ "text", { { + tags = {}, + text = "before recursion " + }, { + tags = {}, + text = "4" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "2" + } } } +{ "text", { { + tags = {}, + text = "start: " + }, { + tags = {}, + text = "1" + } } } +{ "text", { { + tags = {}, + text = "before recursion " + }, { + tags = {}, + text = "5" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "2" + } } } +{ "text", { { + tags = {}, + text = "after recursion " + }, { + tags = {}, + text = "4" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "2" + } } } +{ "text", { { + tags = {}, + text = "after recursion " + }, { + tags = {}, + text = "3" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "2" + } } } +{ "text", { { + tags = {}, + text = "after recursion " + }, { + tags = {}, + text = "2" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "2" + } } } +{ "text", { { + tags = {}, + text = "after recursion " + }, { + tags = {}, + text = "1" + }, { + tags = {}, + text = ": " + }, { + tags = {}, + text = "2" + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/function scoped.ans b/test/tests/function scoped.ans new file mode 100644 index 0000000..d358b84 --- /dev/null +++ b/test/tests/function scoped.ans @@ -0,0 +1,32 @@ +(FIXME compare with fe/janet types + + +$ f() + :a = 1 + + {a} + + ~ a := a + 1 + +$ g + :a = 1 + + {a} + + ~ a := a + 1 + +scoped: + +~ f + +~ f + +~ f + +unscoped: + +~ g + +~ g + +~ g \ No newline at end of file diff --git a/test/tests/function scoped.lua b/test/tests/function scoped.lua new file mode 100644 index 0000000..7ceb049 --- /dev/null +++ b/test/tests/function scoped.lua @@ -0,0 +1,70 @@ +local _={} +_[33]={} +_[32]={} +_[31]={} +_[30]={} +_[29]={} +_[28]={} +_[27]={} +_[26]={} +_[25]={text="3",tags=_[33]} +_[24]={text="2",tags=_[32]} +_[23]={text="1",tags=_[31]} +_[22]={text="unscoped:",tags=_[30]} +_[21]={text="1",tags=_[29]} +_[20]={text="1",tags=_[28]} +_[19]={text="1",tags=_[27]} +_[18]={text="scoped:",tags=_[26]} +_[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", { { + tags = {}, + text = "scoped:" + } } } +{ "text", { { + tags = {}, + text = "1" + } } } +{ "text", { { + tags = {}, + text = "1" + } } } +{ "text", { { + tags = {}, + text = "1" + } } } +{ "text", { { + tags = {}, + text = "unscoped:" + } } } +{ "text", { { + tags = {}, + text = "1" + } } } +{ "text", { { + tags = {}, + text = "2" + } } } +{ "text", { { + tags = {}, + text = "3" + } } } +{ "return" } +]]-- \ No newline at end of file