1
0
Fork 0
mirror of https://github.com/Reuh/anselme.git synced 2025-10-28 09:09: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:
Étienne Fildadut 2021-06-03 15:29:25 +02:00
parent 4b139019c9
commit 64bc85741a
86 changed files with 2096 additions and 1012 deletions

View file

@ -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