mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-28 00:59:31 +00:00
Anselme v2.0.0-alpha rewrite
Woke up and felt like changing a couple things. It's actually been worked on for a while, little at a time... The goal was to make the language and implementation much simpler. Well I don't know if it really ended up being simpler but it sure is more robust. Main changes: * proper first class functions and closures supports! proper scoping rules! no more namespace shenanigans! * everything is an expression, no more statements! make the implementation both simpler and more complex, but it's much more consistent now! the syntax has massively changed as a result though. * much more organized and easy to modify codebase: one file for each AST node, no more random fields or behavior set by some random node exceptionally, everything should now follow the same API defined in ast.abstract.Node Every foundational feature should be implemented right now. The vast majority of things that were possible in v2 are possible now; some things aren't, but that's usually because v2 is a bit more sane. The main missing things before a proper release are tests and documentation. There's a few other things that might be implemented later, see the ideas.md file.
This commit is contained in:
parent
2ff494d108
commit
fe351b5ca4
484 changed files with 7099 additions and 18084 deletions
38
parser/Source.lua
Normal file
38
parser/Source.lua
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
local class = require("class")
|
||||
|
||||
local Source
|
||||
Source = class {
|
||||
name = "?",
|
||||
line = -1,
|
||||
position = -1,
|
||||
|
||||
init = function(self, name, line, position)
|
||||
self.name = name
|
||||
self.line = line
|
||||
self.position = position
|
||||
end,
|
||||
increment = function(self, n, ...)
|
||||
self.position = self.position + n
|
||||
end,
|
||||
count = function(self, capture, ...)
|
||||
self:increment(utf8.len(capture))
|
||||
return capture, ...
|
||||
end,
|
||||
consume = function(self, capture, ...)
|
||||
self:increment(utf8.len(capture))
|
||||
return ...
|
||||
end,
|
||||
|
||||
clone = function(self)
|
||||
return Source:new(self.name, self.line, self.position)
|
||||
end,
|
||||
set = function(self, other)
|
||||
self.name, self.line, self.position = other.name, other.line, other.position
|
||||
end,
|
||||
|
||||
__tostring = function(self)
|
||||
return ("%s:%s:%s"):format(self.name, self.line, self.position)
|
||||
end
|
||||
}
|
||||
|
||||
return Source
|
||||
65
parser/code_to_tree.lua
Normal file
65
parser/code_to_tree.lua
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
--- transform raw code string into a nested tree of lines
|
||||
|
||||
local Source = require("parser.Source")
|
||||
|
||||
local function indented_to_tree(indented)
|
||||
local tree = {}
|
||||
local current_parent = tree
|
||||
local current_level = 0
|
||||
local last_line_empty = nil
|
||||
|
||||
for _, l in ipairs(indented) do
|
||||
-- indentation of empty line is determined using the next line
|
||||
-- (consecutive empty lines are merged into one)
|
||||
if l.content == "" then
|
||||
last_line_empty = l
|
||||
else
|
||||
-- raise indentation
|
||||
if l.level > current_level then
|
||||
if #current_parent == 0 then -- can't add children to nil
|
||||
error(("invalid indentation; at %s"):format(l.source))
|
||||
end
|
||||
current_parent = current_parent[#current_parent]
|
||||
current_level = l.level
|
||||
-- lower indentation
|
||||
elseif l.level < current_level then
|
||||
current_parent = tree
|
||||
current_level = 0
|
||||
while current_level < l.level do -- find correct level starting back from the root
|
||||
current_parent = current_parent[#current_parent]
|
||||
current_level = current_parent[1].level
|
||||
end
|
||||
if current_level ~= l.level then
|
||||
error(("invalid indentation; at %s"):format(l.source))
|
||||
end
|
||||
end
|
||||
-- add line
|
||||
if last_line_empty then
|
||||
last_line_empty.level = current_level
|
||||
table.insert(current_parent, last_line_empty)
|
||||
last_line_empty = nil
|
||||
end
|
||||
table.insert(current_parent, l)
|
||||
end
|
||||
end
|
||||
|
||||
return tree
|
||||
end
|
||||
|
||||
local function code_to_indented(code, source_name)
|
||||
local indented = {}
|
||||
|
||||
local i = 1
|
||||
for line in (code.."\n"):gmatch("(.-)\n") do
|
||||
local indent, rem = line:match("^(%s*)(.-)$")
|
||||
local indent_len = utf8.len(indent)
|
||||
table.insert(indented, { level = indent_len, content = rem, source = Source:new(source_name, i, 1+indent_len) })
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
return indented
|
||||
end
|
||||
|
||||
return function(code, source_name)
|
||||
return indented_to_tree(code_to_indented(code, source_name or "?"))
|
||||
end
|
||||
|
|
@ -1,342 +0,0 @@
|
|||
local expression
|
||||
|
||||
local escapeCache = {}
|
||||
|
||||
local common
|
||||
|
||||
--- rewrite name to use defined aliases (under namespace only)
|
||||
-- namespace should not contain aliases
|
||||
-- returns the final fqm
|
||||
local replace_aliases = function(aliases, namespace, name)
|
||||
local name_list = common.split(name)
|
||||
local prefix = namespace
|
||||
for i=1, #name_list, 1 do -- search alias for each part of the fqm
|
||||
local n = ("%s%s%s"):format(prefix, prefix == "" and "" or ".", name_list[i])
|
||||
if aliases[n] then
|
||||
prefix = aliases[n]
|
||||
else
|
||||
prefix = n
|
||||
end
|
||||
end
|
||||
return prefix
|
||||
end
|
||||
|
||||
local disallowed_set = ("~`^+-=<>/[]*{}|\\_!?,;:()\"@&$#%"):gsub("[^%w]", "%%%1")
|
||||
|
||||
common = {
|
||||
--- valid identifier pattern
|
||||
identifier_pattern = "%s*[^0-9%s"..disallowed_set.."][^"..disallowed_set.."]*",
|
||||
-- names allowed for a function that aren't valid identifiers, mainly for overloading operators
|
||||
special_functions_names = {
|
||||
-- operators not included here and why:
|
||||
-- * 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 ~ operators: are lazy and don't behave like regular functions
|
||||
-- * # operator: need to set tag state _before_ evaluating the left arg
|
||||
|
||||
-- prefix unop
|
||||
"-_", "!_",
|
||||
"&_",
|
||||
-- binop
|
||||
"_;_",
|
||||
"_=_", "_:_",
|
||||
"_!=_", "_==_", "_>=_", "_<=_", "_<_", "_>_",
|
||||
"_+_", "_-_",
|
||||
"_*_", "_//_", "_/_", "_%_",
|
||||
"_::_",
|
||||
"_^_",
|
||||
"_!_",
|
||||
"_._",
|
||||
-- suffix unop
|
||||
"_;",
|
||||
"_!",
|
||||
-- special
|
||||
"()",
|
||||
"{}"
|
||||
},
|
||||
-- escapement code and their value in strings
|
||||
-- only includes the "special" escape codes, as the generic \. -> . is handled by default in parse_text
|
||||
-- I don't think there's a point in supporting form feed, carriage return, and other printer and terminal related codes
|
||||
string_escapes = {
|
||||
["\\\\"] = "\\",
|
||||
["\\n"] = "\n",
|
||||
["\\t"] = "\t",
|
||||
},
|
||||
-- list of possible injections and their associated name in vm.state.inject
|
||||
injections = {
|
||||
["function start"] = "function_start", ["function end"] = "function_end", ["function return"] = "function_return",
|
||||
["scoped function start"] = "scoped_function_start", ["scoped function end"] = "scoped_function_end", ["scoped function return"] = "scoped_function_return",
|
||||
["checkpoint start"] = "checkpoint_start", ["checkpoint end"] = "checkpoint_end",
|
||||
["class start"] = "class_start", ["class end"] = "class_end"
|
||||
},
|
||||
--- escape a string to be used as an exact match pattern
|
||||
escape = function(str)
|
||||
if not escapeCache[str] then
|
||||
escapeCache[str] = str:gsub("[^%w]", "%%%1")
|
||||
end
|
||||
return escapeCache[str]
|
||||
end,
|
||||
--- trim a string by removing whitespace at the start and end
|
||||
trim = function(str)
|
||||
return str:match("^%s*(.-)%s*$")
|
||||
end,
|
||||
--- split a string separated by .
|
||||
split = function(str)
|
||||
local address = {}
|
||||
for name in (str.."."):gmatch("(.-)%.") do
|
||||
table.insert(address, name)
|
||||
end
|
||||
return address
|
||||
end,
|
||||
--- find a variable/function in a list, going up through the namespace hierarchy
|
||||
-- will apply aliases
|
||||
-- returns value, fqm in case of success
|
||||
-- returns nil, err in case of error
|
||||
find = function(aliases, list, namespace, name)
|
||||
if namespace ~= "" then
|
||||
local ns = common.split(namespace:gsub("%.$", ""))
|
||||
for i=#ns, 1, -1 do
|
||||
local current_namespace = table.concat(ns, ".", 1, i)
|
||||
local fqm = replace_aliases(aliases, current_namespace, name)
|
||||
if list[fqm] then
|
||||
return list[fqm], fqm
|
||||
end
|
||||
end
|
||||
end
|
||||
-- root namespace
|
||||
name = replace_aliases(aliases, "", name)
|
||||
if list[name] then
|
||||
return list[name], name
|
||||
end
|
||||
return nil, ("can't find %q in namespace %s"):format(name, namespace)
|
||||
end,
|
||||
--- same as find, but return a list of every encoutered possibility
|
||||
-- returns a list of fqm
|
||||
find_all = function(aliases, list, namespace, name)
|
||||
local l = {}
|
||||
if namespace ~= "" then
|
||||
local ns = common.split(namespace:gsub("%.$", ""))
|
||||
for i=#ns, 1, -1 do
|
||||
local current_namespace = table.concat(ns, ".", 1, i)
|
||||
local fqm = replace_aliases(aliases, current_namespace, name)
|
||||
if list[fqm] then
|
||||
table.insert(l, fqm)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- root namespace
|
||||
name = replace_aliases(aliases, "", name)
|
||||
if list[name] then
|
||||
table.insert(l, name)
|
||||
end
|
||||
return l
|
||||
end,
|
||||
--- transform an identifier into a clean version (trim each part)
|
||||
format_identifier = function(identifier)
|
||||
local r = identifier:gsub("[^%.]+", function(str)
|
||||
return common.trim(str)
|
||||
end)
|
||||
return r
|
||||
end,
|
||||
--- flatten a nested list expression into a list of expressions
|
||||
flatten_list = function(list, t)
|
||||
t = t or {}
|
||||
if list.type == "list" then
|
||||
table.insert(t, 1, list.right)
|
||||
common.flatten_list(list.left, t)
|
||||
else
|
||||
table.insert(t, 1, list)
|
||||
end
|
||||
return t
|
||||
end,
|
||||
-- parse interpolated expressions in a text
|
||||
-- type sets the type of the returned expression (text is in text field)
|
||||
-- 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 an expression with given type (string by default) and as a value a list of strings and expressions (text elements)
|
||||
-- * if allow_binops is given, also returns remaining string (if the right expression stop before the end of the text)
|
||||
-- * nil, err: in case of error
|
||||
parse_text = function(text, state, namespace, type, allow_binops, allow_subtext, in_subtext)
|
||||
local l = {}
|
||||
local text_exp = { type = type, text = l }
|
||||
local delimiters = ""
|
||||
if allow_binops then
|
||||
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
|
||||
-- handle \{ and binop 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 .. t2 -- don't need to remove \ as it will be stripped with other escapes codes 3 lines later
|
||||
r = r2
|
||||
end
|
||||
-- replace escape codes
|
||||
local escaped = t:gsub("\\.", function(escape)
|
||||
return common.string_escapes[escape] or escape:match("^\\(.)$")
|
||||
end)
|
||||
table.insert(l, escaped)
|
||||
end
|
||||
-- expr
|
||||
if r:match("^{") then
|
||||
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
|
||||
local variant, err = common.find_function(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*}(.*)$")
|
||||
-- start subtext
|
||||
elseif allow_subtext and r:match("^%[") then
|
||||
local exp, rem = common.parse_text(r:gsub("^%[", ""), state, namespace, "text", "#~", 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 text_exp
|
||||
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, "text binop suffix", 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
|
||||
if allow_binops then
|
||||
return text_exp, ""
|
||||
else
|
||||
return text_exp
|
||||
end
|
||||
end,
|
||||
-- find a list of compatible function variants from a fully qualified name
|
||||
-- this functions does not guarantee that the returned variants are fully compatible with the given arguments and only performs a pre-selection without the ones which definitely aren't
|
||||
-- * list of compatible variants: if success
|
||||
-- * nil, err: if error
|
||||
find_function_variant_from_fqm = function(fqm, state, arg)
|
||||
local err = ("compatible function %q variant not found"):format(fqm)
|
||||
local func = state.functions[fqm]
|
||||
local args = arg and common.flatten_list(arg) or {}
|
||||
local variants = {}
|
||||
for _, variant in ipairs(func) do
|
||||
local ok = true
|
||||
-- arity check
|
||||
-- note: because named args can't be predicted in advance (pairs need to be evaluated), this arity check isn't enough to guarantee a compatible arity
|
||||
-- (e.g., if there's 3 required args but only provide 3 optional arg in a call, will pass)
|
||||
local min, max = variant.arity[1], variant.arity[2]
|
||||
if #args < min or #args > max then
|
||||
if min == max then
|
||||
err = ("function %q expected %s arguments but received %s"):format(fqm, min, #args)
|
||||
else
|
||||
err = ("function %q expected between %s and %s arguments but received %s"):format(fqm, min, max, #args)
|
||||
end
|
||||
ok = false
|
||||
end
|
||||
-- done
|
||||
if ok then
|
||||
table.insert(variants, variant)
|
||||
end
|
||||
end
|
||||
if #variants > 0 then
|
||||
return variants
|
||||
else
|
||||
return nil, err
|
||||
end
|
||||
end,
|
||||
--- same as find_function_variant_from_fqm, but will search every function from the current namespace and up using find
|
||||
-- returns directly a function expression in case of success
|
||||
-- return nil, err otherwise
|
||||
find_function = function(state, namespace, name, arg, paren_call, implicit_call)
|
||||
local l = common.find_all(state.aliases, state.functions, namespace, name)
|
||||
return common.find_function_from_list(state, namespace, name, l, arg, paren_call, implicit_call)
|
||||
end,
|
||||
--- same as find_function, but take a list of already found ffqm instead of searching
|
||||
find_function_from_list = function(state, namespace, name, names, arg, paren_call, implicit_call)
|
||||
local variants = {}
|
||||
local err = ("compatible function %q variant not found"):format(name)
|
||||
local l = common.find_all(state.aliases, state.functions, namespace, name)
|
||||
for _, ffqm in ipairs(l) do
|
||||
local found
|
||||
found, err = common.find_function_variant_from_fqm(ffqm, state, arg)
|
||||
if found then
|
||||
for _, v in ipairs(found) do
|
||||
table.insert(variants, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
if #variants > 0 then
|
||||
return {
|
||||
type = "function call",
|
||||
called_name = name, -- name of the called function
|
||||
paren_call = paren_call, -- was call with parantheses?
|
||||
implicit_call = implicit_call, -- was call implicitely (no ! or parentheses)?
|
||||
variants = variants, -- list of potential variants
|
||||
argument = { -- wrap everything in a list literal to simplify later things (otherwise may be nil, single value, list constructor)
|
||||
type = "map brackets",
|
||||
expression = arg
|
||||
}
|
||||
}
|
||||
else
|
||||
return nil, err -- returns last error
|
||||
end
|
||||
end,
|
||||
-- returns the function's signature text
|
||||
signature = function(fn)
|
||||
if fn.signature then return fn.signature end
|
||||
local signature
|
||||
local function make_param_signature(p)
|
||||
local sig = p.name
|
||||
if p.vararg then
|
||||
sig = sig .. "..."
|
||||
end
|
||||
if p.alias then
|
||||
sig = sig .. ":" .. p.alias
|
||||
end
|
||||
if p.type_constraint then
|
||||
sig = sig .. "::" .. p.type_constraint
|
||||
end
|
||||
if p.default then
|
||||
sig = sig .. "=" .. p.default
|
||||
end
|
||||
return sig
|
||||
end
|
||||
local arg_sig = {}
|
||||
for j, p in ipairs(fn.params) do
|
||||
arg_sig[j] = make_param_signature(p)
|
||||
end
|
||||
if fn.assignment then
|
||||
signature = ("%s(%s) := %s"):format(fn.name, table.concat(arg_sig, ", "), make_param_signature(fn.assignment))
|
||||
else
|
||||
signature = ("%s(%s)"):format(fn.name, table.concat(arg_sig, ", "))
|
||||
end
|
||||
return signature
|
||||
end,
|
||||
-- same as signature, format the signature for displaying to the user and add some debug information
|
||||
pretty_signature = function(fn)
|
||||
return ("%s (at %s)"):format(common.signature(fn), fn.source)
|
||||
end,
|
||||
}
|
||||
|
||||
package.loaded[...] = common
|
||||
expression = require((...):gsub("common$", "expression"))
|
||||
|
||||
return common
|
||||
|
|
@ -1,558 +0,0 @@
|
|||
local identifier_pattern, format_identifier, find, escape, find_function, parse_text, find_all, split, find_function_from_list, preparse
|
||||
|
||||
--- binop priority
|
||||
local binops_prio = {
|
||||
[1] = { ";" },
|
||||
[2] = { ":=", "+=", "-=", "//=", "/=", "*=", "%=", "^=" },
|
||||
[3] = { "," },
|
||||
[4] = { "~?", "~", "#" },
|
||||
[5] = { "=", ":" },
|
||||
[6] = { "|", "&" },
|
||||
[7] = { "!=", "==", ">=", "<=", "<", ">" },
|
||||
[8] = { "+", "-" },
|
||||
[9] = { "*", "//", "/", "%" },
|
||||
[10] = { "::" },
|
||||
[11] = {}, -- unary operators
|
||||
[12] = { "^" },
|
||||
[13] = { "!" },
|
||||
[14] = {},
|
||||
[15] = { "." }
|
||||
}
|
||||
local call_priority = 13 -- note: higher priority operators will have to deal with potential functions expressions
|
||||
local implicit_call_priority = 12.5 -- just below call priority so explicit calls automatically take precedence
|
||||
local pair_priority = 5
|
||||
local implicit_multiply_priority = 9.5 -- just above / so 1/2x gives 1/(2x)
|
||||
-- unop priority
|
||||
local prefix_unops_prio = {
|
||||
[1] = {},
|
||||
[2] = {},
|
||||
[3] = { "$" },
|
||||
[4] = {},
|
||||
[5] = {},
|
||||
[6] = {},
|
||||
[7] = {},
|
||||
[8] = {},
|
||||
[9] = {},
|
||||
[10] = {},
|
||||
[11] = { "-", "!" },
|
||||
[12] = {},
|
||||
[13] = {},
|
||||
[14] = { "&" },
|
||||
[15] = {}
|
||||
}
|
||||
local suffix_unops_prio = {
|
||||
[1] = { ";" },
|
||||
[2] = {},
|
||||
[3] = {},
|
||||
[4] = {},
|
||||
[5] = {},
|
||||
[6] = {},
|
||||
[7] = {},
|
||||
[8] = {},
|
||||
[9] = {},
|
||||
[10] = {},
|
||||
[11] = {},
|
||||
[12] = {},
|
||||
[13] = { "!" },
|
||||
[14] = {},
|
||||
[15] = {}
|
||||
}
|
||||
|
||||
local function get_text_in_litteral(s, start_pos)
|
||||
local d, r
|
||||
-- find end of string
|
||||
start_pos = start_pos or 2
|
||||
local i = start_pos
|
||||
while true do
|
||||
local skip
|
||||
skip = s:match("^[^%\\\"]-%b{}()", i) -- skip interpolated expressions
|
||||
if skip then i = skip end
|
||||
skip = s:match("^[^%\\\"]-\\.()", i) -- skip escape codes (need to skip every escape code in order to correctly parse \\": the " is not escaped)
|
||||
if skip then i = skip end
|
||||
if not skip then -- nothing skipped
|
||||
local end_pos = s:match("^[^%\"]-\"()", i) -- search final double quote
|
||||
if end_pos then
|
||||
d, r = s:sub(start_pos, end_pos-2), s:sub(end_pos)
|
||||
break
|
||||
else
|
||||
return nil, ("expected \" to finish string near %q"):format(s:sub(i))
|
||||
end
|
||||
end
|
||||
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, source, current_priority, operating_on)
|
||||
s = s:match("^%s*(.*)$")
|
||||
current_priority = current_priority or 0
|
||||
if not operating_on then
|
||||
-- number
|
||||
if s:match("^%d*%.%d+") or s:match("^%d+") then
|
||||
local d, r = s:match("^(%d*%.%d+)(.*)$")
|
||||
if not d then
|
||||
d, r = s:match("^(%d+)(.*)$")
|
||||
end
|
||||
return expression(r, state, namespace, source, current_priority, {
|
||||
type = "number",
|
||||
value = tonumber(d)
|
||||
})
|
||||
-- string
|
||||
elseif s:match("^%\"") then
|
||||
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, 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, source, current_priority, {
|
||||
type = "text buffer",
|
||||
text = v
|
||||
})
|
||||
-- paranthesis
|
||||
elseif s:match("^%b()") then
|
||||
local content, r = s:match("^(%b())(.*)$")
|
||||
content = content:gsub("^%(", ""):gsub("%)$", "")
|
||||
local exp
|
||||
if content:match("[^%s]") then
|
||||
local r_paren
|
||||
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, source, current_priority, {
|
||||
type = "parentheses",
|
||||
expression = exp
|
||||
})
|
||||
-- list parenthesis
|
||||
elseif s:match("^%b[]") then
|
||||
local content, r = s:match("^(%b[])(.*)$")
|
||||
content = content:gsub("^%[", ""):gsub("%]$", "")
|
||||
local exp
|
||||
if content:match("[^%s]") then
|
||||
local r_paren
|
||||
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, source, current_priority, {
|
||||
type = "list brackets",
|
||||
expression = exp
|
||||
})
|
||||
-- map parenthesis
|
||||
elseif s:match("^%b{}") then
|
||||
local content, r = s:match("^(%b{})(.*)$")
|
||||
content = content:gsub("^%{", ""):gsub("%}$", "")
|
||||
local exp
|
||||
if content:match("[^%s]") then
|
||||
local r_paren
|
||||
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, source, current_priority, {
|
||||
type = "map brackets",
|
||||
expression = exp
|
||||
})
|
||||
-- identifier
|
||||
elseif s:match("^"..identifier_pattern) then
|
||||
local name, r = s:match("^("..identifier_pattern..")(.-)$")
|
||||
name = format_identifier(name)
|
||||
-- string:value pair shorthand using =
|
||||
if r:match("^=[^=]") and pair_priority > current_priority then
|
||||
local val
|
||||
val, r = expression(r:match("^=(.*)$"), state, namespace, source, pair_priority)
|
||||
if not val then return val, r end
|
||||
local args = {
|
||||
type = "list",
|
||||
left = {
|
||||
type = "string",
|
||||
text = { name }
|
||||
},
|
||||
right = val
|
||||
}
|
||||
-- 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, 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
|
||||
local nl = split(name)
|
||||
for i=#nl, 1, -1 do
|
||||
local name_prefix = table.concat(nl, ".", 1, i)
|
||||
local var, vfqm = find(state.aliases, state.variables, namespace, name_prefix)
|
||||
if var then
|
||||
if i < #nl then
|
||||
r = "."..table.concat(nl, ".", i+1, #nl)..r
|
||||
end
|
||||
return expression(r, state, namespace, source, current_priority, {
|
||||
type = "variable",
|
||||
name = vfqm
|
||||
})
|
||||
end
|
||||
end
|
||||
-- functions. This is a temporary expression that will either be transformed into a reference by the &_ operator, or an (implicit) function call otherwise.
|
||||
for i=#nl, 1, -1 do
|
||||
local name_prefix = table.concat(nl, ".", 1, i)
|
||||
local lfnqm = find_all(state.aliases, state.functions, namespace, name_prefix)
|
||||
if #lfnqm > 0 then
|
||||
if i < #nl then
|
||||
r = "."..table.concat(nl, ".", i+1, #nl)..r
|
||||
end
|
||||
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 in namespace %q"):format(name, namespace)
|
||||
end
|
||||
-- prefix unops
|
||||
for prio, oplist in ipairs(prefix_unops_prio) do
|
||||
for _, op in ipairs(oplist) do
|
||||
local escaped = escape(op)
|
||||
if s:match("^"..escaped) then
|
||||
local sright = s:match("^"..escaped.."(.*)$")
|
||||
-- function and variable reference
|
||||
if op == "&" then
|
||||
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, source, current_priority, {
|
||||
type = "function reference",
|
||||
names = right.names
|
||||
})
|
||||
elseif right.type == "variable" then
|
||||
return expression(r, state, namespace, source, current_priority, {
|
||||
type = "variable reference",
|
||||
name = right.name,
|
||||
expression = right
|
||||
})
|
||||
else
|
||||
-- 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, 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, 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, source, current_priority, variant)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil, ("no valid expression before %q"):format(s)
|
||||
else
|
||||
-- transform potential function/variable calls into actual calls automatically
|
||||
-- need to do this before every other operator, since once the code finds the next operator it won't go back to check if this applied and assume it
|
||||
-- didn't skip anything since it didn't see any other operator before, even if it's actually higher priority...
|
||||
-- the problems of an implicit operator I guess
|
||||
if implicit_call_priority > current_priority then
|
||||
-- implicit call of a function. Unlike for variables, can't be cancelled since there's not any other value this could return, we don't
|
||||
-- have first class functions here...
|
||||
if operating_on.type == "potential function" then
|
||||
local args, paren_call, implicit_call
|
||||
local r = s
|
||||
if r:match("^%b()") then
|
||||
paren_call = true
|
||||
local content, rem = r:match("^(%b())(.*)$")
|
||||
content = content:gsub("^%(", ""):gsub("%)$", "")
|
||||
r = rem
|
||||
-- get arguments
|
||||
if content:match("[^%s]") then
|
||||
local err
|
||||
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
|
||||
else -- implicit call; will be changed if there happens to be a ! after in the suffix operator code
|
||||
implicit_call = true
|
||||
end
|
||||
-- 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, 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, source, current_priority, {
|
||||
type = "implicit call if reference",
|
||||
variant = implicit_call_variant,
|
||||
expression = operating_on
|
||||
})
|
||||
end
|
||||
end
|
||||
-- binop
|
||||
for prio, oplist in ipairs(binops_prio) do
|
||||
if prio > current_priority then
|
||||
-- cancel implicit call operator if we are handling a binop of higher priority
|
||||
-- see comment a bit above on why the priority handling is stupid for implicit operators
|
||||
local operating_on = operating_on
|
||||
if prio > implicit_call_priority and operating_on.type == "implicit call if reference" then
|
||||
operating_on = operating_on.expression
|
||||
end
|
||||
for _, op in ipairs(oplist) do
|
||||
local escaped = escape(op)
|
||||
if s:match("^"..escaped) then
|
||||
local sright = s:match("^"..escaped.."(.*)$")
|
||||
-- suffix call
|
||||
if op == "!" and sright:match("^"..identifier_pattern) then
|
||||
local name, r = sright:match("^("..identifier_pattern..")(.-)$")
|
||||
name = format_identifier(name)
|
||||
local args, paren_call
|
||||
if r:match("^%b()") then
|
||||
paren_call = true
|
||||
local content, rem = r:match("^(%b())(.*)$")
|
||||
content = content:gsub("^%(", ""):gsub("%)$", "")
|
||||
r = rem
|
||||
-- get arguments
|
||||
if content:match("[^%s]") then
|
||||
local err
|
||||
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
|
||||
end
|
||||
-- add first argument
|
||||
if not args then
|
||||
args = operating_on
|
||||
else
|
||||
if args.type == "list" then -- insert as first element
|
||||
local first_list = args
|
||||
while first_list.left.type == "list" do
|
||||
first_list = first_list.left
|
||||
end
|
||||
first_list.left = {
|
||||
type = "list",
|
||||
left = operating_on,
|
||||
right = first_list.left
|
||||
}
|
||||
else
|
||||
args = {
|
||||
type = "list",
|
||||
left = operating_on,
|
||||
right = args
|
||||
}
|
||||
end
|
||||
end
|
||||
-- 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, source, current_priority, variant)
|
||||
-- namespace
|
||||
elseif op == "." and sright:match("^"..identifier_pattern) then
|
||||
local name, r = sright:match("^("..identifier_pattern..")(.-)$")
|
||||
name = format_identifier(name)
|
||||
-- find variant
|
||||
local args = {
|
||||
type = "list",
|
||||
left = operating_on,
|
||||
right = { type = "string", text = { name } }
|
||||
}
|
||||
local variant, err = find_function(state, namespace, "_._", args, true)
|
||||
if not variant then return variant, err end
|
||||
return expression(r, state, namespace, source, current_priority, variant)
|
||||
-- other binops
|
||||
else
|
||||
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, source, current_priority, {
|
||||
type = "list",
|
||||
left = operating_on,
|
||||
right = right
|
||||
})
|
||||
-- special binops
|
||||
elseif op == ":=" or op == "+=" or op == "-=" or op == "//=" or op == "/=" or op == "*=" or op == "%=" or op == "^=" then
|
||||
-- cancel implicit call on right variable
|
||||
if operating_on.type == "implicit call if reference" then
|
||||
operating_on = operating_on.expression
|
||||
end
|
||||
-- rewrite assignment + arithmetic operators into a normal assignment
|
||||
if op ~= ":=" then
|
||||
local args = {
|
||||
type = "list",
|
||||
left = operating_on,
|
||||
right = right
|
||||
}
|
||||
local variant, err = find_function(state, namespace, "_"..op:match("^(.*)%=$").."_", args, true)
|
||||
if not variant then return variant, err end
|
||||
right = variant
|
||||
end
|
||||
-- assign to a function
|
||||
if operating_on.type == "function call" then
|
||||
-- remove non-assignment functions
|
||||
for i=#operating_on.variants, 1, -1 do
|
||||
if not operating_on.variants[i].assignment then
|
||||
table.remove(operating_on.variants, i)
|
||||
end
|
||||
end
|
||||
if #operating_on.variants == 0 then
|
||||
return nil, ("trying to perform assignment on function %s with no compatible assignment variant"):format(operating_on.called_name)
|
||||
end
|
||||
-- rewrite function to perform assignment
|
||||
operating_on.assignment = right
|
||||
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, 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, source, current_priority, {
|
||||
type = op,
|
||||
left = operating_on,
|
||||
right = right
|
||||
})
|
||||
-- normal binop
|
||||
else
|
||||
-- find variant
|
||||
local args = {
|
||||
type = "list",
|
||||
left = operating_on,
|
||||
right = right
|
||||
}
|
||||
local variant, err = find_function(state, namespace, "_"..op.."_", args, true)
|
||||
if not variant then return variant, err end
|
||||
return expression(r, state, namespace, source, current_priority, variant)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- suffix unop
|
||||
for prio, oplist in ipairs(suffix_unops_prio) do
|
||||
if prio > current_priority then
|
||||
-- cancel implit call operator if we are handling an operator of higher priority
|
||||
-- see comment a bit above on why the priority handling is stupid for implicit operators
|
||||
local operating_on = operating_on
|
||||
if prio > implicit_call_priority and operating_on.type == "implicit call if reference" then
|
||||
operating_on = operating_on.expression
|
||||
end
|
||||
for _, op in ipairs(oplist) do
|
||||
local escaped = escape(op)
|
||||
if s:match("^"..escaped) then
|
||||
local r = s:match("^"..escaped.."(.*)$")
|
||||
-- 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, 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, source, current_priority, variant)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- index / call
|
||||
if call_priority > current_priority and s:match("^%b()") then
|
||||
if operating_on.type == "implicit call if reference" then
|
||||
operating_on = operating_on.expression -- replaced with current call
|
||||
end
|
||||
local args = operating_on
|
||||
local content, r = s:match("^(%b())(.*)$")
|
||||
content = content:gsub("^%(", ""):gsub("%)$", "")
|
||||
-- get arguments
|
||||
if content:match("[^%s]") then
|
||||
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, 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, source, implicit_multiply_priority)
|
||||
if right then
|
||||
local args = {
|
||||
type = "list",
|
||||
left = operating_on,
|
||||
right = right
|
||||
}
|
||||
local variant, err = find_function(state, namespace, "_*_", args, true)
|
||||
if not variant then return variant, err end
|
||||
return expression(r, state, namespace, source, current_priority, variant)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- nothing to operate
|
||||
return operating_on, s
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
57
parser/expression/comment.lua
Normal file
57
parser/expression/comment.lua
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
local primary = require("parser.expression.primary.primary")
|
||||
|
||||
local comment
|
||||
comment = primary {
|
||||
match = function(self, str)
|
||||
return str:match("^%(%(")
|
||||
end,
|
||||
parse = function(self, source, str, limit_pattern)
|
||||
local rem = source:consume(str:match("^(%(%()(.*)$"))
|
||||
|
||||
local content_list = {}
|
||||
while not rem:match("^%)%)") do
|
||||
local content
|
||||
content, rem = rem:match("^([^%(%)]*)(.-)$")
|
||||
|
||||
-- cut the text prematurely at limit_pattern if relevant
|
||||
if limit_pattern and content:match(limit_pattern) then
|
||||
local pos = content:match("()"..limit_pattern) -- limit_pattern can contain $, so can't directly extract with captures
|
||||
content, rem = source:count(content:sub(1, pos-1)), ("))%s%s"):format(content:sub(pos), rem)
|
||||
source:increment(-2)
|
||||
else
|
||||
source:count(content)
|
||||
end
|
||||
|
||||
table.insert(content_list, content)
|
||||
|
||||
-- nested comment
|
||||
if rem:match("^%(%(") then
|
||||
local subcomment
|
||||
subcomment, rem = comment:parse(source, rem, limit_pattern)
|
||||
|
||||
table.insert(content_list, "((")
|
||||
for _, c in ipairs(subcomment) do table.insert(content_list, c) end
|
||||
table.insert(content_list, "))")
|
||||
-- no end token after the comment
|
||||
elseif not rem:match("^%)%)") then
|
||||
-- single ) or (, keep on commentin'
|
||||
if rem:match("^[%)%(]") then
|
||||
local s
|
||||
s, rem = source:count(rem:match("^([%)%(])(.-)$"))
|
||||
table.insert(content_list, s)
|
||||
-- anything other than end-of-line
|
||||
elseif rem:match("[^%s]") then
|
||||
error(("unexpected %q at end of comment"):format(rem), 0)
|
||||
-- consumed everything until end-of-line, close your eyes and imagine the text has been closed
|
||||
else
|
||||
rem = rem .. "))"
|
||||
end
|
||||
end
|
||||
end
|
||||
rem = source:consume(rem:match("^(%)%))(.*)$"))
|
||||
|
||||
return table.concat(content_list, ""), rem
|
||||
end
|
||||
}
|
||||
|
||||
return comment
|
||||
40
parser/expression/contextual/function_parameter.lua
Normal file
40
parser/expression/contextual/function_parameter.lua
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
local primary = require("parser.expression.primary.primary")
|
||||
local identifier = require("parser.expression.primary.identifier")
|
||||
local expression_to_ast = require("parser.expression.to_ast")
|
||||
|
||||
local ast = require("ast")
|
||||
local FunctionParameter = ast.FunctionParameter
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
local assignment_priority = operator_priority["_=_"]
|
||||
local type_check_priority = operator_priority["_::_"]
|
||||
|
||||
return primary {
|
||||
match = function(self, str)
|
||||
return identifier:match(str)
|
||||
end,
|
||||
parse = function(self, source, str, limit_pattern, no_default_value)
|
||||
local source_param = source:clone()
|
||||
|
||||
-- name
|
||||
local ident, rem = identifier:parse(source, str)
|
||||
|
||||
-- type check
|
||||
local type_check
|
||||
if rem:match("^%s*::") then
|
||||
local scheck = source:consume(rem:match("^(%s*::%s*)(.*)$"))
|
||||
type_check, rem = expression_to_ast(source, scheck, limit_pattern, type_check_priority)
|
||||
end
|
||||
|
||||
-- default value
|
||||
local default
|
||||
if not no_default_value then
|
||||
if rem:match("^%s*=") then
|
||||
local sdefault = source:consume(rem:match("^(%s*=%s*)(.*)$"))
|
||||
default, rem = expression_to_ast(source, sdefault, limit_pattern, assignment_priority)
|
||||
end
|
||||
end
|
||||
|
||||
return FunctionParameter:new(ident, default, type_check):set_source(source_param), rem
|
||||
end
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
local function_parameter = require("parser.expression.contextual.function_parameter")
|
||||
|
||||
return function_parameter {
|
||||
parse = function(self, source, str, limit_pattern)
|
||||
return function_parameter:parse(source, str, limit_pattern, true)
|
||||
end
|
||||
}
|
||||
49
parser/expression/contextual/parameter_tuple.lua
Normal file
49
parser/expression/contextual/parameter_tuple.lua
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
local primary = require("parser.expression.primary.primary")
|
||||
local function_parameter = require("parser.expression.contextual.function_parameter")
|
||||
local function_parameter_no_default = require("parser.expression.contextual.function_parameter_no_default")
|
||||
|
||||
local ast = require("ast")
|
||||
local ParameterTuple = ast.ParameterTuple
|
||||
|
||||
return primary {
|
||||
match = function(self, str)
|
||||
return str:match("^%(")
|
||||
end,
|
||||
parse = function(self, source, str, limit_pattern)
|
||||
local source_start = source:clone()
|
||||
local parameters = ParameterTuple:new()
|
||||
local rem = source:consume(str:match("^(%()(.*)$"))
|
||||
|
||||
-- i would LOVE to reuse the regular list parsing code for this, but unfortunately the list parsing code
|
||||
-- itself depends on this and expect this to be available quite early, and it's ANNOYING
|
||||
while not rem:match("^%s*%)") do
|
||||
-- parameter
|
||||
local func_param
|
||||
func_param, rem = function_parameter:expect(source, rem, limit_pattern)
|
||||
|
||||
-- next! comma separator
|
||||
if not rem:match("^%s*%)") then
|
||||
if not rem:match("^%s*,") then
|
||||
error(("unexpected %q at end of argument list"):format(rem), 0)
|
||||
end
|
||||
rem = source:consume(rem:match("^(%s*,)(.*)$"))
|
||||
end
|
||||
|
||||
-- add
|
||||
parameters:insert(func_param)
|
||||
end
|
||||
rem = rem:match("^%s*%)(.*)$")
|
||||
|
||||
-- assigment param
|
||||
if rem:match("^%s*=") then
|
||||
rem = source:consume(rem:match("^(%s*=%s*)(.*)$"))
|
||||
|
||||
local func_param
|
||||
func_param, rem = function_parameter_no_default:expect(source, rem, limit_pattern)
|
||||
|
||||
parameters:insert_assignment(func_param)
|
||||
end
|
||||
|
||||
return parameters:set_source(source_start), rem
|
||||
end
|
||||
}
|
||||
16
parser/expression/primary/block_identifier.lua
Normal file
16
parser/expression/primary/block_identifier.lua
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
local primary = require("parser.expression.primary.primary")
|
||||
|
||||
local ast = require("ast")
|
||||
local Identifier, Call, ArgumentTuple = ast.Identifier, ast.Call, ast.ArgumentTuple
|
||||
|
||||
return primary {
|
||||
match = function(self, str)
|
||||
return str:match("^_")
|
||||
end,
|
||||
|
||||
parse = function(self, source, str)
|
||||
local source_start = source:clone()
|
||||
local rem = source:consume(str:match("^(_)(.-)$"))
|
||||
return Call:new(Identifier:new("_"), ArgumentTuple:new()):set_source(source_start), rem
|
||||
end
|
||||
}
|
||||
205
parser/expression/primary/function_definition.lua
Normal file
205
parser/expression/primary/function_definition.lua
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
local primary = require("parser.expression.primary.primary")
|
||||
local function_parameter_no_default = require("parser.expression.contextual.function_parameter_no_default")
|
||||
local parameter_tuple = require("parser.expression.contextual.parameter_tuple")
|
||||
local identifier = require("parser.expression.primary.identifier")
|
||||
|
||||
local expression_to_ast = require("parser.expression.to_ast")
|
||||
local escape = require("common").escape
|
||||
|
||||
local ast = require("ast")
|
||||
local Symbol, Definition, Function, ParameterTuple = ast.Symbol, ast.Definition, ast.Function, ast.ParameterTuple
|
||||
|
||||
local regular_operators = require("common").regular_operators
|
||||
local prefixes = regular_operators.prefixes
|
||||
local suffixes = regular_operators.suffixes
|
||||
local infixes = regular_operators.infixes
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
-- same as function_parameter_no_default, but allow wrapping in (evenetual) parentheses
|
||||
-- in order to solve some priotity issues (_._ has higher priority than _::_, leading to not being possible to overload it with type filtering without parentheses)
|
||||
local function_parameter_maybe_parenthesis = function_parameter_no_default {
|
||||
match = function(self, str)
|
||||
if str:match("^%(") then
|
||||
return function_parameter_no_default:match(str:match("^%((.*)$"))
|
||||
else
|
||||
return function_parameter_no_default:match(str)
|
||||
end
|
||||
end,
|
||||
parse = function(self, source, str, limit_pattern)
|
||||
if str:match("^%(") then
|
||||
str = source:consume(str:match("^(%()(.*)$"))
|
||||
|
||||
local exp, rem = function_parameter_no_default:parse(source, str, limit_pattern)
|
||||
|
||||
if not rem:match("^%s*%)") then error(("unexpected %q at end of parenthesis"):format(rem), 0) end
|
||||
rem = source:consume(rem:match("^(%s*%))(.-)$"))
|
||||
|
||||
return exp, rem
|
||||
else
|
||||
return function_parameter_no_default:parse(source, str, limit_pattern)
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
|
||||
-- signature type 1: unary prefix
|
||||
-- :$-parameter exp
|
||||
-- returns symbol, parameter_tuple, rem if success
|
||||
-- return nil otherwise
|
||||
local function search_prefix_signature(modifiers, source, str, limit_pattern)
|
||||
for _, pfx in ipairs(prefixes) do
|
||||
local prefix = pfx[1]
|
||||
local prefix_pattern = "%s*"..escape(prefix).."%s*"
|
||||
if str:match("^"..prefix_pattern) then
|
||||
-- operator name
|
||||
local rem = source:consume(str:match("^("..prefix_pattern..")(.*)$"))
|
||||
local symbol = Symbol:new(prefix.."_", modifiers):set_source(source:clone():increment(-1))
|
||||
|
||||
-- parameters
|
||||
local parameter
|
||||
parameter, rem = function_parameter_maybe_parenthesis:expect(source, rem, limit_pattern)
|
||||
|
||||
local parameters = ParameterTuple:new()
|
||||
parameters:insert(parameter)
|
||||
|
||||
return symbol, parameters, rem
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- signature type 2: binary infix
|
||||
-- should be checked before suffix signature
|
||||
-- :$parameterA + parameterB exp
|
||||
-- returns symbol, parameter_tuple, rem if success
|
||||
-- return nil otherwise
|
||||
local function search_infix_signature(modifiers, source, str, limit_pattern)
|
||||
if function_parameter_maybe_parenthesis:match(str) then
|
||||
local src = source:clone() -- operate on clone source since search success is not yet guaranteed
|
||||
local parameter_a, rem = function_parameter_maybe_parenthesis:parse(src, str, limit_pattern)
|
||||
|
||||
local parameters = ParameterTuple:new()
|
||||
parameters:insert(parameter_a)
|
||||
|
||||
for _, ifx in ipairs(infixes) do
|
||||
local infix = ifx[1]
|
||||
local infix_pattern = "%s*"..escape(infix).."%s*"
|
||||
if rem:match("^"..infix_pattern) then
|
||||
-- operator name
|
||||
rem = src:consume(rem:match("^("..infix_pattern..")(.*)$"))
|
||||
local symbol = Symbol:new("_"..infix.."_", modifiers):set_source(src:clone():increment(-1))
|
||||
|
||||
-- parameters
|
||||
if function_parameter_maybe_parenthesis:match(rem) then
|
||||
local parameter_b
|
||||
parameter_b, rem = function_parameter_maybe_parenthesis:parse(src, rem, limit_pattern)
|
||||
|
||||
parameters:insert(parameter_b)
|
||||
|
||||
source:set(src)
|
||||
return symbol, parameters, rem
|
||||
else
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- signature type 3: unary suffix
|
||||
-- :$parameter! exp
|
||||
-- returns symbol, parameter_tuple, rem if success
|
||||
-- return nil otherwise
|
||||
local function search_suffix_signature(modifiers, source, str, limit_pattern)
|
||||
if function_parameter_maybe_parenthesis:match(str) then
|
||||
local src = source:clone() -- operate on clone source since search success is not yet guaranteed
|
||||
local parameter_a, rem = function_parameter_maybe_parenthesis:parse(src, str, limit_pattern)
|
||||
|
||||
local parameters = ParameterTuple:new()
|
||||
parameters:insert(parameter_a)
|
||||
|
||||
for _, sfx in ipairs(suffixes) do
|
||||
local suffix = sfx[1]
|
||||
local suffix_pattern = "%s*"..escape(suffix).."%s*"
|
||||
if rem:match("^"..suffix_pattern) then
|
||||
-- operator name
|
||||
rem = src:count(rem:match("^("..suffix_pattern..")(.*)$"))
|
||||
local symbol = Symbol:new("_"..suffix, modifiers):set_source(src:clone():increment(-1))
|
||||
|
||||
source:set(src)
|
||||
return symbol, parameters, rem
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- signature type 4: regular function
|
||||
-- :$identifier(parameter_tuple, ...) exp
|
||||
-- returns symbol, parameter_tuple, rem if success
|
||||
-- return nil otherwise
|
||||
local function search_function_signature(modifiers, source, str, limit_pattern)
|
||||
if identifier:match(str) then
|
||||
local name_source = source:clone()
|
||||
local name, rem = identifier:parse(source, str, limit_pattern)
|
||||
|
||||
-- name
|
||||
local symbol = name:to_symbol(modifiers):set_source(name_source)
|
||||
|
||||
-- parse eventual parameters
|
||||
local parameters
|
||||
if parameter_tuple:match(rem) then
|
||||
parameters, rem = parameter_tuple:parse(source, rem)
|
||||
else
|
||||
parameters = ParameterTuple:new()
|
||||
end
|
||||
|
||||
return symbol, parameters, rem
|
||||
end
|
||||
end
|
||||
|
||||
return primary {
|
||||
match = function(self, str)
|
||||
return str:match("^%::?[&@]?%$")
|
||||
end,
|
||||
|
||||
parse = function(self, source, str, limit_pattern)
|
||||
local source_start = source:clone()
|
||||
local mod_const, mod_exported, rem = source:consume(str:match("^(%:(:?)([&@]?)%$)(.-)$"))
|
||||
|
||||
-- get modifiers
|
||||
local constant, exported, persistent
|
||||
if mod_const == ":" then constant = true end
|
||||
if mod_exported == "@" then exported = true
|
||||
elseif mod_exported == "&" then persistent = true end
|
||||
local modifiers = { constant = constant, exported = exported, persistent = persistent }
|
||||
|
||||
-- search for a valid signature
|
||||
local symbol, parameters
|
||||
local s, p, r = search_prefix_signature(modifiers, source, rem, limit_pattern)
|
||||
if s then symbol, parameters, rem = s, p, r
|
||||
else
|
||||
s, p, r = search_infix_signature(modifiers, source, rem, limit_pattern)
|
||||
if s then symbol, parameters, rem = s, p, r
|
||||
else
|
||||
s, p, r = search_suffix_signature(modifiers, source, rem, limit_pattern)
|
||||
if s then symbol, parameters, rem = s, p, r
|
||||
else
|
||||
s, p, r = search_function_signature(modifiers, source, rem, limit_pattern)
|
||||
if s then symbol, parameters, rem = s, p, r end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- done
|
||||
if symbol then
|
||||
-- parse expression
|
||||
local right
|
||||
s, right, rem = pcall(expression_to_ast, source, rem, limit_pattern, operator_priority["$_"])
|
||||
if not s then error(("invalid expression after unop %q: %s"):format(self.operator, right), 0) end
|
||||
|
||||
-- return function
|
||||
local fn = Function:new(parameters, right):set_source(source_start)
|
||||
return Definition:new(symbol, fn):set_source(source_start), rem
|
||||
end
|
||||
end
|
||||
}
|
||||
40
parser/expression/primary/identifier.lua
Normal file
40
parser/expression/primary/identifier.lua
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
local primary = require("parser.expression.primary.primary")
|
||||
|
||||
local Identifier = require("ast.Identifier")
|
||||
|
||||
local disallowed_set = (".~`^+-=<>/[]*{}|\\_!?,;:()\"@&$#%"):gsub("[^%w]", "%%%1")
|
||||
local identifier_pattern = "%s*[^0-9%s'"..disallowed_set.."][^"..disallowed_set.."]*"
|
||||
|
||||
local common = require("common")
|
||||
local trim, escape = common.trim, common.escape
|
||||
|
||||
-- for operator identifiers
|
||||
local regular_operators = require("common").regular_operators
|
||||
local operators = {}
|
||||
for _, prefix in ipairs(regular_operators.prefixes) do table.insert(operators, prefix[1].."_") end
|
||||
for _, infix in ipairs(regular_operators.infixes) do table.insert(operators, "_"..infix[1].."_") end
|
||||
for _, suffix in ipairs(regular_operators.suffixes) do table.insert(operators, "_"..suffix[1]) end
|
||||
|
||||
-- all valid identifier patterns
|
||||
local identifier_patterns = { identifier_pattern }
|
||||
for _, operator in ipairs(operators) do table.insert(identifier_patterns, "%s*"..escape(operator)) end
|
||||
|
||||
return primary {
|
||||
match = function(self, str)
|
||||
for _, pat in ipairs(identifier_patterns) do
|
||||
if str:match("^"..pat) then return true end
|
||||
end
|
||||
return false
|
||||
end,
|
||||
|
||||
parse = function(self, source, str)
|
||||
for _, pat in ipairs(identifier_patterns) do
|
||||
if str:match("^"..pat) then
|
||||
local start_source = source:clone()
|
||||
local name, rem = source:count(str:match("^("..pat..")(.-)$"))
|
||||
name = trim(name)
|
||||
return Identifier:new(name):set_source(start_source), rem
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
42
parser/expression/primary/init.lua
Normal file
42
parser/expression/primary/init.lua
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
--- try to parse a primary expression
|
||||
|
||||
local function r(name)
|
||||
return require("parser.expression.primary."..name), nil
|
||||
end
|
||||
|
||||
local primaries = {
|
||||
r("number"),
|
||||
r("string"),
|
||||
r("text"),
|
||||
r("parenthesis"),
|
||||
r("function_definition"),
|
||||
r("symbol"),
|
||||
r("identifier"),
|
||||
r("block_identifier"),
|
||||
r("tuple"),
|
||||
r("struct"),
|
||||
|
||||
-- prefixes
|
||||
-- 1
|
||||
r("prefix.semicolon"),
|
||||
r("prefix.function"),
|
||||
-- 2
|
||||
r("prefix.return"),
|
||||
-- 3.5
|
||||
r("prefix.else"),
|
||||
-- 11
|
||||
r("prefix.negation"),
|
||||
r("prefix.not"),
|
||||
r("prefix.mutable"),
|
||||
}
|
||||
|
||||
return {
|
||||
-- returns exp, rem if expression found
|
||||
-- returns nil if no expression found
|
||||
search = function(self, source, str, limit_pattern)
|
||||
for _, primary in ipairs(primaries) do
|
||||
local exp, rem = primary:search(source, str, limit_pattern)
|
||||
if exp then return exp, rem end
|
||||
end
|
||||
end
|
||||
}
|
||||
19
parser/expression/primary/number.lua
Normal file
19
parser/expression/primary/number.lua
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
local primary = require("parser.expression.primary.primary")
|
||||
|
||||
local Number = require("ast.Number")
|
||||
|
||||
return primary {
|
||||
match = function(self, str)
|
||||
return str:match("^%d*%.%d+") or str:match("^%d+")
|
||||
end,
|
||||
parse = function(self, source, str)
|
||||
local start_source = source:clone()
|
||||
local d, r = str:match("^(%d*%.%d+)(.*)$")
|
||||
if not d then
|
||||
d, r = source:count(str:match("^(%d+)(.*)$"))
|
||||
else
|
||||
source:count(d)
|
||||
end
|
||||
return Number:new(tonumber(d)):set_source(start_source), r
|
||||
end
|
||||
}
|
||||
31
parser/expression/primary/parenthesis.lua
Normal file
31
parser/expression/primary/parenthesis.lua
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
-- either parentheses or nil ()
|
||||
|
||||
local primary = require("parser.expression.primary.primary")
|
||||
|
||||
local ast = require("ast")
|
||||
local Nil = ast.Nil
|
||||
|
||||
local expression_to_ast = require("parser.expression.to_ast")
|
||||
|
||||
return primary {
|
||||
match = function(self, str)
|
||||
return str:match("^%(")
|
||||
end,
|
||||
parse = function(self, source, str)
|
||||
local start_source = source:clone()
|
||||
local rem = source:consume(str:match("^(%()(.*)$"))
|
||||
|
||||
local exp
|
||||
if rem:match("^%s*%)") then
|
||||
exp = Nil:new()
|
||||
else
|
||||
local s
|
||||
s, exp, rem = pcall(expression_to_ast, source, rem, "%)")
|
||||
if not s then error("invalid expression inside parentheses: "..exp, 0) end
|
||||
if not rem:match("^%s*%)") then error(("unexpected %q at end of parenthesis"):format(rem), 0) end
|
||||
end
|
||||
rem = source:consume(rem:match("^(%s*%))(.*)$"))
|
||||
|
||||
return exp:set_source(start_source), rem
|
||||
end
|
||||
}
|
||||
8
parser/expression/primary/prefix/else.lua
Normal file
8
parser/expression/primary/prefix/else.lua
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
local prefix_quote_right = require("parser.expression.primary.prefix.prefix_quote_right")
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return prefix_quote_right {
|
||||
operator = "~",
|
||||
identifier = "~_",
|
||||
priority = operator_priority["~_"]
|
||||
}
|
||||
35
parser/expression/primary/prefix/function.lua
Normal file
35
parser/expression/primary/prefix/function.lua
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
local prefix = require("parser.expression.primary.prefix.prefix")
|
||||
local parameter_tuple = require("parser.expression.contextual.parameter_tuple")
|
||||
local escape = require("common").escape
|
||||
local expression_to_ast = require("parser.expression.to_ast")
|
||||
|
||||
local ast = require("ast")
|
||||
local Function, ParameterTuple = ast.Function, ast.ParameterTuple
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return prefix {
|
||||
operator = "$",
|
||||
priority = operator_priority["$_"],
|
||||
|
||||
parse = function(self, source, str, limit_pattern)
|
||||
local source_start = source:clone()
|
||||
local escaped = escape(self.operator)
|
||||
local rem = source:consume(str:match("^("..escaped..")(.*)$"))
|
||||
|
||||
-- parse eventual parameters
|
||||
local parameters
|
||||
if parameter_tuple:match(rem) then
|
||||
parameters, rem = parameter_tuple:parse(source, rem)
|
||||
else
|
||||
parameters = ParameterTuple:new()
|
||||
end
|
||||
|
||||
-- parse expression
|
||||
local s, right
|
||||
s, right, rem = pcall(expression_to_ast, source, rem, limit_pattern, self.priority)
|
||||
if not s then error(("invalid expression after unop %q: %s"):format(self.operator, right), 0) end
|
||||
|
||||
return Function:new(parameters, right):set_source(source_start), rem
|
||||
end
|
||||
}
|
||||
9
parser/expression/primary/prefix/mutable.lua
Normal file
9
parser/expression/primary/prefix/mutable.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local prefix = require("parser.expression.primary.prefix.prefix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return prefix {
|
||||
operator = "*",
|
||||
identifier = "*_",
|
||||
priority = operator_priority["*_"]
|
||||
}
|
||||
9
parser/expression/primary/prefix/negation.lua
Normal file
9
parser/expression/primary/prefix/negation.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local prefix = require("parser.expression.primary.prefix.prefix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return prefix {
|
||||
operator = "-",
|
||||
identifier = "-_",
|
||||
priority = operator_priority["-_"]
|
||||
}
|
||||
9
parser/expression/primary/prefix/not.lua
Normal file
9
parser/expression/primary/prefix/not.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local prefix = require("parser.expression.primary.prefix.prefix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return prefix {
|
||||
operator = "!",
|
||||
identifier = "!_",
|
||||
priority = operator_priority["!_"]
|
||||
}
|
||||
34
parser/expression/primary/prefix/prefix.lua
Normal file
34
parser/expression/primary/prefix/prefix.lua
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
-- unary prefix operators, for example: the - in -5
|
||||
|
||||
local primary = require("parser.expression.primary.primary")
|
||||
local escape = require("common").escape
|
||||
local expression_to_ast = require("parser.expression.to_ast")
|
||||
|
||||
local ast = require("ast")
|
||||
local Call, Identifier, ArgumentTuple = ast.Call, ast.Identifier, ast.ArgumentTuple
|
||||
|
||||
return primary {
|
||||
operator = nil,
|
||||
identifier = nil,
|
||||
priority = nil,
|
||||
|
||||
match = function(self, str)
|
||||
local escaped = escape(self.operator)
|
||||
return str:match("^"..escaped)
|
||||
end,
|
||||
|
||||
parse = function(self, source, str, limit_pattern)
|
||||
local source_start = source:clone()
|
||||
local escaped = escape(self.operator)
|
||||
|
||||
local sright = source:consume(str:match("^("..escaped..")(.*)$"))
|
||||
local s, right, rem = pcall(expression_to_ast, source, sright, limit_pattern, self.priority)
|
||||
if not s then error(("invalid expression after prefix operator %q: %s"):format(self.operator, right), 0) end
|
||||
|
||||
return self:build_ast(right):set_source(source_start), rem
|
||||
end,
|
||||
|
||||
build_ast = function(self, right)
|
||||
return Call:new(Identifier:new(self.identifier), ArgumentTuple:new(right))
|
||||
end
|
||||
}
|
||||
11
parser/expression/primary/prefix/prefix_quote_right.lua
Normal file
11
parser/expression/primary/prefix/prefix_quote_right.lua
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
local prefix = require("parser.expression.primary.prefix.prefix")
|
||||
|
||||
local ast = require("ast")
|
||||
local Call, Identifier, ArgumentTuple, Quote = ast.Call, ast.Identifier, ast.ArgumentTuple, ast.Quote
|
||||
|
||||
return prefix {
|
||||
build_ast = function(self, right)
|
||||
right = Quote:new(right)
|
||||
return Call:new(Identifier:new(self.identifier), ArgumentTuple:new(right))
|
||||
end
|
||||
}
|
||||
15
parser/expression/primary/prefix/return.lua
Normal file
15
parser/expression/primary/prefix/return.lua
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
local prefix = require("parser.expression.primary.prefix.prefix")
|
||||
|
||||
local ast = require("ast")
|
||||
local Return = ast.Return
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return prefix {
|
||||
operator = "@",
|
||||
priority = operator_priority["@_"],
|
||||
|
||||
build_ast = function(self, right)
|
||||
return Return:new(right)
|
||||
end
|
||||
}
|
||||
13
parser/expression/primary/prefix/semicolon.lua
Normal file
13
parser/expression/primary/prefix/semicolon.lua
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
local prefix = require("parser.expression.primary.prefix.prefix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return prefix {
|
||||
operator = ";",
|
||||
identifier = ";_",
|
||||
priority = operator_priority[";_"],
|
||||
|
||||
build_ast = function(self, right)
|
||||
return right
|
||||
end
|
||||
}
|
||||
33
parser/expression/primary/primary.lua
Normal file
33
parser/expression/primary/primary.lua
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
local class = require("class")
|
||||
|
||||
return class {
|
||||
new = false, -- static class
|
||||
|
||||
-- returns exp, rem if expression found
|
||||
-- returns nil if no expression found
|
||||
search = function(self, source, str, limit_pattern)
|
||||
if not self:match(str) then
|
||||
return nil
|
||||
end
|
||||
return self:parse(source, str, limit_pattern)
|
||||
end,
|
||||
-- return bool
|
||||
-- (not needed if you redefined :search)
|
||||
match = function(self, str)
|
||||
return false
|
||||
end,
|
||||
-- return AST, rem
|
||||
-- (not needed if you redefined :search)
|
||||
parse = function(self, source, str, limit_pattern)
|
||||
error("unimplemented")
|
||||
end,
|
||||
|
||||
-- class helpers --
|
||||
|
||||
-- return AST, rem
|
||||
expect = function(self, source, str, limit_pattern)
|
||||
local exp, rem = self:search(source, str, limit_pattern)
|
||||
if not exp then error(("expected %s but got %s"):format(self.type, str)) end
|
||||
return exp, rem
|
||||
end
|
||||
}
|
||||
78
parser/expression/primary/string.lua
Normal file
78
parser/expression/primary/string.lua
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
-- note: this is reused in primary.text, hence all the configurable fields
|
||||
|
||||
local primary = require("parser.expression.primary.primary")
|
||||
|
||||
local StringInterpolation = require("ast.StringInterpolation")
|
||||
|
||||
local ast = require("ast")
|
||||
local String = ast.String
|
||||
|
||||
local expression_to_ast = require("parser.expression.to_ast")
|
||||
|
||||
local escape = require("common").escape
|
||||
|
||||
local escape_code = {
|
||||
["n"] = "\n",
|
||||
["t"] = "\t",
|
||||
-- everything else is identity by default
|
||||
}
|
||||
|
||||
return primary {
|
||||
type = "string", -- interpolation type - used for errors
|
||||
start_pattern = "\"", -- pattern that start the string interpolation
|
||||
stop_char = "\"", -- character that stops the string interpolation - must be a single character!
|
||||
|
||||
allow_implicit_stop = false, -- set to true to allow the string to be closed implicitely when reaching the end of the expression or limit_pattern
|
||||
|
||||
interpolation = StringInterpolation,
|
||||
|
||||
match = function(self, str)
|
||||
return str:match("^"..self.start_pattern)
|
||||
end,
|
||||
parse = function(self, source, str, limit_pattern)
|
||||
local interpolation = self.interpolation:new()
|
||||
|
||||
local stop_pattern = escape(self.stop_char)
|
||||
local start_source = source:clone()
|
||||
local rem = source:consume(str:match("^("..self.start_pattern..")(.-)$"))
|
||||
|
||||
while not rem:match("^"..stop_pattern) do
|
||||
local text_source = source:clone()
|
||||
local text
|
||||
text, rem = rem:match("^([^%{%\\"..stop_pattern.."]*)(.-)$") -- get all text until something potentially happens
|
||||
|
||||
-- cut the text prematurely at limit_pattern if relevant
|
||||
if self.allow_implicit_stop and limit_pattern and text:match(limit_pattern) then
|
||||
local pos = text:match("()"..limit_pattern) -- limit_pattern can contain $, so can't directly extract with captures
|
||||
text, rem = source:count(text:sub(1, pos-1)), ("%s%s%s"):format(self.stop_char, text:sub(pos), rem)
|
||||
source:increment(-1)
|
||||
else
|
||||
source:count(text)
|
||||
end
|
||||
|
||||
interpolation:insert(String:new(text):set_source(text_source))
|
||||
|
||||
if rem:match("^%{") then
|
||||
local ok, exp
|
||||
ok, exp, rem = pcall(expression_to_ast, source, source:consume(rem:match("^(%{)(.*)$")), "%}")
|
||||
if not ok then error("invalid expression inside interpolation: "..exp, 0) end
|
||||
if not rem:match("^%s*%}") then error(("unexpected %q at end of interpolation"):format(rem), 0) end
|
||||
rem = source:consume(rem:match("^(%s*%})(.*)$"))
|
||||
interpolation:insert(exp)
|
||||
elseif rem:match("^\\") then
|
||||
text, rem = source:consume(rem:match("^(\\(.))(.*)$"))
|
||||
interpolation:insert(String:new(escape_code[text] or text))
|
||||
elseif not rem:match("^"..stop_pattern) then
|
||||
if not self.allow_implicit_stop or rem:match("[^%s]") then
|
||||
error(("unexpected %q at end of "..self.type):format(rem), 0)
|
||||
-- consumed everything until end-of-line, implicit stop allowed, close your eyes and imagine the text has been closed
|
||||
else
|
||||
rem = rem .. self.stop_char
|
||||
end
|
||||
end
|
||||
end
|
||||
rem = source:consume(rem:match("^("..stop_pattern..")(.*)$"))
|
||||
|
||||
return interpolation:set_source(start_source), rem
|
||||
end
|
||||
}
|
||||
17
parser/expression/primary/struct.lua
Normal file
17
parser/expression/primary/struct.lua
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
local primary = require("parser.expression.primary.primary")
|
||||
local tuple = require("parser.expression.primary.tuple")
|
||||
|
||||
local ast = require("ast")
|
||||
local Struct = ast.Struct
|
||||
|
||||
return primary {
|
||||
match = function(self, str)
|
||||
return str:match("^%{")
|
||||
end,
|
||||
|
||||
parse = function(self, source, str)
|
||||
local l, rem = tuple:parse_tuple(source, str, "{", '}')
|
||||
|
||||
return Struct:from_tuple(l), rem
|
||||
end
|
||||
}
|
||||
40
parser/expression/primary/symbol.lua
Normal file
40
parser/expression/primary/symbol.lua
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
local primary = require("parser.expression.primary.primary")
|
||||
local type_check = require("parser.expression.secondary.infix.type_check")
|
||||
|
||||
local identifier = require("parser.expression.primary.identifier")
|
||||
|
||||
local ast = require("ast")
|
||||
local Nil = ast.Nil
|
||||
|
||||
return primary {
|
||||
match = function(self, str)
|
||||
if str:match("^%::?[&@]?") then
|
||||
return identifier:match(str:match("^%::?[&@]?(.-)$"))
|
||||
end
|
||||
return false
|
||||
end,
|
||||
|
||||
parse = function(self, source, str)
|
||||
local mod_const, mod_export, rem = source:consume(str:match("^(%:(:?)([&@]?))(.-)$"))
|
||||
local constant, persistent, type_check_exp, exported
|
||||
|
||||
-- get modifier
|
||||
if mod_const == ":" then constant = true end
|
||||
if mod_export == "&" then persistent = true
|
||||
elseif mod_export == "@" then exported = true end
|
||||
|
||||
-- name
|
||||
local ident
|
||||
ident, rem = identifier:parse(source, rem)
|
||||
|
||||
-- type check
|
||||
local nil_val = Nil:new()
|
||||
if type_check:match(rem, 0, nil_val) then
|
||||
local exp
|
||||
exp, rem = type_check:parse(source, rem, nil, 0, nil_val)
|
||||
type_check_exp = exp.arguments.list[2]
|
||||
end
|
||||
|
||||
return ident:to_symbol{ constant = constant, persistent = persistent, exported = exported, type_check = type_check_exp }:set_source(source), rem
|
||||
end
|
||||
}
|
||||
24
parser/expression/primary/text.lua
Normal file
24
parser/expression/primary/text.lua
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
local string = require("parser.expression.primary.string")
|
||||
|
||||
local ast = require("ast")
|
||||
local TextInterpolation = ast.TextInterpolation
|
||||
|
||||
return string {
|
||||
type = "text",
|
||||
start_pattern = "|%s?",
|
||||
stop_char = "|",
|
||||
allow_implicit_stop = true,
|
||||
interpolation = TextInterpolation,
|
||||
|
||||
parse = function(self, source, str, limit_pattern)
|
||||
local interpolation, rem = string.parse(self, source, str, limit_pattern)
|
||||
|
||||
-- restore | when chaining with a choice operator
|
||||
if rem:match("^>") then
|
||||
rem = "|" .. rem
|
||||
source:increment(-1)
|
||||
end
|
||||
|
||||
return interpolation, rem
|
||||
end
|
||||
}
|
||||
41
parser/expression/primary/tuple.lua
Normal file
41
parser/expression/primary/tuple.lua
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
local primary = require("parser.expression.primary.primary")
|
||||
|
||||
local ast = require("ast")
|
||||
local Tuple = ast.Tuple
|
||||
|
||||
local expression_to_ast = require("parser.expression.to_ast")
|
||||
|
||||
local escape = require("common").escape
|
||||
|
||||
return primary {
|
||||
match = function(self, str)
|
||||
return str:match("^%[")
|
||||
end,
|
||||
|
||||
parse = function(self, source, str)
|
||||
return self:parse_tuple(source, str, "[", "]")
|
||||
end,
|
||||
|
||||
parse_tuple = function(self, source, str, start_char, end_char)
|
||||
local start_source = source:clone()
|
||||
local rem = source:consume(str:match("^("..escape(start_char)..")(.*)$"))
|
||||
local end_match = escape(end_char)
|
||||
|
||||
local l
|
||||
if not rem:match("^%s*"..end_match) then
|
||||
local s
|
||||
s, l, rem = pcall(expression_to_ast, source, rem, end_match)
|
||||
if not s then error("invalid expression in list: "..l, 0) end
|
||||
end
|
||||
|
||||
if not Tuple:is(l) or l.explicit then l = Tuple:new(l) end -- single or no element
|
||||
|
||||
if not rem:match("^%s*"..end_match) then
|
||||
error(("unexpected %q at end of list"):format(rem), 0)
|
||||
end
|
||||
rem = source:consume(rem:match("^(%s*"..end_match..")(.*)$"))
|
||||
|
||||
l.explicit = true
|
||||
return l:set_source(start_source), rem
|
||||
end,
|
||||
}
|
||||
9
parser/expression/secondary/infix/addition.lua
Normal file
9
parser/expression/secondary/infix/addition.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix {
|
||||
operator = "+",
|
||||
identifier = "_+_",
|
||||
priority = operator_priority["_+_"]
|
||||
}
|
||||
9
parser/expression/secondary/infix/and.lua
Normal file
9
parser/expression/secondary/infix/and.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix_quote_right = require("parser.expression.secondary.infix.infix_quote_right")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix_quote_right {
|
||||
operator = "&",
|
||||
identifier = "_&_",
|
||||
priority = operator_priority["_&_"]
|
||||
}
|
||||
23
parser/expression/secondary/infix/assignment.lua
Normal file
23
parser/expression/secondary/infix/assignment.lua
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
local escape = require("common").escape
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local ast = require("ast")
|
||||
local Identifier, Assignment = ast.Identifier, ast.Assignment
|
||||
|
||||
return infix {
|
||||
operator = "=",
|
||||
identifier = "_=_",
|
||||
priority = operator_priority["_=_"],
|
||||
|
||||
-- return bool
|
||||
match = function(self, str, current_priority, primary)
|
||||
local escaped = escape(self.operator)
|
||||
return self.priority > current_priority and str:match("^"..escaped) and Identifier:is(primary)
|
||||
end,
|
||||
|
||||
build_ast = function(self, left, right)
|
||||
return Assignment:new(left, right)
|
||||
end
|
||||
}
|
||||
24
parser/expression/secondary/infix/assignment_call.lua
Normal file
24
parser/expression/secondary/infix/assignment_call.lua
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
local escape = require("common").escape
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local ast = require("ast")
|
||||
local Call = ast.Call
|
||||
|
||||
return infix {
|
||||
operator = "=",
|
||||
identifier = "_=_",
|
||||
priority = operator_priority["_=_"],
|
||||
|
||||
-- return bool
|
||||
match = function(self, str, current_priority, primary)
|
||||
local escaped = escape(self.operator)
|
||||
return self.priority > current_priority and str:match("^"..escaped) and Call:is(primary)
|
||||
end,
|
||||
|
||||
build_ast = function(self, left, right)
|
||||
left.arguments:set_assignment(right)
|
||||
return Call:new(left.func, left.arguments) -- recreate Call since we modified left.arguments
|
||||
end,
|
||||
}
|
||||
35
parser/expression/secondary/infix/assignment_with_infix.lua
Normal file
35
parser/expression/secondary/infix/assignment_with_infix.lua
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
local ast = require("ast")
|
||||
local Call, Identifier, ArgumentTuple = ast.Call, ast.Identifier, ast.ArgumentTuple
|
||||
|
||||
local assignment = require("parser.expression.secondary.infix.assignment")
|
||||
local assignment_call = require("parser.expression.secondary.infix.assignment_call")
|
||||
|
||||
local infixes = require("common").regular_operators.infixes
|
||||
|
||||
local generated = {}
|
||||
|
||||
for _, infix in ipairs(infixes) do
|
||||
local operator = infix[1].."="
|
||||
local identifier = "_=_"
|
||||
local infix_identifier = "_"..infix[1].."_"
|
||||
|
||||
table.insert(generated, assignment {
|
||||
operator = operator,
|
||||
identifier = identifier,
|
||||
build_ast = function(self, left, right)
|
||||
right = Call:new(Identifier:new(infix_identifier), ArgumentTuple:new(left, right))
|
||||
return assignment.build_ast(self, left, right)
|
||||
end
|
||||
})
|
||||
|
||||
table.insert(generated, assignment_call {
|
||||
operator = operator,
|
||||
identifier = identifier,
|
||||
build_ast = function(self, left, right)
|
||||
right = Call:new(Identifier:new(infix_identifier), ArgumentTuple:new(left, right))
|
||||
return assignment_call.build_ast(self, left, right)
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
return generated
|
||||
28
parser/expression/secondary/infix/call.lua
Normal file
28
parser/expression/secondary/infix/call.lua
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
local escape = require("common").escape
|
||||
local identifier = require("parser.expression.primary.identifier")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local ast = require("ast")
|
||||
local Call, ArgumentTuple = ast.Call, ast.ArgumentTuple
|
||||
|
||||
return infix {
|
||||
operator = "!",
|
||||
identifier = "_!_",
|
||||
priority = operator_priority["_!_"],
|
||||
|
||||
match = function(self, str, current_priority, primary)
|
||||
local escaped = escape(self.operator)
|
||||
return self.priority > current_priority and str:match("^"..escaped) and identifier:match(str:match("^"..escaped.."%s*(.-)$"))
|
||||
end,
|
||||
|
||||
build_ast = function(self, left, right)
|
||||
if Call:is(right) then
|
||||
right.arguments:insert_positional(1, left)
|
||||
return right
|
||||
else
|
||||
return Call:new(right, ArgumentTuple:new(left))
|
||||
end
|
||||
end
|
||||
}
|
||||
17
parser/expression/secondary/infix/choice.lua
Normal file
17
parser/expression/secondary/infix/choice.lua
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local ast = require("ast")
|
||||
local Call, Identifier, ArgumentTuple, ResumeParentFunction, ParameterTuple, Function = ast.Call, ast.Identifier, ast.ArgumentTuple, ast.ResumeParentFunction, ast.ParameterTuple, ast.Function
|
||||
|
||||
return infix {
|
||||
operator = "|>",
|
||||
identifier = "_|>_",
|
||||
priority = operator_priority["_|>_"],
|
||||
|
||||
build_ast = function(self, left, right)
|
||||
right = Function:new(ParameterTuple:new(), ResumeParentFunction:new(right))
|
||||
return Call:new(Identifier:new(self.identifier), ArgumentTuple:new(left, right))
|
||||
end
|
||||
}
|
||||
22
parser/expression/secondary/infix/definition.lua
Normal file
22
parser/expression/secondary/infix/definition.lua
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
local escape = require("common").escape
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local ast = require("ast")
|
||||
local Definition, Symbol = ast.Definition, ast.Symbol
|
||||
|
||||
return infix {
|
||||
operator = "=",
|
||||
identifier = "_=_",
|
||||
priority = operator_priority["_=_"],
|
||||
|
||||
match = function(self, str, current_priority, primary)
|
||||
local escaped = escape(self.operator)
|
||||
return self.priority > current_priority and str:match("^"..escaped) and Symbol:is(primary)
|
||||
end,
|
||||
|
||||
build_ast = function(self, left, right)
|
||||
return Definition:new(left, right)
|
||||
end
|
||||
}
|
||||
9
parser/expression/secondary/infix/different.lua
Normal file
9
parser/expression/secondary/infix/different.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix {
|
||||
operator = "!=",
|
||||
identifier = "_!=_",
|
||||
priority = operator_priority["_!=_"]
|
||||
}
|
||||
9
parser/expression/secondary/infix/division.lua
Normal file
9
parser/expression/secondary/infix/division.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix {
|
||||
operator = "/",
|
||||
identifier = "_/_",
|
||||
priority = operator_priority["_/_"]
|
||||
}
|
||||
9
parser/expression/secondary/infix/equal.lua
Normal file
9
parser/expression/secondary/infix/equal.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix {
|
||||
operator = "==",
|
||||
identifier = "_==_",
|
||||
priority = operator_priority["_==_"]
|
||||
}
|
||||
9
parser/expression/secondary/infix/exponent.lua
Normal file
9
parser/expression/secondary/infix/exponent.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix {
|
||||
operator = "^",
|
||||
identifier = "_^_",
|
||||
priority = operator_priority["_^_"]
|
||||
}
|
||||
9
parser/expression/secondary/infix/greater.lua
Normal file
9
parser/expression/secondary/infix/greater.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix {
|
||||
operator = ">",
|
||||
identifier = "_>_",
|
||||
priority = operator_priority["_>_"]
|
||||
}
|
||||
9
parser/expression/secondary/infix/greater_equal.lua
Normal file
9
parser/expression/secondary/infix/greater_equal.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix {
|
||||
operator = ">=",
|
||||
identifier = "_>=_",
|
||||
priority = operator_priority["_>=_"]
|
||||
}
|
||||
9
parser/expression/secondary/infix/if.lua
Normal file
9
parser/expression/secondary/infix/if.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix_quote_right = require("parser.expression.secondary.infix.infix_quote_right")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix_quote_right {
|
||||
operator = "~",
|
||||
identifier = "_~_",
|
||||
priority = operator_priority["_~_"]
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
local identifier = require("parser.expression.primary.identifier")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local ast = require("ast")
|
||||
local Call, Identifier, ArgumentTuple = ast.Call, ast.Identifier, ast.ArgumentTuple
|
||||
|
||||
return infix {
|
||||
operator = "*",
|
||||
identifier = "_*_",
|
||||
priority = operator_priority["_*_"]+.5, -- just above / so 1/2x gives 1/(2x)
|
||||
|
||||
match = function(self, str, current_priority, primary)
|
||||
return self.priority > current_priority and identifier:match(str)
|
||||
end,
|
||||
|
||||
parse = function(self, source, str, limit_pattern, current_priority, primary)
|
||||
local start_source = source:clone()
|
||||
local right, rem = identifier:parse(source, str, limit_pattern)
|
||||
return Call:new(Identifier:new(self.identifier), ArgumentTuple:new(primary, right)):set_source(start_source), rem
|
||||
end
|
||||
}
|
||||
19
parser/expression/secondary/infix/index.lua
Normal file
19
parser/expression/secondary/infix/index.lua
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local ast = require("ast")
|
||||
local Call, Identifier, ArgumentTuple = ast.Call, ast.Identifier, ast.ArgumentTuple
|
||||
|
||||
return infix {
|
||||
operator = ".",
|
||||
identifier = "_._",
|
||||
priority = operator_priority["_._"],
|
||||
|
||||
build_ast = function(self, left, right)
|
||||
if Identifier:is(right) then
|
||||
right = right:to_string()
|
||||
end
|
||||
return Call:new(Identifier:new(self.identifier), ArgumentTuple:new(left, right))
|
||||
end
|
||||
}
|
||||
34
parser/expression/secondary/infix/infix.lua
Normal file
34
parser/expression/secondary/infix/infix.lua
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
local secondary = require("parser.expression.secondary.secondary")
|
||||
local escape = require("common").escape
|
||||
local expression_to_ast = require("parser.expression.to_ast")
|
||||
|
||||
local ast = require("ast")
|
||||
local Call, Identifier, ArgumentTuple = ast.Call, ast.Identifier, ast.ArgumentTuple
|
||||
|
||||
return secondary {
|
||||
operator = nil,
|
||||
identifier = nil,
|
||||
priority = nil,
|
||||
|
||||
-- return bool
|
||||
match = function(self, str, current_priority, primary)
|
||||
local escaped = escape(self.operator)
|
||||
return self.priority > current_priority and str:match("^"..escaped)
|
||||
end,
|
||||
|
||||
-- return AST, rem
|
||||
parse = function(self, source, str, limit_pattern, current_priority, primary)
|
||||
local start_source = source:clone()
|
||||
local escaped = escape(self.operator)
|
||||
|
||||
local sright = source:consume(str:match("^("..escaped..")(.*)$"))
|
||||
local s, right, rem = pcall(expression_to_ast, source, sright, limit_pattern, self.priority)
|
||||
if not s then error(("invalid expression after binary operator %q: %s"):format(self.operator, right), 0) end
|
||||
|
||||
return self:build_ast(primary, right):set_source(start_source), rem
|
||||
end,
|
||||
|
||||
build_ast = function(self, left, right)
|
||||
return Call:new(Identifier:new(self.identifier), ArgumentTuple:new(left, right))
|
||||
end
|
||||
}
|
||||
32
parser/expression/secondary/infix/infix_or_suffix.lua
Normal file
32
parser/expression/secondary/infix/infix_or_suffix.lua
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
-- same as infix, but skip if no valid expression after the operator instead of erroring
|
||||
-- useful for operators that are both valid as infix and as suffix
|
||||
|
||||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
local escape = require("common").escape
|
||||
local expression_to_ast = require("parser.expression.to_ast")
|
||||
|
||||
return infix {
|
||||
-- returns exp, rem if expression found
|
||||
-- returns nil if no expression found
|
||||
search = function(self, source, str, limit_pattern, current_priority, operating_on_primary)
|
||||
if not self:match(str, current_priority, operating_on_primary) then
|
||||
return nil
|
||||
end
|
||||
return self:maybe_parse(source, str, limit_pattern, current_priority, operating_on_primary)
|
||||
end,
|
||||
|
||||
parse = function() error("no guaranteed parse for this operator") end,
|
||||
|
||||
-- return AST, rem
|
||||
-- return nil
|
||||
maybe_parse = function(self, source, str, limit_pattern, current_priority, primary)
|
||||
local start_source = source:clone()
|
||||
local escaped = escape(self.operator)
|
||||
|
||||
local sright = source:consume(str:match("^("..escaped..")(.*)$"))
|
||||
local s, right, rem = pcall(expression_to_ast, source, sright, limit_pattern, self.priority)
|
||||
if not s then return nil end
|
||||
|
||||
return self:build_ast(primary, right):set_source(start_source), rem
|
||||
end,
|
||||
}
|
||||
12
parser/expression/secondary/infix/infix_quote_both.lua
Normal file
12
parser/expression/secondary/infix/infix_quote_both.lua
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
|
||||
local ast = require("ast")
|
||||
local Call, Identifier, ArgumentTuple, Quote = ast.Call, ast.Identifier, ast.ArgumentTuple, ast.Quote
|
||||
|
||||
return infix {
|
||||
build_ast = function(self, left, right)
|
||||
left = Quote:new(left)
|
||||
right = Quote:new(right)
|
||||
return Call:new(Identifier:new(self.identifier), ArgumentTuple:new(left, right))
|
||||
end
|
||||
}
|
||||
11
parser/expression/secondary/infix/infix_quote_right.lua
Normal file
11
parser/expression/secondary/infix/infix_quote_right.lua
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
|
||||
local ast = require("ast")
|
||||
local Call, Identifier, ArgumentTuple, Quote = ast.Call, ast.Identifier, ast.ArgumentTuple, ast.Quote
|
||||
|
||||
return infix {
|
||||
build_ast = function(self, left, right)
|
||||
right = Quote:new(right)
|
||||
return Call:new(Identifier:new(self.identifier), ArgumentTuple:new(left, right))
|
||||
end
|
||||
}
|
||||
9
parser/expression/secondary/infix/integer_division.lua
Normal file
9
parser/expression/secondary/infix/integer_division.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix {
|
||||
operator = "//",
|
||||
identifier = "_//_",
|
||||
priority = operator_priority["_//_"]
|
||||
}
|
||||
9
parser/expression/secondary/infix/lower.lua
Normal file
9
parser/expression/secondary/infix/lower.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix {
|
||||
operator = "<",
|
||||
identifier = "_<_",
|
||||
priority = operator_priority["_<_"]
|
||||
}
|
||||
9
parser/expression/secondary/infix/lower_equal.lua
Normal file
9
parser/expression/secondary/infix/lower_equal.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix {
|
||||
operator = "<=",
|
||||
identifier = "_<=_",
|
||||
priority = operator_priority["_<=_"]
|
||||
}
|
||||
9
parser/expression/secondary/infix/modulo.lua
Normal file
9
parser/expression/secondary/infix/modulo.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix {
|
||||
operator = "%",
|
||||
identifier = "_%_",
|
||||
priority = operator_priority["_%_"]
|
||||
}
|
||||
9
parser/expression/secondary/infix/multiplication.lua
Normal file
9
parser/expression/secondary/infix/multiplication.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix {
|
||||
operator = "*",
|
||||
identifier = "_*_",
|
||||
priority = operator_priority["_*_"]
|
||||
}
|
||||
9
parser/expression/secondary/infix/or.lua
Normal file
9
parser/expression/secondary/infix/or.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix_quote_right = require("parser.expression.secondary.infix.infix_quote_right")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix_quote_right {
|
||||
operator = "|",
|
||||
identifier = "_|_",
|
||||
priority = operator_priority["_|_"]
|
||||
}
|
||||
9
parser/expression/secondary/infix/pair.lua
Normal file
9
parser/expression/secondary/infix/pair.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix {
|
||||
operator = ":",
|
||||
identifier = "_:_",
|
||||
priority = operator_priority["_:_"]
|
||||
}
|
||||
9
parser/expression/secondary/infix/semicolon.lua
Normal file
9
parser/expression/secondary/infix/semicolon.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix_or_suffix = require("parser.expression.secondary.infix.infix_or_suffix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix_or_suffix {
|
||||
operator = ";",
|
||||
identifier = "_;_",
|
||||
priority = operator_priority["_;_"]
|
||||
}
|
||||
9
parser/expression/secondary/infix/substraction.lua
Normal file
9
parser/expression/secondary/infix/substraction.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix {
|
||||
operator = "-",
|
||||
identifier = "_-_",
|
||||
priority = operator_priority["_-_"]
|
||||
}
|
||||
9
parser/expression/secondary/infix/tag.lua
Normal file
9
parser/expression/secondary/infix/tag.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix_quote_right = require("parser.expression.secondary.infix.infix_quote_right")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix_quote_right {
|
||||
operator = "#",
|
||||
identifier = "_#_",
|
||||
priority = operator_priority["_#_"]
|
||||
}
|
||||
36
parser/expression/secondary/infix/tuple.lua
Normal file
36
parser/expression/secondary/infix/tuple.lua
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
local escape = require("common").escape
|
||||
local expression_to_ast = require("parser.expression.to_ast")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local ast = require("ast")
|
||||
local Tuple = ast.Tuple
|
||||
|
||||
return infix {
|
||||
operator = ",",
|
||||
identifier = "_,_",
|
||||
priority = operator_priority["_,_"],
|
||||
|
||||
-- reminder: this :parse method is also called from primary.list as an helper to build list bracket litterals
|
||||
parse = function(self, source, str, limit_pattern, current_priority, primary)
|
||||
local start_source = source:clone()
|
||||
local l = Tuple:new()
|
||||
l:insert(primary)
|
||||
|
||||
local escaped = escape(self.operator)
|
||||
local rem = str
|
||||
while rem:match("^%s*"..escaped) do
|
||||
rem = source:consume(rem:match("^(%s*"..escaped..")(.*)$"))
|
||||
|
||||
local s, right
|
||||
s, right, rem = pcall(expression_to_ast, source, rem, limit_pattern, self.priority)
|
||||
if not s then error(("invalid expression after binop %q: %s"):format(self.operator, right), 0) end
|
||||
|
||||
l:insert(right)
|
||||
end
|
||||
|
||||
l.explicit = false
|
||||
return l:set_source(start_source), rem
|
||||
end
|
||||
}
|
||||
9
parser/expression/secondary/infix/type_check.lua
Normal file
9
parser/expression/secondary/infix/type_check.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix = require("parser.expression.secondary.infix.infix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix {
|
||||
operator = "::",
|
||||
identifier = "_::_",
|
||||
priority = operator_priority["_::_"]
|
||||
}
|
||||
9
parser/expression/secondary/infix/while.lua
Normal file
9
parser/expression/secondary/infix/while.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix_quote_both = require("parser.expression.secondary.infix.infix_quote_both")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix_quote_both {
|
||||
operator = "~?",
|
||||
identifier = "_~?_",
|
||||
priority = operator_priority["_~?_"]
|
||||
}
|
||||
78
parser/expression/secondary/init.lua
Normal file
78
parser/expression/secondary/init.lua
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
--- try to parse a secondary expression
|
||||
|
||||
local function r(name)
|
||||
return require("parser.expression.secondary."..name), nil
|
||||
end
|
||||
|
||||
local secondaries = {
|
||||
-- binary infix operators
|
||||
-- 1
|
||||
r("infix.semicolon"),
|
||||
-- 2
|
||||
r("infix.tuple"),
|
||||
r("infix.tag"),
|
||||
-- 4
|
||||
r("infix.while"),
|
||||
r("infix.if"),
|
||||
-- 6
|
||||
r("infix.choice"),
|
||||
r("infix.and"),
|
||||
r("infix.or"),
|
||||
-- 7
|
||||
r("infix.equal"),
|
||||
r("infix.different"),
|
||||
r("infix.greater_equal"),
|
||||
r("infix.lower_equal"),
|
||||
r("infix.greater"),
|
||||
r("infix.lower"),
|
||||
-- 8
|
||||
r("infix.addition"),
|
||||
r("infix.substraction"),
|
||||
-- 9
|
||||
r("infix.multiplication"),
|
||||
r("infix.integer_division"),
|
||||
r("infix.division"),
|
||||
r("infix.modulo"),
|
||||
-- 9.5
|
||||
r("infix.implicit_multiplication"),
|
||||
-- 10
|
||||
r("infix.exponent"),
|
||||
-- 11
|
||||
r("infix.type_check"),
|
||||
-- 12
|
||||
r("infix.call"),
|
||||
-- 14
|
||||
r("infix.index"),
|
||||
-- 3
|
||||
r("infix.assignment"), -- deported after equal
|
||||
r("infix.assignment_call"),
|
||||
r("infix.definition"),
|
||||
-- 5
|
||||
r("infix.pair"), -- deported after type_check
|
||||
|
||||
-- unary suffix operators
|
||||
-- 1
|
||||
r("suffix.semicolon"),
|
||||
-- 12
|
||||
r("suffix.exclamation_call"),
|
||||
-- 13
|
||||
r("suffix.call"),
|
||||
}
|
||||
|
||||
-- add generated assignement+infix operator combos, before the rest
|
||||
local assignment_operators = r("infix.assignment_with_infix")
|
||||
for i, op in ipairs(assignment_operators) do
|
||||
table.insert(secondaries, i, op)
|
||||
end
|
||||
|
||||
return {
|
||||
-- returns exp, rem if expression found
|
||||
-- returns nil if no expression found
|
||||
-- returns nil, err if error
|
||||
search = function(self, source, str, limit_pattern, current_priority, primary)
|
||||
for _, secondary in ipairs(secondaries) do
|
||||
local exp, rem = secondary:search(source, str, limit_pattern, current_priority, primary)
|
||||
if exp then return exp, rem end
|
||||
end
|
||||
end
|
||||
}
|
||||
34
parser/expression/secondary/secondary.lua
Normal file
34
parser/expression/secondary/secondary.lua
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
local class = require("class")
|
||||
|
||||
return class {
|
||||
new = false, -- static class
|
||||
|
||||
-- returns exp, rem if expression found
|
||||
-- returns nil if no expression found
|
||||
search = function(self, source, str, limit_pattern, current_priority, operating_on_primary)
|
||||
if not self:match(str, current_priority, operating_on_primary) then
|
||||
return nil
|
||||
end
|
||||
return self:parse(source, str, limit_pattern, current_priority, operating_on_primary)
|
||||
end,
|
||||
-- return bool
|
||||
-- (not needed if you redefined :search)
|
||||
match = function(self, str, current_priority, operating_on_primary)
|
||||
return false
|
||||
end,
|
||||
-- return AST, rem
|
||||
-- (not needed if you redefined :search)
|
||||
-- assumes that :match was checked before, and can not return nil (may error though)
|
||||
parse = function(self, source, str, limit_pattern, current_priority, operating_on_primary)
|
||||
error("unimplemented")
|
||||
end,
|
||||
|
||||
-- class helpers --
|
||||
|
||||
-- return AST, rem
|
||||
expect = function(self, source, str, limit_pattern, current_priority, operating_on_primary)
|
||||
local exp, rem = self:search(source, str, limit_pattern, current_priority, operating_on_primary)
|
||||
if not exp then error(("expected %s but got %s"):format(self.type, str)) end
|
||||
return exp, rem
|
||||
end
|
||||
}
|
||||
40
parser/expression/secondary/suffix/call.lua
Normal file
40
parser/expression/secondary/suffix/call.lua
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
-- index/call
|
||||
|
||||
local secondary = require("parser.expression.secondary.secondary")
|
||||
local parenthesis = require("parser.expression.primary.parenthesis")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local ast = require("ast")
|
||||
local Call, ArgumentTuple, Tuple, Assignment, Nil = ast.Call, ast.ArgumentTuple, ast.Tuple, ast.Assignment, ast.Nil
|
||||
|
||||
return secondary {
|
||||
priority = operator_priority["_()"],
|
||||
|
||||
match = function(self, str, current_priority, primary)
|
||||
return self.priority > current_priority and parenthesis:match(str)
|
||||
end,
|
||||
|
||||
parse = function(self, source, str, limit_pattern, current_priority, primary)
|
||||
local start_source = source:clone()
|
||||
local args = ArgumentTuple:new()
|
||||
|
||||
local exp, rem = parenthesis:parse(source, str, limit_pattern)
|
||||
|
||||
if Nil:is(exp) then
|
||||
exp = Tuple:new()
|
||||
elseif not Tuple:is(exp) or exp.explicit then -- single argument
|
||||
exp = Tuple:new(exp)
|
||||
end
|
||||
|
||||
for i, v in ipairs(exp.list) do
|
||||
if Assignment:is(v) then
|
||||
args:set_named(v.identifier, v.expression)
|
||||
else
|
||||
args:set_positional(i, v)
|
||||
end
|
||||
end
|
||||
|
||||
return Call:new(primary, args):set_source(start_source), rem
|
||||
end
|
||||
}
|
||||
15
parser/expression/secondary/suffix/exclamation_call.lua
Normal file
15
parser/expression/secondary/suffix/exclamation_call.lua
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
local suffix = require("parser.expression.secondary.suffix.suffix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local ast = require("ast")
|
||||
local Call, ArgumentTuple = ast.Call, ast.ArgumentTuple
|
||||
|
||||
return suffix {
|
||||
operator = "!",
|
||||
priority = operator_priority["_!"],
|
||||
|
||||
build_ast = function(self, left)
|
||||
return Call:new(left, ArgumentTuple:new())
|
||||
end
|
||||
}
|
||||
9
parser/expression/secondary/suffix/semicolon.lua
Normal file
9
parser/expression/secondary/suffix/semicolon.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local suffix = require("parser.expression.secondary.suffix.suffix")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return suffix {
|
||||
operator = ";",
|
||||
identifier = "_;",
|
||||
priority = operator_priority["_;"]
|
||||
}
|
||||
31
parser/expression/secondary/suffix/suffix.lua
Normal file
31
parser/expression/secondary/suffix/suffix.lua
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
-- unary suffix operators, for example the ! in func!
|
||||
|
||||
local secondary = require("parser.expression.secondary.secondary")
|
||||
local escape = require("common").escape
|
||||
|
||||
local ast = require("ast")
|
||||
local Call, Identifier, ArgumentTuple = ast.Call, ast.Identifier, ast.ArgumentTuple
|
||||
|
||||
return secondary {
|
||||
operator = nil,
|
||||
identifier = nil,
|
||||
priority = nil,
|
||||
|
||||
match = function(self, str, current_priority, primary)
|
||||
local escaped = escape(self.operator)
|
||||
return self.priority > current_priority and str:match("^"..escaped)
|
||||
end,
|
||||
|
||||
parse = function(self, source, str, limit_pattern, current_priority, primary)
|
||||
local start_source = source:clone()
|
||||
local escaped = escape(self.operator)
|
||||
|
||||
local rem = source:consume(str:match("^("..escaped..")(.*)$"))
|
||||
|
||||
return self:build_ast(primary):set_source(start_source), rem
|
||||
end,
|
||||
|
||||
build_ast = function(self, left)
|
||||
return Call:new(Identifier:new(self.identifier), ArgumentTuple:new(left))
|
||||
end
|
||||
}
|
||||
50
parser/expression/to_ast.lua
Normal file
50
parser/expression/to_ast.lua
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
--- transform an expression string into raw AST
|
||||
|
||||
local primary, secondary
|
||||
|
||||
local comment = require("parser.expression.comment")
|
||||
|
||||
-- parse an expression, starting from a secondary element operating on operating_on_primary
|
||||
-- returns expr, remaining
|
||||
local function from_secondary(source, s, limit_pattern, current_priority, operating_on_primary)
|
||||
s = source:consume(s:match("^(%s*)(.*)$"))
|
||||
current_priority = current_priority or 0
|
||||
-- if there is a comment, restart the parsing after the comment ends
|
||||
local c, c_rem = comment:search(source, s, limit_pattern)
|
||||
if c then return from_secondary(source, c_rem, limit_pattern, current_priority, operating_on_primary) end
|
||||
-- secondary elements
|
||||
local exp, rem = secondary:search(source, s, limit_pattern, current_priority, operating_on_primary)
|
||||
if exp then return from_secondary(source, rem, limit_pattern, current_priority, exp) end
|
||||
-- nothing to apply on primary
|
||||
return operating_on_primary, s
|
||||
end
|
||||
|
||||
--- parse an expression
|
||||
-- current_priority: only elements of strictly higher priority will be parser
|
||||
-- limit_pattern: set to a string pattern that will trigger the end of elements that would otherwise consume everything until end-of-line (pattern is not consumed)
|
||||
-- fallback_exp: if no primary expression can be found, will return this instead. Used to avoid raising an error where an empty or comment-only expression is allowed.
|
||||
-- return expr, remaining
|
||||
local function expression_to_ast(source, s, limit_pattern, current_priority, fallback_exp)
|
||||
s = source:consume(s:match("^(%s*)(.*)$"))
|
||||
current_priority = current_priority or 0
|
||||
-- if there is a comment, restart the parsing after the comment ends
|
||||
local c, c_rem = comment:search(source, s, limit_pattern)
|
||||
if c then return expression_to_ast(source, c_rem, limit_pattern, current_priority, fallback_exp) end
|
||||
-- primary elements
|
||||
local exp, rem = primary:search(source, s, limit_pattern)
|
||||
if exp then return from_secondary(source, rem, limit_pattern, current_priority, exp) end
|
||||
-- no valid primary expression
|
||||
if fallback_exp then return fallback_exp, s end
|
||||
error(("no valid expression before %q"):format(s), 0)
|
||||
end
|
||||
|
||||
package.loaded[...] = expression_to_ast
|
||||
|
||||
primary = require("parser.expression.primary")
|
||||
secondary = require("parser.expression.secondary")
|
||||
|
||||
-- return expr, remaining
|
||||
return function(source, s, limit_pattern, current_priority, operating_on_primary, fallback_exp)
|
||||
if operating_on_primary then return from_secondary(source, s, limit_pattern, current_priority, operating_on_primary)
|
||||
else return expression_to_ast(source, s, limit_pattern, current_priority, fallback_exp) end
|
||||
end
|
||||
13
parser/init.lua
Normal file
13
parser/init.lua
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
local code_to_tree = require("parser.code_to_tree")
|
||||
local tree_to_ast = require("parser.tree_to_ast")
|
||||
|
||||
-- parse code (string) with the associated source (Source)
|
||||
-- the returned AST tree is stateless and can be stored/evaluated/etc as you please
|
||||
return function(code, source)
|
||||
local tree = code_to_tree(code, source)
|
||||
local block = tree_to_ast(tree)
|
||||
|
||||
block:prepare()
|
||||
|
||||
return block
|
||||
end
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
local expression
|
||||
local parse_text
|
||||
|
||||
-- * true: if success
|
||||
-- * nil, error: in case of error
|
||||
local function parse(state)
|
||||
-- expression parsing
|
||||
for i=#state.queued_lines, 1, -1 do
|
||||
local l = state.queued_lines[i]
|
||||
local line, namespace = l.line, l.namespace
|
||||
-- default arguments and type constraints
|
||||
if line.type == "function" then
|
||||
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, 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)
|
||||
end
|
||||
state.variable_metadata[param.full_name].constraint = { pending = type_exp }
|
||||
end
|
||||
-- get default value
|
||||
if param.default then
|
||||
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)
|
||||
end
|
||||
param.default = default_exp
|
||||
-- extract type constraint from default value
|
||||
if default_exp.type == "function call" and default_exp.called_name == "_::_" then
|
||||
state.variable_metadata[param.full_name].constraint = { pending = default_exp.argument.expression.right }
|
||||
end
|
||||
end
|
||||
end
|
||||
-- assignment argument
|
||||
if line.assignment and line.assignment.type_constraint then
|
||||
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)
|
||||
end
|
||||
state.variable_metadata[line.assignment.full_name].constraint = { pending = type_exp }
|
||||
end
|
||||
-- get list of scoped variables
|
||||
-- (note includes every variables in the namespace of subnamespace, so subfunctions are scoped alongside this function)
|
||||
if line.scoped then
|
||||
line.scoped = {}
|
||||
for name in pairs(state.variables) do
|
||||
if name:sub(1, #namespace) == namespace then
|
||||
if state.variable_metadata[name].persistent then return nil, ("variable %q can not be persistent as it is in a scoped function"):format(name) end
|
||||
table.insert(line.scoped, name)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- get list of properties
|
||||
-- (unlike scoped, does not includes subnamespaces)
|
||||
if line.properties then
|
||||
line.properties = {}
|
||||
for name in pairs(state.variables) do
|
||||
if name:sub(1, #namespace) == namespace and not name:sub(#namespace+1):match("%.") then
|
||||
table.insert(line.properties, name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- expressions
|
||||
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
|
||||
-- variable pending definition: expression will be evaluated when variable is needed
|
||||
if line.type == "definition" then
|
||||
state.variables[line.name].value.expression = line.expression
|
||||
-- parse constraints
|
||||
if line.constraint then
|
||||
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)
|
||||
end
|
||||
state.variable_metadata[line.name].constraint = { pending = type_exp }
|
||||
end
|
||||
end
|
||||
end
|
||||
-- text (text & choice lines)
|
||||
if line.text then
|
||||
local txt, err = parse_text(line.text, state, namespace, "text", "#~", 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
|
||||
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
|
||||
expression = require((...):gsub("postparser$", "expression"))
|
||||
local common = require((...):gsub("postparser$", "common"))
|
||||
parse_text = common.parse_text
|
||||
|
||||
--- postparse shit: parse expressions and do variable existence and type checking
|
||||
return parse
|
||||
|
|
@ -1,569 +0,0 @@
|
|||
local format_identifier, identifier_pattern, escape, special_functions_names, pretty_signature, signature, copy, injections
|
||||
|
||||
local parse_indented
|
||||
|
||||
--- try to define an alias using rem, the text that follows the identifier
|
||||
-- returns true, new_rem, alias_name in case of success
|
||||
-- returns true, rem in case of no alias and no error
|
||||
-- returns nil, err in case of alias and error
|
||||
local function maybe_alias(rem, fqm, namespace, line, state)
|
||||
local alias
|
||||
if rem:match("^%:[^%:%=]") then
|
||||
local param_content = rem:sub(2)
|
||||
alias, rem = param_content:match("^("..identifier_pattern..")(.-)$")
|
||||
if not alias then return nil, ("expected an identifier in alias, but got %q; at %s"):format(param_content, line.source) end
|
||||
alias = format_identifier(alias)
|
||||
-- format alias
|
||||
local aliasfqm = ("%s%s"):format(namespace, alias)
|
||||
-- define alias
|
||||
if state.aliases[aliasfqm] ~= nil and state.aliases[aliasfqm] ~= fqm then
|
||||
return nil, ("trying to define alias %q for %q, but already exist and refer to %q; at %s"):format(aliasfqm, fqm, state.aliases[aliasfqm], line.source)
|
||||
end
|
||||
state.aliases[aliasfqm] = fqm
|
||||
end
|
||||
return true, rem, alias
|
||||
end
|
||||
|
||||
--- inject lines defined for the injection that match parent_function type and inject_type in inject_in starting from index inject_at
|
||||
local function inject(state, parent_function, inject_type, inject_in, inject_at)
|
||||
inject_at = inject_at or #inject_in+1
|
||||
local prefix
|
||||
if parent_function.subtype == "checkpoint" then
|
||||
prefix = "checkpoint"
|
||||
elseif parent_function.subtype == "class" then
|
||||
prefix = "class"
|
||||
elseif parent_function.scoped then
|
||||
prefix = "scoped_function"
|
||||
else
|
||||
prefix = "function"
|
||||
end
|
||||
local ninject = ("%s_%s"):format(prefix, inject_type)
|
||||
if state.inject[ninject] then
|
||||
for i, ll in ipairs(state.inject[ninject]) do
|
||||
table.insert(inject_in, inject_at+i-1, copy(ll))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- parse a single line into AST
|
||||
-- * ast: if success
|
||||
-- * nil, error: in case of error
|
||||
local function parse_line(line, state, namespace, parent_resumable, in_scoped)
|
||||
local l = line.content
|
||||
local r = {
|
||||
source = line.source
|
||||
}
|
||||
-- else-condition, condition & while
|
||||
if l:match("^~[~%?]?") then
|
||||
if l:match("^~~") then
|
||||
r.type = "else-condition"
|
||||
elseif l:match("^~%?") then
|
||||
r.type = "while"
|
||||
else
|
||||
r.type = "condition"
|
||||
end
|
||||
r.child = true
|
||||
local expr = l:match("^~[~%?]?(.*)$")
|
||||
if expr:match("[^%s]") then
|
||||
r.expression = expr
|
||||
else
|
||||
r.expression = "1"
|
||||
end
|
||||
-- choice
|
||||
elseif l:match("^>") then
|
||||
r.type = "choice"
|
||||
r.child = true
|
||||
r.text = l:match("^>%s*(.-)$")
|
||||
-- definition
|
||||
elseif l:match("^:") then
|
||||
local lr = l:match("^:(.*)$")
|
||||
-- immediately run variable
|
||||
local run_immediately = false
|
||||
if lr:match("^~") then
|
||||
lr = lr:match("^~(.*)$")
|
||||
run_immediately = true
|
||||
end
|
||||
-- function & checkpoint
|
||||
if lr:match("^%$") or lr:match("^%!") or lr:match("^%%") then -- § is a 2-bytes caracter, DO NOT USE LUA PATTERN OPERATORS as they operate on single bytes
|
||||
r.type = "function"
|
||||
r.child = true
|
||||
-- subtype options
|
||||
local allow_params = true
|
||||
local allow_assign = true
|
||||
local keep_in_ast = false
|
||||
if lr:match("^%$") then
|
||||
r.subtype = "function"
|
||||
r.resumable = true
|
||||
elseif lr:match("^%%") then
|
||||
r.subtype = "class"
|
||||
r.resumable = true
|
||||
r.properties = true
|
||||
allow_params = false
|
||||
allow_assign = false
|
||||
elseif lr:match("^%!") then
|
||||
r.subtype = "checkpoint"
|
||||
allow_params = false
|
||||
allow_assign = false
|
||||
keep_in_ast = true
|
||||
if not parent_resumable then return nil, ("checkpoint definition line is not in a function; at %s"):format(line.source) end
|
||||
r.parent_resumable = parent_resumable -- store parent resumable function and run checkpoint when line is read
|
||||
else
|
||||
error("unknown function line type")
|
||||
end
|
||||
-- don't keep function node in block AST
|
||||
if not keep_in_ast then
|
||||
r.remove_from_block_ast = true
|
||||
end
|
||||
-- lua function
|
||||
if r.subtype == "function" and state.global_state.link_next_function_definition_to_lua_function then
|
||||
r.lua_function = state.global_state.link_next_function_definition_to_lua_function
|
||||
state.global_state.link_next_function_definition_to_lua_function = nil
|
||||
end
|
||||
-- get identifier
|
||||
local lc = lr:match("^[%$%%%!](.-)$")
|
||||
local identifier, rem = lc:match("^("..identifier_pattern..")(.-)$")
|
||||
if not identifier then
|
||||
for _, name in ipairs(special_functions_names) do
|
||||
identifier, rem = lc:match("^(%s*"..escape(name).."%s*)(.-)$")
|
||||
if identifier then break end
|
||||
end
|
||||
end
|
||||
if not identifier then
|
||||
return nil, ("no valid identifier in function definition line %q; at %s"):format(lc, line.source)
|
||||
end
|
||||
-- format identifier
|
||||
local fqm = ("%s%s"):format(namespace, format_identifier(identifier))
|
||||
local func_namespace = fqm .. "."
|
||||
-- get alias
|
||||
local ok_alias
|
||||
ok_alias, rem = maybe_alias(rem, fqm, namespace, line, state)
|
||||
if not ok_alias then return ok_alias, rem end
|
||||
-- anything else are argument, isolate function it its own namespace
|
||||
-- (to not mix its args and variables with the main variant)
|
||||
if rem:match("[^%s]") then
|
||||
func_namespace = ("%s(%s)."):format(fqm, tostring(r))
|
||||
r.private_namespace = true
|
||||
end
|
||||
-- define function
|
||||
if state.variables[fqm] then return nil, ("trying to define %s %s, but a variable with the same name exists; at %s"):format(r.type, fqm, line.source) end
|
||||
r.namespace = func_namespace
|
||||
r.name = fqm
|
||||
-- get params
|
||||
r.params = {}
|
||||
if allow_params and rem:match("^%b()") then
|
||||
r.scoped = true
|
||||
local content
|
||||
content, rem = rem:match("^(%b())%s*(.*)$")
|
||||
content = content:gsub("^%(", ""):gsub("%)$", "")
|
||||
for param in content:gmatch("[^%,]+") do
|
||||
-- get identifier
|
||||
local param_identifier, param_rem = param:match("^("..identifier_pattern..")(.-)$")
|
||||
if not param_identifier then return nil, ("no valid identifier in function parameter %q; at %s"):format(param, line.source) end
|
||||
param_identifier = format_identifier(param_identifier)
|
||||
-- format identifier
|
||||
local param_fqm = ("%s%s"):format(func_namespace, param_identifier)
|
||||
-- get alias
|
||||
local ok_param_alias, param_alias
|
||||
ok_param_alias, param_rem, param_alias = maybe_alias(param_rem, param_fqm, func_namespace, line, state)
|
||||
if not ok_param_alias then return ok_param_alias, param_rem end
|
||||
-- get potential type constraints and default value
|
||||
local type_constraint, default
|
||||
if param_rem:match("^::") then
|
||||
type_constraint = param_rem:match("^::(.*)$")
|
||||
elseif param_rem:match("^=") then
|
||||
default = param_rem:match("^=(.*)$")
|
||||
elseif param_rem:match("[^%s]") then
|
||||
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param_fqm, param_rem, line.source)
|
||||
end
|
||||
-- add parameter
|
||||
table.insert(r.params, { name = param_identifier, alias = param_alias, full_name = param_fqm, type_constraint = type_constraint, default = default, vararg = nil })
|
||||
end
|
||||
end
|
||||
-- get assignment param
|
||||
if allow_assign and rem:match("^%:%=") then
|
||||
local param = rem:match("^%:%=(.*)$")
|
||||
-- get identifier
|
||||
local param_identifier, param_rem = param:match("^("..identifier_pattern..")(.-)$")
|
||||
if not param_identifier then return nil, ("no valid identifier in function parameter %q; at %s"):format(param, line.source) end
|
||||
param_identifier = format_identifier(param_identifier)
|
||||
-- format identifier
|
||||
local param_fqm = ("%s%s"):format(func_namespace, param_identifier)
|
||||
-- get alias
|
||||
local ok_param_alias, param_alias
|
||||
ok_param_alias, param_rem, param_alias = maybe_alias(param_rem, param_fqm, func_namespace, line, state)
|
||||
if not ok_param_alias then return ok_param_alias, param_rem end
|
||||
-- get potential type constraint
|
||||
local type_constraint
|
||||
if param_rem:match("^::") then
|
||||
type_constraint = param_rem:match("^::(.*)$")
|
||||
elseif param_rem:match("[^%s]") then
|
||||
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param_fqm, param_rem, line.source)
|
||||
end
|
||||
-- add parameter
|
||||
r.assignment = { name = param_identifier, alias = param_alias, full_name = param_fqm, type_constraint = type_constraint, default = nil, vararg = nil }
|
||||
elseif rem:match("[^%s]") then
|
||||
return nil, ("expected end-of-line at end of function definition line, but got %q; at %s"):format(rem, line.source)
|
||||
end
|
||||
-- calculate arity
|
||||
local minarity, maxarity = #r.params, #r.params
|
||||
for _, param in ipairs(r.params) do -- params with default values
|
||||
if param.default then
|
||||
minarity = minarity - 1
|
||||
end
|
||||
end
|
||||
-- varargs
|
||||
if maxarity > 0 and r.params[maxarity].full_name:match("%.%.%.$") then
|
||||
r.params[maxarity].name = r.params[maxarity].name:match("^(.*)%.%.%.$")
|
||||
r.params[maxarity].full_name = r.params[maxarity].full_name:match("^(.*)%.%.%.$")
|
||||
r.params[maxarity].vararg = true
|
||||
minarity = minarity - 1
|
||||
maxarity = math.huge
|
||||
end
|
||||
r.arity = { minarity, maxarity }
|
||||
r.signature = signature(r)
|
||||
r.pretty_signature = pretty_signature(r)
|
||||
-- check for signature conflict with functions with the same fqm
|
||||
if state.functions[fqm] then
|
||||
for _, variant in ipairs(state.functions[fqm]) do
|
||||
if r.signature == variant.signature then
|
||||
return nil, ("trying to define %s %s, but another function with same signature %s exists; at %s"):format(r.type, r.pretty_signature, variant.pretty_signature, line.source)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- define variables
|
||||
if not line.children then line.children = {} end
|
||||
local scoped = in_scoped or r.scoped
|
||||
-- define 👁️ variable
|
||||
local seen_alias = state.global_state.builtin_aliases["👁️"]
|
||||
table.insert(line.children, 1, { content = (":%s👁️%s=0"):format(scoped and "" or "@", seen_alias and ":"..seen_alias or ""), source = line.source })
|
||||
-- define 🔖 variable
|
||||
if r.resumable then
|
||||
local checkpoint_alias = state.global_state.builtin_aliases["🔖"]
|
||||
table.insert(line.children, 1, { content = (":%s🔖%s=()"):format(scoped and "" or "@", checkpoint_alias and ":"..checkpoint_alias or ""), source = line.source })
|
||||
end
|
||||
-- define 🏁 variable
|
||||
if r.subtype == "checkpoint" then
|
||||
local reached_alias = state.global_state.builtin_aliases["🏁"]
|
||||
table.insert(line.children, 1, { content = (":%s🏁%s=0"):format(scoped and "" or "@", reached_alias and ":"..reached_alias or ""), source = line.source })
|
||||
end
|
||||
-- custom code injection
|
||||
inject(state, r, "start", line.children, 2)
|
||||
inject(state, r, "end", line.children)
|
||||
-- update 👁️ variable
|
||||
table.insert(line.children, { content = "~👁️+=1", source = line.source })
|
||||
-- define args
|
||||
for _, param in ipairs(r.params) do
|
||||
if not state.variables[param.full_name] then
|
||||
state.variables[param.full_name] = {
|
||||
type = "undefined argument",
|
||||
value = nil
|
||||
}
|
||||
state.variable_metadata[param.full_name] = {}
|
||||
else
|
||||
return nil, ("trying to define parameter %q, but a variable with the same name exists; at %s"):format(param.full_name, line.source)
|
||||
end
|
||||
end
|
||||
if r.assignment then
|
||||
if not state.variables[r.assignment.full_name] then
|
||||
state.variables[r.assignment.full_name] = {
|
||||
type = "undefined argument",
|
||||
value = nil
|
||||
}
|
||||
state.variable_metadata[r.assignment.full_name] = {}
|
||||
else
|
||||
return nil, ("trying to define parameter %q, but a variable with the same name exists; at %s"):format(r.assignment.full_name, line.source)
|
||||
end
|
||||
end
|
||||
-- define new function, no other variant yet
|
||||
if not state.functions[fqm] then
|
||||
state.functions[fqm] = { r }
|
||||
-- overloading
|
||||
else
|
||||
table.insert(state.functions[fqm], r)
|
||||
end
|
||||
-- variable and constants
|
||||
else
|
||||
r.type = "definition"
|
||||
r.remove_from_block_ast = true
|
||||
local rem = lr
|
||||
-- check if constant
|
||||
if rem:match("^:") then
|
||||
rem = rem:match("^:(.*)$")
|
||||
r.constant = true
|
||||
elseif rem:match("^@") then
|
||||
rem = rem:match("^@(.*)$")
|
||||
r.persistent = true
|
||||
end
|
||||
-- get identifier
|
||||
local identifier
|
||||
identifier, rem = rem:match("^("..identifier_pattern..")(.-)$")
|
||||
if not identifier then return nil, ("no valid identifier at start of definition line %q; at %s"):format(l, line.source) end
|
||||
-- format identifier
|
||||
local fqm = ("%s%s"):format(namespace, format_identifier(identifier))
|
||||
-- get alias
|
||||
local ok_alias
|
||||
ok_alias, rem = maybe_alias(rem, fqm, namespace, line, state)
|
||||
if not ok_alias then return ok_alias, rem end
|
||||
-- type constraint
|
||||
if rem:match("^::(.-)=") then
|
||||
r.constraint, rem = rem:match("^::%s*(.-)%s*(=.*)$")
|
||||
end
|
||||
-- get expression
|
||||
local exp = rem:match("^=(.*)$")
|
||||
if not exp then return nil, ("expected \"= expression\" after %q in definition line; at %s"):format(rem, line.source) end
|
||||
-- define identifier
|
||||
if state.functions[fqm] then return nil, ("trying to define variable %q, but a function with the same name exists; at %s"):format(fqm, line.source) end
|
||||
if state.variables[fqm] then
|
||||
if state.variables[fqm].type == "pending definition" then
|
||||
return nil, ("trying to define variable %q but it is already defined at %s; at %s"):format(fqm, state.variables[fqm].value.source, line.source)
|
||||
else
|
||||
return nil, ("trying to define variable %q but it is already defined; at %s"):format(fqm, line.source)
|
||||
end
|
||||
end
|
||||
r.name = fqm
|
||||
r.expression = exp
|
||||
state.variables[fqm] = { type = "pending definition", value = { expression = nil, source = r.source } }
|
||||
state.variable_metadata[fqm] = {}
|
||||
if r.constant then state.variable_metadata[fqm].constant = true end
|
||||
if r.persistent then state.variable_metadata[fqm].persistent = true end
|
||||
end
|
||||
-- add expression line after to perform the immediate execution
|
||||
if run_immediately then
|
||||
line.line_after = { content = "~ "..r.name, source = line.source }
|
||||
end
|
||||
-- tag
|
||||
elseif l:match("^%#") then
|
||||
r.type = "tag"
|
||||
r.child = true
|
||||
local expr = l:match("^%#(.*)$")
|
||||
r.expression = ("{%s}"):format(expr)
|
||||
-- return
|
||||
elseif l:match("^%@") then
|
||||
if not parent_resumable then return nil, ("return line is not in a function; at %s"):format(line.source) end
|
||||
r.type = "return"
|
||||
r.child = true
|
||||
local expr = l:match("^%@(.*)$")
|
||||
if expr:match("[^%s]") then
|
||||
r.expression = expr
|
||||
else
|
||||
r.expression = "()"
|
||||
end
|
||||
-- custom code injection
|
||||
if not line.children then line.children = {} end
|
||||
inject(state, parent_resumable, "return", line.children)
|
||||
-- update 👁️ variable
|
||||
table.insert(line.children, { content = "~👁️+=1", source = line.source })
|
||||
-- text
|
||||
elseif l:match("[^%s]") then
|
||||
r.type = "text"
|
||||
r.text = l
|
||||
-- flush events
|
||||
else
|
||||
r.type = "flush events"
|
||||
end
|
||||
if not r.type then return nil, ("unknown line %s type"):format(line.source) end
|
||||
return r
|
||||
end
|
||||
|
||||
--- parse an indented into final AST
|
||||
-- * block: in case of success
|
||||
-- * nil, err: in case of error
|
||||
local function parse_block(indented, state, namespace, parent_resumable, in_scoped)
|
||||
local block = { type = "block" }
|
||||
for i, l in ipairs(indented) do
|
||||
-- parsable line
|
||||
local ast, err = parse_line(l, state, namespace, parent_resumable, in_scoped)
|
||||
if err then return nil, err end
|
||||
-- add to block AST
|
||||
if not ast.remove_from_block_ast then
|
||||
ast.parent_block = block
|
||||
-- add ast node
|
||||
ast.parent_position = #block+1
|
||||
table.insert(block, ast)
|
||||
end
|
||||
-- add child
|
||||
if ast.child then ast.child = { type = "block", parent_line = ast } end
|
||||
-- queue in expression evalution
|
||||
table.insert(state.queued_lines, { namespace = ast.namespace or namespace, line = ast })
|
||||
|
||||
-- indented block
|
||||
if l.children then
|
||||
if not ast.child then
|
||||
return nil, ("line %s (%s) can't have children"):format(ast.source, ast.type)
|
||||
else
|
||||
local r, e = parse_block(l.children, state, ast.namespace or namespace, (ast.type == "function" and ast.resumable) and ast or parent_resumable, (ast.type == "function" and ast.scoped) or in_scoped)
|
||||
if not r then return r, e end
|
||||
r.parent_line = ast
|
||||
ast.child = r
|
||||
end
|
||||
end
|
||||
|
||||
-- insert line after
|
||||
if l.line_after then
|
||||
table.insert(indented, i+1, l.line_after)
|
||||
end
|
||||
end
|
||||
return block
|
||||
end
|
||||
|
||||
-- returns new_indented
|
||||
local function transform_indented(indented)
|
||||
local i = 1
|
||||
while i <= #indented do
|
||||
local l = indented[i]
|
||||
-- comment
|
||||
if l.content:match("^%(") then
|
||||
table.remove(indented, i)
|
||||
else
|
||||
i = i + 1
|
||||
-- indented block
|
||||
if l.children then
|
||||
transform_indented(l.children)
|
||||
end
|
||||
end
|
||||
end
|
||||
return indented
|
||||
end
|
||||
|
||||
--- returns the nested list of lines {content="", line=1, children={lines...} or nil}, parsing indentation
|
||||
-- multiple empty lines are merged
|
||||
-- * list, last line, insert_empty_line: in case of success
|
||||
-- * nil, err: in case of error
|
||||
local function parse_indent(lines, source, i, indentLevel, insert_empty_line)
|
||||
i = i or 1
|
||||
indentLevel = indentLevel or 0
|
||||
local indented = {}
|
||||
while i <= #lines do
|
||||
if lines[i]:match("[^%s]") then
|
||||
local indent, line = lines[i]:match("^(%s*)(.*)$")
|
||||
if #indent == indentLevel then
|
||||
if insert_empty_line then
|
||||
table.insert(indented, { content = "", source = ("%s:%s"):format(source, insert_empty_line) })
|
||||
insert_empty_line = false
|
||||
end
|
||||
table.insert(indented, { content = line, source = ("%s:%s"):format(source, i) })
|
||||
elseif #indent > indentLevel then
|
||||
if #indented == 0 then
|
||||
return nil, ("unexpected indentation; at %s:%s"):format(source, i)
|
||||
else
|
||||
local t
|
||||
t, i, insert_empty_line = parse_indent(lines, source, i, #indent, insert_empty_line)
|
||||
if not t then return nil, i end
|
||||
indented[#indented].children = t
|
||||
end
|
||||
else
|
||||
return indented, i-1, insert_empty_line
|
||||
end
|
||||
elseif not insert_empty_line then
|
||||
insert_empty_line = i
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
return indented, i-1, insert_empty_line
|
||||
end
|
||||
|
||||
--- return the list of raw lines of s
|
||||
local function parse_lines(s)
|
||||
local lines = {}
|
||||
for l in (s.."\n"):gmatch("(.-)\n") do
|
||||
table.insert(lines, l)
|
||||
end
|
||||
return lines
|
||||
end
|
||||
|
||||
--- make indented from intial string
|
||||
-- * list: in case of success
|
||||
-- * nil, err: in case of error
|
||||
parse_indented = function(s, fnname, source)
|
||||
source = source or fnname
|
||||
-- parse lines
|
||||
local lines = parse_lines(s)
|
||||
local indented, e = parse_indent(lines, source)
|
||||
if not indented then return nil, e end
|
||||
-- wrap in named function if neccessary
|
||||
if fnname ~= nil and fnname ~= "" then
|
||||
if not fnname:match("^"..identifier_pattern.."$") then
|
||||
return nil, ("invalid function name %q"):format(fnname)
|
||||
end
|
||||
indented = {
|
||||
{ content = ":$ "..fnname, source = ("%s:%s"):format(source, 0), children = indented },
|
||||
}
|
||||
end
|
||||
-- transform ast
|
||||
indented = transform_indented(indented)
|
||||
return indented
|
||||
end
|
||||
|
||||
--- preparse shit: create AST structure, define variables and functions, but don't parse expression or perform any type checking
|
||||
-- (wait for other files to be parsed before doing this with postparse)
|
||||
-- * block: in case of success
|
||||
-- * nil, err: in case of error
|
||||
local function parse(state, s, name, source)
|
||||
-- get indented
|
||||
local indented, e = parse_indented(s, name, source)
|
||||
if not indented then return nil, e end
|
||||
-- build state proxy
|
||||
local state_proxy = {
|
||||
inject = {},
|
||||
aliases = setmetatable({}, { __index = state.aliases }),
|
||||
variable_metadata = setmetatable({}, { __index = state.variable_metadata }),
|
||||
variables = setmetatable({}, { __index = state.aliases }),
|
||||
functions = setmetatable({}, {
|
||||
__index = function(self, key)
|
||||
if state.functions[key] then
|
||||
local t = {} -- need to copy to allow ipairs over variants
|
||||
for k, v in ipairs(state.functions[key]) do
|
||||
t[k] = v
|
||||
end
|
||||
self[key] = t
|
||||
return t
|
||||
end
|
||||
return nil
|
||||
end
|
||||
}),
|
||||
queued_lines = {},
|
||||
global_state = state
|
||||
}
|
||||
-- parse injects
|
||||
for tinject, ninject in pairs(injections) do
|
||||
if state.inject[ninject] then
|
||||
local inject_indented, err = parse_indented(state.inject[ninject], nil, "injected "..tinject)
|
||||
if not inject_indented then return nil, err end
|
||||
state_proxy.inject[ninject] = inject_indented
|
||||
end
|
||||
end
|
||||
-- parse
|
||||
local root, err = parse_block(indented, state_proxy, "")
|
||||
if not root then return nil, err end
|
||||
-- merge back state proxy into global state
|
||||
for k,v in pairs(state_proxy.aliases) do
|
||||
state.aliases[k] = v
|
||||
end
|
||||
for k,v in pairs(state_proxy.variable_metadata) do
|
||||
state.variable_metadata[k] = v
|
||||
end
|
||||
for k,v in pairs(state_proxy.variables) do
|
||||
state.variables[k] = v
|
||||
end
|
||||
for k,v in pairs(state_proxy.functions) do
|
||||
if not state.functions[k] then
|
||||
state.functions[k] = v
|
||||
else
|
||||
for i,w in ipairs(v) do
|
||||
state.functions[k][i] = w
|
||||
end
|
||||
end
|
||||
end
|
||||
for _,l in ipairs(state_proxy.queued_lines) do
|
||||
table.insert(state.queued_lines, l)
|
||||
end
|
||||
-- return block
|
||||
return root
|
||||
end
|
||||
|
||||
package.loaded[...] = parse
|
||||
local common = require((...):gsub("preparser$", "common"))
|
||||
format_identifier, identifier_pattern, escape, special_functions_names, pretty_signature, signature, injections = common.format_identifier, common.identifier_pattern, common.escape, common.special_functions_names, common.pretty_signature, common.signature, common.injections
|
||||
copy = require((...):gsub("parser%.preparser$", "common")).copy
|
||||
|
||||
return parse
|
||||
52
parser/tree_to_ast.lua
Normal file
52
parser/tree_to_ast.lua
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
--- transform a tree of lines into raw AST
|
||||
|
||||
local tree_to_block
|
||||
|
||||
local ast = require("ast")
|
||||
local Block, Flush, AttachBlock
|
||||
|
||||
local expression_to_ast = require("parser.expression.to_ast")
|
||||
|
||||
-- wrapper for expression_to_ast to check that there is no crap remaining after the expression has been parsed
|
||||
-- return AST
|
||||
local function expect_end(exp, rem)
|
||||
if rem:match("[^%s]") then
|
||||
error(("expected end of expression before %q"):format(rem))
|
||||
end
|
||||
return exp
|
||||
end
|
||||
local function expect_end_block(exp, rem)
|
||||
if rem:match("[^%s]") and not rem:match("^ ?_$") then
|
||||
error(("expected end of expression before %q"):format(rem))
|
||||
end
|
||||
return exp
|
||||
end
|
||||
|
||||
-- return AST
|
||||
local function line_to_expression(content, tree)
|
||||
if #tree > 0 then
|
||||
local child_block = tree_to_block(tree)
|
||||
return AttachBlock:new(expect_end_block(expression_to_ast(tree.source:clone(), content.." _", " _$")), child_block):set_source(tree.source)
|
||||
else
|
||||
return expect_end(expression_to_ast(tree.source:clone(), content, nil, nil, nil, Flush:new())):set_source(tree.source)
|
||||
end
|
||||
end
|
||||
|
||||
-- return AST (Block)
|
||||
tree_to_block = function(tree)
|
||||
local block = Block:new()
|
||||
|
||||
for _, l in ipairs(tree) do
|
||||
local s, expression = pcall(line_to_expression, l.content, l)
|
||||
if not s then error(("%s; at %s"):format(expression, l.source), 0) end
|
||||
|
||||
block:add(expression)
|
||||
end
|
||||
|
||||
return block
|
||||
end
|
||||
|
||||
package.loaded[...] = tree_to_block
|
||||
Block, Flush, AttachBlock = ast.Block, ast.Flush, ast.AttachBlock
|
||||
|
||||
return tree_to_block
|
||||
Loading…
Add table
Add a link
Reference in a new issue