1
0
Fork 0
mirror of https://github.com/Reuh/anselme.git synced 2025-10-27 16:49:31 +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") ~ 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: 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 ##### 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. `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 -- api is incremented a each update which may break Lua API compatibility
versions = { versions = {
save = 1, save = 1,
language = 20, language = 21,
api = 4 api = 4
}, },
-- version is incremented at each update -- version is incremented at each update
version = 21, version = 22,
--- currently running interpreter --- currently running interpreter
running = nil running = nil
} }
@ -198,6 +198,7 @@ local interpreter_methods = {
if not success then if not success then
return nil, event return nil, event
elseif event == "error" then elseif event == "error" then
self.end_event = "error"
return nil, data return nil, data
elseif event ~= "return" then elseif event ~= "return" then
return nil, ("evaluated expression generated an %q event; at %s"):format(event, self.state.interpreter.running_line.source) 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 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({}, { variables = setmetatable({}, {
__index = function(variables, k) __index = function(variables, k)
local mt = getmetatable(variables) local cache = getmetatable(variables).cache
local cache = mt.cache
if cache[k] == nil then if cache[k] == nil then
cache[k] = copy(self.state.variables[k], getmetatable(variables).copy_cache) cache[k] = copy(self.state.variables[k], getmetatable(variables).copy_cache)
end end
@ -490,6 +490,9 @@ local vm_mt = {
copy_cache = {}, -- table of [original table] = copied table 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 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 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 = { interpreter = {
-- constant -- constant

View file

@ -1,5 +1,3 @@
local identifier_pattern
--- replace values recursively in table t according to to_replace ([old table] = new table) --- 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 -- 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) local function replace_in_table(t, to_replace, already_replaced)
@ -65,6 +63,5 @@ common = {
} }
package.loaded[...] = common package.loaded[...] = common
identifier_pattern = require((...):gsub("common$", "parser.common")).identifier_pattern
return common return common

View file

@ -58,9 +58,7 @@ common = {
mt.cache = {} mt.cache = {}
-- merge modified re-assigned variables -- merge modified re-assigned variables
for var, value in pairs(state.variables) do for var, value in pairs(state.variables) do
if common.should_keep_variable(state, var) then global.variables[var] = value
global.variables[var] = value
end
state.variables[var] = nil state.variables[var] = nil
end end
end, end,
@ -81,16 +79,62 @@ common = {
return var return var
end end
end, end,
--- set the value of a variable
set_variable = function(state, name, val) set_variable = function(state, name, val)
state.variables[name] = val state.variables[name] = val
end, 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 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) mark_as_modified = function(state, v)
local modified = getmetatable(state.variables).modified_tables local modified = getmetatable(state.variables).modified_tables
table.insert(modified, v) table.insert(modified, v)
end, end,
--- returns true if a variable should be persisted on save/merge --- returns true if a variable should be persisted on save
-- will exclude: undefined variables, variables in functions defined with parentheses, internal anselme variables -- will exclude: undefined variables, variables in scoped functions, internal anselme variables
should_keep_variable = function(state, name) should_keep_variable = function(state, name)
local v = state.variables[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%.") 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 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 local run
@ -65,20 +65,6 @@ local function eval(state, exp)
type = "list", type = "list",
value = l 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 -- assignment
elseif exp.type == ":=" then elseif exp.type == ":=" then
if exp.left.type == "variable" then if exp.left.type == "variable" then
@ -203,7 +189,7 @@ local function eval(state, exp)
end end
-- try to select a function -- try to select a function
local tried_function_error_messages = {} 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 for _, fn in ipairs(variants) do
-- checkpoint: no args, nothing to select on -- checkpoint: no args, nothing to select on
if fn.type == "checkpoint" then if fn.type == "checkpoint" then
@ -218,6 +204,7 @@ local function eval(state, exp)
if not fn.assignment or exp.assignment then if not fn.assignment or exp.assignment then
local ok = true local ok = true
-- get and set args -- get and set args
local variant_args = {}
local used_args = {} local used_args = {}
local depths = { assignment = nil } local depths = { assignment = nil }
for j, param in ipairs(fn.params) do for j, param in ipairs(fn.params) do
@ -257,11 +244,11 @@ local function eval(state, exp)
depths[j] = math.huge depths[j] = math.huge
end end
-- set -- set
set_variable(state, param.full_name, val) variant_args[param.full_name] = val
-- default: evaluate once function is selected -- 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 -- 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 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 else
ok = false 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)) 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 depths.assignment = math.huge
end end
-- set -- set
set_variable(state, param.full_name, assignment) variant_args[param.full_name] = assignment
end end
if ok then if ok then
if not selected_variant.variant then if not selected_variant.variant then
selected_variant.depths = depths selected_variant.depths = depths
selected_variant.variant = fn selected_variant.variant = fn
selected_variant.args_to_set = variant_args
else else
-- check specificity order -- check specificity order
local lower local lower
@ -330,6 +318,7 @@ local function eval(state, exp)
if lower then if lower then
selected_variant.depths = depths selected_variant.depths = depths
selected_variant.variant = fn selected_variant.variant = fn
selected_variant.args_to_set = variant_args
elseif lower == nil then -- equal, ambigous dispatch 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) 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 end
@ -349,6 +338,17 @@ local function eval(state, exp)
return r return r
elseif fn.type == "function" then elseif fn.type == "function" then
local ret 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 -- get function vars
local checkpoint, checkpointe = get_variable(state, fn.namespace.."🔖") local checkpoint, checkpointe = get_variable(state, fn.namespace.."🔖")
if not checkpoint then return nil, checkpointe end if not checkpoint then return nil, checkpointe end
@ -435,6 +435,10 @@ local function eval(state, exp)
type = "number", type = "number",
value = seen.value + 1 value = seen.value + 1
}) })
-- pop scope
if fn.scoped then
scope:pop(state, fn)
end
-- return value -- return value
if not ret then return nil, ("function %q didn't return a value"):format(exp.called_name) end if not ret then return nil, ("function %q didn't return a value"):format(exp.called_name) end
return ret return ret
@ -456,6 +460,20 @@ local function eval(state, exp)
called_name = called_name .. " := " .. pretty_type(assignment) called_name = called_name .. " := " .. pretty_type(assignment)
end 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")) 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 else
return nil, ("unknown expression %q"):format(tostring(exp.type)) return nil, ("unknown expression %q"):format(tostring(exp.type))
end end
@ -466,6 +484,6 @@ run = require((...):gsub("expression$", "interpreter")).run
expression = require((...):gsub("interpreter%.expression$", "parser.expression")) expression = require((...):gsub("interpreter%.expression$", "parser.expression"))
flatten_list = require((...):gsub("interpreter%.expression$", "parser.common")).flatten_list flatten_list = require((...):gsub("interpreter%.expression$", "parser.common")).flatten_list
local common = require((...):gsub("expression$", "common")) 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 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. 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 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: 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. 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... would allow more flexibility esp. for tags...
a func def would be: a func def would be:

View file

@ -43,6 +43,16 @@ local function parse(state)
end end
line.assignment.type_annotation = type_exp line.assignment.type_annotation = type_exp
end 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 end
-- expressions -- expressions
if line.expression then if line.expression then

View file

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

View file

@ -296,18 +296,7 @@ lua_functions = {
value = is_of_type(v, t) or 0 value = is_of_type(v, t) or 0
} }
end 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 = [[ local anselme_functions = [[
@ -315,13 +304,25 @@ $ random(l...)
~ l(rand(1, l!len))! ~ l(rand(1, l!len))!
$ next(l...) $ next(l...)
~ l!len == 1 | l(1).👁 == 0 :f = l(len(l))
~ l(1)! $ find first not seen(j)
~~ ~ l(j).👁 == 0
~ l!remove(1) ~ f := l(j)
~ next(l=l) ~~ 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 = { local functions = {

View file

@ -5,10 +5,14 @@ $ f
b b
$ c $ c
c c
~ cycle("a","b","c") ~ cycle(&a,&b,&c)
~ f ~ f
~ f ~ f
~ 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" }
]]--