Working on more extensive table info gathering.
This commit is contained in:
parent
d848c96fb1
commit
e2570ee780
187
analyze_structure.lua
Normal file
187
analyze_structure.lua
Normal file
|
@ -0,0 +1,187 @@
|
|||
|
||||
local RESERVED_LUA_WORDS = {
|
||||
['and'] = true,
|
||||
['break'] = true,
|
||||
['do'] = true,
|
||||
['else'] = true,
|
||||
['elseif'] = true,
|
||||
['end'] = true,
|
||||
['false'] = true,
|
||||
['for'] = true,
|
||||
['function'] = true,
|
||||
['if'] = true,
|
||||
['in'] = true,
|
||||
['local'] = true,
|
||||
['nil'] = true,
|
||||
['not'] = true,
|
||||
['or'] = true,
|
||||
['repeat'] = true,
|
||||
['return'] = true,
|
||||
['then'] = true,
|
||||
['true'] = true,
|
||||
['until'] = true,
|
||||
['while'] = true,
|
||||
['goto'] = true,
|
||||
}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local function is_identifier(str)
|
||||
-- An identier is defined in the lua reference guide
|
||||
|
||||
return str:match('^[_%a][_%w]*$') and not RESERVED_LUA_WORDS[str]
|
||||
end
|
||||
|
||||
local function get_key_types (t)
|
||||
local types = { nr_types = -1 }
|
||||
for key, _ in pairs(t) do
|
||||
types[type(key)] = true
|
||||
end
|
||||
--
|
||||
for type_name, _ in pairs(types) do
|
||||
types.nr_types = types.nr_types + 1
|
||||
end
|
||||
return types
|
||||
end
|
||||
|
||||
local function get_value_types (t)
|
||||
local types = { nr_types = -1 }
|
||||
for _, value in pairs(t) do
|
||||
types[type(value)] = true
|
||||
end
|
||||
--
|
||||
for type_name, _ in pairs(types) do
|
||||
types.nr_types = types.nr_types + 1
|
||||
end
|
||||
return types
|
||||
end
|
||||
|
||||
|
||||
local function largest_number_index (t)
|
||||
-- Returns the largest number index in t.
|
||||
|
||||
local max_index = 0
|
||||
for k,v in pairs(t) do
|
||||
if type(k) == 'number' then
|
||||
max_index = math.max(max_index, k)
|
||||
end
|
||||
end
|
||||
return max_index
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local function contains_only_nice_string_keys (t)
|
||||
-- Predicate: Does t contain only string keys which could be used as
|
||||
-- identifiers, eg.
|
||||
|
||||
for k, _ in pairs(t) do
|
||||
if type(k) ~= 'string' or not is_identifier(k) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function contains_only_nice_number_indexes (t)
|
||||
-- Predicate: Does t contain only number keys, all of which are integer,
|
||||
-- larger than/equal 1 and less than the maximum index.
|
||||
|
||||
local max_index = largest_number_index(t)
|
||||
for k, v in pairs(t) do
|
||||
if type(k) ~= 'number' or k < 1 or max_index < k or k ~= math.floor(k) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return #t > 0
|
||||
end
|
||||
|
||||
local function is_set (t)
|
||||
-- Predicate: Does t contain only boolean values.
|
||||
local value_types = get_value_types(t)
|
||||
return value_types.boolean and value_types.nr_types == 1
|
||||
end
|
||||
|
||||
local function is_tabular (t)
|
||||
-- Predicate: Does t contain sub-tables with identical substructure.
|
||||
|
||||
-- Quick return if table is empty, or not conctaining only values of type table.
|
||||
local value_types = get_value_types(t)
|
||||
if not value_types.table or value_types.nr_types ~= 1 then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Determine keys of first child.
|
||||
local children_keys = {}
|
||||
for key, _ in pairs(t[next(t)]) do
|
||||
children_keys[key] = true
|
||||
end
|
||||
|
||||
-- Make sure every other child has exact same sub-structure.
|
||||
for _, child in pairs(t) do
|
||||
for key, _ in pairs(children_keys) do
|
||||
if not child[key] then return false end
|
||||
end
|
||||
for key, _ in pairs(child) do
|
||||
if not children_keys[key] then return false end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local function get_table_info (t)
|
||||
local key_types = get_key_types(t)
|
||||
|
||||
local info = {}
|
||||
info.has_seq = (#t > 0)
|
||||
info.has_map = key_types.nr_types > (key_types.number and 1 or 0)
|
||||
info.is_set = is_set(t)
|
||||
info.is_tabular = is_tabular(t)
|
||||
|
||||
-- Determine type of table
|
||||
if not info.has_seq and not info.has_map then info.type = 'empty'
|
||||
elseif info.has_seq and not info.has_map then info.type = 'sequence'
|
||||
elseif info.is_set then info.type = 'set'
|
||||
elseif info.has_seq then info.type = 'mixed'
|
||||
elseif contains_only_nice_string_keys(t) then info.type = 'string_map'
|
||||
else info.type = 'pure_map'
|
||||
end
|
||||
|
||||
return info
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
local function visit_table (t, info, visited)
|
||||
|
||||
-- Who've been visited? Bookkeeping
|
||||
visited[t] = (visited[t] or 0) + 1
|
||||
if visited[t] == 2 then
|
||||
info[t].marker, info.next_mark = info.next_mark, info.next_mark + 1
|
||||
end
|
||||
if visited[t] >= 2 then return end
|
||||
|
||||
-- Get table info
|
||||
info[t] = get_table_info(t)
|
||||
|
||||
-- Visit children.
|
||||
for k,v in pairs(t) do
|
||||
if type(k) == 'table' then visit_table(k, info, visited) end
|
||||
if type(v) == 'table' then visit_table(v, info, visited) end
|
||||
end
|
||||
end
|
||||
|
||||
local function analyze_structure (t)
|
||||
local info, visited = { root = t, next_mark = 1 }, {}
|
||||
visit_table(t, info, visited)
|
||||
info.next_mark = nil
|
||||
return info
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
return analyze_structure
|
144
test/test_analyze_structure.lua
Normal file
144
test/test_analyze_structure.lua
Normal file
|
@ -0,0 +1,144 @@
|
|||
|
||||
local SUITE = require('TestSuite').new('analyze_structure')
|
||||
SUITE:setEnviroment { analyze_structure = require('analyze_structure') }
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- analyze_structure
|
||||
|
||||
SUITE:addTest('Empty Table', function ()
|
||||
local input = {} -- Empty!
|
||||
local table_info = analyze_structure(input)[input]
|
||||
|
||||
assert(table_info.type == 'empty', 'Returned bad type: '..table_info.type)
|
||||
assert(table_info.has_seq == false)
|
||||
assert(table_info.has_map == false)
|
||||
end)
|
||||
|
||||
SUITE:addTest('Sequence', function ()
|
||||
local input = { 1, 2, 3 }
|
||||
local table_info = analyze_structure(input)[input]
|
||||
|
||||
assert(table_info.type == 'sequence', 'Returned bad type: '..table_info.type)
|
||||
assert(table_info.has_seq == true)
|
||||
assert(table_info.has_map == false)
|
||||
end)
|
||||
|
||||
SUITE:addTest('Pure Map', function ()
|
||||
local input = { a = 1, [true] = 2, c = 3 }
|
||||
local table_info = analyze_structure(input)[input]
|
||||
|
||||
assert(table_info.type == 'pure_map', 'Returned bad type: '..table_info.type)
|
||||
assert(table_info.has_seq == false)
|
||||
assert(table_info.has_map == true)
|
||||
end)
|
||||
|
||||
SUITE:addTest('String Map', function ()
|
||||
local input = { a = 1, b = 2, c = 3 }
|
||||
local table_info = analyze_structure(input)[input]
|
||||
|
||||
assert(table_info.type == 'string_map', 'Returned bad type: '..table_info.type)
|
||||
assert(table_info.has_seq == false)
|
||||
assert(table_info.has_map == true)
|
||||
end)
|
||||
|
||||
SUITE:addTest('Set', function ()
|
||||
local input = { a = true, b = true, c = true }
|
||||
local table_info = analyze_structure(input)[input]
|
||||
|
||||
assert(table_info.type == 'set', 'Returned bad type: '..table_info.type)
|
||||
assert(table_info.is_set == true)
|
||||
end)
|
||||
|
||||
SUITE:addTest('Tabular of sequences', function ()
|
||||
local input = { a = {1, 2, 3}, b = {4, 5, 6}, c = {7, 8, 9} }
|
||||
local table_info = analyze_structure(input)[input]
|
||||
|
||||
assert(table_info.is_tabular == true)
|
||||
end)
|
||||
|
||||
SUITE:addTest('Tabular of maps', function ()
|
||||
local input = { a = {a = 1, b = 2}, b = {a = 3, b = 4}, c = {a = 2, b = 7} }
|
||||
local table_info = analyze_structure(input)[input]
|
||||
|
||||
assert(table_info.is_tabular == true)
|
||||
end)
|
||||
|
||||
SUITE:addTest('Not Tabular, due to no-sub-tables', function ()
|
||||
local input = { a = 1, b = {4}, c = 7 }
|
||||
local table_info = analyze_structure(input)[input]
|
||||
|
||||
assert(table_info.is_tabular == false)
|
||||
end)
|
||||
|
||||
SUITE:addTest('Not Tabular, due to not being identical sub-tables', function ()
|
||||
local input = { a = { a = 1 }, b = { b = 2 }, c = { c = 3 } }
|
||||
local table_info = analyze_structure(input)[input]
|
||||
|
||||
assert(table_info.is_tabular == false)
|
||||
end)
|
||||
|
||||
SUITE:addTest('Not Tabular, due to varying lengths', function ()
|
||||
local input = { { 1 }, { 2, 3 }, { 4, 5, 6 } }
|
||||
local table_info = analyze_structure(input)[input]
|
||||
|
||||
assert(table_info.is_tabular == false)
|
||||
end)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Corner cases.
|
||||
|
||||
SUITE:addTest('goto is special', function ()
|
||||
local input = { ['goto'] = 'hi' }
|
||||
local table_info = analyze_structure(input)[input]
|
||||
|
||||
assert(table_info.type == 'pure_map', 'Returned bad type: '..table_info.type)
|
||||
end)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- analyze_structure
|
||||
|
||||
SUITE:addTest('Recursive Numeration, Simple', function ()
|
||||
local input = {}
|
||||
input[1] = input
|
||||
local info = analyze_structure(input)
|
||||
|
||||
assert(info[input].marker == 1)
|
||||
end)
|
||||
|
||||
SUITE:addTest('Recursive Numeration, Multiple-appear', function ()
|
||||
local input = { {}, { {} } }
|
||||
input[1][1] = input[2][1]
|
||||
local info = analyze_structure(input)
|
||||
|
||||
assert(info[input[1][1]].marker == 1)
|
||||
end)
|
||||
|
||||
SUITE:addTest('Recursive Numeration, Multiple at once', function ()
|
||||
local input = { {}, { {} } }
|
||||
input[1][1] = input[2][1]
|
||||
input[1][2] = input
|
||||
local info = analyze_structure(input)
|
||||
|
||||
assert(type(info[input[1][1]].marker) == 'number')
|
||||
assert(type(info[input].marker) == 'number')
|
||||
end)
|
||||
|
||||
SUITE:addTest('Recursive Numeration, Through Keys', function ()
|
||||
local input = { }
|
||||
input[input] = input
|
||||
local info = analyze_structure(input)
|
||||
|
||||
assert(info[input].marker == 1)
|
||||
end)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- API stuff
|
||||
|
||||
SUITE:addTest('next_mark does not escape', function ()
|
||||
local info = analyze_structure( {} )
|
||||
assert(not info.next_mark)
|
||||
end)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
return SUITE
|
Loading…
Reference in New Issue
Block a user