mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-27 16:49: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
|
|
@ -1,5 +1,5 @@
|
|||
local expression
|
||||
local to_lua, from_lua, eval_text
|
||||
local to_lua, from_lua, eval_text, is_of_type, truthy, format, pretty_type, get_variable
|
||||
|
||||
local run
|
||||
|
||||
|
|
@ -52,9 +52,6 @@ local function eval(state, exp)
|
|||
value = {}
|
||||
}
|
||||
end
|
||||
-- variable
|
||||
elseif exp.type == "variable" then
|
||||
return state.variables[exp.name]
|
||||
-- list
|
||||
elseif exp.type == "list" then
|
||||
local l = {}
|
||||
|
|
@ -72,107 +69,319 @@ local function eval(state, exp)
|
|||
type = "list",
|
||||
value = l
|
||||
}
|
||||
-- assignment
|
||||
elseif exp.type == ":=" then
|
||||
if exp.left.type == "variable" then
|
||||
local name = exp.left.name
|
||||
local val, vale = eval(state, exp.right)
|
||||
if not val then return val, vale end
|
||||
state.variables[name] = val
|
||||
return val
|
||||
else
|
||||
return nil, ("don't know how to perform assignment on %s expression"):format(exp.left.type)
|
||||
end
|
||||
-- lazy boolean operators
|
||||
elseif exp.type == "&" then
|
||||
local left, lefte = eval(state, exp.left)
|
||||
if not left then return left, lefte end
|
||||
if truthy(left) then
|
||||
local right, righte = eval(state, exp.right)
|
||||
if not right then return right, righte end
|
||||
if truthy(right) then
|
||||
return {
|
||||
type = "number",
|
||||
value = 1
|
||||
}
|
||||
end
|
||||
end
|
||||
return {
|
||||
type = "number",
|
||||
value = 0
|
||||
}
|
||||
elseif exp.type == "|" then
|
||||
local left, lefte = eval(state, exp.left)
|
||||
if not left then return left, lefte end
|
||||
if truthy(left) then
|
||||
return {
|
||||
type = "number",
|
||||
value = 1
|
||||
}
|
||||
end
|
||||
local right, righte = eval(state, exp.right)
|
||||
if not right then return right, righte end
|
||||
return {
|
||||
type = "number",
|
||||
value = truthy(right) and 1 or 0
|
||||
}
|
||||
-- variable
|
||||
elseif exp.type == "variable" then
|
||||
return get_variable(state, exp.name)
|
||||
-- function
|
||||
elseif exp.type == "function" then
|
||||
local fn = exp.variant
|
||||
-- custom lua functions
|
||||
if fn.mode == "custom" then
|
||||
return fn.value(state, exp)
|
||||
else
|
||||
-- eval args: list_brackets
|
||||
local args = {}
|
||||
if exp.argument then
|
||||
local arg, arge = eval(state, exp.argument)
|
||||
if not arg then return arg, arge end
|
||||
args = arg.value
|
||||
-- eval args: list_brackets
|
||||
local args = {}
|
||||
if exp.argument then
|
||||
local arg, arge = eval(state, exp.argument)
|
||||
if not arg then return arg, arge end
|
||||
args = arg.value
|
||||
end
|
||||
-- map named arguments
|
||||
local named_args = {}
|
||||
for i, arg in ipairs(args) do
|
||||
if arg.type == "pair" and arg.value[1].type == "string" then
|
||||
named_args[arg.value[1].value] = { i, arg.value[2] }
|
||||
end
|
||||
-- anselme function
|
||||
if type(fn.value) == "table" then
|
||||
-- checkpoint
|
||||
if fn.value.type == "checkpoint" then
|
||||
local r, e = run(state, fn.value.child, not exp.explicit_call)
|
||||
if not r then return r, e end
|
||||
return r
|
||||
-- function
|
||||
elseif fn.value.type == "function" then
|
||||
-- map named arguments
|
||||
for _, arg in ipairs(args) do
|
||||
if arg.type == "pair" and arg.value[1].type == "string" then
|
||||
args[arg.value[1].value] = arg.value[2]
|
||||
end
|
||||
end
|
||||
end
|
||||
-- eval assignment arg
|
||||
local assignment
|
||||
if exp.assignment then
|
||||
local arge
|
||||
assignment, arge = eval(state, exp.assignment)
|
||||
if not assignment then return assignment, arge end
|
||||
end
|
||||
-- try to select a function
|
||||
local tried_function_error_messages = {}
|
||||
local selected_variant = { depths = { assignment = nil }, variant = nil }
|
||||
for _, fn in ipairs(exp.variants) do
|
||||
-- checkpoint: no args, nothing to select on
|
||||
if fn.type == "checkpoint" then
|
||||
if not selected_variant.variant then
|
||||
selected_variant.depths = {}
|
||||
selected_variant.variant = fn
|
||||
else
|
||||
return nil, ("checkpoint call %q is ambigous; may be at least either:\n\t%s\n\t%s"):format(exp.called_name, fn.pretty_signature, selected_variant.variant.pretty_signature)
|
||||
end
|
||||
-- function
|
||||
elseif fn.type == "function" then
|
||||
if not fn.assignment or exp.assignment then
|
||||
local ok = true
|
||||
-- get and set args
|
||||
for j, param in ipairs(fn.value.params) do
|
||||
local used_args = {}
|
||||
local depths = { assignment = nil }
|
||||
for j, param in ipairs(fn.params) do
|
||||
local val
|
||||
-- named
|
||||
if param.alias and args[param.alias] then
|
||||
val = args[param.alias]
|
||||
elseif args[param.name] then
|
||||
val = args[param.name]
|
||||
if param.alias and named_args[param.alias] then
|
||||
val = named_args[param.alias][2]
|
||||
used_args[named_args[param.alias][1]] = true
|
||||
elseif named_args[param.name] then
|
||||
val = named_args[param.name][2]
|
||||
used_args[named_args[param.name][1]] = true
|
||||
-- vararg
|
||||
elseif param.vararg then
|
||||
val = { type = "list", value = {} }
|
||||
for k=j, #args do
|
||||
table.insert(val.value, args[k])
|
||||
used_args[k] = true
|
||||
end
|
||||
-- positional
|
||||
elseif args[j] and args[j].type ~= "pair" then
|
||||
val = args[j]
|
||||
-- default
|
||||
elseif param.default then
|
||||
local v, e = eval(state, param.default)
|
||||
if not v then return v, e end
|
||||
val = v
|
||||
used_args[j] = true
|
||||
end
|
||||
if val then
|
||||
-- check type annotation
|
||||
if param.type_annotation then
|
||||
local v, e = eval(state, param.type_annotation)
|
||||
if not v then return v, e end
|
||||
local depth = is_of_type(val, v)
|
||||
if not depth then
|
||||
ok = false
|
||||
table.insert(tried_function_error_messages, ("%s: argument %s is not of expected type %s"):format(fn.pretty_signature, param.name, format(v) or v))
|
||||
break
|
||||
end
|
||||
depths[j] = depth
|
||||
else
|
||||
depths[j] = math.huge
|
||||
end
|
||||
-- set
|
||||
state.variables[param.full_name] = val
|
||||
-- default: evaluate once function is selected
|
||||
-- there's no need to type check because the type annotation is already the default value's type, because of syntax
|
||||
elseif param.default then
|
||||
state.variables[param.full_name] = { type = "pending definition", value = { expression = param.default, source = fn.source } }
|
||||
else
|
||||
return nil, ("missing mandatory argument %q in function %q call"):format(param.name, fn.value.name)
|
||||
ok = false
|
||||
table.insert(tried_function_error_messages, ("%s: missing mandatory argument %q in function %q call"):format(fn.pretty_signature, param.name, fn.name))
|
||||
break
|
||||
end
|
||||
end
|
||||
-- eval function
|
||||
local r, e
|
||||
if exp.explicit_call or state.variables[fn.value.namespace.."🔖"].value == "" then
|
||||
r, e = run(state, fn.value.child)
|
||||
-- resume at last checkpoint
|
||||
else
|
||||
local expr, err = expression(state.variables[fn.value.namespace.."🔖"].value, state, "")
|
||||
if not expr then return expr, err end
|
||||
r, e = eval(state, expr)
|
||||
-- check for unused arguments
|
||||
if ok then
|
||||
for i, arg in ipairs(args) do
|
||||
if not used_args[i] then
|
||||
ok = false
|
||||
if arg.type == "pair" and arg.value[1].type == "string" then
|
||||
table.insert(tried_function_error_messages, ("%s: unexpected %s argument"):format(fn.pretty_signature, arg.value[1].value))
|
||||
else
|
||||
table.insert(tried_function_error_messages, ("%s: unexpected argument in position %s"):format(fn.pretty_signature, i))
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
-- assignment arg
|
||||
if ok and exp.assignment then
|
||||
-- check type annotation
|
||||
local param = fn.assignment
|
||||
if param.type_annotation then
|
||||
local v, e = eval(state, param.type_annotation)
|
||||
if not v then return v, e end
|
||||
local depth = is_of_type(assignment, v)
|
||||
if not depth then
|
||||
ok = false
|
||||
table.insert(tried_function_error_messages, ("%s: argument %s is not of expected type %s"):format(fn.pretty_signature, param.name, format(v) or v))
|
||||
else
|
||||
depths.assignment = depth
|
||||
end
|
||||
else
|
||||
depths.assignment = math.huge
|
||||
end
|
||||
-- set
|
||||
state.variables[param.full_name] = assignment
|
||||
end
|
||||
if ok then
|
||||
if not selected_variant.variant then
|
||||
selected_variant.depths = depths
|
||||
selected_variant.variant = fn
|
||||
else
|
||||
-- check specificity order
|
||||
local lower
|
||||
for j, d in ipairs(depths) do
|
||||
local current_depth = selected_variant.depths[j] or math.huge -- not every arg may be set on every variant (varargs)
|
||||
if d < current_depth then -- stricly lower, i.e. more specific function
|
||||
lower = true
|
||||
break
|
||||
elseif d > current_depth then -- stricly greater, i.e. less specific function
|
||||
lower = false
|
||||
break
|
||||
end
|
||||
end
|
||||
if lower == nil and exp.assignment then -- use assignment if still ambigous
|
||||
local current_depth = selected_variant.depths.assignment
|
||||
if depths.assignment < current_depth then -- stricly lower, i.e. more specific function
|
||||
lower = true
|
||||
elseif depths.assignment > current_depth then -- stricly greater, i.e. less specific function
|
||||
lower = false
|
||||
end
|
||||
end
|
||||
if lower then
|
||||
selected_variant.depths = depths
|
||||
selected_variant.variant = fn
|
||||
elseif lower == nil then -- equal, ambigous dispatch
|
||||
return nil, ("function call %q is ambigous; may be at least either:\n\t%s\n\t%s"):format(exp.called_name, fn.pretty_signature, selected_variant.variant.pretty_signature)
|
||||
end
|
||||
end
|
||||
end
|
||||
if not r then return r, e end
|
||||
state.variables[fn.value.namespace.."👁️"] = {
|
||||
type = "number",
|
||||
value = state.variables[fn.value.namespace.."👁️"].value + 1
|
||||
}
|
||||
return r
|
||||
else
|
||||
return nil, ("unknown function type %q"):format(fn.value.type)
|
||||
end
|
||||
-- lua functions
|
||||
-- TODO: handle named and default arguments
|
||||
else
|
||||
if fn.mode == "raw" then
|
||||
return fn.value(unpack(args))
|
||||
else
|
||||
local l_lua = {}
|
||||
for _, v in ipairs(args) do
|
||||
table.insert(l_lua, to_lua(v))
|
||||
end
|
||||
local r, e
|
||||
if _VERSION == "Lua 5.1" and not jit then -- PUC Lua 5.1 doesn't allow yield from a pcall
|
||||
r, e = true, fn.value(unpack(l_lua))
|
||||
else
|
||||
r, e = pcall(fn.value, unpack(l_lua)) -- pcall to produce a more informative error message (instead of full coroutine crash)
|
||||
end
|
||||
if r then
|
||||
return from_lua(e)
|
||||
else
|
||||
return nil, ("%s; in Lua function %q"):format(e, exp.name)
|
||||
end
|
||||
end
|
||||
return nil, ("unknown function type %q"):format(fn.type)
|
||||
end
|
||||
end
|
||||
-- function successfully selected
|
||||
if selected_variant.variant then
|
||||
local fn = selected_variant.variant
|
||||
if fn.type == "checkpoint" then
|
||||
local r, e = run(state, fn.child, not exp.explicit_call)
|
||||
if not r then return r, e end
|
||||
return r
|
||||
elseif fn.type == "function" then
|
||||
local ret
|
||||
-- get function vars
|
||||
local checkpoint, checkpointe = get_variable(state, fn.namespace.."🔖")
|
||||
if not checkpoint then return nil, checkpointe end
|
||||
local seen, seene = get_variable(state, fn.namespace.."👁️")
|
||||
if not seen then return nil, seene end
|
||||
-- execute lua functions
|
||||
-- I guess we could technically skip getting & updating the seen and checkpoints vars since they can't be used from Anselme
|
||||
-- but it's also kinda fun to known how many time a function was ran
|
||||
if fn.lua_function then
|
||||
local lua_fn = fn.lua_function
|
||||
-- get args
|
||||
local final_args = {}
|
||||
for j, param in ipairs(fn.params) do
|
||||
local v, e = get_variable(state, param.full_name)
|
||||
if not v then return v, e end
|
||||
final_args[j] = v
|
||||
end
|
||||
if fn.assignment then
|
||||
local v, e = get_variable(state, fn.assignment.full_name)
|
||||
if not v then return v, e end
|
||||
final_args[#final_args+1] = v
|
||||
end
|
||||
-- execute function
|
||||
-- raw mode: pass raw anselme values to the Lua function
|
||||
if lua_fn.mode == "raw" then
|
||||
ret = lua_fn.value(unpack(final_args))
|
||||
-- untyped raw mode: same as raw, but strips custom types from the arguments
|
||||
elseif lua_fn.mode == "untyped raw" then
|
||||
-- extract value from custom types
|
||||
for i, arg in ipairs(final_args) do
|
||||
if arg.type == "type" then
|
||||
final_args[i] = arg.value[1]
|
||||
end
|
||||
end
|
||||
ret = lua_fn.value(unpack(final_args))
|
||||
-- normal mode: convert args to Lua and convert back Lua value to Anselme
|
||||
elseif lua_fn.mode == nil then
|
||||
local l_lua = {}
|
||||
for _, v in ipairs(final_args) do
|
||||
table.insert(l_lua, to_lua(v))
|
||||
end
|
||||
local r, e
|
||||
if _VERSION == "Lua 5.1" and not jit then -- PUC Lua 5.1 doesn't allow yield from a pcall
|
||||
r, e = true, lua_fn.value(unpack(l_lua))
|
||||
else
|
||||
r, e = pcall(lua_fn.value, unpack(l_lua)) -- pcall to produce a more informative error message (instead of full coroutine crash)
|
||||
end
|
||||
if r then
|
||||
ret = from_lua(e)
|
||||
else
|
||||
return nil, ("%s; in Lua function %q"):format(e, exp.called_name)
|
||||
end
|
||||
else
|
||||
return nil, ("unknown Lua function mode %q"):format(lua_fn.mode)
|
||||
end
|
||||
-- execute anselme functions
|
||||
else
|
||||
local e
|
||||
-- eval function from start
|
||||
if exp.explicit_call or checkpoint.value == "" then
|
||||
ret, e = run(state, fn.child)
|
||||
-- resume at last checkpoint
|
||||
else
|
||||
local expr, err = expression(checkpoint.value, state, "")
|
||||
if not expr then return expr, err end
|
||||
ret, e = eval(state, expr)
|
||||
end
|
||||
if not ret then return ret, e end
|
||||
end
|
||||
-- update function vars
|
||||
state.variables[fn.namespace.."👁️"] = {
|
||||
type = "number",
|
||||
value = seen.value + 1
|
||||
}
|
||||
-- return value
|
||||
if not ret then return nil, ("function %q didn't return a value"):format(exp.called_name) end
|
||||
return ret
|
||||
end
|
||||
end
|
||||
-- no matching function found
|
||||
local args_txt = {}
|
||||
for _, arg in ipairs(args) do
|
||||
local s = ""
|
||||
if arg.type == "pair" and arg.value[1].type == "string" then
|
||||
s = s .. ("%s="):format(arg.value[1].value)
|
||||
arg = arg.value[2]
|
||||
end
|
||||
s = s .. pretty_type(arg)
|
||||
table.insert(args_txt, s)
|
||||
end
|
||||
local called_name = ("%s(%s)"):format(exp.called_name, table.concat(args_txt, ", "))
|
||||
if assignment then
|
||||
called_name = called_name .. " := " .. pretty_type(assignment)
|
||||
end
|
||||
return nil, ("no compatible function found for call to %s; potential candidates were:\n\t%s"):format(called_name, table.concat(tried_function_error_messages, "\n\t"))
|
||||
else
|
||||
return nil, ("unknown expression %q"):format(tostring(exp.type))
|
||||
end
|
||||
|
|
@ -182,6 +391,6 @@ package.loaded[...] = eval
|
|||
run = require((...):gsub("expression$", "interpreter")).run
|
||||
expression = require((...):gsub("interpreter%.expression$", "parser.expression"))
|
||||
local common = require((...):gsub("expression$", "common"))
|
||||
to_lua, from_lua, eval_text = common.to_lua, common.from_lua, common.eval_text
|
||||
to_lua, from_lua, eval_text, is_of_type, truthy, format, pretty_type, get_variable = common.to_lua, common.from_lua, common.eval_text, common.is_of_type, common.truthy, common.format, common.pretty_type, common.get_variable
|
||||
|
||||
return eval
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue