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:
parent
14d348bad9
commit
f2e74c94c9
31 changed files with 894 additions and 343 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue