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
20
LANGUAGE.md
20
LANGUAGE.md
|
|
@ -139,7 +139,7 @@ $ fn
|
|||
|
||||
The function body is not executed when the line is reached; it must be explicitely called in an expression. See [expressions](#function-calls) to see the different ways of calling a function.
|
||||
|
||||
A parameter list can be optionally given after the identifier. Parameter names are identifiers, with eventually an alias (after a `:`) and a default value (after a `=`), and then a type annotation (after a `::`). It is enclosed with paranthesis and contain a comma-separated list of identifiers:
|
||||
A parameter list can be optionally given after the identifier. Parameter names are identifiers, with eventually an alias (after a `:`) and a default value (after a `=`), and then a type constraint (after a `::`). It is enclosed with paranthesis and contain a comma-separated list of identifiers:
|
||||
|
||||
```
|
||||
$ f(a, b: alias for b, c="default for c", d: alias for d = "default for d")
|
||||
|
|
@ -195,7 +195,7 @@ $ g()
|
|||
|
||||
This is basically the behaviour you'd expect from functions in most other programming languages, and what you would use in Anselme any time you don't care about storing the function variables or want the exact same initial function variables each time you call the function (e.g. recursion). Scoped variables are not kept in save files, and are not affected by checkpointing.
|
||||
|
||||
Functions with the same name can be defined, as long as they have a different arguments. Functions will be selected based on the number of arguments given, their name and their type annotation:
|
||||
Functions with the same name can be defined, as long as they have a different arguments. Functions will be selected based on the number of arguments given, their name and their type constraint:
|
||||
|
||||
```
|
||||
$ f(a, b)
|
||||
|
|
@ -289,7 +289,7 @@ Is 3: {object.a}
|
|||
Is 1: {class.a}
|
||||
```
|
||||
|
||||
Note that the new object returned by the class is also automatically given a type that is a reference to the class. This can be used to define methods/function that operate only on objects based on this specific class.
|
||||
Note that the new object returned by the class is also automatically given an annotation that is a reference to the class. This can be used to define methods/function that operate only on objects based on this specific class.
|
||||
|
||||
```
|
||||
% class
|
||||
|
|
@ -620,7 +620,7 @@ Default types are:
|
|||
|
||||
* `pair`: a couple of values. Types can be mixed. Can be defined using equal sign `"key"=5`. Pairs named by a string that is also a valid identifier can be created using the `key=5` shorthand syntax; `key` will not be interpreted as the variable `key` but the string `"key"` (if `key` is a variable and you want to force the use of its value as a key instead of the string `"key"`, you can wrap it in parentheses).
|
||||
|
||||
* `type`: a couple of values. Types can be mixed. Can be defined using colon `expr::type`. The second value is used in type checks, this is intended to be use to give a custom type to a value.
|
||||
* `annotated`: a couple of values. Types can be mixed. Can be defined using colon `expr::type`. The second value is used in type constraints, this is intended to be use to give a custom type to a value.
|
||||
|
||||
* `function reference`: reference to one or more function(s) with a given name. Can be defined using `&function name`, which will create a reference to every function with this name accessible from the current namespace. Can be called as if it was the original function using `func ref!` and `func ref(args)`.
|
||||
|
||||
|
|
@ -797,7 +797,7 @@ $ f(a, b...)
|
|||
[2,3,4,5]
|
||||
```
|
||||
|
||||
Anselme use dynamic dispatch, meaning the correct function is selected at runtime. The correct function is selected based on number of arguments, argument names, and argument type annotations. The function with the most specific arguments will be selected. If several functions match, an error is thrown.
|
||||
Anselme use dynamic dispatch, meaning the correct function is selected at runtime. The correct function is selected based on number of arguments, argument names, and argument type constraint. The function with the most specific arguments will be selected. If several functions match, an error is thrown.
|
||||
|
||||
```
|
||||
$ fn(x::number, y)
|
||||
|
|
@ -827,6 +827,8 @@ $ g(x, a="t")
|
|||
error, can't select unique function: {g(5)}
|
||||
```
|
||||
|
||||
Note that types constraints are expected to be constant and are evaluated only once. Default values, however, are evaluated each time the function is called (and the user didn't explicitely give an argument that would replace this default).
|
||||
|
||||
#### Checkpoint calls
|
||||
|
||||
Most of the time, you should'nt need to call checkpoints yourself - they will be automatically be set as the active checkpoint when the interperter reach their line, and they will be automatically called when resuming its parent function.
|
||||
|
|
@ -980,7 +982,7 @@ This only works on strings:
|
|||
|
||||
`a = b`: evaluate a and b, returns a new pair with a as key and b as value. If a is an identifier, will interpret it as a string (and not a variable; you can wrap a in parentheses if you want to use the value associated with variable a instead).
|
||||
|
||||
`a :: b`: evaluate a and b, returns a new typed value with a as value and b as type.
|
||||
`a :: b`: evaluate a and b, returns a new annotated value with a as value and b as the annotation. This annotation will be checked in type constraints.
|
||||
|
||||
`a # b`: evaluates b, then evaluates a whith b added to the active tags. Returns a.
|
||||
|
||||
|
|
@ -1030,10 +1032,14 @@ This only works on strings:
|
|||
|
||||
`error(str)`: throw an error with the specified message
|
||||
|
||||
`raw(v)`: return v, stripped of its custom types
|
||||
`annotation(v::annotated)`: returns v's annotation
|
||||
|
||||
`unannotated(v)`: return v, eventual annotations removed
|
||||
|
||||
`type(v)`: return v's type
|
||||
|
||||
`is a(v, type or annotation)`: check if v is of a certain type or annotation
|
||||
|
||||
#### Built-in variables
|
||||
|
||||
Variables for default types (each is associated to a string of the internal variable type name): `nil`, `number`, `string`, `list`, `pair`, `function reference`, `variable reference`.
|
||||
|
|
|
|||
|
|
@ -667,6 +667,7 @@ local vm_mt = {
|
|||
builtin_aliases = self.state.builtin_aliases,
|
||||
aliases = setmetatable({}, { __index = self.state.aliases }),
|
||||
functions = self.state.functions, -- no need for a cache as we can't define or modify any function from the interpreter for now
|
||||
variable_constraints = self.state.variable_constraints, -- no cache as constraints are expected to be constant
|
||||
variables = setmetatable({}, {
|
||||
__index = function(variables, k)
|
||||
local cache = getmetatable(variables).cache
|
||||
|
|
@ -759,6 +760,9 @@ return setmetatable(anselme, {
|
|||
-- }, ...
|
||||
-- }, ...
|
||||
},
|
||||
variable_constraints = {
|
||||
-- foo = { constraint }, ...
|
||||
},
|
||||
variables = {
|
||||
-- foo = {
|
||||
-- type = "number",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -307,8 +307,8 @@ common = {
|
|||
if p.alias then
|
||||
sig = sig .. ":" .. p.alias
|
||||
end
|
||||
if p.type_annotation then
|
||||
sig = sig .. "::" .. p.type_annotation
|
||||
if p.type_constraint then
|
||||
sig = sig .. "::" .. p.type_constraint
|
||||
end
|
||||
if p.default then
|
||||
sig = sig .. "=" .. p.default
|
||||
|
|
|
|||
|
|
@ -8,17 +8,17 @@ local function parse(state)
|
|||
for i=#state.queued_lines, 1, -1 do
|
||||
local l = state.queued_lines[i]
|
||||
local line, namespace = l.line, l.namespace
|
||||
-- default arguments and type annotation
|
||||
-- default arguments and type constraints
|
||||
if line.type == "function" then
|
||||
for _, param in ipairs(line.params) do
|
||||
-- get type annotation
|
||||
if param.type_annotation then
|
||||
local type_exp, rem = expression(param.type_annotation, state, namespace)
|
||||
if not type_exp then return nil, ("in type annotation, %s; at %s"):format(rem, line.source) end
|
||||
-- get type constraints
|
||||
if param.type_constraint then
|
||||
local type_exp, rem = expression(param.type_constraint, state, namespace)
|
||||
if not type_exp then return nil, ("in type constraint, %s; at %s"):format(rem, line.source) end
|
||||
if rem:match("[^%s]") then
|
||||
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param.full_name, rem, line.source)
|
||||
end
|
||||
param.type_annotation = type_exp
|
||||
state.variable_constraints[param.full_name] = { pending = type_exp }
|
||||
end
|
||||
-- get default value
|
||||
if param.default then
|
||||
|
|
@ -28,20 +28,20 @@ local function parse(state)
|
|||
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(param.full_name, rem, line.source)
|
||||
end
|
||||
param.default = default_exp
|
||||
-- extract type annotation from default value
|
||||
-- extract type constraint from default value
|
||||
if default_exp.type == "function call" and default_exp.called_name == "_::_" then
|
||||
param.type_annotation = default_exp.argument.expression.right
|
||||
state.variable_constraints[param.full_name] = { pending = default_exp.argument.expression.right }
|
||||
end
|
||||
end
|
||||
end
|
||||
-- assignment argument
|
||||
if line.assignment and line.assignment.type_annotation then
|
||||
local type_exp, rem = expression(line.assignment.type_annotation, state, namespace)
|
||||
if not type_exp then return nil, ("in type annotation, %s; at %s"):format(rem, line.source) end
|
||||
if line.assignment and line.assignment.type_constraint then
|
||||
local type_exp, rem = expression(line.assignment.type_constraint, state, namespace)
|
||||
if not type_exp then return nil, ("in type constraint, %s; at %s"):format(rem, line.source) end
|
||||
if rem:match("[^%s]") then
|
||||
return nil, ("unexpected characters after parameter %q: %q; at %s"):format(line.assignment.full_name, rem, line.source)
|
||||
end
|
||||
line.assignment.type_annotation = type_exp
|
||||
state.variable_constraints[line.assignment.full_name] = { pending = type_exp }
|
||||
end
|
||||
-- get list of scoped variables
|
||||
-- (note includes every variables in the namespace of subnamespace, so subfunctions are scoped alongside this function)
|
||||
|
|
@ -63,6 +63,15 @@ local function parse(state)
|
|||
-- variable pending definition: expression will be evaluated when variable is needed
|
||||
if line.type == "definition" then
|
||||
state.variables[line.fqm].value.expression = line.expression
|
||||
-- parse constraints
|
||||
if line.constraint then
|
||||
local type_exp, rem2 = expression(line.constraint, state, namespace)
|
||||
if not type_exp then return nil, ("in type constraint, %s; at %s"):format(rem2, line.source) end
|
||||
if rem2:match("[^%s]") then
|
||||
return nil, ("unexpected characters after variable %q: %q; at %s"):format(line.fqm, rem2, line.source)
|
||||
end
|
||||
state.variable_constraints[line.fqm] = { pending = type_exp }
|
||||
end
|
||||
end
|
||||
end
|
||||
-- text (text & choice lines)
|
||||
|
|
|
|||
|
|
@ -134,17 +134,17 @@ 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 annotation and default value
|
||||
local type_annotation, default
|
||||
-- get potential type constraints and default value
|
||||
local type_constraint, default
|
||||
if param_rem:match("^::") then
|
||||
type_annotation = param_rem:match("^::(.*)$")
|
||||
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_annotation = type_annotation, default = default, vararg = nil })
|
||||
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
|
||||
|
|
@ -160,15 +160,15 @@ 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 annotation
|
||||
local type_annotation
|
||||
-- get potential type constraint
|
||||
local type_constraint
|
||||
if param_rem:match("^::") then
|
||||
type_annotation = param_rem:match("^::(.*)$")
|
||||
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)
|
||||
end
|
||||
-- add parameter
|
||||
r.assignment = { name = param_identifier, alias = param_alias, full_name = param_fqm, type_annotation = type_annotation, default = nil, 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
|
||||
|
|
@ -300,6 +300,10 @@ local function parse_line(line, state, namespace, parent_function)
|
|||
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
|
||||
|
|
@ -509,6 +513,7 @@ local function parse(state, s, name, source)
|
|||
local state_proxy = {
|
||||
inject = {},
|
||||
aliases = setmetatable({}, { __index = state.aliases }),
|
||||
variable_constraints = setmetatable({}, { __index = state.variable_constraints }),
|
||||
variables = setmetatable({}, { __index = state.aliases }),
|
||||
functions = setmetatable({}, {
|
||||
__index = function(self, key)
|
||||
|
|
@ -541,6 +546,9 @@ local function parse(state, s, name, source)
|
|||
for k,v in pairs(state_proxy.aliases) do
|
||||
state.aliases[k] = v
|
||||
end
|
||||
for k,v in pairs(state_proxy.variable_constraints) do
|
||||
state.variable_constraints[k] = v
|
||||
end
|
||||
for k,v in pairs(state_proxy.variables) do
|
||||
state.variables[k] = v
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ return [[
|
|||
:function reference="function reference"
|
||||
:variable reference="variable reference"
|
||||
:object="object"
|
||||
:annotated="annotated"
|
||||
|
||||
:pi=3.1415926535898
|
||||
]]
|
||||
|
|
|
|||
|
|
@ -64,19 +64,19 @@ lua_functions = {
|
|||
}
|
||||
end
|
||||
},
|
||||
-- type
|
||||
-- annotate
|
||||
["_::_(a, b)"] = {
|
||||
mode = "raw",
|
||||
value = function(a, b)
|
||||
return {
|
||||
type = "type",
|
||||
type = "annotated",
|
||||
value = { a, b }
|
||||
}
|
||||
end
|
||||
},
|
||||
-- namespace
|
||||
["_._(r::function reference, name::string)"] = {
|
||||
mode = "untyped raw",
|
||||
mode = "unannotated raw",
|
||||
value = function(r, n)
|
||||
local state = anselme.running.state
|
||||
local rval = r.value
|
||||
|
|
@ -100,7 +100,7 @@ lua_functions = {
|
|||
end
|
||||
},
|
||||
["_._(r::function reference, name::string) := v"] = {
|
||||
mode = "untyped raw",
|
||||
mode = "unannotated raw",
|
||||
value = function(r, n, v)
|
||||
local state = anselme.running.state
|
||||
local rval = r.value
|
||||
|
|
@ -108,7 +108,8 @@ lua_functions = {
|
|||
for _, ffqm in ipairs(rval) do
|
||||
local var, vfqm = find(state.aliases, state.interpreter.global_state.variables, "", ffqm.."."..name)
|
||||
if var then
|
||||
set_variable(state, vfqm, v)
|
||||
local s, e = set_variable(state, vfqm, v)
|
||||
if not s then return nil, e end
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
|
@ -116,7 +117,7 @@ lua_functions = {
|
|||
end
|
||||
},
|
||||
["_._(r::object, name::string)"] = {
|
||||
mode = "untyped raw",
|
||||
mode = "unannotated raw",
|
||||
value = function(r, n)
|
||||
local state = anselme.running.state
|
||||
local obj = r.value
|
||||
|
|
@ -139,7 +140,7 @@ lua_functions = {
|
|||
end
|
||||
},
|
||||
["_._(r::object, name::string) := v"] = {
|
||||
mode = "untyped raw",
|
||||
mode = "unannotated raw",
|
||||
value = function(r, n, v)
|
||||
local state = anselme.running.state
|
||||
local obj = r.value
|
||||
|
|
@ -163,13 +164,13 @@ lua_functions = {
|
|||
},
|
||||
-- index
|
||||
["()(l::list, i::number)"] = {
|
||||
mode = "untyped raw",
|
||||
mode = "unannotated raw",
|
||||
value = function(l, i)
|
||||
return l.value[i.value] or { type = "nil", value = nil }
|
||||
end
|
||||
},
|
||||
["()(l::list, i::string)"] = {
|
||||
mode = "untyped raw",
|
||||
mode = "unannotated raw",
|
||||
value = function(l, i)
|
||||
for _, v in ipairs(l.value) do
|
||||
if v.type == "pair" and compare(v.value[1], i) then
|
||||
|
|
@ -183,8 +184,8 @@ lua_functions = {
|
|||
["()(l::list, i::number) := v"] = {
|
||||
mode = "raw",
|
||||
value = function(l, i, v)
|
||||
local lv = l.type == "type" and l.value[1] or l
|
||||
local iv = i.type == "type" and i.value[1] or i
|
||||
local lv = l.type == "annotated" and l.value[1] or l
|
||||
local iv = i.type == "annotated" and i.value[1] or i
|
||||
lv.value[iv.value] = v
|
||||
mark_as_modified(anselme.running.state, lv.value)
|
||||
return v
|
||||
|
|
@ -193,8 +194,8 @@ lua_functions = {
|
|||
["()(l::list, k::string) := v"] = {
|
||||
mode = "raw",
|
||||
value = function(l, k, v)
|
||||
local lv = l.type == "type" and l.value[1] or l
|
||||
local kv = k.type == "type" and k.value[1] or k
|
||||
local lv = l.type == "annotated" and l.value[1] or l
|
||||
local kv = k.type == "annotated" and k.value[1] or k
|
||||
-- update index
|
||||
for _, x in ipairs(lv.value) do
|
||||
if x.type == "pair" and compare(x.value[1], kv) then
|
||||
|
|
@ -219,7 +220,7 @@ lua_functions = {
|
|||
-- bypassed, this case is manually handled in the expression interpreter
|
||||
},
|
||||
["_!(fn::variable reference)"] = {
|
||||
mode = "untyped raw",
|
||||
mode = "unannotated raw",
|
||||
value = function(v)
|
||||
return get_variable(anselme.running.state, v.value)
|
||||
end
|
||||
|
|
@ -233,7 +234,7 @@ lua_functions = {
|
|||
},
|
||||
-- alias
|
||||
["alias(ref::function reference, alias::string)"] = {
|
||||
mode = "untyped raw",
|
||||
mode = "unannotated raw",
|
||||
value = function(ref, alias)
|
||||
-- check identifiers
|
||||
alias = alias.value
|
||||
|
|
@ -252,7 +253,7 @@ lua_functions = {
|
|||
end
|
||||
},
|
||||
["alias(ref::variable reference, alias::string)"] = {
|
||||
mode = "untyped raw",
|
||||
mode = "unannotated raw",
|
||||
value = function(ref, alias)
|
||||
-- check identifiers
|
||||
alias = alias.value
|
||||
|
|
@ -270,20 +271,20 @@ lua_functions = {
|
|||
},
|
||||
-- pair methods
|
||||
["name(p::pair)"] = {
|
||||
mode = "untyped raw",
|
||||
mode = "unannotated raw",
|
||||
value = function(a)
|
||||
return a.value[1]
|
||||
end
|
||||
},
|
||||
["value(p::pair)"] = {
|
||||
mode = "untyped raw",
|
||||
mode = "unannotated raw",
|
||||
value = function(a)
|
||||
return a.value[2]
|
||||
end
|
||||
},
|
||||
-- list methods
|
||||
["len(l::list)"] = {
|
||||
mode = "untyped raw", -- raw to count pairs in the list
|
||||
mode = "unannotated raw", -- raw to count pairs in the list
|
||||
value = function(a)
|
||||
return {
|
||||
type = "number",
|
||||
|
|
@ -294,7 +295,7 @@ lua_functions = {
|
|||
["insert(l::list, v)"] = {
|
||||
mode = "raw",
|
||||
value = function(l, v)
|
||||
local lv = l.type == "type" and l.value[1] or l
|
||||
local lv = l.type == "annotated" and l.value[1] or l
|
||||
table.insert(lv.value, v)
|
||||
mark_as_modified(anselme.running.state, lv.value)
|
||||
return l
|
||||
|
|
@ -303,22 +304,22 @@ lua_functions = {
|
|||
["insert(l::list, i::number, v)"] = {
|
||||
mode = "raw",
|
||||
value = function(l, i, v)
|
||||
local lv = l.type == "type" and l.value[1] or l
|
||||
local iv = i.type == "type" and i.value[1] or i
|
||||
local lv = l.type == "annotated" and l.value[1] or l
|
||||
local iv = i.type == "annotated" and i.value[1] or i
|
||||
table.insert(lv.value, iv.value, v)
|
||||
mark_as_modified(anselme.running.state, lv.value)
|
||||
return l
|
||||
end
|
||||
},
|
||||
["remove(l::list)"] = {
|
||||
mode = "untyped raw",
|
||||
mode = "unannotated raw",
|
||||
value = function(l)
|
||||
mark_as_modified(anselme.running.state, l.value)
|
||||
return table.remove(l.value)
|
||||
end
|
||||
},
|
||||
["remove(l::list, i::number)"] = {
|
||||
mode = "untyped raw",
|
||||
mode = "unannotated raw",
|
||||
value = function(l, i)
|
||||
mark_as_modified(anselme.running.state, l.value)
|
||||
return table.remove(l.value, i.value)
|
||||
|
|
@ -327,7 +328,7 @@ lua_functions = {
|
|||
["find(l::list, v)"] = {
|
||||
mode = "raw",
|
||||
value = function(l, v)
|
||||
local lv = l.type == "type" and l.value[1] or l
|
||||
local lv = l.type == "annotated" and l.value[1] or l
|
||||
for i, x in ipairs(lv.value) do
|
||||
if compare(x, v) then
|
||||
return i
|
||||
|
|
@ -345,10 +346,10 @@ lua_functions = {
|
|||
["rand()"] = function() return math.random() end,
|
||||
["rand(a::number)"] = function(a) return math.random(a) end,
|
||||
["rand(a::number, b::number)"] = function(a, b) return math.random(a, b) end,
|
||||
["raw(v)"] = {
|
||||
["unannotated(v)"] = {
|
||||
mode = "raw",
|
||||
value = function(v)
|
||||
if v.type == "type" then
|
||||
if v.type == "annotated" then
|
||||
return v.value[1]
|
||||
else
|
||||
return v
|
||||
|
|
@ -356,19 +357,21 @@ lua_functions = {
|
|||
end
|
||||
},
|
||||
["type(v)"] = {
|
||||
mode = "raw",
|
||||
mode = "unannotated raw",
|
||||
value = function(v)
|
||||
if v.type == "type" then
|
||||
return v.value[2]
|
||||
else
|
||||
return {
|
||||
type = "string",
|
||||
value = v.type
|
||||
}
|
||||
end
|
||||
return {
|
||||
type = "string",
|
||||
value = v.type
|
||||
}
|
||||
end
|
||||
},
|
||||
["is of type(v, t)"] = {
|
||||
["annotation(v::annotated)"] = {
|
||||
mode = "raw",
|
||||
value = function(v)
|
||||
return v.value[2]
|
||||
end
|
||||
},
|
||||
["is a(v, t)"] = {
|
||||
mode = "raw",
|
||||
value = function(v, t)
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ return [[
|
|||
~ &pair!alias("paire")
|
||||
~ &function reference!alias("réference de fonction")
|
||||
~ &variable reference!alias("réference de variable")
|
||||
~ &annotated!alias("annoté")
|
||||
|
||||
(Built-in functions)
|
||||
(~ &alias!alias("alias")
|
||||
|
|
@ -19,8 +20,8 @@ return [[
|
|||
~ &error!alias("erreur")
|
||||
~ &rand!alias("aléa")
|
||||
~ &raw!alias("brut")
|
||||
(~ &type!alias("type")
|
||||
~ &is of type!alias("est de type")
|
||||
~ &is a!alias("est un")
|
||||
~ &unannotated!alias("non annoté")
|
||||
~ &cycle!alias("cycler")
|
||||
~ &random!alias("aléatoire")
|
||||
~ &next!alias("séquence")
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ types.anselme = {
|
|||
return { [k] = v }
|
||||
end
|
||||
},
|
||||
type = {
|
||||
annotated = {
|
||||
format = function(val)
|
||||
local k, ke = format(val[1])
|
||||
if not k then return k, ke end
|
||||
|
|
|
|||
11
test/tests/constrained variable assignement.ans
Normal file
11
test/tests/constrained variable assignement.ans
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
:weigh::"kg" = 5::"kg"
|
||||
|
||||
{weigh}
|
||||
|
||||
~ weigh := 12::"kg"
|
||||
|
||||
{weigh}
|
||||
|
||||
~ weigh := 32
|
||||
|
||||
{weigh}
|
||||
22
test/tests/constrained variable assignement.lua
Normal file
22
test/tests/constrained variable assignement.lua
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
local _={}
|
||||
_[9]={}
|
||||
_[8]={}
|
||||
_[7]={text="12::kg",tags=_[9]}
|
||||
_[6]={text="5::kg",tags=_[8]}
|
||||
_[5]={_[7]}
|
||||
_[4]={_[6]}
|
||||
_[3]={"error","constraint check failed; while assigning value to variable \"constrained variable assignement.weigh\"; at test/tests/constrained variable assignement.ans:9"}
|
||||
_[2]={"text",_[5]}
|
||||
_[1]={"text",_[4]}
|
||||
return {_[1],_[2],_[3]}
|
||||
--[[
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "5::kg"
|
||||
} } }
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "12::kg"
|
||||
} } }
|
||||
{ "error", 'constraint check failed; while assigning value to variable "constrained variable assignement.weigh"; at test/tests/constrained variable assignement.ans:9' }
|
||||
]]--
|
||||
7
test/tests/constrained variable definition.ans
Normal file
7
test/tests/constrained variable definition.ans
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
:weigh::"kg" = 5::"kg"
|
||||
|
||||
{weigh}
|
||||
|
||||
:not weigh::"kg" = 12
|
||||
|
||||
{not weigh}
|
||||
14
test/tests/constrained variable definition.lua
Normal file
14
test/tests/constrained variable definition.lua
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
local _={}
|
||||
_[5]={}
|
||||
_[4]={text="5::kg",tags=_[5]}
|
||||
_[3]={_[4]}
|
||||
_[2]={"error","constraint check failed; while assigning value to variable \"constrained variable definition.not weigh\"; at test/tests/constrained variable definition.ans:7"}
|
||||
_[1]={"text",_[3]}
|
||||
return {_[1],_[2]}
|
||||
--[[
|
||||
{ "text", { {
|
||||
tags = {},
|
||||
text = "5::kg"
|
||||
} } }
|
||||
{ "error", 'constraint check failed; while assigning value to variable "constrained variable definition.not weigh"; at test/tests/constrained variable definition.ans:7' }
|
||||
]]--
|
||||
Loading…
Add table
Add a link
Reference in a new issue