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

Preprend every definition line type with a colon, remove function decorator, add immediately ran definition line

This commit is contained in:
Étienne Fildadut 2022-09-14 15:28:58 +09:00
parent 7b756ad092
commit d1f49d1894
117 changed files with 686 additions and 658 deletions

View file

@ -53,77 +53,112 @@ local function parse_line(line, state, namespace, parent_function)
r.type = "choice"
r.child = true
r.text = l:match("^>%s*(.-)$")
-- function & checkpoint
elseif l:match("^%$") or 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 = "function"
r.child = true
-- subtype options
local allow_params = true
local allow_assign = true
local keep_in_ast = false
if l:match("^%$") then
r.subtype = "function"
r.resume_boundary = true
elseif l:match("^%%") then
r.subtype = "class"
r.resume_boundary = true
allow_params = false
allow_assign = false
elseif l:match("") then
r.subtype = "checkpoint"
allow_params = false
allow_assign = false
keep_in_ast = true
r.parent_function = parent_function -- store parent function and run checkpoint when line is read
else
error("unknown function line type")
-- 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
-- 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 = l:match("^[%$%%](.-)$") or l: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
-- 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.resume_boundary = true
elseif lr:match("^%%") then
r.subtype = "class"
r.resume_boundary = true
allow_params = false
allow_assign = false
elseif lr:match("^%!") then
r.subtype = "checkpoint"
allow_params = false
allow_assign = false
keep_in_ast = true
r.parent_function = parent_function -- store parent function and run checkpoint when line is read
else
error("unknown function line type")
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
-- 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
@ -134,199 +169,178 @@ local function parse_line(line, state, namespace, parent_function)
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
-- get potential type constraint
local type_constraint
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 })
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
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)
-- 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
-- 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
-- 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
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)
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
end
-- define variables
if not line.children then line.children = {} end
-- define 👁️ variable
local seen_alias = state.global_state.builtin_aliases["👁️"]
if seen_alias then
table.insert(line.children, 1, { content = (":👁️:%s=0"):format(seen_alias), source = line.source })
else
table.insert(line.children, 1, { content = ":👁️=0", source = line.source })
end
if r.subtype ~= "checkpoint" then
-- define 🔖 variable
local checkpoint_alias = state.global_state.builtin_aliases["🔖"]
if checkpoint_alias then
table.insert(line.children, 1, { content = (":🔖:%s=()"):format(checkpoint_alias), source = line.source })
-- define variables
if not line.children then line.children = {} end
-- define 👁️ variable
local seen_alias = state.global_state.builtin_aliases["👁️"]
if seen_alias then
table.insert(line.children, 1, { content = (":👁️:%s=0"):format(seen_alias), source = line.source })
else
table.insert(line.children, 1, { content = ":🔖=()", source = line.source })
table.insert(line.children, 1, { content = ":👁️=0", source = line.source })
end
-- custom code injection
if r.scoped then
if state.inject.scoped_function_start then
for i, ll in ipairs(state.inject.scoped_function_start) do
if r.subtype ~= "checkpoint" then
-- define 🔖 variable
local checkpoint_alias = state.global_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
-- custom code injection
if r.scoped then
if state.inject.scoped_function_start then
for i, ll in ipairs(state.inject.scoped_function_start) do
table.insert(line.children, 1+i, copy(ll))
end
end
if state.inject.scoped_function_end then
for _, ll in ipairs(state.inject.scoped_function_end) do
table.insert(line.children, copy(ll))
end
end
else
if state.inject.function_start then
for i, ll in ipairs(state.inject.function_start) do
table.insert(line.children, 1+i, copy(ll))
end
end
if state.inject.function_end then
for _, ll in ipairs(state.inject.function_end) do
table.insert(line.children, copy(ll))
end
end
end
elseif r.subtype == "checkpoint" then
-- define 🏁 variable
local reached_alias = state.global_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
-- custom code injection
if state.inject.checkpoint_start then
for i, ll in ipairs(state.inject.checkpoint_start) do
table.insert(line.children, 1+i, copy(ll))
end
end
if state.inject.scoped_function_end then
for _, ll in ipairs(state.inject.scoped_function_end) do
table.insert(line.children, copy(ll))
end
end
else
if state.inject.function_start then
for i, ll in ipairs(state.inject.function_start) do
table.insert(line.children, 1+i, copy(ll))
end
end
if state.inject.function_end then
for _, ll in ipairs(state.inject.function_end) do
if state.inject.checkpoint_end then
for _, ll in ipairs(state.inject.checkpoint_end) do
table.insert(line.children, copy(ll))
end
end
end
elseif r.subtype == "checkpoint" then
-- define 🏁 variable
local reached_alias = state.global_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
-- custom code injection
if state.inject.checkpoint_start then
for i, ll in ipairs(state.inject.checkpoint_start) do
table.insert(line.children, 1+i, copy(ll))
-- 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
}
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 state.inject.checkpoint_end then
for _, ll in ipairs(state.inject.checkpoint_end) do
table.insert(line.children, copy(ll))
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
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 = nil
}
-- define new function, no other variant yet
if not state.functions[fqm] then
state.functions[fqm] = { r }
-- overloading
else
return nil, ("trying to define parameter %q, but a variable with the same name exists; at %s"):format(param.full_name, line.source)
table.insert(state.functions[fqm], r)
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
-- variable and constants
else
table.insert(state.functions[fqm], r)
end
-- definition
elseif l:match("^:") then
r.type = "definition"
r.remove_from_block_ast = true
local rem = l:match("^:(.*)$")
-- check if constant
if rem:match("^:") then
rem = rem:match("^:(.*)$")
r.constant = 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)
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
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 } }
if r.constant then state.variable_constants[fqm] = 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
r.fqm = fqm
r.expression = exp
state.variables[fqm] = { type = "pending definition", value = { expression = nil, source = r.source } }
if r.constant then state.variable_constants[fqm] = true end
-- tag
elseif l:match("^%#") then
r.type = "tag"
@ -376,7 +390,7 @@ end
-- * nil, err: in case of error
local function parse_block(indented, state, namespace, parent_function)
local block = { type = "block" }
for _, l in ipairs(indented) do
for i, l in ipairs(indented) do
-- parsable line
local ast, err = parse_line(l, state, namespace, parent_function)
if err then return nil, err end
@ -403,6 +417,11 @@ local function parse_block(indented, state, namespace, parent_function)
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
@ -416,17 +435,7 @@ local function transform_indented(indented)
if l.content:match("^%(") then
table.remove(indented, i)
else
-- function decorator
if l.content:match("^.-[^\\]%$"..identifier_pattern.."$") then
local name
l.content, name = l.content:match("^(.-[^\\])%$("..identifier_pattern..")$")
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
i = i + 1
-- indented block
if l.children then
transform_indented(l.children)
@ -497,7 +506,7 @@ parse_indented = function(s, fnname, source)
return nil, ("invalid function name %q"):format(fnname)
end
indented = {
{ content = "$ "..fnname, source = ("%s:%s"):format(source, 0), children = indented },
{ content = ":$ "..fnname, source = ("%s:%s"):format(source, 0), children = indented },
}
end
-- transform ast