From bac5cdde01c4f25315daa74ea234b8780f66a2c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Reuh=20Fildadut?= Date: Fri, 9 Sep 2022 14:54:45 +0900 Subject: [PATCH] Change equality test to reference comparison for mutable values --- LANGUAGE.md | 37 +++++++- interpreter/common.lua | 63 ++++++++---- stdlib/types.lua | 16 +++- test/tests/equality operator.ans | 28 ++++-- test/tests/equality operator.lua | 153 +++++++++++++++++++----------- test/tests/object comparaison.ans | 23 +++++ test/tests/object comparaison.lua | 78 +++++++++++++++ test/tests/object constructor.lua | 4 +- 8 files changed, 315 insertions(+), 87 deletions(-) create mode 100644 test/tests/object comparaison.ans create mode 100644 test/tests/object comparaison.lua diff --git a/LANGUAGE.md b/LANGUAGE.md index 48bfe75..2c52a26 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -682,6 +682,39 @@ These can be used to represent some caracters in string and other text elements Only `0` and `nil` are false. Everything else is considered true. +#### Equality + +Anselme consider two objects to be equal if they can *always* be used interchangeably. + +In practice, this results in two main cases for equality tests: + +* immutable values (strings, numbers, constants, ...). They are compared by recursively making sure all of their values and structure are equal. + +``` +(All of the following if true) +~ 5 == 5 +~ "foo" == "foo" +~ constant([1,2,3]) == constant([1,2,3]) +``` + +* mutable values (list, map, objects that are not constant). They are compared by reference, i.e. they are only considered equal if they are not distinct objects, even if they contain the same values and structure. + +``` +:a = [1,2,3] +:b = a +:c = [1,2,3] + +(True:) +~ a == b + +(False:) +~ a == c + +(a and c are not interchangeable as they are two distinct lists; if we do:) +~ a(1) = 42 +(This will change the first value of both a and b, but not c.) +``` + #### Refering to an identifier Any defined identifier can be accessed from an expression by using its name; the identifier will be first searched in the current namespace, then go up until it finds it as described in [identifiers](#identifiers). @@ -930,9 +963,9 @@ Built-in operators: ##### Comparaison -`a == b`: returns `1` if a and b have the same value (will recursively compare list and pairs), `0` otherwise +`a == b`: returns `1` if a and b are considered equal, `0` otherwise -`a != b`: returns `1` if a and b do not have the same value, `0` otherwise +`a != b`: returns `1` if a and b are not equal, `0` otherwise These only work on numbers: diff --git a/interpreter/common.lua b/interpreter/common.lua index 64806e0..1c683c5 100644 --- a/interpreter/common.lua +++ b/interpreter/common.lua @@ -150,10 +150,17 @@ common = { get_variable = function(state, fqm) local var = state.variables[fqm] if var.type == "pending definition" then + -- evaluate local v, e = eval(state, var.value.expression) if not v then return nil, ("%s; while evaluating default value for variable %q defined at %s"):format(e, fqm, var.value.source) end + -- make constant if variable is constant + if state.variable_constants[fqm] then + v = copy(v) + common.mark_constant(v) + end + -- set variable local s, err = common.set_variable(state, fqm, v, state.variable_constants[fqm]) if not s then return nil, err end return v @@ -164,13 +171,10 @@ common = { --- set the value of a variable -- returns true -- returns nil, err - set_variable = function(state, name, val, defining_a_constant) + set_variable = function(state, name, val, bypass_constant_check) if val.type ~= "pending definition" then -- check constant - if defining_a_constant then - val = copy(val) - common.mark_constant(val) - else + if not bypass_constant_check then local s, e = common.check_mutable(state, name) if not s then return nil, ("%s; while assigning value to variable %q"):format(e, name) @@ -270,23 +274,15 @@ common = { return true end end, - --- compare two anselme value for equality + --- compare two anselme values for equality. + -- for immutable values or constants: compare by value + -- for mutable values: compare by reference compare = function(a, b) - if a.type ~= b.type then + if a.type ~= b.type or a.constant ~= b.constant then return false end 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 - return false - end - for i, v in ipairs(a.value) do - if not common.compare(v, b.value[i]) then - return false - end - end - return true elseif a.type == "function reference" then if #a.value ~= #b.value then return false @@ -304,6 +300,39 @@ common = { end end return true + -- mutable types: need to be constant + elseif a.constant and a.type == "list" then + if #a.value ~= #b.value then + return false + end + for i, v in ipairs(a.value) do + if not common.compare(v, b.value[i]) then + return false + end + end + return true + elseif a.constant and a.type == "object" then + if a.value.class ~= b.value.class then + return false + end + -- check every attribute redefined in a and b + -- NOTE: comparaison will fail if an attribute has been redefined in only one of the object, even if it was set to the same value as the original class attribute + local compared = {} + for name, v in pairs(a.value.attributes) do + compared[name] = true + if not b.value.attributes[name] or not common.compare(v, b.value.attributes[name]) then + return false + end + end + for name, v in pairs(b.value.attributes) do + if not compared[name] then + if not a.value.attributes[name] or not common.compare(v, a.value.attributes[name]) then + return false + end + end + end + return true + -- the rest else return a.value == b.value end diff --git a/stdlib/types.lua b/stdlib/types.lua index 944d6f8..f4cc466 100644 --- a/stdlib/types.lua +++ b/stdlib/types.lua @@ -1,4 +1,4 @@ -local format, to_lua, from_lua, events, anselme +local format, to_lua, from_lua, events, anselme, escape local types = {} types.lua = { @@ -99,8 +99,7 @@ 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 + -- 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) @@ -168,7 +167,15 @@ types.anselme = { }, object = { format = function(val) - return ("%%%s"):format(val.class) + 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 + return ("%%%s(%s)"):format(val.class, table.concat(attributes, ", ")) + else + return ("%%%s"):format(val.class) + end end, to_lua = nil }, @@ -186,5 +193,6 @@ 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 anselme = require((...):gsub("stdlib%.types$", "anselme")) +escape = require((...):gsub("stdlib%.types$", "parser.common")).escape return types diff --git a/test/tests/equality operator.ans b/test/tests/equality operator.ans index 4cc418f..30f746a 100644 --- a/test/tests/equality operator.ans +++ b/test/tests/equality operator.ans @@ -1,19 +1,29 @@ -:a = [1=2] +::a = [1=2] -0 = {a == [5=2]} +0 = {a == [5=2]!constant} -0 = {a == [1=3]} +0 = {a == [1=3]!constant} -1 = {a == [1=2]} +1 = {a == [1=2]!constant} -:b = [1,2,3] +::b = [1,2,3] 0 = {b == a} -0 = {b == []} +0 = {b == []!constant} -0 = {b == [3,1,2]} +0 = {b == [3,1,2]!constant} -0 = {b == [1,2,3,4]} +0 = {b == [1,2,3,4]!constant} -1 = {b == [1,2,3]} +1 = {b == [1,2,3]!constant} + +:c = [1,2,3] + +0 = {c == b} + +1 = {c!constant == b} + +::d = [1,2,3] + +1 = {d == b} diff --git a/test/tests/equality operator.lua b/test/tests/equality operator.lua index de9c43d..17f5f10 100644 --- a/test/tests/equality operator.lua +++ b/test/tests/equality operator.lua @@ -1,101 +1,148 @@ local _={} -_[41]={} -_[40]={} -_[39]={} -_[38]={} -_[37]={} -_[36]={} -_[35]={} -_[34]={} -_[33]={tags=_[41],text="1"} -_[32]={tags=_[41],text="1 = "} -_[31]={tags=_[40],text="0"} -_[30]={tags=_[40],text="0 = "} -_[29]={tags=_[39],text="0"} -_[28]={tags=_[39],text="0 = "} -_[27]={tags=_[38],text="0"} -_[26]={tags=_[38],text="0 = "} -_[25]={tags=_[37],text="0"} -_[24]={tags=_[37],text="0 = "} -_[23]={tags=_[36],text="1"} -_[22]={tags=_[36],text="1 = "} -_[21]={tags=_[35],text="0"} -_[20]={tags=_[35],text="0 = "} -_[19]={tags=_[34],text="0"} -_[18]={tags=_[34],text="0 = "} +_[67]={} +_[66]={} +_[65]={} +_[64]={} +_[63]={} +_[62]={} +_[61]={} +_[60]={} +_[59]={} +_[58]={} +_[57]={} +_[56]={} +_[55]={} +_[54]={} +_[53]={} +_[52]={} +_[51]={} +_[50]={} +_[49]={} +_[48]={} +_[47]={} +_[46]={} +_[45]={tags=_[67],text="1"} +_[44]={tags=_[66],text="1 = "} +_[43]={tags=_[65],text="1"} +_[42]={tags=_[64],text="1 = "} +_[41]={tags=_[63],text="0"} +_[40]={tags=_[62],text="0 = "} +_[39]={tags=_[61],text="1"} +_[38]={tags=_[60],text="1 = "} +_[37]={tags=_[59],text="0"} +_[36]={tags=_[58],text="0 = "} +_[35]={tags=_[57],text="0"} +_[34]={tags=_[56],text="0 = "} +_[33]={tags=_[55],text="0"} +_[32]={tags=_[54],text="0 = "} +_[31]={tags=_[53],text="0"} +_[30]={tags=_[52],text="0 = "} +_[29]={tags=_[51],text="1"} +_[28]={tags=_[50],text="1 = "} +_[27]={tags=_[49],text="0"} +_[26]={tags=_[48],text="0 = "} +_[25]={tags=_[47],text="0"} +_[24]={tags=_[46],text="0 = "} +_[23]={_[44],_[45]} +_[22]={_[42],_[43]} +_[21]={_[40],_[41]} +_[20]={_[38],_[39]} +_[19]={_[36],_[37]} +_[18]={_[34],_[35]} _[17]={_[32],_[33]} _[16]={_[30],_[31]} _[15]={_[28],_[29]} _[14]={_[26],_[27]} _[13]={_[24],_[25]} -_[12]={_[22],_[23]} -_[11]={_[20],_[21]} -_[10]={_[18],_[19]} -_[9]={"return"} -_[8]={"text",_[17]} -_[7]={"text",_[16]} -_[6]={"text",_[15]} -_[5]={"text",_[14]} -_[4]={"text",_[13]} -_[3]={"text",_[12]} -_[2]={"text",_[11]} -_[1]={"text",_[10]} -return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9]} +_[12]={"return"} +_[11]={"text",_[23]} +_[10]={"text",_[22]} +_[9]={"text",_[21]} +_[8]={"text",_[20]} +_[7]={"text",_[19]} +_[6]={"text",_[18]} +_[5]={"text",_[17]} +_[4]={"text",_[16]} +_[3]={"text",_[15]} +_[2]={"text",_[14]} +_[1]={"text",_[13]} +return {_[1],_[2],_[3],_[4],_[5],_[6],_[7],_[8],_[9],_[10],_[11],_[12]} --[[ { "text", { { - tags = <1>{}, + tags = {}, text = "0 = " }, { - tags = , + tags = {}, text = "0" } } } { "text", { { - tags = <1>{}, + tags = {}, text = "0 = " }, { - tags =
, + tags = {}, text = "0" } } } { "text", { { - tags = <1>{}, + tags = {}, text = "1 = " }, { - tags =
, + tags = {}, text = "1" } } } { "text", { { - tags = <1>{}, + tags = {}, text = "0 = " }, { - tags =
, + tags = {}, text = "0" } } } { "text", { { - tags = <1>{}, + tags = {}, text = "0 = " }, { - tags =
, + tags = {}, text = "0" } } } { "text", { { - tags = <1>{}, + tags = {}, text = "0 = " }, { - tags =
, + tags = {}, text = "0" } } } { "text", { { - tags = <1>{}, + tags = {}, text = "0 = " }, { - tags =
, + tags = {}, text = "0" } } } { "text", { { - tags = <1>{}, + tags = {}, text = "1 = " }, { - tags =
, + tags = {}, + text = "1" + } } } +{ "text", { { + tags = {}, + text = "0 = " + }, { + tags = {}, + text = "0" + } } } +{ "text", { { + tags = {}, + text = "1 = " + }, { + tags = {}, + text = "1" + } } } +{ "text", { { + tags = {}, + text = "1 = " + }, { + tags = {}, text = "1" } } } { "return" } diff --git a/test/tests/object comparaison.ans b/test/tests/object comparaison.ans new file mode 100644 index 0000000..af40f6e --- /dev/null +++ b/test/tests/object comparaison.ans @@ -0,0 +1,23 @@ +% class + :a:b = "foo" + :c = "bar" + +:o = class +:a = class +::b = class + +0 = {o == a} +0 = {o == b} +1 = {o!constant == b} + +~ o.b := "haha" + +0 = {o!constant == b} + +~ a.b := "haha" + +1 = {o!constant == a!constant} + +~ o.b := "foo" + +0 = {o!constant == b} diff --git a/test/tests/object comparaison.lua b/test/tests/object comparaison.lua new file mode 100644 index 0000000..94b32f7 --- /dev/null +++ b/test/tests/object comparaison.lua @@ -0,0 +1,78 @@ +local _={} +_[33]={} +_[32]={} +_[31]={} +_[30]={} +_[29]={} +_[28]={} +_[27]={} +_[26]={} +_[25]={} +_[24]={} +_[23]={} +_[22]={} +_[21]={tags=_[33],text="0"} +_[20]={tags=_[32],text="0 = "} +_[19]={tags=_[31],text="1"} +_[18]={tags=_[30],text="1 = "} +_[17]={tags=_[29],text="0"} +_[16]={tags=_[28],text="0 = "} +_[15]={tags=_[27],text="1"} +_[14]={tags=_[26],text="1 = "} +_[13]={tags=_[25],text="0"} +_[12]={tags=_[24],text="0 = "} +_[11]={tags=_[23],text="0"} +_[10]={tags=_[22],text="0 = "} +_[9]={_[20],_[21]} +_[8]={_[18],_[19]} +_[7]={_[16],_[17]} +_[6]={_[10],_[11],_[12],_[13],_[14],_[15]} +_[5]={"return"} +_[4]={"text",_[9]} +_[3]={"text",_[8]} +_[2]={"text",_[7]} +_[1]={"text",_[6]} +return {_[1],_[2],_[3],_[4],_[5]} +--[[ +{ "text", { { + tags = {}, + text = "0 = " + }, { + tags = {}, + text = "0" + }, { + tags = {}, + text = "0 = " + }, { + tags = {}, + text = "0" + }, { + tags = {}, + text = "1 = " + }, { + tags = {}, + text = "1" + } } } +{ "text", { { + tags = {}, + text = "0 = " + }, { + tags = {}, + text = "0" + } } } +{ "text", { { + tags = {}, + text = "1 = " + }, { + tags = {}, + text = "1" + } } } +{ "text", { { + tags = {}, + text = "0 = " + }, { + tags = {}, + text = "0" + } } } +{ "return" } +]]-- \ No newline at end of file diff --git a/test/tests/object constructor.lua b/test/tests/object constructor.lua index 66030b1..669eb7c 100644 --- a/test/tests/object constructor.lua +++ b/test/tests/object constructor.lua @@ -14,7 +14,7 @@ _[12]={tags=_[21],text="hoho"} _[11]={tags=_[20],text="bar"} _[10]={tags=_[19],text=" == "} _[9]={tags=_[18],text="bar"} -_[8]={tags=_[17],text="%object constructor.class::&object constructor.class"} +_[8]={tags=_[17],text="%object constructor.class(c=hoho)::&object constructor.class"} _[7]={tags=_[16],text=", "} _[6]={tags=_[15],text="%object constructor.class::&object constructor.class"} _[5]={_[9],_[10],_[11],_[12],_[13],_[14]} @@ -32,7 +32,7 @@ return {_[1],_[2],_[3]} text = ", " }, { tags = {}, - text = "%object constructor.class::&object constructor.class" + text = "%object constructor.class(c=hoho)::&object constructor.class" } } } { "text", { { tags = {},