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

Add default and named arguments, rename equality operator to ==, shortcut for string pairs

This commit is contained in:
Étienne Fildadut 2021-04-25 18:40:45 +02:00
parent 17751c5c59
commit 151c70ed26
28 changed files with 396 additions and 146 deletions

View file

@ -107,6 +107,9 @@ common = {
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
-- (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
@ -123,6 +126,7 @@ common = {
ok = false
end
end
-- custom check
if ok and variant.check then
local s, e = variant.check(state, args)
if not s then
@ -131,6 +135,7 @@ common = {
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
@ -139,6 +144,7 @@ common = {
end
end
end
-- done
if ok then
if variant.rewrite then
local r, e = variant.rewrite(fqm, state, arg, explicit_call)
@ -156,7 +162,11 @@ common = {
name = fqm,
explicit_call = explicit_call,
variant = variant,
argument = arg
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
}
}
end
end

View file

@ -6,7 +6,7 @@ local binops_prio = {
[2] = { ":=", "+=", "-=", "//=", "/=", "*=", "%=", "^=" },
[3] = { "," },
[4] = { "|", "&" },
[5] = { "!=", "=", ">=", "<=", "<", ">" },
[5] = { "!=", "==", ">=", "<=", "<", ">" },
[6] = { "+", "-" },
[7] = { "*", "//", "/", "%" },
[8] = {}, -- unary operators
@ -89,7 +89,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
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, currentPriority, {
type = "list_parentheses",
type = "list_brackets",
return_type = "list",
expression = exp
})
@ -97,6 +97,26 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
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("^=[^=]") then
local val
val, r = expression(r:match("^=(.*)$"), state, namespace, 9)
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)
if not variant then return variant, err end
return expression(r, state, namespace, currentPriority, variant)
end
-- variables
local var, vfqm = find(state.aliases, state.variables, namespace, name)
if var then
@ -132,6 +152,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
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
end
-- find compatible variant
@ -181,6 +202,7 @@ local function expression(s, state, namespace, currentPriority, operatingOn)
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
end
-- add first argument

View file

@ -6,6 +6,21 @@ local parse_text
local function parse(state)
for _, l in ipairs(state.queued_lines) do
local line, namespace = l.line, l.namespace
-- default arguments
if line.type == "function" then
for i, param in ipairs(line.params) do
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
end
end
end
end
-- expressions
if line.expression then
local exp, rem = expression(line.expression, state, namespace)

View file

@ -2,6 +2,28 @@ local expression
local format_identifier, identifier_pattern
local eval
-- 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
-- * ast: if success
-- * nil, error: in case of error
local function parse_line(line, state, namespace)
@ -9,12 +31,6 @@ local function parse_line(line, state, namespace)
local r = {
source = line.source
}
-- comment
if l:match("^%(") then
r.type = "comment"
r.remove_from_block_ast = true
return r
end
-- else-condition & condition
if l:match("^~~?") then
r.type = l:match("^~~") and "else-condition" or "condition"
@ -42,19 +58,9 @@ local function parse_line(line, state, namespace)
-- format identifier
local fqm = ("%s%s"):format(namespace, format_identifier(identifier))
-- get alias
if rem:match("^%:") then
local content = rem:sub(2)
local alias
alias, rem = content:match("^("..identifier_pattern..")(.-)$")
if not alias then return nil, ("expected an identifier in alias in checkpoint/function definition line, but got %q; at %s"):format(content, line.source) end
-- format alias
local aliasfqm = ("%s%s"):format(namespace, format_identifier(alias))
-- define alias
if state.aliases[aliasfqm] ~= nil and state.aliases[aliasfqm] ~= fqm then
return nil, ("trying to define alias %q for checkpoint/function %q, but already exist and refer to %q; at %s"):format(aliasfqm, fqm, state.aliases[aliasfqm], line.source)
end
state.aliases[aliasfqm] = fqm
end
local ok_alias
ok_alias, rem = maybe_alias(rem, fqm, namespace, line, state)
if not ok_alias then return ok_alias, rem end
-- get params
r.params = {}
if r.type == "function" and rem:match("^%b()$") then
@ -63,36 +69,39 @@ local function parse_line(line, state, namespace)
-- 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
param_identifier = format_identifier(param_identifier)
-- format identifier
local param_fqm = ("%s.%s"):format(fqm, format_identifier(param_identifier))
local param_fqm = ("%s.%s"):format(fqm, param_identifier)
-- get alias
if param_rem:match("^%:") then
local param_content = param_rem:sub(2)
local alias
alias, param_rem = param_content:match("^("..identifier_pattern..")(.-)$")
if not alias then return nil, ("expected an identifier in alias in parameter, but got %q; at %s"):format(param_content, line.source) end
-- format alias
local aliasfqm = ("%s.%s"):format(fqm, format_identifier(alias))
-- define alias
if state.aliases[aliasfqm] ~= nil and state.aliases[aliasfqm] ~= param_fqm then
return nil, ("trying to define alias %q for parameter %q, but already exist and refer to %q; at %s"):format(aliasfqm, param_fqm, state.aliases[aliasfqm], line.source)
end
state.aliases[aliasfqm] = param_fqm
end
if param_rem:match("[^%s]") then
local ok_param_alias, param_alias
ok_param_alias, param_rem, param_alias = maybe_alias(param_rem, param_fqm, fqm..".", 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
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, param_fqm)
table.insert(r.params, { name = param_identifier, alias = param_alias, full_name = param_fqm, default = default })
end
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
local arity, vararg = #r.params, nil
if arity > 0 and r.params[arity]:match("%.%.%.$") then -- varargs
r.params[arity] = r.params[arity]:match("^(.*)%.%.%.$")
vararg = arity
arity = { arity-1, math.huge }
-- 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
if maxarity > 0 and r.params[maxarity].full_name:match("%.%.%.$") then -- varargs
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
@ -107,9 +116,8 @@ local function parse_line(line, state, namespace)
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 = arity,
arity = { minarity, maxarity },
types = {},
vararg = vararg,
value = r
}
-- new function (no overloading yet)
@ -191,13 +199,13 @@ local function parse_line(line, state, namespace)
end
-- define args and set type check information
for i, param in ipairs(r.params) do
if not state.variables[param] then
state.variables[param] = {
if not state.variables[param.full_name] then
state.variables[param.full_name] = {
type = "undefined argument",
value = { r.variant, i }
}
elseif state.variables[param].type ~= "undefined argument" then
r.variant.types[i] = state.variables[param].type
elseif state.variables[param.full_name].type ~= "undefined argument" then
r.variant.types[i] = state.variables[param.full_name].type
end
end
-- definition
@ -208,25 +216,17 @@ local function parse_line(line, state, namespace)
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, rem2 = rem:match("^("..identifier_pattern..")(.-)$")
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
-- format identifier
local fqm = ("%s%s"):format(namespace, format_identifier(identifier))
-- get alias
if rem2:match("^%:") then
local content = rem2:sub(2)
local alias, rem3 = content:match("^("..identifier_pattern..")(.-)$")
if not alias then return nil, ("expected an identifier in alias in definition line, but got %q; at %s"):format(content, line.source) end
if rem3:match("[^%s]") then return nil, ("expected end-of-line after identifier in alias in definition line, but got %q; at %s"):format(rem3, line.source) end
-- format alias
local aliasfqm = ("%s%s"):format(namespace, format_identifier(alias))
-- define alias
if state.aliases[aliasfqm] ~= nil and state.aliases[aliasfqm] ~= fqm then
return nil, ("trying to define alias %s for variable %s, but already exist and refer to different variable %s; at %s"):format(aliasfqm, fqm, state.aliases[aliasfqm], line.source)
end
state.aliases[aliasfqm] = fqm
elseif rem2:match("[^%s]") then
return nil, ("expected end-of-line after identifier in definition line, but got %q; at %s"):format(rem2, line.source)
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
-- 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
@ -297,8 +297,8 @@ local function parse_block(indented, state, namespace, parent_function)
-- queue in expression evalution
table.insert(state.queued_lines, { namespace = ast.namespace or namespace, line = ast })
-- indented block (ignore block comments)
if l.children and ast.type ~= "comment" then
-- 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
@ -317,31 +317,35 @@ local function transform_indented(indented)
local i = 1
while i <= #indented do
local l = indented[i]
-- condition decorator
if l.content:match("^.-%s*[^~]%~[^#~$]-$") then
local decorator
l.content, decorator = l.content:match("^(..-)%s*(%~[^#~$]-)$")
indented[i] = { content = decorator, source = l.source, children = { l } }
-- tag decorator
elseif l.content:match("^..-%s*%#[^#~$]-$") then
local decorator
l.content, decorator = l.content:match("^(..-)%s*(%#[^#~$]-)$")
indented[i] = { content = decorator, source = l.source, children = { l } }
-- function decorator
elseif l.content:match("^..-%s*%$[^#~$]-$") then
local name
l.content, name = l.content:match("^(..-)%s*%$([^#~$]-)$")
indented[i] = { content = "~"..name, source = l.source }
table.insert(indented, i+1, { content = "$"..name, source = l.source, children = { l } })
i = i + 1 -- $ line should not contain any decorator anymore
-- comment
if l.content:match("^%(") then
table.remove(indented, i)
else
i = i + 1 -- only increment when no decorator, as there may be several decorators per line
end
-- condition decorator
if l.content:match("^.-%s*[^~]%~[^#~$]-$") then
local decorator
l.content, decorator = l.content:match("^(..-)%s*(%~[^#~$]-)$")
indented[i] = { content = decorator, source = l.source, children = { l } }
-- tag decorator
elseif l.content:match("^..-%s*%#[^#~$]-$") then
local decorator
l.content, decorator = l.content:match("^(..-)%s*(%#[^#~$]-)$")
indented[i] = { content = decorator, source = l.source, children = { l } }
-- function decorator
elseif l.content:match("^..-%s*%$[^#~$]-$") then
local name
l.content, name = l.content:match("^(..-)%s*%$([^#~$]-)$")
indented[i] = { content = "~"..name, source = l.source }
table.insert(indented, i+1, { content = "$"..name, source = l.source, children = { l } })
i = i + 1 -- $ line should not contain any decorator anymore
else
i = i + 1 -- only increment when no decorator, as there may be several decorators per line
end
-- indented block
if l.children then
transform_indented(l.children)
-- indented block
if l.children then
transform_indented(l.children)
end
end
end
return indented