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

Changed a few things

* Integrated # and ~ decorators into the expression system. Created associated operators.
* # and ~ decorators only affect their current line. That's more useful...
* Fix our priority system to evaluate left-to-right instead of right-to-left (if there was a reason why I did it this way initially, I don't remember it so ¯\_(ツ)_/¯)
* a lotta internal changes

Various other small adjustments, see the diff of REFERENCE.md for details.
This commit is contained in:
Étienne Fildadut 2021-11-28 01:43:54 +01:00
parent 14d348bad9
commit f2e74c94c9
31 changed files with 894 additions and 343 deletions

View file

@ -28,7 +28,8 @@ common = {
-- operators not included here:
-- * assignment operators (:=, +=, -=, //=, /=, *=, %=, ^=): handled with its own syntax (function assignment)
-- * list operator (,): is used when calling every functions, sounds like more trouble than it's worth
-- * | and & oprators: are lazy and don't behave like regular functions
-- * |, & and ~ operators: are lazy and don't behave like regular functions
-- * # operator: need to set tag state before evaluating the left arg
-- * . operator: don't behave like regular functions either
";",
"!=", "==", ">=", "<=", "<", ">",
@ -45,7 +46,11 @@ common = {
["\\\\"] = "\\",
["\\\""] = "\"",
["\\n"] = "\n",
["\\t"] = "\t"
["\\t"] = "\t",
["\\~"] = "~",
["\\#"] = "#",
["\\$"] = "$", -- FIXME
["\\{"] = "{"
},
--- escape a string to be used as an exact match pattern
escape = function(str)
@ -116,38 +121,90 @@ common = {
flatten_list = function(list, t)
t = t or {}
if list.type == "list" then
table.insert(t, list.left)
common.flatten_list(list.right, t)
table.insert(t, 1, list.right)
common.flatten_list(list.left, t)
else
table.insert(t, list)
table.insert(t, 1, list)
end
return t
end,
-- parse interpolated expressions in a text
-- * list of strings and expressions
-- allow_subtext (bool) to enable or not [subtext] support
-- if allow_binops is given, if one of the caracters of allow_binops appear unescaped in the text, it will interpreter a binary operator expression
-- * returns a text expression, remaining (if the right expression stop before the end of the text)
-- if allow_binops is not given:
-- * returns a list of strings and expressions (text elements)
-- * nil, err: in case of error
parse_text = function(text, state, namespace)
parse_text = function(text, state, namespace, allow_binops, allow_subtext, in_subtext)
local l = {}
while text:match("[^%{]+") do
local t, e = text:match("^([^%{]*)(.-)$")
local text_exp
local delimiters = ""
if allow_binops then
text_exp = { type = "text", text = l }
delimiters = allow_binops
end
if allow_subtext then
delimiters = delimiters .. "%["
end
if in_subtext then
delimiters = delimiters .. "%]"
end
while text:match(("[^{%s]+"):format(delimiters)) do
local t, r = text:match(("^([^{%s]*)(.-)$"):format(delimiters))
-- text
if t ~= "" then table.insert(l, t) end
if t ~= "" then
-- handle \{ escape: skip to next { until it's not escaped
while t:match("\\$") and r:match(("^[{%s]"):format(delimiters)) do
local t2, r2 = r:match(("^([{%s][^{%s]*)(.-)$"):format(delimiters, delimiters))
t = t:match("^(.-)\\$") .. t2
r = r2
end
-- replace other escape codes
local escaped = t:gsub("\\.", common.string_escapes)
table.insert(l, escaped)
end
-- expr
if e:match("^{") then
local exp, rem = expression(e:gsub("^{", ""), state, namespace)
if r:match("^{") then
local exp, rem = expression(r:gsub("^{", ""), state, namespace)
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
local variant, err = common.find_function_variant(state, namespace, "{}", exp, true)
local variant, err = common.find_function_variant(state, namespace, "{}", { type = "parentheses", expression = exp }, true)
if not variant then return variant, err end
-- add to text
table.insert(l, variant)
text = rem:match("^%s*}(.*)$")
else
-- start subtext
elseif allow_subtext and r:match("^%[") then
local exp, rem = common.parse_text(r:gsub("^%[", ""), state, namespace, allow_binops, allow_subtext, true)
if not exp then return nil, rem end
if not rem:match("^%]") then return nil, ("expected closing ] at end of subtext before %q"):format(rem) end
-- add to text
table.insert(l, exp)
text = rem:match("^%](.*)$")
-- end subtext
elseif in_subtext and r:match("^%]") then
if allow_binops then
return text_exp, r
else
return l
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)
if not exp then return nil, rem end
return exp, rem
elseif r == "" then
break
else
error(("unexpected %q at end of text or string"):format(r))
end
end
return l
if allow_binops then
return text_exp, ""
else
return l
end
end,
-- find compatible function variants from a fully qualified name
-- this functions does not guarantee that functions are fully compatible with the given arguments and only performs a pre-selection without the ones which definitely aren't

View file

@ -1,11 +1,11 @@
local identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text, string_escapes
local identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text
--- binop priority
local binops_prio = {
[1] = { ";" },
[2] = { ":=", "+=", "-=", "//=", "/=", "*=", "%=", "^=" },
[3] = { "," },
[4] = { "|", "&" },
[4] = { "|", "&", "~", "#" },
[5] = { "!=", "==", ">=", "<=", "<", ">" },
[6] = { "+", "-" },
[7] = { "*", "//", "/", "%" },
@ -70,12 +70,6 @@ local function expression(s, state, namespace, current_priority, operating_on)
-- parse interpolated expressions
local l, e = parse_text(d, state, namespace)
if not l then return l, e end
-- escape the string parts
for j, ls in ipairs(l) do
if type(ls) == "string" then
l[j] = ls:gsub("\\.", string_escapes)
end
end
return expression(r, state, namespace, current_priority, {
type = "string",
value = l
@ -191,7 +185,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
else
-- binop
for prio, oplist in ipairs(binops_prio) do
if prio >= current_priority then
if prio > current_priority then
for _, op in ipairs(oplist) do
local escaped = escape(op)
if s:match("^"..escaped) then
@ -275,7 +269,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
left = operating_on,
right = right
})
elseif op == "&" or op == "|" then
elseif op == "&" or op == "|" or op == "~" or op == "#" then
return expression(r, state, namespace, current_priority, {
type = op,
left = operating_on,
@ -287,8 +281,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
local args = {
type = "list",
left = operating_on,
-- wrap in parentheses to avoid appending to argument list if right is a list
right = { type = "parentheses", expression = right }
right = right
}
local variant, err = find_function_variant(state, namespace, op, args, true)
if not variant then return variant, err end
@ -318,6 +311,6 @@ end
package.loaded[...] = expression
local common = require((...):gsub("expression$", "common"))
identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text, string_escapes = common.identifier_pattern, common.format_identifier, common.find, common.escape, common.find_function_variant, common.parse_text, common.string_escapes
identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text = common.identifier_pattern, common.format_identifier, common.find, common.escape, common.find_function_variant, common.parse_text
return expression

View file

@ -55,10 +55,11 @@ local function parse(state)
state.variables[line.fqm].value.expression = line.expression
end
end
-- text
-- text (text & choice lines)
if line.text then
local txt, err = parse_text(line.text, state, namespace)
if err then return nil, ("%s; at %s"):format(err, line.source) end
local txt, err = parse_text(line.text, state, namespace, "#~", true)
if not txt then return nil, ("%s; at %s"):format(err, line.source) 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
end
state.queued_lines[i] = nil

View file

@ -325,18 +325,8 @@ local function transform_indented(indented)
if l.content:match("^%(") then
table.remove(indented, i)
else
-- condition decorator
if l.content:match("^.-[^~]%~[^#~$]-$") then
local decorator
l.content, decorator = l.content:match("^(..-)(%~[^#~$]-)$")
indented[i] = { content = decorator, source = l.source, children = { l } }
-- tag decorator
elseif l.content:match("^..-%#[^#~$]-$") then
local decorator
l.content, decorator = l.content:match("^(..-)(%#[^#~$]-)$")
indented[i] = { content = decorator, source = l.source, children = { l } }
-- function decorator
elseif l.content:match("^..-%$[^#~$]-$") then
if l.content:match("^.-[^\\]%$[^#~$]-$") then -- FIXME
local name
l.content, name = l.content:match("^(..-)%$([^#~$]-)$")
indented[i] = { content = "~"..name, source = l.source }