1
0
pretty/analyze_structure.lua
Jon Michael Aanes 155c877987 Alternative process for determining short tables
This one is based on the representative width of the table. Not only
does this produce better results, but it's also more futureproof.
2017-04-14 12:19:23 +02:00

255 lines
7.1 KiB
Lua

local TABLE_TYPE
do
local thispath = ... and select('1', ...):match('.+%.') or ''
was_loaded, TABLE_TYPE = pcall(require, thispath..'table_type')
assert(was_loaded, '[pretty]: Could not load vital library: table_type')
end
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 SIMPLE_VALUE_TYPES = {
['nil'] = true,
['boolean'] = true,
['number'] = true,
}
local SHORT_STRING_MAX_LEN = 7
--------------------------------------------------------------------------------
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 nr_elements_in_map (t)
local k, count = nil, -1
repeat
k, count = next(t, k), count + 1
until not k
return count
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 has_seq (t)
if not contains_only_nice_number_indexes(t) then return false end
-- Contain list of keys
local keys = {[0] = 0}
for i, _ in pairs(t) do keys[#keys+1] = i end
table.sort(keys)
-- Check to see that no indice jumps more than 2
for indice_i = 1, #keys do
if keys[indice_i - 1] < keys[indice_i] - 2 then return false end
end
return true, (#keys ~= keys[#keys])
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 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.
if type(value) ~= 'table' then
error(('[pretty/internal]: Only tables allowed in function analyze_structure.is_short_table, but was given %s (%s)'):format(value, type(value)), 2)
end
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
--------------------------------------------------------------------------------
local function get_table_info (t)
local key_types = get_key_types(t)
local info = {}
info.has_seq, info.has_holes = has_seq(t)
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)
info.is_short = is_short_table(t)
info.nr_elems = nr_elements_in_map(t) -- TODO: Use this for something.
-- Determine type of table
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
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