Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
lua-nucleo/lua-nucleo/tdeepequals.lua
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
308 lines (277 sloc)
8.03 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -------------------------------------------------------------------------------- | |
| --- Test arbitrary lua tables for equality. | |
| -- @module lua-nucleo.tdeepequals | |
| -- This file is a part of lua-nucleo library | |
| -- @copyright lua-nucleo authors (see file `COPYRIGHT` for the license) | |
| -------------------------------------------------------------------------------- | |
| -- Tests arbitrary lua tables for equality. | |
| -- Functions, threads, etc. are supported | |
| -- Metatables are ignored. | |
| -- The tmore function introduces linear ordering in the set of all lua tables. | |
| -- That is, for two arbitrary tables we can define an ordering | |
| -- '>=' : t1>=t2 <=> tmore(t1,t2)>=0 , | |
| -- the following statements are hold for all tables a, b and c: | |
| -- If a ≤ b and b ≤ a then a = b (antisymmetry); (if tmore(a,b)=0 then a=b) | |
| -- If a ≤ b and b ≤ c then a ≤ c (transitivity); | |
| -- (tmore(a,b)*tmore(b,c)*tmore(c,a)<=0) | |
| -- a ≤ b or b ≤ a (totality). | |
| local pairs, type, ipairs, tostring, assert | |
| = pairs, type, ipairs, tostring, assert | |
| local table_concat, table_sort = table.concat, table.sort | |
| local string_format, string_match = string.format, string.match | |
| local tmore | |
| do | |
| local p_table -- table, containing hashes of pointer-like data: | |
| --functions, threads, userdata | |
| ------------------------------------------------------------ | |
| -------------------- UTILITY FUNCTIONS ------------------- | |
| ------------------------------------------------------------ | |
| --1.make a duplicate(copy) of a table | |
| local table_dup = function(t) | |
| assert(type(t) == "table") | |
| local td = {} | |
| for k,v in pairs(t) do | |
| td[k] = v | |
| end | |
| return td | |
| end | |
| --2.Generic more(for strings and numbers) | |
| local more = function(t1, t2) | |
| if t1 > t2 then | |
| return 1 | |
| elseif t1 < t2 then | |
| return -1 | |
| else | |
| return 0 | |
| end | |
| end | |
| --3.Boolean more(for strings and numbers) | |
| local bool_more = function(t1, t2) | |
| if not t1 and t2 then | |
| return -1 | |
| end | |
| if ((not t1) and (not t2)) or (t1 and t2) then | |
| return 0 | |
| end | |
| return 1 | |
| end | |
| --4.More for threads, functions and userdata | |
| -- (using hash table p_table) and also nil | |
| local p_more = function(t1, t2) | |
| if not t1 and not t2 then | |
| return 0 | |
| end | |
| if not t1 then | |
| return -1 | |
| end | |
| if not t2 then | |
| return 1 | |
| end | |
| if not p_table[t1] then | |
| p_table.n = p_table.n + 1 | |
| p_table[t1] = p_table.n | |
| end | |
| if not p_table[t2] then | |
| p_table.n = p_table.n + 1 | |
| p_table[t2] = p_table.n | |
| end | |
| return p_table[t1] - p_table[t2] | |
| end | |
| --5.Compare (less) utility for boolean | |
| local bool_comp = function(t1, t2) | |
| return not t1 and t2 | |
| end | |
| --6.Compare (less) utility for userdata, threads, functions | |
| local p_comp = function(t1, t2) | |
| return p_more(t1, t2) < 0 | |
| end | |
| --7. Compare (less) utility generator for key-value pairs, | |
| -- where key is a table. | |
| local table_comp = function(visited) | |
| return function(t1, t2) | |
| local vis1 = table_dup(visited) | |
| local vis2 = table_dup(visited) | |
| local m = tmore(t1[1], t2[1], vis1, vis2) | |
| if m == 0 then | |
| m = tmore(t1[2], t2[2], vis1, vis2) | |
| end | |
| return m < 0 | |
| end | |
| end | |
| --8. Generic compare for everything except tables | |
| local nontable_comp = function(t1,t2) | |
| return tmore(t1, t2) < 0 | |
| end | |
| ------------------------------------------------------------ | |
| ------------------------ MAIN WORK ----------------------- | |
| ------------------------------------------------------------ | |
| --for a given table returns a number of sorted arrays - | |
| --ikeys contains integer keys etc. | |
| --tkeys contains sorted {key,value} pairs in which key is a table | |
| --visited is a hash of already visited tables - used to cope with | |
| --recursive tables and tables having shared sub_tables: | |
| --e.g. 1 (recursive): local t = {} t[t] = 1 | |
| --e.g. 2 (shared) : local t1 = {} local t = {t1,t1} | |
| local analyze = function(t) | |
| local tkeys = {} | |
| local keys = {} | |
| for k, v in pairs(t) do | |
| local k_type = type(k) | |
| if k_type == "table" then | |
| local ind = #tkeys + 1 | |
| tkeys[ind] = {} | |
| tkeys[ind][1] = k | |
| tkeys[ind][2] = t[k] | |
| else | |
| keys[#keys + 1] = k | |
| end | |
| end | |
| table_sort(keys, nontable_comp) | |
| return keys, tkeys | |
| end | |
| -- compares two generic pieces of lua data - first and second | |
| -- vis1 and vis2 are hashes of visited tables for first and second | |
| local function tmoreimpl(first, second, vis1, vis2) | |
| local type1, type2 = type(first), type(second) | |
| vis1, vis2 = vis1 or { ['n'] = 0 }, vis2 or { ['n'] = 0 } | |
| if type1 ~= type2 then | |
| return more(type1, type2) | |
| else | |
| if type1 == "number" or type1 == "string" then | |
| return more(first, second) | |
| elseif type1 == "boolean" then | |
| return bool_more(first, second) | |
| elseif type1 == "table" then | |
| if vis1[first] and vis2[second] then | |
| return more(vis1[first], vis2[second]) | |
| end | |
| if vis1[first] then | |
| return 1 | |
| end | |
| if vis2[second] then | |
| return -1 | |
| end | |
| vis1.n = vis1.n + 1 | |
| vis1[first] = vis1.n | |
| vis2.n=vis2.n + 1 | |
| vis2[second] = vis2.n | |
| local keys1, tkeys1 = analyze(first) | |
| local keys2, tkeys2 = analyze(second) | |
| local i | |
| local m | |
| -- nontable keys | |
| i = 1 | |
| if keys1 or keys2 then | |
| while i <= #keys1 and i <= #keys2 do | |
| m = tmore(keys1[i], keys2[i]) | |
| if m ~= 0 then | |
| return m | |
| end | |
| m = tmore(first[keys1[i]], second[keys2[i]], vis1, vis2) | |
| if m ~= 0 then | |
| return m | |
| end | |
| i = i + 1 | |
| end | |
| m = more(#keys1 - i, #keys2 - i) | |
| if m ~= 0 then | |
| return m | |
| end | |
| end | |
| -- table keys | |
| if tkeys1 or tkeys2 then | |
| table_sort(tkeys1, table_comp(vis1)); | |
| table_sort(tkeys2, table_comp(vis2)); | |
| i = 1 | |
| while i <= #tkeys1 and i <= #tkeys2 do | |
| local m = tmore(tkeys1[i][1], tkeys2[i][1], vis1, vis2) | |
| if m ~= 0 then | |
| return m | |
| end | |
| m = tmore(tkeys1[i][2], tkeys2[i][2], vis1, vis2) | |
| if m ~= 0 then | |
| return m | |
| end | |
| i = i + 1 | |
| end | |
| m = more(#tkeys1 - i, #tkeys2 - i) | |
| if m ~= 0 then | |
| return m | |
| end | |
| end | |
| return 0 | |
| else -- userdata, thread, function | |
| return p_more(first,second) | |
| end | |
| end | |
| end | |
| tmore = function(first, second, vis1, vis2) | |
| p_table = { n = 0 } | |
| local r = tmoreimpl(first, second, vis1, vis2) | |
| p_table = nil | |
| return r | |
| end | |
| end | |
| --- tdeepequals | |
| local tdeepequals = function(t1, t2) | |
| return tmore(t1, t2) == 0 | |
| end | |
| --- tless | |
| local tless = function(lhs, rhs) | |
| return tmore(lhs, rhs) < 0 | |
| end | |
| --- less_kv | |
| local less_kv = function(lhs, rhs) | |
| return (lhs.k < rhs.k) or (lhs.k == rhs.k and lhs.v < rhs.v) | |
| end | |
| --- tless_kv | |
| local tless_kv = function(lhs, rhs) | |
| local kless = tmore(lhs.k, rhs.k) | |
| return (kless < 0) | |
| or (kless == 0 and tmore(lhs.v, rhs.v) < 0) | |
| end | |
| --- tsort_kv | |
| -- WARNING: Slow! | |
| local tsort_kv = function(t, less) | |
| less = less or tless_kv | |
| local r = { } | |
| for k, v in pairs(t) do | |
| r[#r + 1] = { k = k, v = v } | |
| end | |
| table_sort(r, less) | |
| return r | |
| end | |
| --- Create iterator for table `t` that traverses keys in alphabetical order. | |
| -- @tparam table t Table to traverse. | |
| -- @tparam[opt] function less Less function, `tless` by default | |
| -- @return Iterator. | |
| local ordered_pairs = function(t, less) | |
| less = less or tless | |
| local keys = { } | |
| for k, _ in pairs(t) do | |
| keys[#keys + 1] = k | |
| end | |
| table_sort(keys, less) | |
| -- TODO: Try to move that out of this function. | |
| local i = 1 | |
| local ordered_next = function(t) | |
| local key = keys[i] | |
| i = i + 1 | |
| if key ~= nil then | |
| return key, t[key] | |
| end | |
| return key, nil | |
| end | |
| return ordered_next, t, nil | |
| end | |
| --- @export | |
| return | |
| { | |
| tdeepequals = tdeepequals; | |
| tmore = tmore; | |
| tless = tless; | |
| less_kv = less_kv; | |
| tless_kv = tless_kv; | |
| tsort_kv = tsort_kv; | |
| ordered_pairs = ordered_pairs; | |
| } |