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.
`$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
`&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 bootscript = require(anselme_root.."stdlib.bootscript")
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
local function list_directory(path)
@ -168,7 +169,7 @@ local interpreter_methods = {
local line = self.state.interpreter.running_line
local namespace = self:current_namespace()
-- 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
local r, e = self.vm:run(exp)
if not r then return "error", e end
@ -235,7 +236,7 @@ local interpreter_methods = {
end
-- parse
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
-- run
local r, e
@ -267,7 +268,7 @@ local interpreter_methods = {
end
-- parse
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
-- run
local co = coroutine.create(function()
@ -586,11 +587,15 @@ local vm_mt = {
--- Save script state.
-- See `vm:load`.
--
-- Returns save data.
-- Returns save data in case of success.
--
-- Returns nil, error message in case of error.
save = function(self)
local vars = {}
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
end
end
@ -608,11 +613,11 @@ local vm_mt = {
--
-- 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)
if #self.state.queued_lines > 0 then
local r, e = postparse(self.state)
if not r then return r, e end
if not r then return nil, e end
end
return self
end,
@ -653,7 +658,7 @@ local vm_mt = {
if not s then return s, e end
--
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
-- interpreter state
local interpreter

View file

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

View file

@ -7,7 +7,7 @@ local copy
local function random_identifier()
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))
end
return r
@ -94,12 +94,40 @@ common = {
end,
--- mark a value as constant, recursively affecting all the potentially mutable subvalues
mark_constant = function(v)
if atypes[v.type] and atypes[v.type].mark_constant then
atypes[v.type].mark_constant(v)
return assert(common.traverse(v, function(v)
if v.hash_id then v.hash_id = nil end -- no longer need to compare by id
else
error(("don't know how to mark type %s as constant"):format(v.type))
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
error(("don't know how to traverse type %s"):format(v.type))
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
-- if you're sure the variable has already been evaluated, use state.variables[fqm] directly
@ -138,6 +166,13 @@ common = {
return nil, ("%s; while assigning value to variable %q"):format(e, name)
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
local s, e = common.check_constraint(state, name, val)
if not s then
@ -220,7 +255,8 @@ common = {
--- returns true if a variable should be persisted on save
-- 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.
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
end,
--- check truthyness of an anselme value

View file

@ -495,7 +495,7 @@ local function eval(state, exp)
ret, e = run(state, fn.child)
-- resume at last checkpoint
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
ret, e = eval(state, expr)
end
@ -567,6 +567,11 @@ local function eval(state, exp)
type = "event buffer",
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)
elseif exp.type == "value passthrough" then
return exp.value

View file

@ -108,3 +108,7 @@ Disadvantages:
TODO: write a translation guide/simplify translation process
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
-- expr
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 rem:match("^%s*}") then return nil, ("expected closing } at end of expression before %q"):format(rem) end
-- wrap in format() call
@ -213,7 +213,7 @@ common = {
end
-- binop expression at the end of the text
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
return exp, rem
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
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 = {
[1] = {},
[2] = {},
[3] = {},
[3] = { "$" },
[4] = {},
[5] = {},
[6] = {},
@ -81,11 +81,22 @@ local function get_text_in_litteral(s, start_pos)
end
return d, r
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
-- return expr, remaining if success
-- 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*(.*)$")
current_priority = current_priority or 0
if not operating_on then
@ -95,7 +106,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
if not d then
d, r = s:match("^(%d+)(.*)$")
end
return expression(r, state, namespace, current_priority, {
return expression(r, state, namespace, source, current_priority, {
type = "number",
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 l, e = parse_text(d, state, namespace, "string") -- parse interpolated expressions
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
elseif s:match("^%%%[") then
local text = s:match("^%%(.*)$")
local v, r = parse_text(text, state, namespace, "text", "#~", true)
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",
text = v
})
@ -121,13 +132,13 @@ local function expression(s, state, namespace, current_priority, operating_on)
local exp
if content:match("[^%s]") then
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 r_paren:match("[^%s]") then return nil, ("unexpected %q at end of parenthesis expression"):format(r_paren) end
else
exp = { type = "nil", value = nil }
end
return expression(r, state, namespace, current_priority, {
return expression(r, state, namespace, source, current_priority, {
type = "parentheses",
expression = exp
})
@ -138,11 +149,11 @@ local function expression(s, state, namespace, current_priority, operating_on)
local exp
if content:match("[^%s]") then
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 r_paren:match("[^%s]") then return nil, ("unexpected %q at end of list parenthesis expression"):format(r_paren) end
end
return expression(r, state, namespace, current_priority, {
return expression(r, state, namespace, source, current_priority, {
type = "list brackets",
expression = exp
})
@ -153,11 +164,11 @@ local function expression(s, state, namespace, current_priority, operating_on)
local exp
if content:match("[^%s]") then
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 r_paren:match("[^%s]") then return nil, ("unexpected %q at end of map parenthesis expression"):format(r_paren) end
end
return expression(r, state, namespace, current_priority, {
return expression(r, state, namespace, source, current_priority, {
type = "map brackets",
expression = exp
})
@ -168,7 +179,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
-- string:value pair shorthand using =
if r:match("^=[^=]") and pair_priority > current_priority then
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
local args = {
type = "list",
@ -181,7 +192,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
-- find compatible variant
local variant, err = find_function(state, namespace, "_=_", args, true)
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
-- 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
@ -193,7 +204,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
if i < #nl then
r = "."..table.concat(nl, ".", i+1, #nl)..r
end
return expression(r, state, namespace, current_priority, {
return expression(r, state, namespace, source, current_priority, {
type = "variable",
name = vfqm
})
@ -207,14 +218,14 @@ local function expression(s, state, namespace, current_priority, operating_on)
if i < #nl then
r = "."..table.concat(nl, ".", i+1, #nl)..r
end
return expression(r, state, namespace, current_priority, {
return expression(r, state, namespace, source, current_priority, {
type = "potential function",
called_name = name,
names = lfnqm
})
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
-- prefix unops
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.."(.*)$")
-- function and variable reference
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 right.type == "potential function" then
return expression(r, state, namespace, current_priority, {
return expression(r, state, namespace, source, current_priority, {
type = "function reference",
names = right.names
})
elseif right.type == "variable" then
return expression(r, state, namespace, current_priority, {
return expression(r, state, namespace, source, current_priority, {
type = "variable reference",
name = right.name,
expression = right
@ -241,16 +252,45 @@ local function expression(s, state, namespace, current_priority, operating_on)
-- find variant
local variant, err = find_function(state, namespace, op.."_", right, true)
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
-- 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
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
-- find variant
local variant, err = find_function(state, namespace, op.."_", right, true)
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
@ -275,7 +315,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
-- get arguments
if content:match("[^%s]") then
local err
args, err = expression(content, state, namespace)
args, err = expression(content, state, namespace, source)
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
end
@ -285,12 +325,12 @@ local function expression(s, state, namespace, current_priority, operating_on)
-- find compatible variant
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
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.
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)
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",
variant = implicit_call_variant,
expression = operating_on
@ -323,7 +363,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
-- get arguments
if content:match("[^%s]") then
local err
args, err = expression(content, state, namespace)
args, err = expression(content, state, namespace, source)
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
end
@ -353,7 +393,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
-- find compatible variant
local variant, err = find_function(state, namespace, name, args, paren_call)
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
elseif op == "." and sright:match("^"..identifier_pattern) then
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)
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
else
local right, r = expression(sright, state, namespace, prio)
local right, r = expression(sright, state, namespace, source, prio)
if right then
-- list constructor (can't do this through a function call since we need to build a list for its arguments)
if op == "," then
return expression(r, state, namespace, current_priority, {
return expression(r, state, namespace, source, current_priority, {
type = "list",
left = operating_on,
right = right
@ -408,18 +448,18 @@ local function expression(s, state, namespace, current_priority, operating_on)
end
-- rewrite function to perform assignment
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
return nil, ("trying to perform assignment on a %s expression"):format(operating_on.type)
end
-- assign to a variable
return expression(r, state, namespace, current_priority, {
return expression(r, state, namespace, source, current_priority, {
type = ":=",
left = operating_on,
right = right
})
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,
left = operating_on,
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)
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
@ -458,12 +498,12 @@ local function expression(s, state, namespace, current_priority, operating_on)
-- remove ! after a previously-assumed implicit function call
if op == "!" and operating_on.type == "function call" and operating_on.implicit_call then
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
else
local variant, err = find_function(state, namespace, "_"..op, operating_on, true)
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
@ -479,19 +519,19 @@ local function expression(s, state, namespace, current_priority, operating_on)
content = content:gsub("^%(", ""):gsub("%)$", "")
-- get arguments
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 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 }
end
local variant, err = find_function(state, namespace, "()", args, true)
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
-- implicit multiplication
if implicit_multiply_priority > current_priority 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
local args = {
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)
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
@ -513,4 +553,6 @@ package.loaded[...] = expression
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
preparse = require((...):gsub("expression$", "preparser"))
return expression

View file

@ -13,7 +13,7 @@ local function parse(state)
for _, param in ipairs(line.params) do
-- get type constraints
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 rem:match("[^%s]") then
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
-- get default value
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 rem:match("[^%s]") then
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
-- assignment argument
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 rem:match("[^%s]") then
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
-- expressions
if line.expression then
local exp, rem = expression(line.expression, state, namespace)
if line.expression and type(line.expression) == "string" then
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 rem:match("[^%s]") then return nil, ("expected end of expression before %q; at %s"):format(rem, line.source) end
line.expression = exp
@ -76,7 +76,7 @@ local function parse(state)
state.variables[line.name].value.expression = line.expression
-- parse constraints
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 rem2:match("[^%s]") then
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
line.text = txt
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
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 = {}
types.lua = {
@ -88,7 +88,7 @@ types.anselme = {
hash = function()
return "nil()"
end,
mark_constant = function() end,
traverse = function() return true end,
},
number = {
format = function(val)
@ -100,7 +100,7 @@ types.anselme = {
hash = function(val)
return ("n(%s)"):format(val)
end,
mark_constant = function() end,
traverse = function() return true end,
},
string = {
format = function(val)
@ -112,7 +112,7 @@ types.anselme = {
hash = function(val)
return ("s(%s)"):format(val)
end,
mark_constant = function() end,
traverse = function() return true end,
},
pair = {
format = function(val)
@ -136,9 +136,12 @@ types.anselme = {
if not v then return v, ve end
return ("p(%s=%s)"):format(k, v)
end,
mark_constant = function(v)
mark_constant(v.value[1])
mark_constant(v.value[2])
traverse = function(val, callback, pertype_callback)
local k, ke = traverse(val[1], callback, pertype_callback)
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,
},
annotated = {
@ -161,9 +164,12 @@ types.anselme = {
if not v then return v, ve end
return ("a(%s::%s)"):format(k, v)
end,
mark_constant = function(v)
mark_constant(v.value[1])
mark_constant(v.value[2])
traverse = function(val, callback, pertype_callback)
local k, ke = traverse(val[1], callback, pertype_callback)
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,
},
list = {
@ -195,12 +201,16 @@ types.anselme = {
end
return ("l(%s)"):format(table.concat(l, ","))
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)
v.constant = true
for _, item in ipairs(v.value) do
mark_constant(item)
end
end
end,
},
map = {
mutable = true,
@ -239,12 +249,17 @@ types.anselme = {
table.sort(l)
return ("m(%s)"):format(table.concat(l, ","))
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)
v.constant = true
for _, val in pairs(v.value) do
mark_constant(val[1])
mark_constant(val[2])
end
update_hashes(v)
end,
},
@ -296,6 +311,13 @@ types.anselme = {
table.sort(attributes)
return ("%%(%s;%s)"):format(val.class, table.concat(attributes, ","))
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)
v.constant = true
end,
@ -312,8 +334,7 @@ types.anselme = {
hash = function(val)
return ("&f(%s)"):format(table.concat(val, ", "))
end,
mark_constant = function() end,
traverse = function() return true end,
},
["variable reference"] = {
format = function(val)
@ -323,9 +344,9 @@ types.anselme = {
hash = function(val)
return ("&v(%s)"):format(val)
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"] = {
format = function(val) -- triggered from subtexts
local v, e = events:write_buffer(anselme.running.state, val)
@ -365,13 +386,25 @@ types.anselme = {
end
return ("eb(%s)"):format(table.concat(l, ","))
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
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"))
local pcommon = require((...):gsub("stdlib%.types$", "parser.common"))
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)
end
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
-- test mode
@ -147,7 +152,7 @@ else
-- simple random to get the same result across lua versions
local prev = 0
local function badrandom(a, b)
prev = (42424242424242 * prev + 242) % 2^32
prev = (15485863 * prev + 11) % 2038074743
return a + prev % (b-a+1)
end
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]={}
_[18]={}
_[17]={}
_[16]={text="a",tags=_[21]}
_[15]={text="b",tags=_[20]}
_[14]={text="b",tags=_[19]}
_[13]={text="c",tags=_[18]}
_[12]={text="c",tags=_[17]}
_[16]={tags=_[21],text="c"}
_[15]={tags=_[20],text="a"}
_[14]={tags=_[19],text="c"}
_[13]={tags=_[18],text="b"}
_[12]={tags=_[17],text="c"}
_[11]={_[16]}
_[10]={_[15]}
_[9]={_[14]}
@ -26,21 +26,21 @@ return {_[1],_[2],_[3],_[4],_[5],_[6]}
tags = {},
text = "c"
} } }
{ "text", { {
tags = {},
text = "b"
} } }
{ "text", { {
tags = {},
text = "c"
} } }
{ "text", { {
tags = {},
text = "b"
} } }
{ "text", { {
tags = {},
text = "b"
} } }
{ "text", { {
tags = {},
text = "a"
} } }
{ "text", { {
tags = {},
text = "c"
} } }
{ "return" }
]]--

View file

@ -1,6 +1,6 @@
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]}
--[[
{ "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" }
]]--