2017-01-19 13:02:34 +00:00
|
|
|
|
2017-04-03 09:24:51 +00:00
|
|
|
local TABLE_TYPE = require "table_type"
|
|
|
|
|
2017-01-19 13:02:34 +00:00
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
2017-01-19 13:23:34 +00:00
|
|
|
local SIMPLE_VALUE_TYPES = {
|
|
|
|
['nil'] = true,
|
|
|
|
['boolean'] = true,
|
|
|
|
['number'] = true,
|
|
|
|
}
|
|
|
|
|
|
|
|
local SHORT_STRING_MAX_LEN = 7
|
|
|
|
|
2017-01-19 13:02:34 +00:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2017-01-19 13:23:34 +00:00
|
|
|
local is_short_table, is_simple_value
|
|
|
|
|
|
|
|
function is_short_table (value)
|
|
|
|
-- Predicate: value is either an empty table, or one with a single simple
|
|
|
|
-- non-function element.
|
|
|
|
|
|
|
|
assert( type(value) == 'table', '[analyze_structure]: Only tables allowed!' )
|
|
|
|
|
|
|
|
local first_key = next(value, nil)
|
|
|
|
if not first_key then
|
|
|
|
return true
|
|
|
|
elseif not next(value, first_key) == nil then
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
|
|
|
return type(value[first_key]) ~= 'table'
|
|
|
|
and is_simple_value( value[first_key] )
|
|
|
|
end
|
|
|
|
|
|
|
|
function is_simple_value (value)
|
|
|
|
-- Predicate: value is either nil, a boolean, a number, a short string or a
|
|
|
|
-- short table.
|
|
|
|
|
|
|
|
return SIMPLE_VALUE_TYPES[ type(value) ]
|
|
|
|
or type(value) == 'string' and #value <= SHORT_STRING_MAX_LEN
|
|
|
|
or type(value) == 'table' and is_short_table(value)
|
|
|
|
end
|
|
|
|
|
2017-01-19 13:02:34 +00:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
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)
|
2017-01-19 13:23:34 +00:00
|
|
|
info.is_short = is_short_table(t)
|
2017-01-19 13:02:34 +00:00
|
|
|
|
|
|
|
-- Determine type of table
|
2017-04-03 09:24:51 +00:00
|
|
|
if not info.has_seq and not info.has_map then info.type = TABLE_TYPE.EMPTY
|
|
|
|
elseif info.has_seq and not info.has_map then info.type = TABLE_TYPE.SEQUENCE
|
|
|
|
elseif info.is_set then info.type = TABLE_TYPE.SET
|
|
|
|
elseif info.has_seq then info.type = TABLE_TYPE.MIXED
|
|
|
|
elseif contains_only_nice_string_keys(t) then info.type = TABLE_TYPE.STRING_MAP
|
|
|
|
else info.type = TABLE_TYPE.PURE_MAP
|
2017-01-19 13:02:34 +00:00
|
|
|
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
|