From 95462391e3870c8b009b8968be4242b304b7d300 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Reuh=20Fildadut?= Date: Fri, 9 Sep 2022 21:39:37 +0900 Subject: [PATCH] Add maps; remove map emulation functionality from list; function and tags now internally use maps instead of lists --- LANGUAGE.md | 22 ++- interpreter/common.lua | 70 +++++-- interpreter/expression.lua | 80 +++++--- parser/common.lua | 2 +- parser/expression.lua | 19 +- parser/preparser.lua | 6 +- stdlib/bootscript.lua | 1 + stdlib/functions.lua | 64 ++++--- stdlib/types.lua | 187 +++++++++++++++---- test/tests/custom text formatting.ans | 2 +- test/tests/list assignement.ans | 7 +- test/tests/list assignement.lua | 28 +-- test/tests/list index.ans | 11 ++ test/tests/list index.lua | 68 +++++++ test/tests/map assignement.ans | 20 ++ test/tests/map assignement.lua | 78 ++++++++ test/tests/map index accross checkpoints.ans | 29 +++ test/tests/map index accross checkpoints.lua | 92 +++++++++ test/tests/map index.ans | 7 + test/tests/map index.lua | 45 +++++ 20 files changed, 699 insertions(+), 139 deletions(-) create mode 100644 test/tests/list index.ans create mode 100644 test/tests/list index.lua create mode 100644 test/tests/map assignement.ans create mode 100644 test/tests/map assignement.lua create mode 100644 test/tests/map index accross checkpoints.ans create mode 100644 test/tests/map index accross checkpoints.lua create mode 100644 test/tests/map index.ans create mode 100644 test/tests/map index.lua diff --git a/LANGUAGE.md b/LANGUAGE.md index 2c52a26..7886e3c 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -303,7 +303,7 @@ $ show(object::&class) ~ 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" @@ -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]'. +* `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. -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: @@ -648,7 +650,9 @@ How conversions are handled from Anselme to Lua: * `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. @@ -660,7 +664,7 @@ How conservions are handled from Lua to Anselme: * `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. @@ -819,7 +823,7 @@ $ f(a, b, c) {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. @@ -953,7 +957,7 @@ Built-in operators: `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. @@ -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`: 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). `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. diff --git a/interpreter/common.lua b/interpreter/common.lua index 1c683c5..fa8aa6e 100644 --- a/interpreter/common.lua +++ b/interpreter/common.lua @@ -48,6 +48,14 @@ local function post_process_text(state, text) return r 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 = { --- merge interpreter state with global state merge_state = function(state) @@ -129,18 +137,11 @@ common = { end, --- mark a value as constant, recursively affecting all the potentially mutable subvalues mark_constant = function(v) - if v.type == "list" then - v.constant = true - for _, item in ipairs(v.value) do - common.mark_constant(item) - end - 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") + if atypes[v.type] and atypes[v.type].mark_constant then + atypes[v.type].mark_constant(v) + if v.hash_id then v.hash_id = nil end -- no longer need to compare by id + else + error(("don't know how to mark type %s as constant"):format(v.type)) end end, --- returns a variable's value, evaluating a pending expression if neccessary @@ -311,6 +312,8 @@ common = { end end 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 if a.value.class ~= b.value.class then return false @@ -348,6 +351,35 @@ common = { return nil, ("no formatter for type %q"):format(val.type) 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 -- lua value: if success (may be nil!) -- nil, err: if error @@ -373,7 +405,8 @@ common = { -- nil, err: if error eval_text = function(state, text) 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) end, --- 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, --- tag management 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) - local new = { type = "list", value = {} } + local new = { type = "map", value = {} } -- copy 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 - if val.type ~= "list" then val = { type = "list", value = { val } } end - for _, v in ipairs(val.value) do table.insert(new.value, v) end + for k, v in pairs(val.value) do new.value[k] = v end -- add table.insert(state.interpreter.tags, new) end, @@ -458,7 +490,7 @@ common = { end, --- return current lua tags table 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, --- returns length of tags stack len = function(self, state) diff --git a/interpreter/expression.lua b/interpreter/expression.lua index 2cbc22e..ca442ff 100644 --- a/interpreter/expression.lua +++ b/interpreter/expression.lua @@ -1,5 +1,5 @@ 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 @@ -52,6 +52,30 @@ local function eval(state, exp) value = {} } 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 elseif exp.type == "list" then local flat = flatten_list(exp) @@ -142,7 +166,7 @@ local function eval(state, exp) } -- tag 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 tags:push(state, right) local left, lefte = eval(state, exp.left) @@ -165,12 +189,24 @@ local function eval(state, exp) } -- function elseif exp.type == "function call" then - -- eval args: list_brackets + -- eval args: map_brackets local args = {} + local last_contiguous_positional = 0 if exp.argument then local arg, arge = eval(state, exp.argument) 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 -- function reference: call the referenced function local variants = exp.variants @@ -191,13 +227,6 @@ local function eval(state, exp) 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 local assignment if exp.assignment then @@ -222,21 +251,21 @@ local function eval(state, exp) for j, param in ipairs(fn.params) do local val -- named - if param.alias and named_args[param.alias] then - val = named_args[param.alias][2] - used_args[named_args[param.alias][1]] = true - elseif named_args[param.name] then - val = named_args[param.name][2] - used_args[named_args[param.name][1]] = true + if param.alias and args[param.alias] then + val = args[param.alias] + used_args[param.alias] = true + elseif args[param.name] then + val = args[param.name] + used_args[param.name] = true -- vararg elseif param.vararg then val = { type = "list", value = {} } - for k=j, #args do + for k=j, last_contiguous_positional do table.insert(val.value, args[k]) used_args[k] = true end -- positional - elseif args[j] and args[j].type ~= "pair" then + elseif args[j] then val = args[j] used_args[j] = true end @@ -264,8 +293,8 @@ local function eval(state, exp) end -- check for unused arguments if ok then - for i, arg in ipairs(args) do - if not used_args[i] then + for key, arg in pairs(args) do + if not used_args[key] then ok = false if arg.type == "pair" and arg.value[1].type == "string" then table.insert(tried_function_error_messages, ("%s: unexpected %s argument"):format(fn.pretty_signature, arg.value[1].value)) @@ -473,11 +502,10 @@ local function eval(state, exp) end -- no matching function found local args_txt = {} - for _, arg in ipairs(args) do + for key, arg in pairs(args) do local s = "" - if arg.type == "pair" and arg.value[1].type == "string" then - s = s .. ("%s="):format(arg.value[1].value) - arg = arg.value[2] + if type(key) == "string" or (type(key) == "number" and key > last_contiguous_positional) then + s = s .. ("%s="):format(key) end s = s .. pretty_type(arg) table.insert(args_txt, s) @@ -511,6 +539,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, 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 diff --git a/parser/common.lua b/parser/common.lua index 7f3ca19..6353615 100644 --- a/parser/common.lua +++ b/parser/common.lua @@ -287,7 +287,7 @@ common = { implicit_call = implicit_call, -- was call implicitely (no ! or parentheses)? 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) - type = "list_brackets", + type = "map_brackets", expression = arg } } diff --git a/parser/expression.lua b/parser/expression.lua index 60cc882..52a6633 100644 --- a/parser/expression.lua +++ b/parser/expression.lua @@ -132,6 +132,21 @@ local function expression(s, state, namespace, current_priority, operating_on) type = "list_brackets", 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 elseif s:match("^"..identifier_pattern) then local name, r = s:match("^("..identifier_pattern..")(.-)$") @@ -182,7 +197,7 @@ local function expression(s, state, namespace, current_priority, operating_on) local err args, err = expression(content, state, namespace) if not args then return args, err end - if err:match("[^%s]") then return nil, ("unexpected %q at end of argument list"):format(err) end + if err:match("[^%s]") then return nil, ("unexpected %q at end of argument map"):format(err) end end else -- implicit call; will be changed if there happens to be a ! after in the suffix operator code implicit_call = true @@ -267,7 +282,7 @@ local function expression(s, state, namespace, current_priority, operating_on) local err args, err = expression(content, state, namespace) if not args then return args, err end - if err:match("[^%s]") then return nil, ("unexpected %q at end of argument list"):format(err) end + if err:match("[^%s]") then return nil, ("unexpected %q at end of argument map"):format(err) end end end -- add first argument diff --git a/parser/preparser.lua b/parser/preparser.lua index 8490192..296ace6 100644 --- a/parser/preparser.lua +++ b/parser/preparser.lua @@ -332,11 +332,7 @@ local function parse_line(line, state, namespace, parent_function) r.type = "tag" r.child = true local expr = l:match("^%#(.*)$") - if expr:match("[^%s]") then - r.expression = expr - else - r.expression = "()" - end + r.expression = ("{%s}"):format(expr) -- return elseif l:match("^%@") then r.type = "return" diff --git a/stdlib/bootscript.lua b/stdlib/bootscript.lua index c000af2..f6695d2 100644 --- a/stdlib/bootscript.lua +++ b/stdlib/bootscript.lua @@ -5,6 +5,7 @@ return [[ :number="number" :string="string" :list="list" +:map="map" :pair="pair" :function reference="function reference" :variable reference="variable reference" diff --git a/stdlib/functions.lua b/stdlib/functions.lua index a551338..8b86900 100644 --- a/stdlib/functions.lua +++ b/stdlib/functions.lua @@ -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 lua_functions = { @@ -173,18 +173,24 @@ lua_functions = { ["()(l::list, i::number)"] = { mode = "unannotated raw", 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 }, - ["()(l::list, i::string)"] = { - mode = "unannotated raw", + ["()(l::map, k)"] = { + mode = "raw", value = function(l, i) - for _, v in ipairs(l.value) do - if v.type == "pair" and compare(v.value[1], i) then - return v.value[2] - end + local lv = l.type == "annotated" and l.value[1] or l + local h, err = hash(i) + if not h then return nil, err end + local v = lv.value[h] + if v then + return v[2] + else + return { type = "nil", value = nil } end - return { type = "nil", value = nil } end }, -- index assignment @@ -194,30 +200,34 @@ lua_functions = { local lv = l.type == "annotated" and l.value[1] or l 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 - 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) return v end }, - ["()(l::list, k::string) := v"] = { + ["()(l::map, k) := v::nil"] = { mode = "raw", value = function(l, k, v) 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 list" end - -- update index - for _, x in ipairs(lv.value) do - if x.type == "pair" and compare(x.value[1], kv) then - x.value[2] = v - mark_as_modified(anselme.running.state, x.value) -- FIXME i thought pairs were immutable... - return v - end - end - -- new index - table.insert(lv.value, { - type = "pair", - value = { kv, v } - }) + if lv.constant then return nil, "can't change the contents of a constant map" end + local h, err = hash(k) + if not h then return nil, err end + lv.value[h] = nil + mark_as_modified(anselme.running.state, lv.value) + return v + end + }, + ["()(l::map, k) := v"] = { + mode = "raw", + value = function(l, k, v) + local lv = l.type == "annotated" and l.value[1] or l + if lv.constant then return nil, "can't change the contents of a constant map" end + 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) return v end @@ -435,7 +445,7 @@ local functions = { package.loaded[...] = functions 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")) identifier_pattern, format_identifier, find = pcommon.identifier_pattern, pcommon.format_identifier, pcommon.find anselme = require((...):gsub("stdlib%.functions$", "anselme")) diff --git a/stdlib/types.lua b/stdlib/types.lua index f4cc466..a9a3b6b 100644 --- a/stdlib/types.lua +++ b/stdlib/types.lua @@ -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 = {} types.lua = { @@ -36,7 +36,9 @@ types.lua = { }, table = { to_anselme = function(val) + local is_map = false local l = {} + local m = {} for _, v in ipairs(val) do local r, e = from_lua(v) if not r then return r, e end @@ -44,20 +46,33 @@ types.lua = { end for k, v in pairs(val) do if not l[k] then + is_map = true local kv, ke = from_lua(k) if not k then return k, ke end local vv, ve = from_lua(v) if not v then return v, ve end - table.insert(l, { - type = "pair", - value = { kv, vv } - }) + local h, err = hash(kv) + if not h then return nil, err end + m[h] = { kv, vv } end end - return { - type = "list", - value = l - } + if is_map then + for i, v in ipairs(l) do + 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 } } @@ -69,7 +84,11 @@ types.anselme = { end, to_lua = function() return nil - end + end, + hash = function() + return "nil()" + end, + mark_constant = function() end, }, number = { format = function(val) @@ -77,7 +96,11 @@ types.anselme = { end, to_lua = function(val) return val - end + end, + hash = function(val) + return ("n(%s)"):format(val) + end, + mark_constant = function() end, }, string = { format = function(val) @@ -85,9 +108,14 @@ types.anselme = { end, to_lua = function(val) return val - end + end, + hash = function(val) + return ("s(%s)"):format(val) + end, + mark_constant = function() end, }, list = { + mutable = true, format = function(val) local l = {} for _, v in ipairs(val) do @@ -99,25 +127,74 @@ types.anselme = { end, to_lua = function(val) 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 - if v.type ~= "pair" then - local s, e = to_lua(v) - if not s and e then return s, e end - 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 + local s, e = to_lua(v) + if not s and e then return s, e end + table.insert(l, s) end return l 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 = { format = function(val) @@ -133,7 +210,18 @@ types.anselme = { local v, ve = to_lua(val[2]) if not v and ve then return v, ve end 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 = { format = function(val) @@ -147,7 +235,18 @@ types.anselme = { local k, ke = to_lua(val[1]) if not k and ke then return k, ke end 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"] = { format = function(val) @@ -157,27 +256,49 @@ types.anselme = { return ("&%s"):format(table.concat(val, ", ")) 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"] = { format = function(val) return ("&%s"):format(val) end, - to_lua = nil + to_lua = nil, + hash = function(val) + return ("&v(%s)"):format(val) + end, + mark_constant = function() end, }, object = { + mutable = true, format = 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 if #attributes > 0 then + table.sort(attributes) return ("%%%s(%s)"):format(val.class, table.concat(attributes, ", ")) else return ("%%%s"):format(val.class) 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 ["event buffer"] = { @@ -191,7 +312,7 @@ types.anselme = { package.loaded[...] = types 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")) escape = require((...):gsub("stdlib%.types$", "parser.common")).escape diff --git a/test/tests/custom text formatting.ans b/test/tests/custom text formatting.ans index 06ff053..876231b 100644 --- a/test/tests/custom text formatting.ans +++ b/test/tests/custom text formatting.ans @@ -1,7 +1,7 @@ :person = "personne" $ Person(name, age) - @[name=name, age=age]::person + @{name=name, age=age}::person :abject = Person("Darmanin", 38) diff --git a/test/tests/list assignement.ans b/test/tests/list assignement.ans index c9227b5..ea5f2a0 100644 --- a/test/tests/list assignement.ans +++ b/test/tests/list assignement.ans @@ -6,15 +6,16 @@ {x} -{x("foo") := "a"} +{x(2) := 5} {x} -{x("bar") := "b"} +{x(-1) := 12} {x} -{x("foo") := "c"} +{x(3) := 99} {x} +{x(5) := 0} diff --git a/test/tests/list assignement.lua b/test/tests/list assignement.lua index 986eba4..0350235 100644 --- a/test/tests/list assignement.lua +++ b/test/tests/list assignement.lua @@ -8,12 +8,12 @@ _[32]={} _[31]={} _[30]={} _[29]={} -_[28]={tags=_[37],text="[3, 2, foo=c, bar=b]"} -_[27]={tags=_[36],text="c"} -_[26]={tags=_[35],text="[3, 2, foo=a, bar=b]"} -_[25]={tags=_[34],text="b"} -_[24]={tags=_[33],text="[3, 2, foo=a]"} -_[23]={tags=_[32],text="a"} +_[28]={tags=_[37],text="[3, 12, 99]"} +_[27]={tags=_[36],text="99"} +_[26]={tags=_[35],text="[3, 12]"} +_[25]={tags=_[34],text="12"} +_[24]={tags=_[33],text="[3, 5]"} +_[23]={tags=_[32],text="5"} _[22]={tags=_[31],text="[3, 2]"} _[21]={tags=_[30],text="3"} _[20]={tags=_[29],text="[1, 2]"} @@ -26,7 +26,7 @@ _[14]={_[23]} _[13]={_[22]} _[12]={_[21]} _[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]} _[8]={"text",_[18]} _[7]={"text",_[17]} @@ -52,27 +52,27 @@ return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9],_[10]} } } } { "text", { { tags = {}, - text = "a" + text = "5" } } } { "text", { { tags = {}, - text = "[3, 2, foo=a]" + text = "[3, 5]" } } } { "text", { { tags = {}, - text = "b" + text = "12" } } } { "text", { { tags = {}, - text = "[3, 2, foo=a, bar=b]" + text = "[3, 12]" } } } { "text", { { tags = {}, - text = "c" + text = "99" } } } { "text", { { 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' } ]]-- \ No newline at end of file diff --git a/test/tests/list index.ans b/test/tests/list index.ans new file mode 100644 index 0000000..8b3f4a9 --- /dev/null +++ b/test/tests/list index.ans @@ -0,0 +1,11 @@ +:x = [1,2,3] + +{x} + +{x(1)} == {x(-3)} + +{x(2)} == {x(-2)} + +{x(3)} == {x(-1)} + +{x(-4)} diff --git a/test/tests/list index.lua b/test/tests/list index.lua new file mode 100644 index 0000000..4b4139f --- /dev/null +++ b/test/tests/list index.lua @@ -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' } +]]-- \ No newline at end of file diff --git a/test/tests/map assignement.ans b/test/tests/map assignement.ans new file mode 100644 index 0000000..12b3ef7 --- /dev/null +++ b/test/tests/map assignement.ans @@ -0,0 +1,20 @@ +:x = {1,2} + +{x} + +{x(1) := 3} + +{x} + +{x("foo") := "a"} + +{x} + +{x("bar") := "b"} + +{x} + +{x("foo") := "c"} + +{x} + diff --git a/test/tests/map assignement.lua b/test/tests/map assignement.lua new file mode 100644 index 0000000..d28ce6e --- /dev/null +++ b/test/tests/map assignement.lua @@ -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" } +]]-- \ No newline at end of file diff --git a/test/tests/map index accross checkpoints.ans b/test/tests/map index accross checkpoints.ans new file mode 100644 index 0000000..54abbe2 --- /dev/null +++ b/test/tests/map index accross checkpoints.ans @@ -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)} diff --git a/test/tests/map index accross checkpoints.lua b/test/tests/map index accross checkpoints.lua new file mode 100644 index 0000000..a3596bc --- /dev/null +++ b/test/tests/map index accross checkpoints.lua @@ -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" } +]]-- \ No newline at end of file diff --git a/test/tests/map index.ans b/test/tests/map index.ans new file mode 100644 index 0000000..c2caa83 --- /dev/null +++ b/test/tests/map index.ans @@ -0,0 +1,7 @@ +:t = {ahah=23,k=23,12} + +{t} == \{3=12, ahah=23, k=23} + +{t(3)} == 12 + +{t("ahah")} == 23 diff --git a/test/tests/map index.lua b/test/tests/map index.lua new file mode 100644 index 0000000..48f28f8 --- /dev/null +++ b/test/tests/map index.lua @@ -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" } +]]-- \ No newline at end of file