1
0
Fork 0
mirror of https://github.com/Reuh/anselme.git synced 2025-10-27 08:39:30 +00:00

Add scoped functions

This commit is contained in:
Étienne Fildadut 2021-12-10 23:22:06 +01:00
parent 3d32f35d67
commit fef498b3d7
16 changed files with 1164 additions and 58 deletions

View file

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

View file

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

View file

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

View file

@ -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%.")

View file

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

View file

@ -43,7 +43,7 @@ Reserved symbols that are still not used as a line type: `^+-=</[]*{}|\_!?.,;)"&
Broad goals and ideas that may never be implemented. Mostly used as personal post-it notes.
TODO: rewrite cycle, ... using function references
TODO: a way to make loops
TODO: consider something like :identifier to create a string "identifier", might simplify the identifier=value special syntax and free up the = operator
@ -53,10 +53,6 @@ TODO: the function decorator feels a bit glued-on to the current syntax
TODO: simplify language, it is much too complicated. Less line types? (var def, func, checkpoint, tag). Rewrite some ad hoc syntax using the expression system?
TODO: functions: might be nice to have actual scoping for functions that are called with arguments; to allow proper recursion & stuff (as right now if you call the same function from itself both instances will share variables...). Would require a fair amount of changes to the code though.
TODO: a way to make loops
TODO: fn/checkpoint/tag: maybe consider them a regular func call that takes children as arg; can keep compatibility using $/§ as shortcut for the actual call.
would allow more flexibility esp. for tags...
a func def would be:

View file

@ -43,6 +43,16 @@ local function parse(state)
end
line.assignment.type_annotation = type_exp
end
-- get list of scoped variables
-- (note includes every variables in the namespace of subnamespace, so subfunctions are scoped alongside this function)
if line.scoped then
line.scoped = {}
for name in pairs(state.variables) do
if name:sub(1, #namespace) == namespace then
table.insert(line.scoped, name)
end
end
end
end
-- expressions
if line.expression then

View file

@ -93,6 +93,7 @@ local function parse_line(line, state, namespace)
-- get params
r.params = {}
if r.type == "function" and rem:match("^%b()") then
r.scoped = true
local content
content, rem = rem:match("^(%b())%s*(.*)$")
content = content:gsub("^%(", ""):gsub("%)$", "")

View file

@ -296,18 +296,7 @@ lua_functions = {
value = is_of_type(v, t) or 0
}
end
},
["cycle(l...)"] = function(l)
local f, fseen = l[1], assert(anselme.running:eval(l[1]..".👁️", anselme.running:current_namespace()))
for j=2, #l do
local seen = assert(anselme.running:eval(l[j]..".👁️", anselme.running:current_namespace()))
if seen < fseen then
f = l[j]
break
end
end
return anselme.running:run(f, anselme.running:current_namespace())
end,
}
}
local anselme_functions = [[
@ -315,13 +304,25 @@ $ random(l...)
~ l(rand(1, l!len))!
$ next(l...)
~ l!len == 1 | l(1).👁 == 0
~ l(1)!
~~
~ l!remove(1)
~ next(l=l)
:f = l(len(l))
$ find first not seen(j)
~ l(j).👁 == 0
~ f := l(j)
~~ j < len(l)
~ find first not seen(j+1)
~ find first not seen(1)
~ f!
(TODO: cycle)
$ cycle(l...)
:f = l(1)
$ find first smaller(j)
~ l(j).👁 < f.👁
~ f := l(j)
~~ j < len(l)
~ find first smaller(j+1)
~ len(l) > 1
~ find first smaller(2)
~ f!
]]
local functions = {

View file

@ -5,10 +5,14 @@ $ f
b
$ c
c
~ cycle("a","b","c")
~ cycle(&a,&b,&c)
~ f
~ f
~ f
~ f
~ f
~ f

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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