diff --git a/LANGUAGE.md b/LANGUAGE.md index c3f68c3..f5e0ce4 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -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. diff --git a/anselme.lua b/anselme.lua index b2b97d8..04edc6d 100644 --- a/anselme.lua +++ b/anselme.lua @@ -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 diff --git a/anselme.md b/anselme.md index afea7a6..c8f6b54 100644 --- a/anselme.md +++ b/anselme.md @@ -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 (...) diff --git a/interpreter/common.lua b/interpreter/common.lua index 0d06df8..be82235 100644 --- a/interpreter/common.lua +++ b/interpreter/common.lua @@ -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,13 +94,41 @@ 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 + 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 mark type %s as constant"):format(v.type)) + 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 -- return var @@ -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 diff --git a/interpreter/expression.lua b/interpreter/expression.lua index 87fcd0d..8497d2d 100644 --- a/interpreter/expression.lua +++ b/interpreter/expression.lua @@ -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 diff --git a/notes.txt b/notes.txt index c1c621e..af7a52b 100644 --- a/notes.txt +++ b/notes.txt @@ -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. diff --git a/parser/common.lua b/parser/common.lua index 4c2aff5..c424575 100644 --- a/parser/common.lua +++ b/parser/common.lua @@ -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 diff --git a/parser/expression.lua b/parser/expression.lua index 9991cf8..b59ee1a 100644 --- a/parser/expression.lua +++ b/parser/expression.lua @@ -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 diff --git a/parser/postparser.lua b/parser/postparser.lua index 85f2463..2a89ba9 100644 --- a/parser/postparser.lua +++ b/parser/postparser.lua @@ -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 - return true end package.loaded[...] = parse diff --git a/stdlib/types.lua b/stdlib/types.lua index 2600eb6..5d9b9cb 100644 --- a/stdlib/types.lua +++ b/stdlib/types.lua @@ -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 diff --git a/test/run.lua b/test/run.lua index a3008f5..e407cb5 100644 --- a/test/run.lua +++ b/test/run.lua @@ -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) diff --git a/test/tests/anonymous function.ans b/test/tests/anonymous function.ans new file mode 100644 index 0000000..aa1e0eb --- /dev/null +++ b/test/tests/anonymous function.ans @@ -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 diff --git a/test/tests/anonymous function.lua b/test/tests/anonymous function.lua new file mode 100644 index 0000000..0300588 --- /dev/null +++ b/test/tests/anonymous function.lua @@ -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" } +]]-- \ No newline at end of file diff --git a/test/tests/function random.lua b/test/tests/function random.lua index 96758e4..74a21ca 100644 --- a/test/tests/function random.lua +++ b/test/tests/function random.lua @@ -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" } ]]-- \ No newline at end of file diff --git a/test/tests/function scope wrong.lua b/test/tests/function scope wrong.lua index 488a4bf..cdbda48 100644 --- a/test/tests/function scope wrong.lua +++ b/test/tests/function scope wrong.lua @@ -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" } ]]-- \ No newline at end of file