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

Add anonymous functions

This commit is contained in:
Étienne Fildadut 2022-09-28 14:54:31 +09:00
parent 2c6d66c222
commit 5e441886c0
15 changed files with 352 additions and 110 deletions

View file

@ -945,7 +945,7 @@ From lowest to highest priority:
``` ```
_;_ _; _;_ _;
_:=_ _+=_ _-=_ _//=_ _/=_ _*=_ _%=_ _^=_ _:=_ _+=_ _-=_ _//=_ _/=_ _*=_ _%=_ _^=_
_,_ _,_ $_
_~?_ _~_ _#_ _~?_ _~_ _#_
_=_ _=_
_|_ _&_ _|_ _&_
@ -1030,6 +1030,10 @@ This only works on strings:
`a!fn(args)`: call the function or function reference with the variable as first argument. Parantheses are optional. `a!fn(args)`: call the function or function reference with the variable as first argument. Parantheses are optional.
`$x`: returns a reference to an anonymous function that returns the expression `x`. Note that the returned reference *can not* be persisted.
`$(parameters)x`: returns a reference to an anonymous function that returns the expression `x` and takes some parameters (same syntax as function definition lines). Note that the returned reference *can not* be persisted.
##### Variable references ##### Variable references
`&var`: returns a variable reference to the given variable. If it is already a reference, returns the same reference. `&var`: returns a variable reference to the given variable. If it is already a reference, returns the same reference.

View file

@ -82,7 +82,8 @@ 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 local should_be_persisted = require(anselme_root.."interpreter.common").should_be_persisted
local check_persistable = require(anselme_root.."interpreter.common").check_persistable
-- wrappers for love.filesystem / luafilesystem -- wrappers for love.filesystem / luafilesystem
local function list_directory(path) local function list_directory(path)
@ -168,7 +169,7 @@ local interpreter_methods = {
local line = self.state.interpreter.running_line local line = self.state.interpreter.running_line
local namespace = self:current_namespace() local namespace = self:current_namespace()
-- replace state with interrupted state -- replace state with interrupted state
local exp, err = expression(expr, self.state.interpreter.global_state, namespace or "") local exp, err = expression(expr, self.state.interpreter.global_state, namespace or "", "interpreter:interrupt")
if not exp then return "error", ("%s; during interrupt %q at %s"):format(err, expr, line and line.source or "unknown") end if not exp then return "error", ("%s; during interrupt %q at %s"):format(err, expr, line and line.source or "unknown") end
local r, e = self.vm:run(exp) local r, e = self.vm:run(exp)
if not r then return "error", e end if not r then return "error", e end
@ -235,7 +236,7 @@ local interpreter_methods = {
end end
-- parse -- parse
local err local err
if type(expr) ~= "table" then expr, err = expression(tostring(expr), self.state.interpreter.global_state, namespace or "") end if type(expr) ~= "table" then expr, err = expression(tostring(expr), self.state.interpreter.global_state, namespace or "", "interpreter:run") end
if not expr then coroutine.yield("error", err) end if not expr then coroutine.yield("error", err) end
-- run -- run
local r, e local r, e
@ -267,7 +268,7 @@ local interpreter_methods = {
end end
-- parse -- parse
local err local err
if type(expr) ~= "table" then expr, err = expression(tostring(expr), self.state.interpreter.global_state, namespace or "") end if type(expr) ~= "table" then expr, err = expression(tostring(expr), self.state.interpreter.global_state, namespace or "", "interpreter:eval") end
if not expr then return nil, err end if not expr then return nil, err end
-- run -- run
local co = coroutine.create(function() local co = coroutine.create(function()
@ -586,11 +587,15 @@ local vm_mt = {
--- Save script state. --- Save script state.
-- See `vm:load`. -- See `vm:load`.
-- --
-- Returns save data. -- Returns save data in case of success.
--
-- Returns nil, error message in case of error.
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 should_keep_variable(self.state, k, v) then if should_be_persisted(self.state, k, v) then
local s, e = check_persistable(v)
if not s then return nil, ("%s; while saving variable %s"):format(e, k) end
vars[k] = v vars[k] = v
end end
end end
@ -608,11 +613,11 @@ local vm_mt = {
-- --
-- Returns self in case of success. -- Returns self in case of success.
-- --
-- Returns nil, error message in case of error -- Returns nil, error message in case of error.
postload = function(self) postload = function(self)
if #self.state.queued_lines > 0 then if #self.state.queued_lines > 0 then
local r, e = postparse(self.state) local r, e = postparse(self.state)
if not r then return r, e end if not r then return nil, e end
end end
return self return self
end, end,
@ -653,7 +658,7 @@ local vm_mt = {
if not s then return s, e end if not s then return s, e end
-- --
local err local err
if type(expr) ~= "table" then expr, err = expression(tostring(expr), self.state, namespace or "") end if type(expr) ~= "table" then expr, err = expression(tostring(expr), self.state, namespace or "", "vm:run") end
if not expr then return expr, err end if not expr then return expr, err end
-- interpreter state -- interpreter state
local interpreter local interpreter

View file

@ -266,7 +266,9 @@ Save/load script state
Save script state. Save script state.
See `vm:load`. See `vm:load`.
Returns save data. Returns save data in case of success.
Returns nil, error message in case of error.
### vm:postload () ### vm:postload ()
@ -275,7 +277,7 @@ Perform parsing that needs to be done after loading code.
Returns self in case of success. Returns self in case of success.
Returns nil, error message in case of error Returns nil, error message in case of error.
### vm:enable (...) ### vm:enable (...)

View file

@ -7,7 +7,7 @@ local copy
local function random_identifier() local function random_identifier()
local r = "" local r = ""
for _=1, 16 do -- that's live 10^31 possibilities, ought to be enough for anyone for _=1, 16 do -- that's like 10^31 possibilities, ought to be enough for anyone
r = r .. string.char(math.random(32, 126)) r = r .. string.char(math.random(32, 126))
end end
return r return r
@ -94,13 +94,41 @@ common = {
end, end,
--- mark a value as constant, recursively affecting all the potentially mutable subvalues --- mark a value as constant, recursively affecting all the potentially mutable subvalues
mark_constant = function(v) mark_constant = function(v)
if atypes[v.type] and atypes[v.type].mark_constant then return assert(common.traverse(v, function(v)
atypes[v.type].mark_constant(v)
if v.hash_id then v.hash_id = nil end -- no longer need to compare by id if v.hash_id then v.hash_id = nil end -- no longer need to compare by id
end, "mark_constant"))
end,
-- traverse v and all the subvalues it contains
-- callback(v) is called on every value after traversing its subvalues
-- if pertype_callback is given, will then call the associated callback(v) in the type table for each value
-- both those callbacks can either returns nil (success) or nil, err (error)
-- returns true
-- return nil, error
traverse = function(v, callback, pertype_callback)
if atypes[v.type] and atypes[v.type].traverse then
local r, e = atypes[v.type].traverse(v.value, callback, pertype_callback)
if not r then return nil, e end
r, e = callback(v)
if e then return nil, e end
if pertype_callback and atypes[v.type][pertype_callback] then
r, e = atypes[v.type][pertype_callback](v)
if e then return nil, e end
end
return true
else else
error(("don't know how to mark type %s as constant"):format(v.type)) error(("don't know how to traverse type %s"):format(v.type))
end end
end, end,
--- checks if the value can be persisted
-- returns true
-- returns nil, persist illegal message
check_persistable = function(v)
return common.traverse(v, function(v)
if v.nonpersistent then
return nil, ("can't put a non persistable %s into a persistent variable"):format(v.type)
end
end)
end,
--- returns a variable's value, evaluating a pending expression if neccessary --- returns a variable's value, evaluating a pending expression if neccessary
-- if you're sure the variable has already been evaluated, use state.variables[fqm] directly -- if you're sure the variable has already been evaluated, use state.variables[fqm] directly
-- return var -- return var
@ -138,6 +166,13 @@ common = {
return nil, ("%s; while assigning value to variable %q"):format(e, name) return nil, ("%s; while assigning value to variable %q"):format(e, name)
end end
end end
-- check persistence
if state.variable_metadata[name].persistent then
local s, e = common.check_persistable(val)
if not s then
return nil, ("%s; while assigning value to variable %q"):format(e, name)
end
end
-- check constraint -- check constraint
local s, e = common.check_constraint(state, name, val) local s, e = common.check_constraint(state, name, val)
if not s then if not s then
@ -220,7 +255,8 @@ common = {
--- returns true if a variable should be persisted on save --- returns true if a variable should be persisted on save
-- will exclude: variable that have not been evaluated yet and non-persistent variable -- will exclude: variable that have not been evaluated yet and non-persistent variable
-- this will by consequence excludes variable in scoped variables (can be neither persistent not evaluated into global state), constants (can not be persistent), internal anselme variables (not marked persistent), etc. -- this will by consequence excludes variable in scoped variables (can be neither persistent not evaluated into global state), constants (can not be persistent), internal anselme variables (not marked persistent), etc.
should_keep_variable = function(state, name, value) -- You may want to check afterwards with check_persistable to check if the value can actually be persisted.
should_be_persisted = function(state, name, value)
return value.type ~= "pending definition" and state.variable_metadata[name].persistent return value.type ~= "pending definition" and state.variable_metadata[name].persistent
end, end,
--- check truthyness of an anselme value --- check truthyness of an anselme value

View file

@ -495,7 +495,7 @@ local function eval(state, exp)
ret, e = run(state, fn.child) ret, e = run(state, fn.child)
-- resume at last checkpoint -- resume at last checkpoint
else else
local expr, err = expression(checkpoint.value[1], state, fn.namespace) local expr, err = expression(checkpoint.value[1], state, fn.namespace, "resume from checkpoint")
if not expr then return nil, err end if not expr then return nil, err end
ret, e = eval(state, expr) ret, e = eval(state, expr)
end end
@ -567,6 +567,11 @@ local function eval(state, exp)
type = "event buffer", type = "event buffer",
value = l value = l
} }
elseif exp.type == "nonpersistent" then
local v, e = eval(state, exp.expression)
if not v then return nil, e end
v.nonpersistent = true
return v
-- pass the value along (internal type, used for variable reference implicit calls) -- pass the value along (internal type, used for variable reference implicit calls)
elseif exp.type == "value passthrough" then elseif exp.type == "value passthrough" then
return exp.value return exp.value

View file

@ -108,3 +108,7 @@ Disadvantages:
TODO: write a translation guide/simplify translation process TODO: write a translation guide/simplify translation process
TODO: make injection nicer. Some decorator-like syntax? to select specific functions to inject to TODO: make injection nicer. Some decorator-like syntax? to select specific functions to inject to
TODO: allow multiple aliases for a single identifier?
TODO: closures. Especially for when returning a subfunction from a scoped variable.

View file

@ -187,7 +187,7 @@ common = {
end end
-- expr -- expr
if r:match("^{") then if r:match("^{") then
local exp, rem = expression(r:gsub("^{", ""), state, namespace) local exp, rem = expression(r:gsub("^{", ""), state, namespace, "interpolated expression")
if not exp then return nil, rem end if not exp then return nil, rem end
if not rem:match("^%s*}") then return nil, ("expected closing } at end of expression before %q"):format(rem) end if not rem:match("^%s*}") then return nil, ("expected closing } at end of expression before %q"):format(rem) end
-- wrap in format() call -- wrap in format() call
@ -213,7 +213,7 @@ common = {
end end
-- binop expression at the end of the text -- binop expression at the end of the text
elseif allow_binops and r:match(("^[%s]"):format(allow_binops)) then elseif allow_binops and r:match(("^[%s]"):format(allow_binops)) then
local exp, rem = expression(r, state, namespace, nil, text_exp) local exp, rem = expression(r, state, namespace, "text binop suffix", nil, text_exp)
if not exp then return nil, rem end if not exp then return nil, rem end
return exp, rem return exp, rem
elseif r == "" then elseif r == "" then

View file

@ -1,4 +1,4 @@
local identifier_pattern, format_identifier, find, escape, find_function, parse_text, find_all, split, find_function_from_list local identifier_pattern, format_identifier, find, escape, find_function, parse_text, find_all, split, find_function_from_list, preparse
--- binop priority --- binop priority
local binops_prio = { local binops_prio = {
@ -26,7 +26,7 @@ local implicit_multiply_priority = 9.5 -- just above / so 1/2x gives 1/(2x)
local prefix_unops_prio = { local prefix_unops_prio = {
[1] = {}, [1] = {},
[2] = {}, [2] = {},
[3] = {}, [3] = { "$" },
[4] = {}, [4] = {},
[5] = {}, [5] = {},
[6] = {}, [6] = {},
@ -81,11 +81,22 @@ local function get_text_in_litteral(s, start_pos)
end end
return d, r return d, r
end end
local function random_identifier_alpha()
local r = ""
for _=1, 18 do -- that's live 10^30 possibilities, ought to be enough for anyone
if math.random(1, 2) == 1 then
r = r .. string.char(math.random(65, 90))
else
r = r .. string.char(math.random(97, 122))
end
end
return r
end
--- parse an expression --- parse an expression
-- return expr, remaining if success -- return expr, remaining if success
-- returns nil, err if error -- returns nil, err if error
local function expression(s, state, namespace, current_priority, operating_on) local function expression(s, state, namespace, source, current_priority, operating_on)
s = s:match("^%s*(.*)$") s = s:match("^%s*(.*)$")
current_priority = current_priority or 0 current_priority = current_priority or 0
if not operating_on then if not operating_on then
@ -95,7 +106,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
if not d then if not d then
d, r = s:match("^(%d+)(.*)$") d, r = s:match("^(%d+)(.*)$")
end end
return expression(r, state, namespace, current_priority, { return expression(r, state, namespace, source, current_priority, {
type = "number", type = "number",
value = tonumber(d) value = tonumber(d)
}) })
@ -104,13 +115,13 @@ local function expression(s, state, namespace, current_priority, operating_on)
local d, r = get_text_in_litteral(s) local d, r = get_text_in_litteral(s)
local l, e = parse_text(d, state, namespace, "string") -- parse interpolated expressions local l, e = parse_text(d, state, namespace, "string") -- parse interpolated expressions
if not l then return l, e end if not l then return l, e end
return expression(r, state, namespace, current_priority, l) return expression(r, state, namespace, source, current_priority, l)
-- text buffer -- text buffer
elseif s:match("^%%%[") then elseif s:match("^%%%[") then
local text = s:match("^%%(.*)$") local text = s:match("^%%(.*)$")
local v, r = parse_text(text, state, namespace, "text", "#~", true) local v, r = parse_text(text, state, namespace, "text", "#~", true)
if not v then return nil, r end if not v then return nil, r end
return expression(r, state, namespace, current_priority, { return expression(r, state, namespace, source, current_priority, {
type = "text buffer", type = "text buffer",
text = v text = v
}) })
@ -121,13 +132,13 @@ local function expression(s, state, namespace, current_priority, operating_on)
local exp local exp
if content:match("[^%s]") then if content:match("[^%s]") then
local r_paren local r_paren
exp, r_paren = expression(content, state, namespace) exp, r_paren = expression(content, state, namespace, source)
if not exp then return nil, "invalid expression inside parentheses: "..r_paren end if not exp then return nil, "invalid expression inside parentheses: "..r_paren end
if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of parenthesis expression"):format(r_paren) end if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of parenthesis expression"):format(r_paren) end
else else
exp = { type = "nil", value = nil } exp = { type = "nil", value = nil }
end end
return expression(r, state, namespace, current_priority, { return expression(r, state, namespace, source, current_priority, {
type = "parentheses", type = "parentheses",
expression = exp expression = exp
}) })
@ -138,11 +149,11 @@ local function expression(s, state, namespace, current_priority, operating_on)
local exp local exp
if content:match("[^%s]") then if content:match("[^%s]") then
local r_paren local r_paren
exp, r_paren = expression(content, state, namespace) exp, r_paren = expression(content, state, namespace, source)
if not exp then return nil, "invalid expression inside list parentheses: "..r_paren end if not exp then return nil, "invalid expression inside list parentheses: "..r_paren end
if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of list parenthesis expression"):format(r_paren) end if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of list parenthesis expression"):format(r_paren) end
end end
return expression(r, state, namespace, current_priority, { return expression(r, state, namespace, source, current_priority, {
type = "list brackets", type = "list brackets",
expression = exp expression = exp
}) })
@ -153,11 +164,11 @@ local function expression(s, state, namespace, current_priority, operating_on)
local exp local exp
if content:match("[^%s]") then if content:match("[^%s]") then
local r_paren local r_paren
exp, r_paren = expression(content, state, namespace) exp, r_paren = expression(content, state, namespace, source)
if not exp then return nil, "invalid expression inside map parentheses: "..r_paren end if not exp then return nil, "invalid expression inside map parentheses: "..r_paren end
if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of map parenthesis expression"):format(r_paren) end if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of map parenthesis expression"):format(r_paren) end
end end
return expression(r, state, namespace, current_priority, { return expression(r, state, namespace, source, current_priority, {
type = "map brackets", type = "map brackets",
expression = exp expression = exp
}) })
@ -168,7 +179,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
-- string:value pair shorthand using = -- string:value pair shorthand using =
if r:match("^=[^=]") and pair_priority > current_priority then if r:match("^=[^=]") and pair_priority > current_priority then
local val local val
val, r = expression(r:match("^=(.*)$"), state, namespace, pair_priority) val, r = expression(r:match("^=(.*)$"), state, namespace, source, pair_priority)
if not val then return val, r end if not val then return val, r end
local args = { local args = {
type = "list", type = "list",
@ -181,7 +192,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
-- find compatible variant -- find compatible variant
local variant, err = find_function(state, namespace, "_=_", args, true) local variant, err = find_function(state, namespace, "_=_", args, true)
if not variant then return variant, err end if not variant then return variant, err end
return expression(r, state, namespace, current_priority, variant) return expression(r, state, namespace, source, current_priority, variant)
end end
-- variables -- variables
-- if name isn't a valid variable, suffix call: detect if a prefix is valid variable, suffix _._ call is handled in the binop section below -- if name isn't a valid variable, suffix call: detect if a prefix is valid variable, suffix _._ call is handled in the binop section below
@ -193,7 +204,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
if i < #nl then if i < #nl then
r = "."..table.concat(nl, ".", i+1, #nl)..r r = "."..table.concat(nl, ".", i+1, #nl)..r
end end
return expression(r, state, namespace, current_priority, { return expression(r, state, namespace, source, current_priority, {
type = "variable", type = "variable",
name = vfqm name = vfqm
}) })
@ -207,14 +218,14 @@ local function expression(s, state, namespace, current_priority, operating_on)
if i < #nl then if i < #nl then
r = "."..table.concat(nl, ".", i+1, #nl)..r r = "."..table.concat(nl, ".", i+1, #nl)..r
end end
return expression(r, state, namespace, current_priority, { return expression(r, state, namespace, source, current_priority, {
type = "potential function", type = "potential function",
called_name = name, called_name = name,
names = lfnqm names = lfnqm
}) })
end end
end end
return nil, ("can't find function or variable named %q"):format(name) return nil, ("can't find function or variable named %q in namespace %q"):format(name, namespace)
end end
-- prefix unops -- prefix unops
for prio, oplist in ipairs(prefix_unops_prio) do for prio, oplist in ipairs(prefix_unops_prio) do
@ -224,15 +235,15 @@ local function expression(s, state, namespace, current_priority, operating_on)
local sright = s:match("^"..escaped.."(.*)$") local sright = s:match("^"..escaped.."(.*)$")
-- function and variable reference -- function and variable reference
if op == "&" then if op == "&" then
local right, r = expression(sright, state, namespace, prio) local right, r = expression(sright, state, namespace, source, prio)
if not right then return nil, ("invalid expression after unop %q: %s"):format(op, r) end if not right then return nil, ("invalid expression after unop %q: %s"):format(op, r) end
if right.type == "potential function" then if right.type == "potential function" then
return expression(r, state, namespace, current_priority, { return expression(r, state, namespace, source, current_priority, {
type = "function reference", type = "function reference",
names = right.names names = right.names
}) })
elseif right.type == "variable" then elseif right.type == "variable" then
return expression(r, state, namespace, current_priority, { return expression(r, state, namespace, source, current_priority, {
type = "variable reference", type = "variable reference",
name = right.name, name = right.name,
expression = right expression = right
@ -241,16 +252,45 @@ local function expression(s, state, namespace, current_priority, operating_on)
-- find variant -- find variant
local variant, err = find_function(state, namespace, op.."_", right, true) local variant, err = find_function(state, namespace, op.."_", right, true)
if not variant then return variant, err end if not variant then return variant, err end
return expression(r, state, namespace, current_priority, variant) return expression(r, state, namespace, source, current_priority, variant)
end end
-- anonymous function
elseif op == "$" then
-- get eventual arguments
local params = "()"
if sright:match("^%b()") then
params, sright = sright:match("^(%b())(.*)$")
end
-- define function
local fn_name = ("%s🥸%s"):format(namespace, random_identifier_alpha())
local s, e = preparse(state, (":$%s%s\n\t@%s"):format(fn_name, params, fn_name), "", source)
if not s then return nil, e end
local fn = state.functions[fn_name][1]
-- parse return expression
local right, r = expression(sright, state, fn.namespace, source, prio)
if not right then return nil, ("invalid expression after unop %q: %s"):format(op, r) end
-- put expression in return line
for _, c in ipairs(fn.child) do
if c.type == "return" and c.expression == fn_name then
c.expression = right
end
end
-- return reference to created function
return expression(r, state, namespace, source, current_priority, {
type = "nonpersistent",
expression = {
type = "function reference",
names = { fn_name }
}
})
-- normal prefix unop -- normal prefix unop
else else
local right, r = expression(sright, state, namespace, prio) local right, r = expression(sright, state, namespace, source, prio)
if not right then return nil, ("invalid expression after unop %q: %s"):format(op, r) end if not right then return nil, ("invalid expression after unop %q: %s"):format(op, r) end
-- find variant -- find variant
local variant, err = find_function(state, namespace, op.."_", right, true) local variant, err = find_function(state, namespace, op.."_", right, true)
if not variant then return variant, err end if not variant then return variant, err end
return expression(r, state, namespace, current_priority, variant) return expression(r, state, namespace, source, current_priority, variant)
end end
end end
end end
@ -275,7 +315,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
-- get arguments -- get arguments
if content:match("[^%s]") then if content:match("[^%s]") then
local err local err
args, err = expression(content, state, namespace) args, err = expression(content, state, namespace, source)
if not args then return args, err end if not args then return args, err end
if err:match("[^%s]") then return nil, ("unexpected %q at end of argument list"):format(err) end if err:match("[^%s]") then return nil, ("unexpected %q at end of argument list"):format(err) end
end end
@ -285,12 +325,12 @@ local function expression(s, state, namespace, current_priority, operating_on)
-- find compatible variant -- find compatible variant
local variant, err = find_function_from_list(state, namespace, operating_on.called_name, operating_on.names, args, paren_call, implicit_call) local variant, err = find_function_from_list(state, namespace, operating_on.called_name, operating_on.names, args, paren_call, implicit_call)
if not variant then return variant, err end if not variant then return variant, err end
return expression(r, state, namespace, current_priority, variant) return expression(r, state, namespace, source, current_priority, variant)
-- implicit call on variable reference. Might be canceled afterwards due to finding a higher priority operator. -- implicit call on variable reference. Might be canceled afterwards due to finding a higher priority operator.
elseif operating_on.type == "variable" or (operating_on.type == "function call" and operating_on.called_name == "_._") then elseif operating_on.type == "variable" or (operating_on.type == "function call" and operating_on.called_name == "_._") then
local implicit_call_variant, err = find_function(state, namespace, "_!", { type = "value passthrough" }, false, true) local implicit_call_variant, err = find_function(state, namespace, "_!", { type = "value passthrough" }, false, true)
if not implicit_call_variant then return implicit_call_variant, err end if not implicit_call_variant then return implicit_call_variant, err end
return expression(s, state, namespace, current_priority, { return expression(s, state, namespace, source, current_priority, {
type = "implicit call if reference", type = "implicit call if reference",
variant = implicit_call_variant, variant = implicit_call_variant,
expression = operating_on expression = operating_on
@ -323,7 +363,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
-- get arguments -- get arguments
if content:match("[^%s]") then if content:match("[^%s]") then
local err local err
args, err = expression(content, state, namespace) args, err = expression(content, state, namespace, source)
if not args then return args, err end if not args then return args, err end
if err:match("[^%s]") then return nil, ("unexpected %q at end of argument map"):format(err) end if err:match("[^%s]") then return nil, ("unexpected %q at end of argument map"):format(err) end
end end
@ -353,7 +393,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
-- find compatible variant -- find compatible variant
local variant, err = find_function(state, namespace, name, args, paren_call) local variant, err = find_function(state, namespace, name, args, paren_call)
if not variant then return variant, err end if not variant then return variant, err end
return expression(r, state, namespace, current_priority, variant) return expression(r, state, namespace, source, current_priority, variant)
-- namespace -- namespace
elseif op == "." and sright:match("^"..identifier_pattern) then elseif op == "." and sright:match("^"..identifier_pattern) then
local name, r = sright:match("^("..identifier_pattern..")(.-)$") local name, r = sright:match("^("..identifier_pattern..")(.-)$")
@ -366,14 +406,14 @@ local function expression(s, state, namespace, current_priority, operating_on)
} }
local variant, err = find_function(state, namespace, "_._", args, true) local variant, err = find_function(state, namespace, "_._", args, true)
if not variant then return variant, err end if not variant then return variant, err end
return expression(r, state, namespace, current_priority, variant) return expression(r, state, namespace, source, current_priority, variant)
-- other binops -- other binops
else else
local right, r = expression(sright, state, namespace, prio) local right, r = expression(sright, state, namespace, source, prio)
if right then if right then
-- list constructor (can't do this through a function call since we need to build a list for its arguments) -- list constructor (can't do this through a function call since we need to build a list for its arguments)
if op == "," then if op == "," then
return expression(r, state, namespace, current_priority, { return expression(r, state, namespace, source, current_priority, {
type = "list", type = "list",
left = operating_on, left = operating_on,
right = right right = right
@ -408,18 +448,18 @@ local function expression(s, state, namespace, current_priority, operating_on)
end end
-- rewrite function to perform assignment -- rewrite function to perform assignment
operating_on.assignment = right operating_on.assignment = right
return expression(r, state, namespace, current_priority, operating_on) return expression(r, state, namespace, source, current_priority, operating_on)
elseif operating_on.type ~= "variable" then elseif operating_on.type ~= "variable" then
return nil, ("trying to perform assignment on a %s expression"):format(operating_on.type) return nil, ("trying to perform assignment on a %s expression"):format(operating_on.type)
end end
-- assign to a variable -- assign to a variable
return expression(r, state, namespace, current_priority, { return expression(r, state, namespace, source, current_priority, {
type = ":=", type = ":=",
left = operating_on, left = operating_on,
right = right right = right
}) })
elseif op == "&" or op == "|" or op == "~?" or op == "~" or op == "#" then elseif op == "&" or op == "|" or op == "~?" or op == "~" or op == "#" then
return expression(r, state, namespace, current_priority, { return expression(r, state, namespace, source, current_priority, {
type = op, type = op,
left = operating_on, left = operating_on,
right = right right = right
@ -434,7 +474,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
} }
local variant, err = find_function(state, namespace, "_"..op.."_", args, true) local variant, err = find_function(state, namespace, "_"..op.."_", args, true)
if not variant then return variant, err end if not variant then return variant, err end
return expression(r, state, namespace, current_priority, variant) return expression(r, state, namespace, source, current_priority, variant)
end end
end end
end end
@ -458,12 +498,12 @@ local function expression(s, state, namespace, current_priority, operating_on)
-- remove ! after a previously-assumed implicit function call -- remove ! after a previously-assumed implicit function call
if op == "!" and operating_on.type == "function call" and operating_on.implicit_call then if op == "!" and operating_on.type == "function call" and operating_on.implicit_call then
operating_on.implicit_call = false operating_on.implicit_call = false
return expression(r, state, namespace, current_priority, operating_on) return expression(r, state, namespace, source, current_priority, operating_on)
-- normal suffix unop -- normal suffix unop
else else
local variant, err = find_function(state, namespace, "_"..op, operating_on, true) local variant, err = find_function(state, namespace, "_"..op, operating_on, true)
if not variant then return variant, err end if not variant then return variant, err end
return expression(r, state, namespace, current_priority, variant) return expression(r, state, namespace, source, current_priority, variant)
end end
end end
end end
@ -479,19 +519,19 @@ local function expression(s, state, namespace, current_priority, operating_on)
content = content:gsub("^%(", ""):gsub("%)$", "") content = content:gsub("^%(", ""):gsub("%)$", "")
-- get arguments -- get arguments
if content:match("[^%s]") then if content:match("[^%s]") then
local right, r_paren = expression(content, state, namespace) local right, r_paren = expression(content, state, namespace, source)
if not right then return right, r_paren end if not right then return right, r_paren end
if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of index/call expression"):format(r_paren) end if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of index/call expression"):format(r_paren) end
args = { type = "list", left = args, right = right } args = { type = "list", left = args, right = right }
end end
local variant, err = find_function(state, namespace, "()", args, true) local variant, err = find_function(state, namespace, "()", args, true)
if not variant then return variant, err end if not variant then return variant, err end
return expression(r, state, namespace, current_priority, variant) return expression(r, state, namespace, source, current_priority, variant)
end end
-- implicit multiplication -- implicit multiplication
if implicit_multiply_priority > current_priority then if implicit_multiply_priority > current_priority then
if s:match("^"..identifier_pattern) then if s:match("^"..identifier_pattern) then
local right, r = expression(s, state, namespace, implicit_multiply_priority) local right, r = expression(s, state, namespace, source, implicit_multiply_priority)
if right then if right then
local args = { local args = {
type = "list", type = "list",
@ -500,7 +540,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
} }
local variant, err = find_function(state, namespace, "_*_", args, true) local variant, err = find_function(state, namespace, "_*_", args, true)
if not variant then return variant, err end if not variant then return variant, err end
return expression(r, state, namespace, current_priority, variant) return expression(r, state, namespace, source, current_priority, variant)
end end
end end
end end
@ -513,4 +553,6 @@ package.loaded[...] = expression
local common = require((...):gsub("expression$", "common")) local common = require((...):gsub("expression$", "common"))
identifier_pattern, format_identifier, find, escape, find_function, parse_text, find_all, split, find_function_from_list = common.identifier_pattern, common.format_identifier, common.find, common.escape, common.find_function, common.parse_text, common.find_all, common.split, common.find_function_from_list identifier_pattern, format_identifier, find, escape, find_function, parse_text, find_all, split, find_function_from_list = common.identifier_pattern, common.format_identifier, common.find, common.escape, common.find_function, common.parse_text, common.find_all, common.split, common.find_function_from_list
preparse = require((...):gsub("expression$", "preparser"))
return expression return expression

View file

@ -13,7 +13,7 @@ local function parse(state)
for _, param in ipairs(line.params) do for _, param in ipairs(line.params) do
-- get type constraints -- get type constraints
if param.type_constraint then if param.type_constraint then
local type_exp, rem = expression(param.type_constraint, state, namespace) local type_exp, rem = expression(param.type_constraint, state, namespace, line.source)
if not type_exp then return nil, ("in type constraint, %s; at %s"):format(rem, line.source) end if not type_exp then return nil, ("in type constraint, %s; at %s"):format(rem, line.source) end
if rem:match("[^%s]") then if rem:match("[^%s]") then
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param.full_name, rem, line.source) return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param.full_name, rem, line.source)
@ -22,7 +22,7 @@ local function parse(state)
end end
-- get default value -- get default value
if param.default then if param.default then
local default_exp, rem = expression(param.default, state, namespace) local default_exp, rem = expression(param.default, state, namespace, line.source)
if not default_exp then return nil, ("in default value, %s; at %s"):format(rem, line.source) end if not default_exp then return nil, ("in default value, %s; at %s"):format(rem, line.source) end
if rem:match("[^%s]") then if rem:match("[^%s]") then
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param.full_name, rem, line.source) return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param.full_name, rem, line.source)
@ -36,7 +36,7 @@ local function parse(state)
end end
-- assignment argument -- assignment argument
if line.assignment and line.assignment.type_constraint then if line.assignment and line.assignment.type_constraint then
local type_exp, rem = expression(line.assignment.type_constraint, state, namespace) local type_exp, rem = expression(line.assignment.type_constraint, state, namespace, line.source)
if not type_exp then return nil, ("in type constraint, %s; at %s"):format(rem, line.source) end if not type_exp then return nil, ("in type constraint, %s; at %s"):format(rem, line.source) end
if rem:match("[^%s]") then if rem:match("[^%s]") then
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(line.assignment.full_name, rem, line.source) return nil, ("unexpected characters after parameter %q: %q; at %s"):format(line.assignment.full_name, rem, line.source)
@ -66,8 +66,8 @@ local function parse(state)
end end
end end
-- expressions -- expressions
if line.expression then if line.expression and type(line.expression) == "string" then
local exp, rem = expression(line.expression, state, namespace) local exp, rem = expression(line.expression, state, namespace, line.source)
if not exp then return nil, ("%s; at %s"):format(rem, line.source) end if not exp then return nil, ("%s; at %s"):format(rem, line.source) end
if rem:match("[^%s]") then return nil, ("expected end of expression before %q; at %s"):format(rem, line.source) end if rem:match("[^%s]") then return nil, ("expected end of expression before %q; at %s"):format(rem, line.source) end
line.expression = exp line.expression = exp
@ -76,7 +76,7 @@ local function parse(state)
state.variables[line.name].value.expression = line.expression state.variables[line.name].value.expression = line.expression
-- parse constraints -- parse constraints
if line.constraint then if line.constraint then
local type_exp, rem2 = expression(line.constraint, state, namespace) local type_exp, rem2 = expression(line.constraint, state, namespace, line.source)
if not type_exp then return nil, ("in type constraint, %s; at %s"):format(rem2, line.source) end if not type_exp then return nil, ("in type constraint, %s; at %s"):format(rem2, line.source) end
if rem2:match("[^%s]") then if rem2:match("[^%s]") then
return nil, ("unexpected characters after variable %q: %q; at %s"):format(line.name, rem2, line.source) return nil, ("unexpected characters after variable %q: %q; at %s"):format(line.name, rem2, line.source)
@ -92,9 +92,13 @@ local function parse(state)
if err:match("[^%s]") then return nil, ("expected end of expression in end-of-text expression before %q"):format(err) end if err:match("[^%s]") then return nil, ("expected end of expression in end-of-text expression before %q"):format(err) end
line.text = txt line.text = txt
end end
state.queued_lines[i] = nil table.remove(state.queued_lines, i)
end
if #state.queued_lines > 0 then -- lines were added during post-parsing, process these
return parse(state)
else
return true
end end
return true
end end
package.loaded[...] = parse package.loaded[...] = parse

View file

@ -1,4 +1,4 @@
local format, to_lua, from_lua, events, anselme, escape, hash, mark_constant, update_hashes, get_variable, find_function_variant_from_fqm, post_process_text local format, to_lua, from_lua, events, anselme, escape, hash, update_hashes, get_variable, find_function_variant_from_fqm, post_process_text, traverse
local types = {} local types = {}
types.lua = { types.lua = {
@ -88,7 +88,7 @@ types.anselme = {
hash = function() hash = function()
return "nil()" return "nil()"
end, end,
mark_constant = function() end, traverse = function() return true end,
}, },
number = { number = {
format = function(val) format = function(val)
@ -100,7 +100,7 @@ types.anselme = {
hash = function(val) hash = function(val)
return ("n(%s)"):format(val) return ("n(%s)"):format(val)
end, end,
mark_constant = function() end, traverse = function() return true end,
}, },
string = { string = {
format = function(val) format = function(val)
@ -112,7 +112,7 @@ types.anselme = {
hash = function(val) hash = function(val)
return ("s(%s)"):format(val) return ("s(%s)"):format(val)
end, end,
mark_constant = function() end, traverse = function() return true end,
}, },
pair = { pair = {
format = function(val) format = function(val)
@ -136,9 +136,12 @@ types.anselme = {
if not v then return v, ve end if not v then return v, ve end
return ("p(%s=%s)"):format(k, v) return ("p(%s=%s)"):format(k, v)
end, end,
mark_constant = function(v) traverse = function(val, callback, pertype_callback)
mark_constant(v.value[1]) local k, ke = traverse(val[1], callback, pertype_callback)
mark_constant(v.value[2]) if not k then return k, ke end
local v, ve = traverse(val[2], callback, pertype_callback)
if not v then return v, ve end
return true
end, end,
}, },
annotated = { annotated = {
@ -161,9 +164,12 @@ types.anselme = {
if not v then return v, ve end if not v then return v, ve end
return ("a(%s::%s)"):format(k, v) return ("a(%s::%s)"):format(k, v)
end, end,
mark_constant = function(v) traverse = function(val, callback, pertype_callback)
mark_constant(v.value[1]) local k, ke = traverse(val[1], callback, pertype_callback)
mark_constant(v.value[2]) if not k then return k, ke end
local v, ve = traverse(val[2], callback, pertype_callback)
if not v then return v, ve end
return true
end, end,
}, },
list = { list = {
@ -195,12 +201,16 @@ types.anselme = {
end end
return ("l(%s)"):format(table.concat(l, ",")) return ("l(%s)"):format(table.concat(l, ","))
end, end,
traverse = function(val, callback, pertype_callback)
for _, item in ipairs(val) do
local s, e = traverse(item, callback, pertype_callback)
if not s then return s, e end
end
return true
end,
mark_constant = function(v) mark_constant = function(v)
v.constant = true v.constant = true
for _, item in ipairs(v.value) do end,
mark_constant(item)
end
end
}, },
map = { map = {
mutable = true, mutable = true,
@ -239,12 +249,17 @@ types.anselme = {
table.sort(l) table.sort(l)
return ("m(%s)"):format(table.concat(l, ",")) return ("m(%s)"):format(table.concat(l, ","))
end, end,
traverse = function(val, callback, pertype_callback)
for _, v in pairs(val) do
local ks, ke = traverse(v[1], callback, pertype_callback)
if not ks then return ks, ke end
local vs, ve = traverse(v[2], callback, pertype_callback)
if not vs then return vs, ve end
end
return true
end,
mark_constant = function(v) mark_constant = function(v)
v.constant = true v.constant = true
for _, val in pairs(v.value) do
mark_constant(val[1])
mark_constant(val[2])
end
update_hashes(v) update_hashes(v)
end, end,
}, },
@ -296,6 +311,13 @@ types.anselme = {
table.sort(attributes) table.sort(attributes)
return ("%%(%s;%s)"):format(val.class, table.concat(attributes, ",")) return ("%%(%s;%s)"):format(val.class, table.concat(attributes, ","))
end, end,
traverse = function(v, callback, pertype_callback)
for _, attrib in pairs(v.attributes) do
local s, e = traverse(attrib, callback, pertype_callback)
if not s then return s, e end
end
return true
end,
mark_constant = function(v) mark_constant = function(v)
v.constant = true v.constant = true
end, end,
@ -312,8 +334,7 @@ types.anselme = {
hash = function(val) hash = function(val)
return ("&f(%s)"):format(table.concat(val, ", ")) return ("&f(%s)"):format(table.concat(val, ", "))
end, end,
mark_constant = function() end, traverse = function() return true end,
}, },
["variable reference"] = { ["variable reference"] = {
format = function(val) format = function(val)
@ -323,9 +344,9 @@ types.anselme = {
hash = function(val) hash = function(val)
return ("&v(%s)"):format(val) return ("&v(%s)"):format(val)
end, end,
mark_constant = function() end, traverse = function() return true end,
}, },
-- event buffer: can only be used outside of Anselme internal for text & flush events (through text buffers) -- event buffer: can only be used outside of Anselme internals for text & flush events (through text buffers)
["event buffer"] = { ["event buffer"] = {
format = function(val) -- triggered from subtexts format = function(val) -- triggered from subtexts
local v, e = events:write_buffer(anselme.running.state, val) local v, e = events:write_buffer(anselme.running.state, val)
@ -365,13 +386,25 @@ types.anselme = {
end end
return ("eb(%s)"):format(table.concat(l, ",")) return ("eb(%s)"):format(table.concat(l, ","))
end, end,
mark_constant = function() end, traverse = function(val, callback, pertype_callback)
for _, event in ipairs(val) do
if event.type == "text" then
for _, t in ipairs(event.value) do
local s, e = traverse(t.tags, callback, pertype_callback)
if not s then return s, e end
end
elseif event.type ~= "flush" then
return nil, ("event %q in event buffer cannot be traversed"):format(event.type)
end
end
return true
end,
}, },
} }
package.loaded[...] = types package.loaded[...] = types
local common = require((...):gsub("stdlib%.types$", "interpreter.common")) local common = require((...):gsub("stdlib%.types$", "interpreter.common"))
format, to_lua, from_lua, events, hash, mark_constant, update_hashes, get_variable, post_process_text = common.format, common.to_lua, common.from_lua, common.events, common.hash, common.mark_constant, common.update_hashes, common.get_variable, common.post_process_text format, to_lua, from_lua, events, hash, update_hashes, get_variable, post_process_text, traverse = common.format, common.to_lua, common.from_lua, common.events, common.hash, common.update_hashes, common.get_variable, common.post_process_text, common.traverse
anselme = require((...):gsub("stdlib%.types$", "anselme")) anselme = require((...):gsub("stdlib%.types$", "anselme"))
local pcommon = require((...):gsub("stdlib%.types$", "parser.common")) local pcommon = require((...):gsub("stdlib%.types$", "parser.common"))
escape, find_function_variant_from_fqm = pcommon.escape, pcommon.find_function_variant_from_fqm escape, find_function_variant_from_fqm = pcommon.escape, pcommon.find_function_variant_from_fqm

View file

@ -125,7 +125,12 @@ if args.script or args.game then
print("error", err) print("error", err)
end end
if args.save then if args.save then
print(inspect(vm:save())) local s, e = vm:save()
if s then
print(inspect(s))
else
print(("Error while saving: %s"):format(e))
end
end end
-- test mode -- test mode
@ -147,7 +152,7 @@ else
-- simple random to get the same result across lua versions -- simple random to get the same result across lua versions
local prev = 0 local prev = 0
local function badrandom(a, b) local function badrandom(a, b)
prev = (42424242424242 * prev + 242) % 2^32 prev = (15485863 * prev + 11) % 2038074743
return a + prev % (b-a+1) return a + prev % (b-a+1)
end end
function math.random(a, b) function math.random(a, b)

View file

@ -0,0 +1,21 @@
:f = $(x)x*x
:$g(x)
@x*x
{f(5)} = {g(5)}
{f(2)} = {g(2)}
:y = 5
:h = $(x)x*x+y
{h(3)} == 14
~ y := 7
{h(5)} == 32
:i = $y*y
{i} == 49

View file

@ -0,0 +1,81 @@
local _={}
_[35]={}
_[34]={}
_[33]={}
_[32]={}
_[31]={}
_[30]={}
_[29]={}
_[28]={}
_[27]={}
_[26]={}
_[25]={}
_[24]={}
_[23]={tags=_[35],text=" == 49"}
_[22]={tags=_[34],text="49"}
_[21]={tags=_[33],text=" == 32"}
_[20]={tags=_[32],text="32"}
_[19]={tags=_[31],text=" == 14"}
_[18]={tags=_[30],text="14"}
_[17]={tags=_[29],text="4"}
_[16]={tags=_[28],text=" = "}
_[15]={tags=_[27],text="4"}
_[14]={tags=_[26],text="25"}
_[13]={tags=_[25],text=" = "}
_[12]={tags=_[24],text="25"}
_[11]={_[22],_[23]}
_[10]={_[20],_[21]}
_[9]={_[18],_[19]}
_[8]={_[15],_[16],_[17]}
_[7]={_[12],_[13],_[14]}
_[6]={"return"}
_[5]={"text",_[11]}
_[4]={"text",_[10]}
_[3]={"text",_[9]}
_[2]={"text",_[8]}
_[1]={"text",_[7]}
return {_[1],_[2],_[3],_[4],_[5],_[6]}
--[[
{ "text", { {
tags = {},
text = "25"
}, {
tags = {},
text = " = "
}, {
tags = {},
text = "25"
} } }
{ "text", { {
tags = {},
text = "4"
}, {
tags = {},
text = " = "
}, {
tags = {},
text = "4"
} } }
{ "text", { {
tags = {},
text = "14"
}, {
tags = {},
text = " == 14"
} } }
{ "text", { {
tags = {},
text = "32"
}, {
tags = {},
text = " == 32"
} } }
{ "text", { {
tags = {},
text = "49"
}, {
tags = {},
text = " == 49"
} } }
{ "return" }
]]--

View file

@ -4,11 +4,11 @@ _[20]={}
_[19]={} _[19]={}
_[18]={} _[18]={}
_[17]={} _[17]={}
_[16]={text="a",tags=_[21]} _[16]={tags=_[21],text="c"}
_[15]={text="b",tags=_[20]} _[15]={tags=_[20],text="a"}
_[14]={text="b",tags=_[19]} _[14]={tags=_[19],text="c"}
_[13]={text="c",tags=_[18]} _[13]={tags=_[18],text="b"}
_[12]={text="c",tags=_[17]} _[12]={tags=_[17],text="c"}
_[11]={_[16]} _[11]={_[16]}
_[10]={_[15]} _[10]={_[15]}
_[9]={_[14]} _[9]={_[14]}
@ -26,21 +26,21 @@ return {_[1],_[2],_[3],_[4],_[5],_[6]}
tags = {}, tags = {},
text = "c" text = "c"
} } } } } }
{ "text", { {
tags = {},
text = "b"
} } }
{ "text", { { { "text", { {
tags = {}, tags = {},
text = "c" text = "c"
} } } } } }
{ "text", { {
tags = {},
text = "b"
} } }
{ "text", { {
tags = {},
text = "b"
} } }
{ "text", { { { "text", { {
tags = {}, tags = {},
text = "a" text = "a"
} } } } } }
{ "text", { {
tags = {},
text = "c"
} } }
{ "return" } { "return" }
]]-- ]]--

View file

@ -1,6 +1,6 @@
local _={} local _={}
_[1]={"error","can't find function or variable named \"b\"; at test/tests/function scope wrong.ans:4"} _[1]={"error","can't find function or variable named \"b\" in namespace \"function scope wrong.\"; at test/tests/function scope wrong.ans:4"}
return {_[1]} return {_[1]}
--[[ --[[
{ "error", "can't find function or variable named \"b\"; at test/tests/function scope wrong.ans:4" } { "error", "can't find function or variable named \"b\" in namespace \"function scope wrong.\"; at test/tests/function scope wrong.ans:4" }
]]-- ]]--