mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-27 16:49:31 +00:00
Add variable constraints, rename type annotation checks to constraints, rename custom type to annotation
This commit is contained in:
parent
92a496e584
commit
3e658e4780
16 changed files with 237 additions and 118 deletions
|
|
@ -96,6 +96,27 @@ common = {
|
|||
common.scope:set_last_scope(state, fn)
|
||||
end
|
||||
end,
|
||||
--- checks if the value is compatible with the variable's (eventual) constraint
|
||||
-- returns depth, or math.huge if no constraint
|
||||
-- returns nil, err
|
||||
check_constraint = function(state, fqm, val)
|
||||
local constraint = state.variable_constraints[fqm]
|
||||
if constraint then
|
||||
if not constraint.value then
|
||||
local v, e = eval(state, constraint.pending)
|
||||
if not v then
|
||||
return nil, ("%s; while evaluating constraint for variable %q"):format(e, fqm)
|
||||
end
|
||||
constraint.value = v
|
||||
end
|
||||
local depth = common.is_of_type(val, constraint.value)
|
||||
if not depth then
|
||||
return nil, ("constraint check failed")
|
||||
end
|
||||
return depth
|
||||
end
|
||||
return math.huge
|
||||
end,
|
||||
--- returns a variable's value, evaluating a pending expression if neccessary
|
||||
-- if you're sure the variable has already been evaluated, use state.variables[fqm] directly
|
||||
-- return var
|
||||
|
|
@ -107,15 +128,25 @@ common = {
|
|||
if not v then
|
||||
return nil, ("%s; while evaluating default value for variable %q defined at %s"):format(e, fqm, var.value.source)
|
||||
end
|
||||
state.variables[fqm] = v
|
||||
local s, err = common.set_variable(state, fqm, v)
|
||||
if not s then return nil, err end
|
||||
return v
|
||||
else
|
||||
return var
|
||||
end
|
||||
end,
|
||||
--- set the value of a variable
|
||||
-- returns true
|
||||
-- returns nil, err
|
||||
set_variable = function(state, name, val)
|
||||
if val.type ~= "pending definition" then
|
||||
local s, e = common.check_constraint(state, name, val)
|
||||
if not s then
|
||||
return nil, ("%s; while assigning value to variable %q"):format(e, name)
|
||||
end
|
||||
end
|
||||
state.variables[name] = val
|
||||
return true
|
||||
end,
|
||||
--- handle scoped function
|
||||
scope = {
|
||||
|
|
@ -207,7 +238,7 @@ common = {
|
|||
if a.type ~= b.type then
|
||||
return false
|
||||
end
|
||||
if a.type == "pair" or a.type == "type" then
|
||||
if a.type == "pair" or a.type == "annotated" then
|
||||
return common.compare(a.value[1], b.value[1]) and common.compare(a.value[2], b.value[2])
|
||||
elseif a.type == "list" then
|
||||
if #a.value ~= #b.value then
|
||||
|
|
@ -300,18 +331,23 @@ common = {
|
|||
end
|
||||
return true
|
||||
end,
|
||||
--- check if an anselme value is of a certain type
|
||||
--- check if an anselme value is of a certain type or annotation
|
||||
-- specificity(number): if var is of type type. lower is more specific
|
||||
-- false: if not
|
||||
is_of_type = function(var, type)
|
||||
local depth = 1
|
||||
-- var has a custom type
|
||||
if var.type == "type" then
|
||||
-- var has a custom annotation
|
||||
if var.type == "annotated" then
|
||||
-- special case: if we just want to see if a value is annotated
|
||||
if type.type == "string" and type.value == "annotated" then
|
||||
return depth
|
||||
end
|
||||
-- check annotation
|
||||
local var_type = var.value[2]
|
||||
while true do
|
||||
if common.compare(var_type, type) then -- same type
|
||||
return depth
|
||||
elseif var_type.type == "type" then -- compare parent type
|
||||
elseif var_type.type == "annotated" then -- compare parent type
|
||||
depth = depth + 1
|
||||
var_type = var_type.value[2]
|
||||
else -- no parent, fall back on base type
|
||||
|
|
@ -326,7 +362,7 @@ common = {
|
|||
end,
|
||||
-- return a pretty printable type value for var
|
||||
pretty_type = function(var)
|
||||
if var.type == "type" then
|
||||
if var.type == "annotated" then
|
||||
return common.format(var.value[2])
|
||||
else
|
||||
return var.type
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
local expression
|
||||
local to_lua, from_lua, eval_text, is_of_type, truthy, format, pretty_type, get_variable, tags, eval_text_callback, events, flatten_list, set_variable, scope
|
||||
local to_lua, from_lua, eval_text, truthy, format, pretty_type, get_variable, tags, eval_text_callback, events, flatten_list, set_variable, scope, check_constraint
|
||||
|
||||
local run
|
||||
|
||||
|
|
@ -71,7 +71,8 @@ local function eval(state, exp)
|
|||
local name = exp.left.name
|
||||
local val, vale = eval(state, exp.right)
|
||||
if not val then return nil, vale end
|
||||
set_variable(state, name, val)
|
||||
local s, e = set_variable(state, name, val)
|
||||
if not s then return nil, e end
|
||||
return val
|
||||
else
|
||||
return nil, ("don't know how to perform assignment on %s expression"):format(exp.left.type)
|
||||
|
|
@ -240,24 +241,19 @@ local function eval(state, exp)
|
|||
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 nil, 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
|
||||
-- check type constraint
|
||||
local depth, err = check_constraint(state, param.full_name, val)
|
||||
if not depth then
|
||||
ok = false
|
||||
local v = state.variable_constraints[param.full_name].value
|
||||
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
|
||||
-- set
|
||||
variant_args[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
|
||||
-- there's no need to type check because the type constraint is already the default value's type, because of syntax
|
||||
elseif param.default then
|
||||
variant_args[param.full_name] = { type = "pending definition", value = { expression = param.default, source = fn.source } }
|
||||
else
|
||||
|
|
@ -282,21 +278,15 @@ local function eval(state, exp)
|
|||
end
|
||||
-- assignment arg
|
||||
if ok and exp.assignment then
|
||||
-- check type annotation
|
||||
-- check type constraint
|
||||
local param = fn.assignment
|
||||
if param.type_annotation then
|
||||
local v, e = eval(state, param.type_annotation)
|
||||
if not v then return nil, 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
|
||||
local depth, err = check_constraint(state, param.full_name, assignment)
|
||||
if not depth then
|
||||
ok = false
|
||||
local v = state.variable_constraints[param.full_name].value
|
||||
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))
|
||||
end
|
||||
depths.assignment = depth
|
||||
-- set
|
||||
variant_args[param.full_name] = assignment
|
||||
end
|
||||
|
|
@ -360,7 +350,8 @@ local function eval(state, exp)
|
|||
end
|
||||
-- set arguments
|
||||
for name, val in pairs(selected_variant.args_to_set) do
|
||||
set_variable(state, name, val)
|
||||
local s, e = set_variable(state, name, val)
|
||||
if not s then return nil, e end
|
||||
end
|
||||
-- get function vars
|
||||
local checkpoint, checkpointe = get_variable(state, fn.namespace.."🔖")
|
||||
|
|
@ -393,11 +384,11 @@ local function eval(state, exp)
|
|||
else
|
||||
return nil, ("%s; in Lua function %q"):format(e or "raw function returned nil and no error message", exp.called_name)
|
||||
end
|
||||
-- untyped raw mode: same as raw, but strips custom types from the arguments
|
||||
elseif lua_fn.mode == "untyped raw" then
|
||||
-- unannotated raw mode: same as raw, but strips custom annotations from the arguments
|
||||
elseif lua_fn.mode == "unannotated raw" then
|
||||
-- extract value from custom types
|
||||
for i, arg in ipairs(final_args) do
|
||||
if arg.type == "type" then
|
||||
if arg.type == "annotated" then
|
||||
final_args[i] = arg.value[1]
|
||||
end
|
||||
end
|
||||
|
|
@ -405,7 +396,7 @@ local function eval(state, exp)
|
|||
if r then
|
||||
ret = r
|
||||
else
|
||||
return nil, ("%s; in Lua function %q"):format(e or "untyped raw function returned nil and no error message", exp.called_name)
|
||||
return nil, ("%s; in Lua function %q"):format(e or "unannotated raw function returned nil and no error message", exp.called_name)
|
||||
end
|
||||
-- normal mode: convert args to Lua and convert back Lua value to Anselme
|
||||
elseif lua_fn.mode == nil then
|
||||
|
|
@ -444,14 +435,15 @@ local function eval(state, exp)
|
|||
if not ret then return nil, e end
|
||||
end
|
||||
-- update function vars
|
||||
set_variable(state, fn.namespace.."👁️", {
|
||||
local s, e = set_variable(state, fn.namespace.."👁️", {
|
||||
type = "number",
|
||||
value = seen.value + 1
|
||||
})
|
||||
if not s then return nil, e end
|
||||
-- for classes: build resulting object
|
||||
if fn.subtype == "class" then
|
||||
local object = {
|
||||
type = "type",
|
||||
type = "annotated",
|
||||
value = {
|
||||
{
|
||||
type = "object",
|
||||
|
|
@ -519,6 +511,6 @@ run = require((...):gsub("expression$", "interpreter")).run
|
|||
expression = require((...):gsub("interpreter%.expression$", "parser.expression"))
|
||||
flatten_list = require((...):gsub("interpreter%.expression$", "parser.common")).flatten_list
|
||||
local common = require((...):gsub("expression$", "common"))
|
||||
to_lua, from_lua, eval_text, is_of_type, truthy, format, pretty_type, get_variable, tags, eval_text_callback, events, set_variable, scope = common.to_lua, common.from_lua, common.eval_text, common.is_of_type, common.truthy, common.format, common.pretty_type, common.get_variable, common.tags, common.eval_text_callback, common.events, common.set_variable, common.scope
|
||||
to_lua, from_lua, eval_text, truthy, format, pretty_type, get_variable, tags, eval_text_callback, events, set_variable, scope, check_constraint = common.to_lua, common.from_lua, common.eval_text, common.truthy, common.format, common.pretty_type, common.get_variable, common.tags, common.eval_text_callback, common.events, common.set_variable, common.scope, common.check_constraint
|
||||
|
||||
return eval
|
||||
|
|
|
|||
|
|
@ -114,14 +114,16 @@ run_line = function(state, line)
|
|||
elseif line.type == "function" and line.subtype == "checkpoint" then
|
||||
local reached, reachede = get_variable(state, line.namespace.."🏁")
|
||||
if not reached then return nil, reachede end
|
||||
set_variable(state, line.namespace.."🏁", {
|
||||
local s, e = set_variable(state, line.namespace.."🏁", {
|
||||
type = "number",
|
||||
value = reached.value + 1
|
||||
})
|
||||
set_variable(state, line.parent_function.namespace.."🔖", {
|
||||
if not s then return nil, e end
|
||||
s, e = set_variable(state, line.parent_function.namespace.."🔖", {
|
||||
type = "function reference",
|
||||
value = { line.name }
|
||||
})
|
||||
if not s then return nil, e end
|
||||
merge_state(state)
|
||||
else
|
||||
return nil, ("unknown line type %q; at %s"):format(line.type, line.source)
|
||||
|
|
@ -160,21 +162,24 @@ run_block = function(state, block, resume_from_there, i, j)
|
|||
if not seen then return nil, seene end
|
||||
local checkpoint, checkpointe = get_variable(state, parent_line.parent_function.namespace.."🔖")
|
||||
if not checkpoint then return nil, checkpointe end
|
||||
set_variable(state, parent_line.namespace.."👁️", {
|
||||
local s, e = set_variable(state, parent_line.namespace.."👁️", {
|
||||
type = "number",
|
||||
value = seen.value + 1
|
||||
})
|
||||
set_variable(state, parent_line.namespace.."🏁", {
|
||||
if not s then return nil, e end
|
||||
s, e = set_variable(state, parent_line.namespace.."🏁", {
|
||||
type = "number",
|
||||
value = reached.value + 1
|
||||
})
|
||||
if not s then return nil, e end
|
||||
-- don't update checkpoint if an already more precise checkpoint is set
|
||||
-- (since we will go up the whole checkpoint hierarchy when resuming from a nested checkpoint)
|
||||
if checkpoint.type == "nil" or not checkpoint.value[1]:match("^"..escape(parent_line.name)) then
|
||||
set_variable(state, parent_line.parent_function.namespace.."🔖", {
|
||||
s, e = set_variable(state, parent_line.parent_function.namespace.."🔖", {
|
||||
type = "function reference",
|
||||
value = { parent_line.name }
|
||||
})
|
||||
if not s then return nil, e end
|
||||
end
|
||||
merge_state(state)
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue