local TABLE_TYPE = require "table_type" 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 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 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 -------------------------------------------------------------------------------- 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) info.is_short = is_short_table(t) -- 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