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:
parent
3d32f35d67
commit
fef498b3d7
16 changed files with 1164 additions and 58 deletions
|
|
@ -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%.")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue