1
0
Fork 0
mirror of https://github.com/Reuh/anselme.git synced 2025-10-27 16:49:31 +00:00

Omit function variables from merge

This commit is contained in:
Étienne Fildadut 2021-12-10 19:43:06 +01:00
parent 40c1616cce
commit 3d32f35d67
7 changed files with 70 additions and 45 deletions

View file

@ -25,11 +25,11 @@ local eval = require(anselme_root.."interpreter.expression")
local run_line = require(anselme_root.."interpreter.interpreter").run_line local run_line = require(anselme_root.."interpreter.interpreter").run_line
local run = require(anselme_root.."interpreter.interpreter").run local run = require(anselme_root.."interpreter.interpreter").run
local to_lua = require(anselme_root.."interpreter.common").to_lua local to_lua = require(anselme_root.."interpreter.common").to_lua
local identifier_pattern = require(anselme_root.."parser.common").identifier_pattern
local merge_state = require(anselme_root.."interpreter.common").merge_state local merge_state = require(anselme_root.."interpreter.common").merge_state
local stdfuncs = require(anselme_root.."stdlib.functions") local stdfuncs = require(anselme_root.."stdlib.functions")
local bootscript = require(anselme_root.."stdlib.bootscript") local bootscript = require(anselme_root.."stdlib.bootscript")
local copy = require(anselme_root.."common").copy local copy = require(anselme_root.."common").copy
local should_keep_variable = require(anselme_root.."interpreter.common").should_keep_variable
-- wrappers for love.filesystem / luafilesystem -- wrappers for love.filesystem / luafilesystem
local function list_directory(path) local function list_directory(path)
@ -410,7 +410,7 @@ local vm_mt = {
save = function(self) save = function(self)
local vars = {} local vars = {}
for k, v in pairs(self.state.variables) do for k, v in pairs(self.state.variables) do
if v.type ~= "undefined argument" and v.type ~= "pending definition" and k:match("^"..identifier_pattern.."$") then if should_keep_variable(self.state, k) then
vars[k] = v vars[k] = v
end end
end end
@ -479,15 +479,17 @@ 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 cache = getmetatable(variables).cache local mt = getmetatable(variables)
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
return cache[k] return cache[k]
end, end,
-- variables that keep current state and should be cleared at each checkpoint
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
}), }),
interpreter = { interpreter = {
-- constant -- constant

View file

@ -1,4 +1,7 @@
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
local function replace_in_table(t, to_replace, already_replaced) local function replace_in_table(t, to_replace, already_replaced)
already_replaced = already_replaced or {} already_replaced = already_replaced or {}
already_replaced[t] = true already_replaced[t] = true
@ -56,8 +59,12 @@ common = {
for _, m in ipairs(copied_to_replace) do for _, m in ipairs(copied_to_replace) do
replace_in_table(m, not_modified) replace_in_table(m, not_modified)
end end
-- replace -- replace in t
replace_in_table(t, to_replace) replace_in_table(t, to_replace)
end end
} }
package.loaded[...] = common
identifier_pattern = require((...):gsub("common$", "parser.common")).identifier_pattern
return common return common

View file

@ -2,6 +2,7 @@ local atypes, ltypes
local eval, run_block local eval, run_block
local replace_with_copied_values local replace_with_copied_values
local common local common
local identifier_pattern
--- copy some text & process it to be suited to be sent to Lua in an event --- copy some text & process it to be suited to be sent to Lua in an event
local function post_process_text(state, text) local function post_process_text(state, text)
@ -57,7 +58,9 @@ 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
global.variables[var] = value if common.should_keep_variable(state, var) then
global.variables[var] = value
end
state.variables[var] = nil state.variables[var] = nil
end end
end, end,
@ -78,6 +81,20 @@ common = {
return var return var
end end
end, end,
set_variable = function(state, name, val)
state.variables[name] = val
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
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%.")
end,
--- check truthyness of an anselme value --- check truthyness of an anselme value
truthy = function(val) truthy = function(val)
if val.type == "number" then if val.type == "number" then
@ -408,5 +425,6 @@ atypes, ltypes = types.anselme, types.lua
eval = require((...):gsub("common$", "expression")) eval = require((...):gsub("common$", "expression"))
run_block = require((...):gsub("common$", "interpreter")).run_block run_block = require((...):gsub("common$", "interpreter")).run_block
replace_with_copied_values = require((...):gsub("interpreter%.common$", "common")).replace_with_copied_values replace_with_copied_values = require((...):gsub("interpreter%.common$", "common")).replace_with_copied_values
identifier_pattern = require((...):gsub("interpreter%.common$", "parser.common")).identifier_pattern
return common return common

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 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 run local run
@ -85,7 +85,7 @@ local function eval(state, exp)
local name = exp.left.name local name = exp.left.name
local val, vale = eval(state, exp.right) local val, vale = eval(state, exp.right)
if not val then return val, vale end if not val then return val, vale end
state.variables[name] = val set_variable(state, name, val)
return val return val
else else
return nil, ("don't know how to perform assignment on %s expression"):format(exp.left.type) return nil, ("don't know how to perform assignment on %s expression"):format(exp.left.type)
@ -257,11 +257,11 @@ local function eval(state, exp)
depths[j] = math.huge depths[j] = math.huge
end end
-- set -- set
state.variables[param.full_name] = val set_variable(state, 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
state.variables[param.full_name] = { type = "pending definition", value = { expression = param.default, source = fn.source } } set_variable(state, 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,7 +300,7 @@ local function eval(state, exp)
depths.assignment = math.huge depths.assignment = math.huge
end end
-- set -- set
state.variables[param.full_name] = assignment set_variable(state, param.full_name, assignment)
end end
if ok then if ok then
if not selected_variant.variant then if not selected_variant.variant then
@ -340,7 +340,7 @@ local function eval(state, exp)
return nil, ("unknown function type %q"):format(fn.type) return nil, ("unknown function type %q"):format(fn.type)
end end
end end
-- function successfully selected -- function successfully selected: run
if selected_variant.variant then if selected_variant.variant then
local fn = selected_variant.variant local fn = selected_variant.variant
if fn.type == "checkpoint" then if fn.type == "checkpoint" then
@ -398,7 +398,9 @@ local function eval(state, exp)
elseif lua_fn.mode == nil then elseif lua_fn.mode == nil then
local l_lua = {} local l_lua = {}
for _, v in ipairs(final_args) do for _, v in ipairs(final_args) do
table.insert(l_lua, to_lua(v)) local lv, e = to_lua(v)
if e then return nil, e end
table.insert(l_lua, lv)
end end
local r, e local r, e
if _VERSION == "Lua 5.1" and not jit then -- PUC Lua 5.1 doesn't allow yield from a pcall if _VERSION == "Lua 5.1" and not jit then -- PUC Lua 5.1 doesn't allow yield from a pcall
@ -429,10 +431,10 @@ local function eval(state, exp)
if not ret then return ret, e end if not ret then return ret, e end
end end
-- update function vars -- update function vars
state.variables[fn.namespace.."👁️"] = { set_variable(state, fn.namespace.."👁️", {
type = "number", type = "number",
value = seen.value + 1 value = seen.value + 1
} })
-- 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
@ -464,6 +466,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 = 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 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
return eval return eval

View file

@ -1,5 +1,5 @@
local eval local eval
local truthy, merge_state, escape, get_variable, tags, events local truthy, merge_state, escape, get_variable, tags, events, set_variable
local run_line, run_block local run_line, run_block
-- returns var in case of success and there is a return -- returns var in case of success and there is a return
@ -92,14 +92,14 @@ run_line = function(state, line)
elseif line.type == "checkpoint" then elseif line.type == "checkpoint" then
local reached, reachede = get_variable(state, line.namespace.."🏁") local reached, reachede = get_variable(state, line.namespace.."🏁")
if not reached then return nil, reachede end if not reached then return nil, reachede end
state.variables[line.namespace.."🏁"] = { set_variable(state, line.namespace.."🏁", {
type = "number", type = "number",
value = reached.value + 1 value = reached.value + 1
} })
state.variables[line.parent_function.namespace.."🔖"] = { set_variable(state, line.parent_function.namespace.."🔖", {
type = "string", type = "string",
value = line.name value = line.name
} })
merge_state(state) merge_state(state)
else else
return nil, ("unknown line type %q; at %s"):format(line.type, line.source) return nil, ("unknown line type %q; at %s"):format(line.type, line.source)
@ -138,22 +138,22 @@ run_block = function(state, block, resume_from_there, i, j)
if not seen then return nil, seene end if not seen then return nil, seene end
local checkpoint, checkpointe = get_variable(state, parent_line.parent_function.namespace.."🔖") local checkpoint, checkpointe = get_variable(state, parent_line.parent_function.namespace.."🔖")
if not checkpoint then return nil, checkpointe end if not checkpoint then return nil, checkpointe end
state.variables[parent_line.namespace.."👁️"] = { set_variable(state, parent_line.namespace.."👁️", {
type = "number", type = "number",
value = seen.value + 1 value = seen.value + 1
} })
state.variables[parent_line.namespace.."🏁"] = { set_variable(state, parent_line.namespace.."🏁", {
type = "number", type = "number",
value = reached.value + 1 value = reached.value + 1
} })
-- don't update checkpoint if an already more precise checkpoint is set -- don't update checkpoint if an already more precise checkpoint is set
-- (since we will go up the whole checkpoint hierarchy when resuming from a nested checkpoint) -- (since we will go up the whole checkpoint hierarchy when resuming from a nested checkpoint)
local current_checkpoint = checkpoint.value local current_checkpoint = checkpoint.value
if not current_checkpoint:match("^"..escape(parent_line.name)) then if not current_checkpoint:match("^"..escape(parent_line.name)) then
state.variables[parent_line.parent_function.namespace.."🔖"] = { set_variable(state, parent_line.parent_function.namespace.."🔖", {
type = "string", type = "string",
value = parent_line.name value = parent_line.name
} })
end end
merge_state(state) merge_state(state)
end end
@ -231,7 +231,7 @@ local interpreter = {
package.loaded[...] = interpreter package.loaded[...] = interpreter
eval = require((...):gsub("interpreter$", "expression")) eval = require((...):gsub("interpreter$", "expression"))
local common = require((...):gsub("interpreter$", "common")) local common = require((...):gsub("interpreter$", "common"))
truthy, merge_state, tags, get_variable, events = common.truthy, common.merge_state, common.tags, common.get_variable, common.events truthy, merge_state, tags, get_variable, events, set_variable = common.truthy, common.merge_state, common.tags, common.get_variable, common.events, common.set_variable
escape = require((...):gsub("interpreter%.interpreter$", "parser.common")).escape escape = require((...):gsub("interpreter%.interpreter$", "parser.common")).escape
return interpreter return interpreter

View file

@ -53,7 +53,9 @@ 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/checkpoint: separate from scoping and/or actual functions? with proper scoping & stuff 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...

View file

@ -1,10 +1,4 @@
local truthy, anselme, compare, is_of_type, identifier_pattern, format_identifier, find, get_variable local truthy, anselme, compare, is_of_type, identifier_pattern, format_identifier, find, get_variable, mark_as_modified
--- mark a table as modified, so it will be merged on the next checkpoint if it appears somewhere in a value
local function mark_as_modified(v)
local modified = getmetatable(anselme.running.state.variables).modified_tables
table.insert(modified, v)
end
local lua_functions local lua_functions
lua_functions = { lua_functions = {
@ -116,7 +110,7 @@ lua_functions = {
local lv = l.type == "type" and l.value[1] or l local lv = l.type == "type" and l.value[1] or l
local iv = i.type == "type" and i.value[1] or i local iv = i.type == "type" and i.value[1] or i
lv.value[iv.value] = v lv.value[iv.value] = v
mark_as_modified(lv.value) mark_as_modified(anselme.running.state, lv.value)
return v return v
end end
}, },
@ -129,7 +123,7 @@ lua_functions = {
for _, x in ipairs(lv.value) do for _, x in ipairs(lv.value) do
if x.type == "pair" and compare(x.value[1], kv) then if x.type == "pair" and compare(x.value[1], kv) then
x.value[2] = v x.value[2] = v
mark_as_modified(x.value) mark_as_modified(anselme.running.state, x.value)
return v return v
end end
end end
@ -138,7 +132,7 @@ lua_functions = {
type = "pair", type = "pair",
value = { kv, v } value = { kv, v }
}) })
mark_as_modified(lv.value) mark_as_modified(anselme.running.state, lv.value)
return v return v
end end
}, },
@ -151,7 +145,7 @@ lua_functions = {
["_!(fn::variable reference)"] = { ["_!(fn::variable reference)"] = {
mode = "untyped raw", mode = "untyped raw",
value = function(v) value = function(v)
return anselme.running.state.variables[v.value] return get_variable(anselme.running.state, v.value)
end end
}, },
-- format -- format
@ -226,7 +220,7 @@ lua_functions = {
value = function(l, v) value = function(l, v)
local lv = l.type == "type" and l.value[1] or l local lv = l.type == "type" and l.value[1] or l
table.insert(lv.value, v) table.insert(lv.value, v)
mark_as_modified(lv.value) mark_as_modified(anselme.running.state, lv.value)
return l return l
end end
}, },
@ -236,21 +230,21 @@ lua_functions = {
local lv = l.type == "type" and l.value[1] or l local lv = l.type == "type" and l.value[1] or l
local iv = i.type == "type" and i.value[1] or i local iv = i.type == "type" and i.value[1] or i
table.insert(lv.value, iv.value, v) table.insert(lv.value, iv.value, v)
mark_as_modified(lv.value) mark_as_modified(anselme.running.state, lv.value)
return l return l
end end
}, },
["remove(l::list)"] = { ["remove(l::list)"] = {
mode = "untyped raw", mode = "untyped raw",
value = function(l) value = function(l)
mark_as_modified(l.value) mark_as_modified(anselme.running.state, l.value)
return table.remove(l.value) return table.remove(l.value)
end end
}, },
["remove(l::list, i::number)"] = { ["remove(l::list, i::number)"] = {
mode = "untyped raw", mode = "untyped raw",
value = function(l, i) value = function(l, i)
mark_as_modified(l.value) mark_as_modified(anselme.running.state, l.value)
return table.remove(l.value, i.value) return table.remove(l.value, i.value)
end end
}, },
@ -337,7 +331,7 @@ local functions = {
package.loaded[...] = functions package.loaded[...] = functions
local icommon = require((...):gsub("stdlib%.functions$", "interpreter.common")) local icommon = require((...):gsub("stdlib%.functions$", "interpreter.common"))
truthy, compare, is_of_type, get_variable = icommon.truthy, icommon.compare, icommon.is_of_type, icommon.get_variable truthy, compare, is_of_type, get_variable, mark_as_modified = icommon.truthy, icommon.compare, icommon.is_of_type, icommon.get_variable, icommon.mark_as_modified
local pcommon = require((...):gsub("stdlib%.functions$", "parser.common")) local pcommon = require((...):gsub("stdlib%.functions$", "parser.common"))
identifier_pattern, format_identifier, find = pcommon.identifier_pattern, pcommon.format_identifier, pcommon.find identifier_pattern, format_identifier, find = pcommon.identifier_pattern, pcommon.format_identifier, pcommon.find
anselme = require((...):gsub("stdlib%.functions$", "anselme")) anselme = require((...):gsub("stdlib%.functions$", "anselme"))