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

Add maps; remove map emulation functionality from list; function and tags now internally use maps instead of lists

This commit is contained in:
Étienne Fildadut 2022-09-09 21:39:37 +09:00
parent bac5cdde01
commit 95462391e3
20 changed files with 699 additions and 139 deletions

View file

@ -303,7 +303,7 @@ $ show(object::&class)
~ object!show ~ object!show
``` ```
* `#`: tag line. Can be followed by an [expression](#expressions); otherwise nil expression is assumed. The results of the [expression](#expressions) will be added to the tags send along with any `choice` or `text` event sent from its children. Can be nested. * `#`: tag line. Can be followed by an [expression](#expressions); otherwise nil expression is assumed. The results of the [expression](#expressions) will be wrapped in a map and added to the tags send along with any `choice` or `text` event sent from its children. Can be nested.
``` ```
# color="red" # color="red"
@ -636,9 +636,11 @@ Default types are:
* `list`: a list of values. Mutable. Types can be mixed. Can be defined between square brackets and use comma as a separator '[1,2,3,4]'. * `list`: a list of values. Mutable. Types can be mixed. Can be defined between square brackets and use comma as a separator '[1,2,3,4]'.
* `map`: a map of keys-values. Mutable. Types can be mixed. Can be defined between curly braces and use comma as a separator, using pairs to define the key-values pairs (otherwise the numeric position of the element is used as a key). '{1=1,2=2,3,4="heh",foo="bar"}'.
* `object`: an object/record. Mutable. Can be created by calling a class function. * `object`: an object/record. Mutable. Can be created by calling a class function.
Every type is immutable, except `list` and `object`. Every type is immutable, except `list`, `map` and `object`.
How conversions are handled from Anselme to Lua: How conversions are handled from Anselme to Lua:
@ -648,7 +650,9 @@ How conversions are handled from Anselme to Lua:
* `string` -> `string` * `string` -> `string`
* `list` -> `table`. Pair elements in the list will be assigned as a key-value pair in the Lua list and its index skipped in the sequential part, e.g. `[1,2,"key"="value",3]` -> `{1,2,3,key="value"}`. * `list` -> `table` (purely sequential table).
* `map` -> `table` (will map each key to a key in the Lua table).
* `pair` -> `table`, with a single key-value pair. * `pair` -> `table`, with a single key-value pair.
@ -660,7 +664,7 @@ How conservions are handled from Lua to Anselme:
* `string` -> `string` * `string` -> `string`
* `table` -> `list`. First add the sequential part of the table in the list, then add pairs for the remaining elements, e.g. `{1,2,key="value",3}` -> `[1,2,3,"key"="value"]` * `table` -> `list` or `map`. Converted to a list if the table is purely sequential, otherwise returns a map; e.g. `{1,2,key="value",3}` -> `{1=1,2=2,3=3,key="value"}` and `{1,2,3}` -> [1,2,3]
* `boolean` -> `number`, 0 for false, 1 for true. * `boolean` -> `number`, 0 for false, 1 for true.
@ -819,7 +823,7 @@ $ f(a, b, c)
{f(1,2,3)} = {f(c=3,b=2,a=1)} = {f(1,2,c=3)} {f(1,2,3)} = {f(c=3,b=2,a=1)} = {f(1,2,c=3)}
``` ```
Anselme actually treat argument list are regular lists; named arguments are actually pairs. Arguments are evaluated left-to-right. Anselme actually treat argument maps are regular maps; named arguments are actually pairs and positional arguments are implicitely converted to pairs with their position as a key. Arguments are evaluated left-to-right. The call will error if one of the keys in the map is not a string or number.
This means that pairs can't be passed directly as arguments to a function (as they will be considered named arguments). If you want to use pairs, always wrap them in a list. This means that pairs can't be passed directly as arguments to a function (as they will be considered named arguments). If you want to use pairs, always wrap them in a list.
@ -953,7 +957,7 @@ Built-in operators:
`a := b`: evaluate b, assign its value to identifier `a`. Returns the new value. `a := b`: evaluate b, assign its value to identifier `a`. Returns the new value.
`a(index) := b`: evaluate b, assign its value to element of specific index in list `a`. Element is searched using the same method as list index operator `a(b)`; if indexing using a string and an associated pair doesn't exist, add a new one at the end of the list. Returns the new value. `a(index) := b`: evaluate b, assign its value to element of specific index in list/map `a`. Element is searched using the same method as list/map index operator `a(b)`; in the case of list, also allows to add a new element to the list by giving `len(a)+1` as the index. In the case of a map, if b is nil `()`, deletes the key-value pair from the map. Returns the new value.
`a.b := v`: if a is a function reference or an object, modify the b variable in the reference function or object. `a.b := v`: if a is a function reference or an object, modify the b variable in the reference function or object.
@ -1025,13 +1029,15 @@ This only works on strings:
`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`: 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. `a # b`: evaluates b, then evaluates a with b added to the active tags (wrap b in a map and merges it with the current tag map). Returns a.
`a.b`: if a is a function reference, returns the first found variable (or reference to a subfunction) named `b` in the referenced function namespace. When overloading this operator, if `b` is an identifier, the operator will interpret it as a string (instead of returning the evaluated value of the variable eventually associated to the identifier). `a.b`: if a is a function reference, returns the first found variable (or reference to a subfunction) named `b` in the referenced function namespace. When overloading this operator, if `b` is an identifier, the operator will interpret it as a string (instead of returning the evaluated value of the variable eventually associated to the identifier).
`object.b`: if object is an object, returns the first found variable (or reference to a subfunction) named `b` in the object, or, if the object does not contain it, its base class. `object.b`: if object is an object, returns the first found variable (or reference to a subfunction) named `b` in the object, or, if the object does not contain it, its base class.
`a(b)`: evaluate b (number), returns the value with this index in a (list). Use 1-based indexing. If b is a string, will search the first pair in the list with this string as its name. Operator is named `()`. `list(b)`: evaluate b (number), returns the value with this index in the list. Use 1-based indexing. If a negative value is given, will look from the end of the list (`-1` is the last element, `-2` the one before, etc.). Error on invalid index. Operator is named `()`.
`map(b)`: evaluate b, returns the value with this key in the map. If the key is not present in the map, returns `nil`.
`{}(v)`: function called when formatting a value in a text interpolation for printing. `{}(v)`: function called when formatting a value in a text interpolation for printing.

View file

@ -48,6 +48,14 @@ local function post_process_text(state, text)
return r return r
end end
local function random_identifier()
local r = ""
for _=1, 16 do -- that's live 10^31 possibilities, ought to be enough for anyone
r = r .. string.char(math.random(32, 126))
end
return r
end
common = { common = {
--- merge interpreter state with global state --- merge interpreter state with global state
merge_state = function(state) merge_state = function(state)
@ -129,18 +137,11 @@ common = {
end, end,
--- mark a value as constant, recursively affecting all the potentially mutable subvalues --- mark a value as constant, recursively affecting all the potentially mutable subvalues
mark_constant = function(v) mark_constant = function(v)
if v.type == "list" then if atypes[v.type] and atypes[v.type].mark_constant then
v.constant = true atypes[v.type].mark_constant(v)
for _, item in ipairs(v.value) do if v.hash_id then v.hash_id = nil end -- no longer need to compare by id
common.mark_constant(item) else
end error(("don't know how to mark type %s as constant"):format(v.type))
elseif v.type == "object" then
v.constant = true
elseif v.type == "pair" or v.type == "annotated" then
common.mark_constant(v.value[1])
common.mark_constant(v.value[2])
elseif v.type ~= "nil" and v.type ~= "number" and v.type ~= "string" and v.type ~= "function reference" and v.type ~= "variable reference" then
error("unknown type")
end end
end, end,
--- returns a variable's value, evaluating a pending expression if neccessary --- returns a variable's value, evaluating a pending expression if neccessary
@ -311,6 +312,8 @@ common = {
end end
end end
return true return true
elseif a.constant and a.type == "map" then
return common.hash(a) == common.hash(b)
elseif a.constant and a.type == "object" then elseif a.constant and a.type == "object" then
if a.value.class ~= b.value.class then if a.value.class ~= b.value.class then
return false return false
@ -348,6 +351,35 @@ common = {
return nil, ("no formatter for type %q"):format(val.type) return nil, ("no formatter for type %q"):format(val.type)
end end
end, end,
--- compute a hash for a value.
-- A hash is a Lua string such as, given two values, they are considered equal by Anselme if and only if their hash are considered equal by Lua.
-- Will generate random identifiers for mutable values (equality test by reference) in order for the identifier to stay the same accross checkpoints and
-- other potential variable copies.
-- str: if success
-- nil, err: if error
hash = function(val)
if atypes[val.type] and atypes[val.type].hash then
if atypes[val.type].mutable and not val.constant then
if not val.hash_id then val.hash_id = random_identifier() end
return ("mut(%s)"):format(val.hash_id)
else
return atypes[val.type].hash(val.value)
end
else
return nil, ("no hasher for type %q"):format(val.type)
end
end,
--- recompute all the hases in a map.
-- str: if success
-- nil, err: if error
update_hashes = function(map)
for k, v in pairs(map.value) do
local hash, e = common.hash(v[1])
if not hash then return nil, e end
map[k] = nil
map[hash] = v
end
end,
--- convert anselme value to lua --- convert anselme value to lua
-- lua value: if success (may be nil!) -- lua value: if success (may be nil!)
-- nil, err: if error -- nil, err: if error
@ -373,7 +405,8 @@ common = {
-- nil, err: if error -- nil, err: if error
eval_text = function(state, text) eval_text = function(state, text)
local l = {} local l = {}
common.eval_text_callback(state, text, function(str) table.insert(l, str) end) local s, e = common.eval_text_callback(state, text, function(str) table.insert(l, str) end)
if not s then return nil, e end
return table.concat(l) return table.concat(l)
end, end,
--- same as eval_text, but instead of building a Lua string, call callback for every evaluated part of the text --- same as eval_text, but instead of building a Lua string, call callback for every evaluated part of the text
@ -436,15 +469,14 @@ common = {
end, end,
--- tag management --- tag management
tags = { tags = {
--- push new tags on top of the stack, from Anselme values --- push new tags on top of the stack, from Anselme values. val is expected to be a map.
push = function(self, state, val) push = function(self, state, val)
local new = { type = "list", value = {} } local new = { type = "map", value = {} }
-- copy -- copy
local last = self:current(state) local last = self:current(state)
for _, v in ipairs(last.value) do table.insert(new.value, v) end for k, v in pairs(last.value) do new.value[k] = v end
-- append new values -- append new values
if val.type ~= "list" then val = { type = "list", value = { val } } end for k, v in pairs(val.value) do new.value[k] = v end
for _, v in ipairs(val.value) do table.insert(new.value, v) end
-- add -- add
table.insert(state.interpreter.tags, new) table.insert(state.interpreter.tags, new)
end, end,
@ -458,7 +490,7 @@ common = {
end, end,
--- return current lua tags table --- return current lua tags table
current = function(self, state) current = function(self, state)
return state.interpreter.tags[#state.interpreter.tags] or { type = "list", value = {} } return state.interpreter.tags[#state.interpreter.tags] or { type = "map", value = {} }
end, end,
--- returns length of tags stack --- returns length of tags stack
len = function(self, state) len = function(self, state)

View file

@ -1,5 +1,5 @@
local expression local expression
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 to_lua, from_lua, eval_text, truthy, format, pretty_type, get_variable, tags, eval_text_callback, events, flatten_list, set_variable, scope, check_constraint, hash
local run local run
@ -52,6 +52,30 @@ local function eval(state, exp)
value = {} value = {}
} }
end end
-- map defined in brackets
elseif exp.type == "map_brackets" then
-- get constructing list
local list, e = eval(state, { type = "list_brackets", expression = exp.expression })
if not list then return nil, e end
-- make map
local map = {}
for i, v in ipairs(list.value) do
local key, value
if v.type == "pair" then
key = v.value[1]
value = v.value[2]
else
key = { type = "number", value = i }
value = v
end
local h, err = hash(key)
if not h then return nil, err end
map[h] = { key, value }
end
return {
type = "map",
value = map
}
-- list defined using , operator -- list defined using , operator
elseif exp.type == "list" then elseif exp.type == "list" then
local flat = flatten_list(exp) local flat = flatten_list(exp)
@ -142,7 +166,7 @@ local function eval(state, exp)
} }
-- tag -- tag
elseif exp.type == "#" then elseif exp.type == "#" then
local right, righte = eval(state, exp.right) local right, righte = eval(state, { type = "map_brackets", expression = exp.right })
if not right then return nil, righte end if not right then return nil, righte end
tags:push(state, right) tags:push(state, right)
local left, lefte = eval(state, exp.left) local left, lefte = eval(state, exp.left)
@ -165,12 +189,24 @@ local function eval(state, exp)
} }
-- function -- function
elseif exp.type == "function call" then elseif exp.type == "function call" then
-- eval args: list_brackets -- eval args: map_brackets
local args = {} local args = {}
local last_contiguous_positional = 0
if exp.argument then if exp.argument then
local arg, arge = eval(state, exp.argument) local arg, arge = eval(state, exp.argument)
if not arg then return nil, arge end if not arg then return nil, arge end
args = arg.value -- map into args table
for _, v in pairs(arg.value) do
if v[1].type == "string" or v[1].type == "number" then
args[v[1].value] = v[2]
else
return nil, ("unexpected key of type %s in argument map; keys must be string or number"):format(v[1].type)
end
end
-- get length of contiguous positional arguments (#args may not be always be equal depending on implementation...)
for i, _ in ipairs(args) do
last_contiguous_positional = i
end
end end
-- function reference: call the referenced function -- function reference: call the referenced function
local variants = exp.variants local variants = exp.variants
@ -191,13 +227,6 @@ local function eval(state, exp)
end end
end end
end 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
end
-- eval assignment arg -- eval assignment arg
local assignment local assignment
if exp.assignment then if exp.assignment then
@ -222,21 +251,21 @@ local function eval(state, exp)
for j, param in ipairs(fn.params) do for j, param in ipairs(fn.params) do
local val local val
-- named -- named
if param.alias and named_args[param.alias] then if param.alias and args[param.alias] then
val = named_args[param.alias][2] val = args[param.alias]
used_args[named_args[param.alias][1]] = true used_args[param.alias] = true
elseif named_args[param.name] then elseif args[param.name] then
val = named_args[param.name][2] val = args[param.name]
used_args[named_args[param.name][1]] = true used_args[param.name] = true
-- vararg -- vararg
elseif param.vararg then elseif param.vararg then
val = { type = "list", value = {} } val = { type = "list", value = {} }
for k=j, #args do for k=j, last_contiguous_positional do
table.insert(val.value, args[k]) table.insert(val.value, args[k])
used_args[k] = true used_args[k] = true
end end
-- positional -- positional
elseif args[j] and args[j].type ~= "pair" then elseif args[j] then
val = args[j] val = args[j]
used_args[j] = true used_args[j] = true
end end
@ -264,8 +293,8 @@ local function eval(state, exp)
end end
-- check for unused arguments -- check for unused arguments
if ok then if ok then
for i, arg in ipairs(args) do for key, arg in pairs(args) do
if not used_args[i] then if not used_args[key] then
ok = false ok = false
if arg.type == "pair" and arg.value[1].type == "string" then 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)) table.insert(tried_function_error_messages, ("%s: unexpected %s argument"):format(fn.pretty_signature, arg.value[1].value))
@ -473,11 +502,10 @@ local function eval(state, exp)
end end
-- no matching function found -- no matching function found
local args_txt = {} local args_txt = {}
for _, arg in ipairs(args) do for key, arg in pairs(args) do
local s = "" local s = ""
if arg.type == "pair" and arg.value[1].type == "string" then if type(key) == "string" or (type(key) == "number" and key > last_contiguous_positional) then
s = s .. ("%s="):format(arg.value[1].value) s = s .. ("%s="):format(key)
arg = arg.value[2]
end end
s = s .. pretty_type(arg) s = s .. pretty_type(arg)
table.insert(args_txt, s) table.insert(args_txt, s)
@ -511,6 +539,6 @@ run = require((...):gsub("expression$", "interpreter")).run
expression = require((...):gsub("interpreter%.expression$", "parser.expression")) expression = require((...):gsub("interpreter%.expression$", "parser.expression"))
flatten_list = require((...):gsub("interpreter%.expression$", "parser.common")).flatten_list flatten_list = require((...):gsub("interpreter%.expression$", "parser.common")).flatten_list
local common = require((...):gsub("expression$", "common")) local common = require((...):gsub("expression$", "common"))
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 to_lua, from_lua, eval_text, truthy, format, pretty_type, get_variable, tags, eval_text_callback, events, set_variable, scope, check_constraint, hash = 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, common.hash
return eval return eval

View file

@ -287,7 +287,7 @@ common = {
implicit_call = implicit_call, -- was call implicitely (no ! or parentheses)? implicit_call = implicit_call, -- was call implicitely (no ! or parentheses)?
variants = variants, -- list of potential variants variants = variants, -- list of potential variants
argument = { -- wrap everything in a list literal to simplify later things (otherwise may be nil, single value, list constructor) argument = { -- wrap everything in a list literal to simplify later things (otherwise may be nil, single value, list constructor)
type = "list_brackets", type = "map_brackets",
expression = arg expression = arg
} }
} }

View file

@ -132,6 +132,21 @@ local function expression(s, state, namespace, current_priority, operating_on)
type = "list_brackets", type = "list_brackets",
expression = exp expression = exp
}) })
-- map parenthesis
elseif s:match("^%b{}") then
local content, r = s:match("^(%b{})(.*)$")
content = content:gsub("^%{", ""):gsub("%}$", "")
local exp
if content:match("[^%s]") then
local r_paren
exp, r_paren = expression(content, state, namespace)
if not exp then return nil, "invalid expression inside map parentheses: "..r_paren end
if r_paren:match("[^%s]") then return nil, ("unexpected %q at end of map parenthesis expression"):format(r_paren) end
end
return expression(r, state, namespace, current_priority, {
type = "map_brackets",
expression = exp
})
-- identifier -- identifier
elseif s:match("^"..identifier_pattern) then elseif s:match("^"..identifier_pattern) then
local name, r = s:match("^("..identifier_pattern..")(.-)$") local name, r = s:match("^("..identifier_pattern..")(.-)$")
@ -182,7 +197,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
local err local err
args, err = expression(content, state, namespace) args, err = expression(content, state, namespace)
if not args then return args, err end 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 if err:match("[^%s]") then return nil, ("unexpected %q at end of argument map"):format(err) end
end end
else -- implicit call; will be changed if there happens to be a ! after in the suffix operator code else -- implicit call; will be changed if there happens to be a ! after in the suffix operator code
implicit_call = true implicit_call = true
@ -267,7 +282,7 @@ local function expression(s, state, namespace, current_priority, operating_on)
local err local err
args, err = expression(content, state, namespace) args, err = expression(content, state, namespace)
if not args then return args, err end 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 if err:match("[^%s]") then return nil, ("unexpected %q at end of argument map"):format(err) end
end end
end end
-- add first argument -- add first argument

View file

@ -332,11 +332,7 @@ local function parse_line(line, state, namespace, parent_function)
r.type = "tag" r.type = "tag"
r.child = true r.child = true
local expr = l:match("^%#(.*)$") local expr = l:match("^%#(.*)$")
if expr:match("[^%s]") then r.expression = ("{%s}"):format(expr)
r.expression = expr
else
r.expression = "()"
end
-- return -- return
elseif l:match("^%@") then elseif l:match("^%@") then
r.type = "return" r.type = "return"

View file

@ -5,6 +5,7 @@ return [[
:number="number" :number="number"
:string="string" :string="string"
:list="list" :list="list"
:map="map"
:pair="pair" :pair="pair"
:function reference="function reference" :function reference="function reference"
:variable reference="variable reference" :variable reference="variable reference"

View file

@ -1,4 +1,4 @@
local truthy, anselme, compare, is_of_type, identifier_pattern, format_identifier, find, get_variable, mark_as_modified, set_variable, check_mutable, copy, mark_constant local truthy, anselme, compare, is_of_type, identifier_pattern, format_identifier, find, get_variable, mark_as_modified, set_variable, check_mutable, copy, mark_constant, hash
local lua_functions local lua_functions
lua_functions = { lua_functions = {
@ -173,18 +173,24 @@ lua_functions = {
["()(l::list, i::number)"] = { ["()(l::list, i::number)"] = {
mode = "unannotated raw", mode = "unannotated raw",
value = function(l, i) value = function(l, i)
return l.value[i.value] or { type = "nil", value = nil } local index = i.value
if index < 0 then index = #l.value + 1 + index end
if index > #l.value or index == 0 then return nil, "list index out of bounds" end
return l.value[index] or { type = "nil", value = nil }
end end
}, },
["()(l::list, i::string)"] = { ["()(l::map, k)"] = {
mode = "unannotated raw", mode = "raw",
value = function(l, i) value = function(l, i)
for _, v in ipairs(l.value) do local lv = l.type == "annotated" and l.value[1] or l
if v.type == "pair" and compare(v.value[1], i) then local h, err = hash(i)
return v.value[2] if not h then return nil, err end
end local v = lv.value[h]
if v then
return v[2]
else
return { type = "nil", value = nil }
end end
return { type = "nil", value = nil }
end end
}, },
-- index assignment -- index assignment
@ -194,30 +200,34 @@ lua_functions = {
local lv = l.type == "annotated" and l.value[1] or l local lv = l.type == "annotated" and l.value[1] or l
local iv = i.type == "annotated" and i.value[1] or i local iv = i.type == "annotated" and i.value[1] or i
if lv.constant then return nil, "can't change the contents of a constant list" end if lv.constant then return nil, "can't change the contents of a constant list" end
lv.value[iv.value] = v local index = iv.value
if index < 0 then index = #lv.value + 1 + index end
if index > #lv.value + 1 or index == 0 then return nil, "list assignment index out of bounds" end
lv.value[index] = v
mark_as_modified(anselme.running.state, lv.value) mark_as_modified(anselme.running.state, lv.value)
return v return v
end end
}, },
["()(l::list, k::string) := v"] = { ["()(l::map, k) := v::nil"] = {
mode = "raw", mode = "raw",
value = function(l, k, v) value = function(l, k, v)
local lv = l.type == "annotated" and l.value[1] or l local lv = l.type == "annotated" and l.value[1] or l
local kv = k.type == "annotated" and k.value[1] or k if lv.constant then return nil, "can't change the contents of a constant map" end
if lv.constant then return nil, "can't change the contents of a constant list" end local h, err = hash(k)
-- update index if not h then return nil, err end
for _, x in ipairs(lv.value) do lv.value[h] = nil
if x.type == "pair" and compare(x.value[1], kv) then mark_as_modified(anselme.running.state, lv.value)
x.value[2] = v return v
mark_as_modified(anselme.running.state, x.value) -- FIXME i thought pairs were immutable... end
return v },
end ["()(l::map, k) := v"] = {
end mode = "raw",
-- new index value = function(l, k, v)
table.insert(lv.value, { local lv = l.type == "annotated" and l.value[1] or l
type = "pair", if lv.constant then return nil, "can't change the contents of a constant map" end
value = { kv, v } local h, err = hash(k)
}) if not h then return nil, err end
lv.value[h] = { k, v }
mark_as_modified(anselme.running.state, lv.value) mark_as_modified(anselme.running.state, lv.value)
return v return v
end end
@ -435,7 +445,7 @@ local functions = {
package.loaded[...] = functions package.loaded[...] = functions
local icommon = require((...):gsub("stdlib%.functions$", "interpreter.common")) local icommon = require((...):gsub("stdlib%.functions$", "interpreter.common"))
truthy, compare, is_of_type, get_variable, mark_as_modified, set_variable, check_mutable, mark_constant = icommon.truthy, icommon.compare, icommon.is_of_type, icommon.get_variable, icommon.mark_as_modified, icommon.set_variable, icommon.check_mutable, icommon.mark_constant truthy, compare, is_of_type, get_variable, mark_as_modified, set_variable, check_mutable, mark_constant, hash = icommon.truthy, icommon.compare, icommon.is_of_type, icommon.get_variable, icommon.mark_as_modified, icommon.set_variable, icommon.check_mutable, icommon.mark_constant, icommon.hash
local pcommon = require((...):gsub("stdlib%.functions$", "parser.common")) local pcommon = require((...):gsub("stdlib%.functions$", "parser.common"))
identifier_pattern, format_identifier, find = pcommon.identifier_pattern, pcommon.format_identifier, pcommon.find identifier_pattern, format_identifier, find = pcommon.identifier_pattern, pcommon.format_identifier, pcommon.find
anselme = require((...):gsub("stdlib%.functions$", "anselme")) anselme = require((...):gsub("stdlib%.functions$", "anselme"))

View file

@ -1,4 +1,4 @@
local format, to_lua, from_lua, events, anselme, escape local format, to_lua, from_lua, events, anselme, escape, hash, mark_constant, update_hashes
local types = {} local types = {}
types.lua = { types.lua = {
@ -36,7 +36,9 @@ types.lua = {
}, },
table = { table = {
to_anselme = function(val) to_anselme = function(val)
local is_map = false
local l = {} local l = {}
local m = {}
for _, v in ipairs(val) do for _, v in ipairs(val) do
local r, e = from_lua(v) local r, e = from_lua(v)
if not r then return r, e end if not r then return r, e end
@ -44,20 +46,33 @@ types.lua = {
end end
for k, v in pairs(val) do for k, v in pairs(val) do
if not l[k] then if not l[k] then
is_map = true
local kv, ke = from_lua(k) local kv, ke = from_lua(k)
if not k then return k, ke end if not k then return k, ke end
local vv, ve = from_lua(v) local vv, ve = from_lua(v)
if not v then return v, ve end if not v then return v, ve end
table.insert(l, { local h, err = hash(kv)
type = "pair", if not h then return nil, err end
value = { kv, vv } m[h] = { kv, vv }
})
end end
end end
return { if is_map then
type = "list", for i, v in ipairs(l) do
value = l local key = { type = "number", value = i }
} local h, err = hash(key)
if not h then return nil, err end
m[h] = { key, v }
end
return {
type = "map",
value = m
}
else
return {
type = "list",
value = l
}
end
end end
} }
} }
@ -69,7 +84,11 @@ types.anselme = {
end, end,
to_lua = function() to_lua = function()
return nil return nil
end end,
hash = function()
return "nil()"
end,
mark_constant = function() end,
}, },
number = { number = {
format = function(val) format = function(val)
@ -77,7 +96,11 @@ types.anselme = {
end, end,
to_lua = function(val) to_lua = function(val)
return val return val
end end,
hash = function(val)
return ("n(%s)"):format(val)
end,
mark_constant = function() end,
}, },
string = { string = {
format = function(val) format = function(val)
@ -85,9 +108,14 @@ types.anselme = {
end, end,
to_lua = function(val) to_lua = function(val)
return val return val
end end,
hash = function(val)
return ("s(%s)"):format(val)
end,
mark_constant = function() end,
}, },
list = { list = {
mutable = true,
format = function(val) format = function(val)
local l = {} local l = {}
for _, v in ipairs(val) do for _, v in ipairs(val) do
@ -99,25 +127,74 @@ types.anselme = {
end, end,
to_lua = function(val) to_lua = function(val)
local l = {} local l = {}
-- handle non-pair before pairs as LuaJIT's table.insert will always insert after the last element even if there are some nil before unlike PUC
for _, v in ipairs(val) do for _, v in ipairs(val) do
if v.type ~= "pair" then local s, e = to_lua(v)
local s, e = to_lua(v) if not s and e then return s, e end
if not s and e then return s, e end table.insert(l, s)
table.insert(l, s)
end
end
for _, v in ipairs(val) do
if v.type == "pair" then
local k, ke = to_lua(v.value[1])
if not k and ke then return k, ke end
local x, xe = to_lua(v.value[2])
if not x and xe then return x, xe end
l[k] = x
end
end end
return l return l
end, end,
hash = function(val)
local l = {}
for _, v in ipairs(val) do
local s, e = hash(v)
if not s then return s, e end
table.insert(l, s)
end
return ("l(%s)"):format(table.concat(l, ","))
end,
mark_constant = function(v)
v.constant = true
for _, item in ipairs(v.value) do
mark_constant(item)
end
end
},
map = {
mutable = true,
format = function(val)
local l = {}
for _, v in pairs(val) do
local ks, ke = format(v[1])
if not ks then return ks, ke end
local vs, ve = format(v[2])
if not vs then return vs, ve end
table.insert(l, ("%s=%s"):format(ks, vs))
end
table.sort(l)
return ("{%s}"):format(table.concat(l, ", "))
end,
to_lua = function(val)
local l = {}
for _, v in pairs(val) do
local kl, ke = to_lua(v[1])
if not kl and ke then return kl, ke end
local xl, xe = to_lua(v[2])
if not xl and xe then return xl, xe end
l[kl] = xl
end
return l
end,
hash = function(val)
local l = {}
for _, v in pairs(val) do
local ks, ke = hash(v[1])
if not ks then return ks, ke end
local vs, ve = hash(v[2])
if not vs then return vs, ve end
table.insert(l, ("%s=%s"):format(ks, vs))
end
table.sort(l)
return ("m(%s)"):format(table.concat(l, ","))
end,
mark_constant = function(v)
v.constant = true
for _, val in pairs(v.value) do
mark_constant(val[1])
mark_constant(val[2])
end
update_hashes(v)
end,
}, },
pair = { pair = {
format = function(val) format = function(val)
@ -133,7 +210,18 @@ types.anselme = {
local v, ve = to_lua(val[2]) local v, ve = to_lua(val[2])
if not v and ve then return v, ve end if not v and ve then return v, ve end
return { [k] = v } return { [k] = v }
end end,
hash = function(val)
local k, ke = hash(val[1])
if not k then return k, ke end
local v, ve = hash(val[2])
if not v then return v, ve end
return ("p(%s=%s)"):format(k, v)
end,
mark_constant = function(v)
mark_constant(v.value[1])
mark_constant(v.value[2])
end,
}, },
annotated = { annotated = {
format = function(val) format = function(val)
@ -147,7 +235,18 @@ types.anselme = {
local k, ke = to_lua(val[1]) local k, ke = to_lua(val[1])
if not k and ke then return k, ke end if not k and ke then return k, ke end
return k return k
end end,
hash = function(val)
local k, ke = hash(val[1])
if not k then return k, ke end
local v, ve = hash(val[2])
if not v then return v, ve end
return ("a(%s::%s)"):format(k, v)
end,
mark_constant = function(v)
mark_constant(v.value[1])
mark_constant(v.value[2])
end,
}, },
["function reference"] = { ["function reference"] = {
format = function(val) format = function(val)
@ -157,27 +256,49 @@ types.anselme = {
return ("&%s"):format(table.concat(val, ", ")) return ("&%s"):format(table.concat(val, ", "))
end end
end, end,
to_lua = nil to_lua = nil,
hash = function(val)
return ("&f(%s)"):format(table.concat(val, ", "))
end,
mark_constant = function() end,
}, },
["variable reference"] = { ["variable reference"] = {
format = function(val) format = function(val)
return ("&%s"):format(val) return ("&%s"):format(val)
end, end,
to_lua = nil to_lua = nil,
hash = function(val)
return ("&v(%s)"):format(val)
end,
mark_constant = function() end,
}, },
object = { object = {
mutable = true,
format = function(val) format = function(val)
local attributes = {} local attributes = {}
for name, v in pairs(val.attributes) do for name, v in pairs(val.attributes) do
table.insert(attributes, ("%s=%s"):format(name:gsub("^"..escape(val.class)..".", ""), format(v))) table.insert(attributes, ("%s=%s"):format(name:gsub("^"..escape(val.class)..".", ""), format(v)))
end end
if #attributes > 0 then if #attributes > 0 then
table.sort(attributes)
return ("%%%s(%s)"):format(val.class, table.concat(attributes, ", ")) return ("%%%s(%s)"):format(val.class, table.concat(attributes, ", "))
else else
return ("%%%s"):format(val.class) return ("%%%s"):format(val.class)
end end
end, end,
to_lua = nil to_lua = nil,
hash = function(val)
local attributes = {}
for name, v in pairs(val.attributes) do
table.insert(attributes, ("%s=%s"):format(name:gsub("^"..escape(val.class)..".", ""), format(v)))
end
table.sort(attributes)
return ("%%(%s;%s)"):format(val.class, table.concat(attributes, ","))
end,
mark_constant = function(v)
v.constant = true
end,
}, },
-- internal types -- internal types
["event buffer"] = { ["event buffer"] = {
@ -191,7 +312,7 @@ types.anselme = {
package.loaded[...] = types package.loaded[...] = types
local common = require((...):gsub("stdlib%.types$", "interpreter.common")) local common = require((...):gsub("stdlib%.types$", "interpreter.common"))
format, to_lua, from_lua, events = common.format, common.to_lua, common.from_lua, common.events format, to_lua, from_lua, events, hash, mark_constant, update_hashes = common.format, common.to_lua, common.from_lua, common.events, common.hash, common.mark_constant, common.update_hashes
anselme = require((...):gsub("stdlib%.types$", "anselme")) anselme = require((...):gsub("stdlib%.types$", "anselme"))
escape = require((...):gsub("stdlib%.types$", "parser.common")).escape escape = require((...):gsub("stdlib%.types$", "parser.common")).escape

View file

@ -1,7 +1,7 @@
:person = "personne" :person = "personne"
$ Person(name, age) $ Person(name, age)
@[name=name, age=age]::person @{name=name, age=age}::person
:abject = Person("Darmanin", 38) :abject = Person("Darmanin", 38)

View file

@ -6,15 +6,16 @@
{x} {x}
{x("foo") := "a"} {x(2) := 5}
{x} {x}
{x("bar") := "b"} {x(-1) := 12}
{x} {x}
{x("foo") := "c"} {x(3) := 99}
{x} {x}
{x(5) := 0}

View file

@ -8,12 +8,12 @@ _[32]={}
_[31]={} _[31]={}
_[30]={} _[30]={}
_[29]={} _[29]={}
_[28]={tags=_[37],text="[3, 2, foo=c, bar=b]"} _[28]={tags=_[37],text="[3, 12, 99]"}
_[27]={tags=_[36],text="c"} _[27]={tags=_[36],text="99"}
_[26]={tags=_[35],text="[3, 2, foo=a, bar=b]"} _[26]={tags=_[35],text="[3, 12]"}
_[25]={tags=_[34],text="b"} _[25]={tags=_[34],text="12"}
_[24]={tags=_[33],text="[3, 2, foo=a]"} _[24]={tags=_[33],text="[3, 5]"}
_[23]={tags=_[32],text="a"} _[23]={tags=_[32],text="5"}
_[22]={tags=_[31],text="[3, 2]"} _[22]={tags=_[31],text="[3, 2]"}
_[21]={tags=_[30],text="3"} _[21]={tags=_[30],text="3"}
_[20]={tags=_[29],text="[1, 2]"} _[20]={tags=_[29],text="[1, 2]"}
@ -26,7 +26,7 @@ _[14]={_[23]}
_[13]={_[22]} _[13]={_[22]}
_[12]={_[21]} _[12]={_[21]}
_[11]={_[20]} _[11]={_[20]}
_[10]={"return"} _[10]={"error","list assignment index out of bounds; in Lua function \"()\"; at test/tests/list assignement.ans:21"}
_[9]={"text",_[19]} _[9]={"text",_[19]}
_[8]={"text",_[18]} _[8]={"text",_[18]}
_[7]={"text",_[17]} _[7]={"text",_[17]}
@ -52,27 +52,27 @@ return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9],_[10]}
} } } } } }
{ "text", { { { "text", { {
tags = {}, tags = {},
text = "a" text = "5"
} } } } } }
{ "text", { { { "text", { {
tags = {}, tags = {},
text = "[3, 2, foo=a]" text = "[3, 5]"
} } } } } }
{ "text", { { { "text", { {
tags = {}, tags = {},
text = "b" text = "12"
} } } } } }
{ "text", { { { "text", { {
tags = {}, tags = {},
text = "[3, 2, foo=a, bar=b]" text = "[3, 12]"
} } } } } }
{ "text", { { { "text", { {
tags = {}, tags = {},
text = "c" text = "99"
} } } } } }
{ "text", { { { "text", { {
tags = {}, tags = {},
text = "[3, 2, foo=c, bar=b]" text = "[3, 12, 99]"
} } } } } }
{ "return" } { "error", 'list assignment index out of bounds; in Lua function "()"; at test/tests/list assignement.ans:21' }
]]-- ]]--

11
test/tests/list index.ans Normal file
View file

@ -0,0 +1,11 @@
:x = [1,2,3]
{x}
{x(1)} == {x(-3)}
{x(2)} == {x(-2)}
{x(3)} == {x(-1)}
{x(-4)}

68
test/tests/list index.lua Normal file
View file

@ -0,0 +1,68 @@
local _={}
_[29]={}
_[28]={}
_[27]={}
_[26]={}
_[25]={}
_[24]={}
_[23]={}
_[22]={}
_[21]={}
_[20]={}
_[19]={text="3",tags=_[29]}
_[18]={text=" == ",tags=_[28]}
_[17]={text="3",tags=_[27]}
_[16]={text="2",tags=_[26]}
_[15]={text=" == ",tags=_[25]}
_[14]={text="2",tags=_[24]}
_[13]={text="1",tags=_[23]}
_[12]={text=" == ",tags=_[22]}
_[11]={text="1",tags=_[21]}
_[10]={text="[1, 2, 3]",tags=_[20]}
_[9]={_[17],_[18],_[19]}
_[8]={_[14],_[15],_[16]}
_[7]={_[11],_[12],_[13]}
_[6]={_[10]}
_[5]={"error","list index out of bounds; in Lua function \"()\"; at test/tests/list index.ans:11"}
_[4]={"text",_[9]}
_[3]={"text",_[8]}
_[2]={"text",_[7]}
_[1]={"text",_[6]}
return {_[1],_[2],_[3],_[4],_[5]}
--[[
{ "text", { {
tags = {},
text = "[1, 2, 3]"
} } }
{ "text", { {
tags = {},
text = "1"
}, {
tags = {},
text = " == "
}, {
tags = {},
text = "1"
} } }
{ "text", { {
tags = {},
text = "2"
}, {
tags = {},
text = " == "
}, {
tags = {},
text = "2"
} } }
{ "text", { {
tags = {},
text = "3"
}, {
tags = {},
text = " == "
}, {
tags = {},
text = "3"
} } }
{ "error", 'list index out of bounds; in Lua function "()"; at test/tests/list index.ans:11' }
]]--

View file

@ -0,0 +1,20 @@
:x = {1,2}
{x}
{x(1) := 3}
{x}
{x("foo") := "a"}
{x}
{x("bar") := "b"}
{x}
{x("foo") := "c"}
{x}

View file

@ -0,0 +1,78 @@
local _={}
_[37]={}
_[36]={}
_[35]={}
_[34]={}
_[33]={}
_[32]={}
_[31]={}
_[30]={}
_[29]={}
_[28]={text="{1=3, 2=2, bar=b, foo=c}",tags=_[37]}
_[27]={text="c",tags=_[36]}
_[26]={text="{1=3, 2=2, bar=b, foo=a}",tags=_[35]}
_[25]={text="b",tags=_[34]}
_[24]={text="{1=3, 2=2, foo=a}",tags=_[33]}
_[23]={text="a",tags=_[32]}
_[22]={text="{1=3, 2=2}",tags=_[31]}
_[21]={text="3",tags=_[30]}
_[20]={text="{1=1, 2=2}",tags=_[29]}
_[19]={_[28]}
_[18]={_[27]}
_[17]={_[26]}
_[16]={_[25]}
_[15]={_[24]}
_[14]={_[23]}
_[13]={_[22]}
_[12]={_[21]}
_[11]={_[20]}
_[10]={"return"}
_[9]={"text",_[19]}
_[8]={"text",_[18]}
_[7]={"text",_[17]}
_[6]={"text",_[16]}
_[5]={"text",_[15]}
_[4]={"text",_[14]}
_[3]={"text",_[13]}
_[2]={"text",_[12]}
_[1]={"text",_[11]}
return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9],_[10]}
--[[
{ "text", { {
tags = {},
text = "{1=1, 2=2}"
} } }
{ "text", { {
tags = {},
text = "3"
} } }
{ "text", { {
tags = {},
text = "{1=3, 2=2}"
} } }
{ "text", { {
tags = {},
text = "a"
} } }
{ "text", { {
tags = {},
text = "{1=3, 2=2, foo=a}"
} } }
{ "text", { {
tags = {},
text = "b"
} } }
{ "text", { {
tags = {},
text = "{1=3, 2=2, bar=b, foo=a}"
} } }
{ "text", { {
tags = {},
text = "c"
} } }
{ "text", { {
tags = {},
text = "{1=3, 2=2, bar=b, foo=c}"
} } }
{ "return" }
]]--

View file

@ -0,0 +1,29 @@
:x = {4}
x={x}
:a = {1,2,3,(x)=4}
:c = a
1={a==c}
a(x)={a(x)}
§ ch a
a(x)={a(x)}
§ ch b
~ x(2) := 3
a(x)={a(x)}
§ ch c
a(x)={a(x)}
~ x:={4}
no={a(x)}

View file

@ -0,0 +1,92 @@
local _={}
_[41]={}
_[40]={}
_[39]={}
_[38]={}
_[37]={}
_[36]={}
_[35]={}
_[34]={}
_[33]={}
_[32]={}
_[31]={}
_[30]={}
_[29]={}
_[28]={tags=_[41],text="no="}
_[27]={tags=_[40],text="4"}
_[26]={tags=_[39],text="a(x)="}
_[25]={tags=_[38],text="4"}
_[24]={tags=_[37],text="a(x)="}
_[23]={tags=_[36],text="4"}
_[22]={tags=_[35],text="a(x)="}
_[21]={tags=_[34],text="4"}
_[20]={tags=_[33],text="a(x)="}
_[19]={tags=_[32],text="1"}
_[18]={tags=_[31],text="1="}
_[17]={tags=_[30],text="{1=4}"}
_[16]={tags=_[29],text="x="}
_[15]={_[28]}
_[14]={_[26],_[27]}
_[13]={_[24],_[25]}
_[12]={_[22],_[23]}
_[11]={_[20],_[21]}
_[10]={_[18],_[19]}
_[9]={_[16],_[17]}
_[8]={"return"}
_[7]={"text",_[15]}
_[6]={"text",_[14]}
_[5]={"text",_[13]}
_[4]={"text",_[12]}
_[3]={"text",_[11]}
_[2]={"text",_[10]}
_[1]={"text",_[9]}
return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8]}
--[[
{ "text", { {
tags = {},
text = "x="
}, {
tags = {},
text = "{1=4}"
} } }
{ "text", { {
tags = {},
text = "1="
}, {
tags = {},
text = "1"
} } }
{ "text", { {
tags = {},
text = "a(x)="
}, {
tags = {},
text = "4"
} } }
{ "text", { {
tags = {},
text = "a(x)="
}, {
tags = {},
text = "4"
} } }
{ "text", { {
tags = {},
text = "a(x)="
}, {
tags = {},
text = "4"
} } }
{ "text", { {
tags = {},
text = "a(x)="
}, {
tags = {},
text = "4"
} } }
{ "text", { {
tags = {},
text = "no="
} } }
{ "return" }
]]--

7
test/tests/map index.ans Normal file
View file

@ -0,0 +1,7 @@
:t = {ahah=23,k=23,12}
{t} == \{3=12, ahah=23, k=23}
{t(3)} == 12
{t("ahah")} == 23

45
test/tests/map index.lua Normal file
View file

@ -0,0 +1,45 @@
local _={}
_[19]={}
_[18]={}
_[17]={}
_[16]={}
_[15]={}
_[14]={}
_[13]={tags=_[19],text=" == 23"}
_[12]={tags=_[18],text="23"}
_[11]={tags=_[17],text=" == 12"}
_[10]={tags=_[16],text="12"}
_[9]={tags=_[15],text=" == {3=12, ahah=23, k=23}"}
_[8]={tags=_[14],text="{3=12, ahah=23, k=23}"}
_[7]={_[12],_[13]}
_[6]={_[10],_[11]}
_[5]={_[8],_[9]}
_[4]={"return"}
_[3]={"text",_[7]}
_[2]={"text",_[6]}
_[1]={"text",_[5]}
return {_[1],_[2],_[3],_[4]}
--[[
{ "text", { {
tags = {},
text = "{3=12, ahah=23, k=23}"
}, {
tags = {},
text = " == {3=12, ahah=23, k=23}"
} } }
{ "text", { {
tags = {},
text = "12"
}, {
tags = {},
text = " == 12"
} } }
{ "text", { {
tags = {},
text = "23"
}, {
tags = {},
text = " == 23"
} } }
{ "return" }
]]--