mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-28 00:59:31 +00:00
Changed a few things
- Bumped to 0.15.0 - Add boot script - Change variable definition syntax, using a = to distinguish more cleary between identifier and value - Variables initial values are evaluated on first use instead of at parsing time - Error on variable redefinition. Means you should make sure to load saves after your scripts. - Improve string parsing, support for escape codes - Remove support for number literals with empty decimal part (42. for 42.0) as there's no distinction in Anselme and it conflicts with .function call suffix - Changed priority of : pair operator - Add type type, and type annotations to variables and function parameters - Change Lua function system to use regular Anselme functions - Defining a function from Lua is now way simpler and require providing a full Anselme function signature - Change Anselme function system - Dynamic dispatch, based on arity, type annotation and parameter names. Will select the most specific function at runtime. - Define way to overload most operators - Allow custom type to text formatters - Allow assignment to custom functions - Index operator ( renamed to () - Functions with parameters each have their own private namespace (scoping ersatz) - Internal: "custom"-mode operators now have their own expression AST type instead of cluttering the function system - Remove static type checker as it is barely useful with new function system. May or may not rewrite one in the future. - Improve error messages here and there - Internal: cleaning
This commit is contained in:
parent
4b139019c9
commit
64bc85741a
86 changed files with 2096 additions and 1012 deletions
|
|
@ -18,9 +18,33 @@ local replace_aliases = function(aliases, namespace, name)
|
|||
return table.concat(name_list, ".")
|
||||
end
|
||||
|
||||
local disallowed_set = ("~`^+-=<>/[]*{}|\\_!?,;:()\"@&$#%"):gsub("[^%w]", "%%%1")
|
||||
|
||||
common = {
|
||||
--- valid identifier pattern
|
||||
identifier_pattern = "[^%%%/%*%+%-%(%)%!%&%|%=%$%§%?%>%<%:%{%}%[%]%,%\"]+",
|
||||
identifier_pattern = "%s*[^0-9%s"..disallowed_set.."][^"..disallowed_set.."]*",
|
||||
-- names allowed for a function that aren't valide identifiers, mainly for overloading operators
|
||||
special_functions_names = {
|
||||
-- operators not included here:
|
||||
-- * assignment operators (:=, +=, -=, //=, /=, *=, %=, ^=): handled with its own syntax (function assignment)
|
||||
-- * list operator (,): is used when calling every functions, sounds like more trouble than it's worth
|
||||
-- * | and & oprators: are lazy and don't behave like regular functions
|
||||
-- * . operator: don't behave like regular functions either
|
||||
";",
|
||||
"!=", "==", ">=", "<=", "<", ">",
|
||||
"+", "-",
|
||||
"*", "//", "/", "%",
|
||||
"!",
|
||||
"^", "::", ":", "()"
|
||||
},
|
||||
-- escapement code and their value in strings
|
||||
-- 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"
|
||||
},
|
||||
--- escape a string to be used as an exact match pattern
|
||||
escape = function(str)
|
||||
if not escapeCache[str] then
|
||||
|
|
@ -42,6 +66,8 @@ common = {
|
|||
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)
|
||||
local ns = common.split(namespace)
|
||||
for i=#ns, 1, -1 do
|
||||
|
|
@ -58,6 +84,25 @@ common = {
|
|||
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 = {}
|
||||
local ns = common.split(namespace)
|
||||
for i=#ns, 1, -1 do
|
||||
local current_namespace = table.concat(ns, ".", 1, i)
|
||||
local fqm = ("%s.%s"):format(current_namespace, replace_aliases(aliases, current_namespace, name))
|
||||
if list[fqm] then
|
||||
table.insert(l, fqm)
|
||||
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)
|
||||
|
|
@ -76,6 +121,7 @@ common = {
|
|||
end
|
||||
return t
|
||||
end,
|
||||
-- parse interpolated expressions in a text
|
||||
-- * list of strings and expressions
|
||||
-- * nil, err: in case of error
|
||||
parse_text = function(text, state, namespace)
|
||||
|
|
@ -89,7 +135,11 @@ common = {
|
|||
local exp, rem = expression(e:gsub("^{", ""), state, namespace)
|
||||
if not exp then return nil, rem end
|
||||
if not rem:match("^%s*}") then return nil, ("expected closing } at end of expression before %q"):format(rem) end
|
||||
table.insert(l, exp)
|
||||
-- wrap in format() call
|
||||
local variant, err = common.find_function_variant(state, namespace, "format", exp, true)
|
||||
if not variant then return variant, err end
|
||||
-- add to text
|
||||
table.insert(l, variant)
|
||||
text = rem:match("^%s*}(.*)$")
|
||||
else
|
||||
break
|
||||
|
|
@ -97,82 +147,106 @@ common = {
|
|||
end
|
||||
return l
|
||||
end,
|
||||
-- find compatible function variant
|
||||
-- * variant: if success
|
||||
-- find compatible function variants from a fully qualified name
|
||||
-- this functions does not guarantee that functions are fully compatible with the given arguments and only performs a pre-selection without the ones which definitely aren't
|
||||
-- * list of variants: if success
|
||||
-- * nil, err: if error
|
||||
find_function_variant = function(fqm, state, arg, explicit_call)
|
||||
local err = ("function %q variant not found"):format(fqm)
|
||||
find_function_variant_from_fqm = function(fqm, state, arg)
|
||||
local err = ("compatible function %q variant not found"):format(fqm)
|
||||
local func = state.functions[fqm] or {}
|
||||
local args = arg and common.flatten_list(arg) or {}
|
||||
local variants = {}
|
||||
for _, variant in ipairs(func) do
|
||||
local ok = true
|
||||
local return_type = variant.return_type
|
||||
-- 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 function
|
||||
-- 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)
|
||||
if variant.arity then
|
||||
local min, max
|
||||
if type(variant.arity) == "table" then
|
||||
min, max = variant.arity[1], variant.arity[2]
|
||||
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
|
||||
min, max = variant.arity, variant.arity
|
||||
end
|
||||
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
|
||||
end
|
||||
-- custom check
|
||||
if ok and variant.check then
|
||||
local s, e = variant.check(state, args)
|
||||
if not s then
|
||||
err = e or ("function %q variant failed to check arguments"):format(fqm)
|
||||
ok = false
|
||||
end
|
||||
return_type = s == true and return_type or s
|
||||
end
|
||||
-- type check
|
||||
if ok and variant.types then
|
||||
for j, t in pairs(variant.types) do
|
||||
if args[j] and args[j].return_type and args[j].return_type ~= t then
|
||||
err = ("function %q expected a %s as argument %s but received a %s"):format(fqm, t, j, args[j].return_type)
|
||||
ok = false
|
||||
end
|
||||
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
|
||||
if variant.rewrite then
|
||||
local r, e = variant.rewrite(fqm, state, arg, explicit_call)
|
||||
if not r then
|
||||
err = e
|
||||
ok = false
|
||||
end
|
||||
if ok then
|
||||
return r
|
||||
end
|
||||
else
|
||||
return {
|
||||
type = "function",
|
||||
return_type = return_type,
|
||||
name = fqm,
|
||||
explicit_call = explicit_call,
|
||||
variant = variant,
|
||||
argument = { -- wrap everything in a list literal to simply later things (otherwise may be nil, single value, list constructor)
|
||||
type = "list_brackets",
|
||||
return_type = "list",
|
||||
expression = arg
|
||||
}
|
||||
}
|
||||
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_variant = function(state, namespace, name, arg, explicit_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
|
||||
return nil, err
|
||||
end
|
||||
if #variants > 0 then
|
||||
return {
|
||||
type = "function",
|
||||
called_name = name,
|
||||
explicit_call = explicit_call,
|
||||
variants = variants,
|
||||
argument = { -- wrap everything in a list literal to simplify later things (otherwise may be nil, single value, list constructor)
|
||||
type = "list_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_annotation then
|
||||
sig = sig .. "::" .. p.type_annotation
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
local identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text
|
||||
local identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text, string_escapes
|
||||
|
||||
--- binop priority
|
||||
local binops_prio = {
|
||||
|
|
@ -9,9 +9,10 @@ local binops_prio = {
|
|||
[5] = { "!=", "==", ">=", "<=", "<", ">" },
|
||||
[6] = { "+", "-" },
|
||||
[7] = { "*", "//", "/", "%" },
|
||||
[8] = {}, -- unary operators
|
||||
[9] = { "^", ":" },
|
||||
[10] = { "." }
|
||||
[8] = { "::", ":" },
|
||||
[9] = {}, -- unary operators
|
||||
[10] = { "^" },
|
||||
[11] = { "." }
|
||||
}
|
||||
-- unop priority
|
||||
local unops_prio = {
|
||||
|
|
@ -22,8 +23,10 @@ local unops_prio = {
|
|||
[5] = {},
|
||||
[6] = {},
|
||||
[7] = {},
|
||||
[8] = { "-", "!" },
|
||||
[9] = {}
|
||||
[8] = {},
|
||||
[9] = { "-", "!" },
|
||||
[10] = {},
|
||||
[11] = {},
|
||||
}
|
||||
|
||||
--- parse an expression
|
||||
|
|
@ -34,29 +37,47 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
|||
currentPriority = currentPriority or 0
|
||||
if not operatingOn then
|
||||
-- number
|
||||
if s:match("^%d+%.%d*") or s:match("^%d*%.%d+") or s:match("^%d+") then
|
||||
local d, r = s:match("^(%d*%.%d*)(.*)$")
|
||||
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, currentPriority, {
|
||||
type = "number",
|
||||
return_type = "number",
|
||||
value = tonumber(d)
|
||||
})
|
||||
-- string
|
||||
elseif s:match("^%\"[^\"]*%\"") then
|
||||
local d, r = s:match("^%\"([^\"]*)%\"(.*)$")
|
||||
while d:match("\\$") and not d:match("\\\\$") do
|
||||
local nd, nr = r:match("([^\"]*)%\"(.*)$")
|
||||
if not nd then return nil, ("unfinished string near %q"):format(r) end
|
||||
d, r = d:sub(1, -2) .. "\"" .. nd, nr
|
||||
elseif s:match("^%\"") then
|
||||
local d, r
|
||||
-- find end of string
|
||||
local i = 2
|
||||
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(2, end_pos-2), s:sub(end_pos)
|
||||
break
|
||||
else
|
||||
return nil, ("expected \" to finish string near %q"):format(s:sub(i))
|
||||
end
|
||||
end
|
||||
end
|
||||
local l, e = parse_text(tostring(d), state, namespace)
|
||||
-- parse interpolated expressions
|
||||
local l, e = parse_text(d, state, namespace)
|
||||
if not l then return l, e end
|
||||
-- escape the string parts
|
||||
for j, ls in ipairs(l) do
|
||||
if type(ls) == "string" then
|
||||
l[j] = ls:gsub("\\.", string_escapes)
|
||||
end
|
||||
end
|
||||
return expression(r, state, namespace, currentPriority, {
|
||||
type = "string",
|
||||
return_type = "string",
|
||||
value = l
|
||||
})
|
||||
-- paranthesis
|
||||
|
|
@ -70,11 +91,10 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
|||
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", return_type = "nil", value = nil }
|
||||
exp = { type = "nil", value = nil }
|
||||
end
|
||||
return expression(r, state, namespace, currentPriority, {
|
||||
type = "parentheses",
|
||||
return_type = exp.return_type,
|
||||
expression = exp
|
||||
})
|
||||
-- list parenthesis
|
||||
|
|
@ -90,7 +110,6 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
|||
end
|
||||
return expression(r, state, namespace, currentPriority, {
|
||||
type = "list_brackets",
|
||||
return_type = "list",
|
||||
expression = exp
|
||||
})
|
||||
-- identifier
|
||||
|
|
@ -104,16 +123,14 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
|||
if not val then return val, r end
|
||||
local args = {
|
||||
type = "list",
|
||||
return_type = "list",
|
||||
left = {
|
||||
type = "string",
|
||||
return_type = "string",
|
||||
value = { name }
|
||||
},
|
||||
right = val
|
||||
}
|
||||
-- find compatible variant
|
||||
local variant, err = find_function_variant(":", state, args, true)
|
||||
local variant, err = find_function_variant(state, namespace, ":", args, true)
|
||||
if not variant then return variant, err end
|
||||
return expression(r, state, namespace, currentPriority, variant)
|
||||
end
|
||||
|
|
@ -122,7 +139,6 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
|||
if var then
|
||||
return expression(r, state, namespace, currentPriority, {
|
||||
type = "variable",
|
||||
return_type = var.type ~= "undefined argument" and var.type or nil,
|
||||
name = vfqm
|
||||
})
|
||||
end
|
||||
|
|
@ -133,34 +149,29 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
|||
if svar then
|
||||
return expression(suffix..r, state, namespace, currentPriority, {
|
||||
type = "variable",
|
||||
return_type = svar.type ~= "undefined argument" and svar.type or nil,
|
||||
name = svfqm
|
||||
})
|
||||
end
|
||||
end
|
||||
-- functions
|
||||
local funcs, ffqm = find(state.aliases, state.functions, namespace, name)
|
||||
if funcs then
|
||||
local args, explicit_call
|
||||
if r:match("^%b()") then
|
||||
explicit_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)
|
||||
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
|
||||
-- function call
|
||||
local args, explicit_call
|
||||
if r:match("^%b()") then
|
||||
explicit_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)
|
||||
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
|
||||
-- find compatible variant
|
||||
local variant, err = find_function_variant(ffqm, state, args, explicit_call)
|
||||
if not variant then return variant, err end
|
||||
return expression(r, state, namespace, currentPriority, variant)
|
||||
end
|
||||
return nil, ("unknown identifier %q"):format(name)
|
||||
-- find compatible variant
|
||||
local variant, err = find_function_variant(state, namespace, name, args, explicit_call)
|
||||
if not variant then return variant, err end
|
||||
return expression(r, state, namespace, currentPriority, variant)
|
||||
end
|
||||
-- unops
|
||||
for prio, oplist in ipairs(unops_prio) do
|
||||
|
|
@ -170,7 +181,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
|||
local right, r = expression(s:match("^"..escaped.."(.*)$"), state, namespace, prio)
|
||||
if not right then return nil, ("invalid expression after unop %q: %s"):format(op, r) end
|
||||
-- find variant
|
||||
local variant, err = find_function_variant(op, state, right, true)
|
||||
local variant, err = find_function_variant(state, namespace, op, right, true)
|
||||
if not variant then return variant, err end
|
||||
return expression(r, state, namespace, currentPriority, variant)
|
||||
end
|
||||
|
|
@ -189,38 +200,35 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
|||
if op == "." and sright:match("^"..identifier_pattern) then
|
||||
local name, r = sright:match("^("..identifier_pattern..")(.-)$")
|
||||
name = format_identifier(name)
|
||||
local funcs, ffqm = find(state.aliases, state.functions, namespace, name)
|
||||
if funcs then
|
||||
local args, explicit_call
|
||||
if r:match("^%b()") then
|
||||
explicit_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)
|
||||
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
|
||||
local args, explicit_call
|
||||
if r:match("^%b()") then
|
||||
explicit_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)
|
||||
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
|
||||
-- add first argument
|
||||
if not args then
|
||||
args = operatingOn
|
||||
else
|
||||
args = {
|
||||
type = "list",
|
||||
return_type = "list",
|
||||
left = operatingOn,
|
||||
right = args
|
||||
}
|
||||
end
|
||||
-- find compatible variant
|
||||
local variant, err = find_function_variant(ffqm, state, args, explicit_call)
|
||||
if not variant then return variant, err end
|
||||
return expression(r, state, namespace, currentPriority, variant)
|
||||
end
|
||||
-- add first argument
|
||||
if not args then
|
||||
args = operatingOn
|
||||
else
|
||||
args = {
|
||||
type = "list",
|
||||
left = operatingOn,
|
||||
right = args
|
||||
}
|
||||
end
|
||||
-- find compatible variant
|
||||
local variant, err = find_function_variant(state, namespace, name, args, explicit_call)
|
||||
if not variant then return variant, err end
|
||||
return expression(r, state, namespace, currentPriority, variant)
|
||||
-- other binops
|
||||
else
|
||||
local right, r = expression(sright, state, namespace, prio)
|
||||
if not right then return nil, ("invalid expression after binop %q: %s"):format(op, r) end
|
||||
|
|
@ -228,7 +236,48 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
|||
if op == "," then
|
||||
return expression(r, state, namespace, currentPriority, {
|
||||
type = "list",
|
||||
return_type = "list",
|
||||
left = operatingOn,
|
||||
right = right
|
||||
})
|
||||
-- special binops
|
||||
elseif op == ":=" or op == "+=" or op == "-=" or op == "//=" or op == "/=" or op == "*=" or op == "%=" or op == "^=" then
|
||||
-- rewrite assignment + arithmetic operators into a normal assignment
|
||||
if op ~= ":=" then
|
||||
local args = {
|
||||
type = "list",
|
||||
left = operatingOn,
|
||||
right = right
|
||||
}
|
||||
local variant, err = find_function_variant(state, namespace, op:match("^(.*)%=$"), args, true)
|
||||
if not variant then return variant, err end
|
||||
right = variant
|
||||
end
|
||||
-- assign to a function
|
||||
if operatingOn.type == "function" then
|
||||
-- remove non-assignment functions
|
||||
for i=#operatingOn.variants, 1, -1 do
|
||||
if not operatingOn.variants[i].assignment then
|
||||
table.remove(operatingOn.variants, i)
|
||||
end
|
||||
end
|
||||
if #operatingOn.variants == 0 then
|
||||
return nil, ("trying to perform assignment on function %s with no compatible assignment variant"):format(operatingOn.called_name)
|
||||
end
|
||||
-- rewrite function to perform assignment
|
||||
operatingOn.assignment = right
|
||||
return expression(r, state, namespace, currentPriority, operatingOn)
|
||||
elseif operatingOn.type ~= "variable" then
|
||||
return nil, ("trying to perform assignment on a %s expression"):format(operatingOn.type)
|
||||
end
|
||||
-- assign to a variable
|
||||
return expression(r, state, namespace, currentPriority, {
|
||||
type = ":=",
|
||||
left = operatingOn,
|
||||
right = right
|
||||
})
|
||||
elseif op == "&" or op == "|" then
|
||||
return expression(r, state, namespace, currentPriority, {
|
||||
type = op,
|
||||
left = operatingOn,
|
||||
right = right
|
||||
})
|
||||
|
|
@ -237,12 +286,11 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
|||
-- find variant
|
||||
local args = {
|
||||
type = "list",
|
||||
return_type = "list",
|
||||
left = operatingOn,
|
||||
-- wrap in parentheses to avoid appending to argument list if right is a list
|
||||
right = { type = "parentheses", return_type = right.return_type, expression = right }
|
||||
right = { type = "parentheses", expression = right }
|
||||
}
|
||||
local variant, err = find_function_variant(op, state, args, true)
|
||||
local variant, err = find_function_variant(state, namespace, op, args, true)
|
||||
if not variant then return variant, err end
|
||||
return expression(r, state, namespace, currentPriority, variant)
|
||||
end
|
||||
|
|
@ -259,7 +307,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
|
|||
if not right then return right, r_paren end
|
||||
if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of index expression"):format(r_paren) end
|
||||
local args = { type = "list", left = operatingOn, right = right }
|
||||
local variant, err = find_function_variant("(", state, args, true)
|
||||
local variant, err = find_function_variant(state, namespace, "()", args, true)
|
||||
if not variant then return variant, err end
|
||||
return expression(r, state, namespace, currentPriority, variant)
|
||||
end
|
||||
|
|
@ -270,6 +318,6 @@ end
|
|||
|
||||
package.loaded[...] = expression
|
||||
local common = require((...):gsub("expression$", "common"))
|
||||
identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text = common.identifier_pattern, common.format_identifier, common.find, common.escape, common.find_function_variant, common.parse_text
|
||||
identifier_pattern, format_identifier, find, escape, find_function_variant, parse_text, string_escapes = common.identifier_pattern, common.format_identifier, common.find, common.escape, common.find_function_variant, common.parse_text, common.string_escapes
|
||||
|
||||
return expression
|
||||
|
|
|
|||
|
|
@ -4,22 +4,44 @@ local parse_text
|
|||
-- * true: if success
|
||||
-- * nil, error: in case of error
|
||||
local function parse(state)
|
||||
-- expression parsing
|
||||
for _, l in ipairs(state.queued_lines) do
|
||||
local line, namespace = l.line, l.namespace
|
||||
-- default arguments
|
||||
-- default arguments and type annotation
|
||||
if line.type == "function" then
|
||||
for i, param in ipairs(line.params) do
|
||||
for _, param in ipairs(line.params) do
|
||||
-- get type annotation
|
||||
if param.type_annotation then
|
||||
local type_exp, rem = expression(param.type_annotation, state, namespace)
|
||||
if not type_exp then return nil, ("in type annotation, %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.type_annotation = type_exp
|
||||
end
|
||||
-- get default value
|
||||
if param.default then
|
||||
local exp, rem = expression(param.default, state, namespace)
|
||||
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
|
||||
param.default = exp
|
||||
-- complete type information
|
||||
if exp.return_type then
|
||||
line.variant.types[i] = exp.return_type
|
||||
local default_exp, rem = expression(param.default, state, namespace)
|
||||
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 annotation from default value
|
||||
if default_exp.type == "function" and default_exp.called_name == "::" then
|
||||
param.type_annotation = default_exp.argument.expression.right
|
||||
end
|
||||
end
|
||||
end
|
||||
-- assignment argument
|
||||
if line.assignment and line.assignment.type_annotation then
|
||||
local type_exp, rem = expression(line.assignment.type_annotation, state, namespace)
|
||||
if not type_exp then return nil, ("in type annotation, %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
|
||||
line.assignment.type_annotation = type_exp
|
||||
end
|
||||
end
|
||||
-- expressions
|
||||
if line.expression then
|
||||
|
|
@ -27,17 +49,9 @@ local function parse(state)
|
|||
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
|
||||
-- function return type information
|
||||
if line.type == "return" then
|
||||
local variant = line.parent_function.variant
|
||||
local return_type = line.expression.return_type
|
||||
if return_type then
|
||||
if not variant.return_type then
|
||||
variant.return_type = return_type
|
||||
elseif variant.return_type ~= return_type then
|
||||
return nil, ("trying to return a %s in a function that returns a %s; at %s"):format(return_type, variant.return_type, line.source)
|
||||
end
|
||||
end
|
||||
-- variable pending definition: expression will be evaluated when variable is needed
|
||||
if line.type == "definition" then
|
||||
state.variables[line.fqm].value.expression = line.expression
|
||||
end
|
||||
end
|
||||
-- text
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
local expression
|
||||
local format_identifier, identifier_pattern
|
||||
local eval
|
||||
local format_identifier, identifier_pattern, escape, special_functions_names, pretty_signature, signature
|
||||
|
||||
-- try to define an alias using rem, the text that follows the identifier
|
||||
-- returns true, new_rem, alias_name in case of success
|
||||
|
|
@ -8,7 +6,7 @@ local eval
|
|||
-- returns nil, err in case of alias and error
|
||||
local function maybe_alias(rem, fqm, namespace, line, state)
|
||||
local alias
|
||||
if rem:match("^%:") then
|
||||
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
|
||||
|
|
@ -51,41 +49,100 @@ local function parse_line(line, state, namespace)
|
|||
elseif l:match("^%$") or l:match("^§") then -- § is a 2-bytes caracter, DO NOT USE LUA PATTERN OPERATORS as they operate on single bytes
|
||||
r.type = l:match("^%$") and "function" or "checkpoint"
|
||||
r.child = true
|
||||
-- store parent function and run checkpoint when line is read
|
||||
if r.type == "checkpoint" then
|
||||
r.parent_function = true
|
||||
end
|
||||
-- don't keep function node in block AST
|
||||
if r.type == "function" then
|
||||
r.remove_from_block_ast = true
|
||||
-- lua function
|
||||
if state.link_next_function_definition_to_lua_function then
|
||||
r.lua_function = state.link_next_function_definition_to_lua_function
|
||||
state.link_next_function_definition_to_lua_function = nil
|
||||
end
|
||||
end
|
||||
-- get identifier
|
||||
local lc = l:match("^%$(.*)$") or l:match("^§(.*)$")
|
||||
local lc = l:match("^%$(.-)$") or l:match("^§(.-)$")
|
||||
local identifier, rem = lc:match("^("..identifier_pattern..")(.-)$")
|
||||
if not identifier then return nil, ("no valid identifier in checkpoint/function definition line %q; at %s"):format(lc, line.source) end
|
||||
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 checkpoint/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, 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 r.type == "function" and rem:match("^%b()$") then
|
||||
local content = rem:gsub("^%(", ""):gsub("%)$", "")
|
||||
if r.type == "function" and rem:match("^%b()") then
|
||||
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 identifier then return nil, ("no valid identifier in function parameter %q; at %s"):format(param, line.source) end
|
||||
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(fqm, param_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, fqm..".", line, state)
|
||||
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 default value
|
||||
local default
|
||||
if param_rem:match("^=") then
|
||||
-- get potential type annotation and default value
|
||||
local type_annotation, default
|
||||
if param_rem:match("^::") then
|
||||
type_annotation = 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, default = default })
|
||||
table.insert(r.params, { name = param_identifier, alias = param_alias, full_name = param_fqm, type_annotation = type_annotation, default = default, vararg = nil })
|
||||
end
|
||||
end
|
||||
-- get assignment param
|
||||
if r.type == "function" 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 annotation
|
||||
local type_annotation
|
||||
if param_rem:match("^::") then
|
||||
type_annotation = 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_annotation = type_annotation, default = nil, vararg = nil }
|
||||
elseif rem:match("[^%s]") then
|
||||
return nil, ("expected end-of-line at end of checkpoint/function definition line, but got %q; at %s"):format(rem, line.source)
|
||||
end
|
||||
|
|
@ -96,152 +153,100 @@ local function parse_line(line, state, namespace)
|
|||
minarity = minarity - 1
|
||||
end
|
||||
end
|
||||
if maxarity > 0 and r.params[maxarity].full_name:match("%.%.%.$") then -- varargs
|
||||
-- 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
|
||||
-- store parent function and run checkpoint when line is read
|
||||
if r.type == "checkpoint" then
|
||||
r.parent_function = true
|
||||
end
|
||||
-- don't keep function node in block AST
|
||||
if r.type == "function" then
|
||||
r.remove_from_block_ast = true
|
||||
end
|
||||
-- define function and variables
|
||||
r.namespace = fqm.."."
|
||||
r.name = fqm
|
||||
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.variant = {
|
||||
arity = { minarity, maxarity },
|
||||
types = {},
|
||||
value = r
|
||||
}
|
||||
-- new function (no overloading yet)
|
||||
if not state.functions[fqm] then
|
||||
state.functions[fqm] = { r.variant }
|
||||
-- define 👁️ variable
|
||||
if not state.variables[fqm..".👁️"] then
|
||||
state.variables[fqm..".👁️"] = {
|
||||
type = "number",
|
||||
value = 0
|
||||
}
|
||||
end
|
||||
-- define alias for 👁️
|
||||
local seen_alias = state.builtin_aliases["👁️"]
|
||||
if seen_alias then
|
||||
local alias = ("%s.%s"):format(fqm, seen_alias)
|
||||
if state.aliases[alias] ~= nil and state.aliases[alias] then
|
||||
return nil, ("trying to define alias %q for variable %q, but already exist and refer to different variable %q; at %s"):format(alias, fqm..".👁️", state.aliases[alias], line.source)
|
||||
end
|
||||
state.aliases[alias] = fqm..".👁️"
|
||||
end
|
||||
if r.type == "function" then
|
||||
-- define 🔖 variable
|
||||
if not state.variables[fqm..".🔖"] then
|
||||
state.variables[fqm..".🔖"] = {
|
||||
type = "string",
|
||||
value = ""
|
||||
}
|
||||
end
|
||||
-- define alias for 🔖
|
||||
local checkpoint_alias = state.builtin_aliases["🔖"]
|
||||
if checkpoint_alias then
|
||||
local alias = ("%s.%s"):format(fqm, checkpoint_alias)
|
||||
if state.aliases[alias] ~= nil and state.aliases[alias] then
|
||||
return nil, ("trying to define alias %q for variable %q, but already exist and refer to different variable %q; at %s"):format(alias, fqm..".🔖", state.aliases[alias], line.source)
|
||||
end
|
||||
state.aliases[alias] = fqm..".🔖"
|
||||
end
|
||||
elseif r.type == "checkpoint" then
|
||||
-- define 🏁 variable
|
||||
if not state.variables[fqm..".🏁"] then
|
||||
state.variables[fqm..".🏁"] = {
|
||||
type = "number",
|
||||
value = 0
|
||||
}
|
||||
end
|
||||
-- define alias for 🏁
|
||||
local reached_alias = state.builtin_aliases["🏁"]
|
||||
if reached_alias then
|
||||
local alias = ("%s.%s"):format(fqm, reached_alias)
|
||||
if state.aliases[alias] ~= nil and state.aliases[alias] then
|
||||
return nil, ("trying to define alias %q for variable %q, but already exist and refer to different variable %q; at %s"):format(alias, fqm..".🏁", state.aliases[alias], line.source)
|
||||
end
|
||||
state.aliases[alias] = fqm..".🏁"
|
||||
end
|
||||
end
|
||||
-- overloading
|
||||
r.arity = { minarity, maxarity }
|
||||
r.signature = signature(r)
|
||||
r.pretty_signature = pretty_signature(r)
|
||||
-- define variables
|
||||
if not line.children then line.children = {} end
|
||||
-- define 👁️ variable
|
||||
local seen_alias = state.builtin_aliases["👁️"]
|
||||
if seen_alias then
|
||||
table.insert(line.children, 1, { content = (":👁️:%s=0"):format(seen_alias), source = line.source })
|
||||
else
|
||||
-- check for arity conflict
|
||||
for _, variant in ipairs(state.functions[fqm]) do
|
||||
local vmin, vmax = 0, math.huge
|
||||
if type(variant.arity) == "table" then
|
||||
vmin, vmax = variant.arity[1], variant.arity[2]
|
||||
elseif variant.arity then
|
||||
vmin, vmax = variant.arity, variant.arity
|
||||
end
|
||||
local min, max = 0, math.huge
|
||||
if type(r.variant.arity) == "table" then
|
||||
min, max = r.variant.arity[1], r.variant.arity[2]
|
||||
elseif r.variant.arity then
|
||||
min, max = variant.arity, r.variant.arity
|
||||
end
|
||||
if min == vmin and max == vmax then
|
||||
return nil, ("trying to define %s %s with arity [%s;%s], but another function with the same name and arity exist; at %s"):format(r.type, fqm, min, max, line.source)
|
||||
end
|
||||
end
|
||||
-- add
|
||||
table.insert(state.functions[fqm], r.variant)
|
||||
table.insert(line.children, 1, { content = ":👁️=0", source = line.source })
|
||||
end
|
||||
-- define args and set type check information
|
||||
for i, param in ipairs(r.params) do
|
||||
if r.type == "function" then
|
||||
-- define 🔖 variable
|
||||
local checkpoint_alias = state.builtin_aliases["🔖"]
|
||||
if checkpoint_alias then
|
||||
table.insert(line.children, 1, { content = (":🔖:%s=\"\""):format(checkpoint_alias), source = line.source })
|
||||
else
|
||||
table.insert(line.children, 1, { content = ":🔖=\"\"", source = line.source })
|
||||
end
|
||||
elseif r.type == "checkpoint" then
|
||||
-- define 🏁 variable
|
||||
local reached_alias = state.builtin_aliases["🏁"]
|
||||
if reached_alias then
|
||||
table.insert(line.children, 1, { content = (":🏁:%s=0"):format(reached_alias), source = line.source })
|
||||
else
|
||||
table.insert(line.children, 1, { content = ":🏁=0", source = line.source })
|
||||
end
|
||||
end
|
||||
-- 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 = { r.variant, i }
|
||||
value = nil
|
||||
}
|
||||
elseif state.variables[param.full_name].type ~= "undefined argument" then
|
||||
r.variant.types[i] = state.variables[param.full_name].type
|
||||
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
|
||||
}
|
||||
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
|
||||
-- check for signature conflict with functions with the same fqm
|
||||
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
|
||||
-- add
|
||||
table.insert(state.functions[fqm], r)
|
||||
end
|
||||
-- definition
|
||||
elseif l:match("^:") then
|
||||
r.type = "definition"
|
||||
r.remove_from_block_ast = true
|
||||
-- get expression
|
||||
local exp, rem = expression(l:match("^:(.*)$"), state, namespace) -- expression parsing is done directly to get type information
|
||||
if not exp then return nil, ("%s; at %s"):format(rem, line.source) end
|
||||
-- get identifier
|
||||
local identifier
|
||||
identifier, rem = rem:match("^("..identifier_pattern..")(.-)$")
|
||||
if not identifier then return nil, ("no valid identifier after expression in definition line %q; at %s"):format(rem, line.source) end
|
||||
local identifier, rem = l: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
|
||||
if rem:match("[^%s]") then
|
||||
return nil, ("expected end-of-line after identifier in definition line, but got %q; at %s"):format(rem, line.source)
|
||||
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 %s, but a function with the same name exists; at %s"):format(fqm, line.source) end
|
||||
if not state.variables[fqm] or state.variables[fqm].type == "undefined argument" then
|
||||
local v, e = eval(state, exp)
|
||||
if not v then return v, e end
|
||||
-- update function typecheck information
|
||||
if state.variables[fqm] and state.variables[fqm].type == "undefined argument" then
|
||||
local und = state.variables[fqm].value
|
||||
und[1].types[und[2]] = v.type
|
||||
end
|
||||
state.variables[fqm] = v
|
||||
elseif state.variables[fqm].type ~= exp.type then
|
||||
return nil, ("trying to define variable %s of type %s but it is already defined with type %s; at %s"):format(fqm, exp.type, state.variables[fqm].type, line.source)
|
||||
end
|
||||
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 return nil, ("trying to define variable %q but it is already defined; at %s"):format(fqm, line.source) end
|
||||
r.fqm = fqm
|
||||
r.expression = exp
|
||||
state.variables[fqm] = { type = "pending definition", value = { expression = nil, source = r.source } }
|
||||
-- tag
|
||||
elseif l:match("^%#") then
|
||||
r.type = "tag"
|
||||
|
|
@ -424,9 +429,7 @@ local function parse(state, s, name, source)
|
|||
end
|
||||
|
||||
package.loaded[...] = parse
|
||||
expression = require((...):gsub("preparser$", "expression"))
|
||||
local common = require((...):gsub("preparser$", "common"))
|
||||
format_identifier, identifier_pattern = common.format_identifier, common.identifier_pattern
|
||||
eval = require((...):gsub("parser%.preparser$", "interpreter.expression"))
|
||||
format_identifier, identifier_pattern, escape, special_functions_names, pretty_signature, signature = common.format_identifier, common.identifier_pattern, common.escape, common.special_functions_names, common.pretty_signature, common.signature
|
||||
|
||||
return parse
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue