1
0

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.
This commit is contained in:
Jon Michael Aanes 2017-04-14 12:19:23 +02:00
parent 5bee4a7378
commit 155c877987
5 changed files with 79 additions and 126 deletions

View File

@ -71,7 +71,6 @@ local function get_value_types (t)
return types return types
end end
local function largest_number_index (t) local function largest_number_index (t)
-- Returns the largest number index in t. -- Returns the largest number index in t.
@ -84,6 +83,14 @@ local function largest_number_index (t)
return max_index return max_index
end 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) local function contains_only_nice_string_keys (t)
@ -200,6 +207,7 @@ local function get_table_info (t)
info.is_set = is_set(t) info.is_set = is_set(t)
info.is_tabular = is_tabular(t) info.is_tabular = is_tabular(t)
info.is_short = is_short_table(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 -- Determine type of table
if not info.has_seq and not info.has_map then info.type = TABLE_TYPE.EMPTY if not info.has_seq and not info.has_map then info.type = TABLE_TYPE.EMPTY

View File

@ -36,6 +36,7 @@ local SINGLE_LINE_TABLE_TYPES = {
local SINGLE_LINE_SEQ_MAX_ELEMENTS = 10 local SINGLE_LINE_SEQ_MAX_ELEMENTS = 10
local SINGLE_LINE_MAP_MAX_ELEMENTS = 5 local SINGLE_LINE_MAP_MAX_ELEMENTS = 5
local NR_CHARS_IN_LONG_STRING = 40 local NR_CHARS_IN_LONG_STRING = 40
local MAX_WIDTH_FOR_SINGLE_LINE_TABLE = 38
local KEY_TYPE_SORT_ORDER = { local KEY_TYPE_SORT_ORDER = {
['number'] = 0, ['number'] = 0,
@ -171,14 +172,6 @@ local function fill_holes_in_key_value_pairs (key_value_pairs)
table.sort(key_value_pairs, compare_key_value_pairs) table.sort(key_value_pairs, compare_key_value_pairs)
end 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 is_identifier(str) local function is_identifier(str)
-- An identier is defined in the lua reference guide -- An identier is defined in the lua reference guide
@ -222,7 +215,7 @@ end
local function width_of_strings_in_l (l, start_i, end_i) local function width_of_strings_in_l (l, start_i, end_i)
local width = 0 local width = 0
for i = start_i or 1, (end_i or #l) do for i = start_i or 1, (end_i or #l) do
width = width + #l[i] width = width + ((type(l[i]) ~= 'string') and 1 or #l[i])
end end
return width return width
end end
@ -236,9 +229,11 @@ local function ignore_alignment_info (l, start_i, stop_i)
end end
local function fix_alignment (l, start_i, stop_i) local function fix_alignment (l, start_i, stop_i)
local start_i, stop_i = start_i or 1, stop_i or #l
-- Find maximums -- Find maximums
local max = {} local max = {}
for i = start_i or 1, stop_i or #l do for i = start_i, stop_i do
if type(l[i]) == 'table' then if type(l[i]) == 'table' then
max[ l[i][2] ] = math.max( l[i][1], max[ l[i][2] ] or 0 ) max[ l[i][2] ] = math.max( l[i][1], max[ l[i][2] ] or 0 )
end end
@ -252,16 +247,23 @@ local function fix_alignment (l, start_i, stop_i)
end end
end end
local function replace_seperator_info (l, replace_with, indent_char, depth, start_i, stop_i)
for i = start_i or 1, stop_i or #l do
if type(l[i]) ~= 'table' then
-- Do nothing
elseif l[i][1] == 'seperator' then
l[i] = replace_with .. indent_char:rep(depth)
elseif l[i][1] == 'indent' then
l[i], depth = '', depth + 1
elseif l[i][1] == 'unindent' then
l[i], depth = '', depth - 1
end
end
end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Identifyer stuff -- Identifyer stuff
local SIMPLE_VALUE_TYPES = {
['nil'] = true,
['boolean'] = true,
['number'] = true,
['string'] = true,
}
local function is_empty_table (value) local function is_empty_table (value)
if type(value) ~= 'table' then if type(value) ~= 'table' then
error(('[pretty/internal]: Only tables allowed in function pretty.is_empty_table, but was given %s (%s)'):format(value, type(value)), 2) error(('[pretty/internal]: Only tables allowed in function pretty.is_empty_table, but was given %s (%s)'):format(value, type(value)), 2)
@ -269,38 +271,6 @@ local function is_empty_table (value)
return next(value) == nil return next(value) == nil
end end
local function is_short_table (value)
-- In this context, a short table is either an empty table, or one with a
-- single element.
if type(value) ~= 'table' then
error(('[pretty/internal]: Only tables allowed in function pretty.is_short_table, but was given %s (%s)'):format(value, type(value)), 2)
end
local first_key = next(value)
return (not first_key or SIMPLE_VALUE_TYPES[type(value[first_key])])
and (next(value, first_key) == nil)
end
local function is_simple_value (value)
-- In this context, a simple value is a either nil, a boolean, a number,
-- a string or a short table.
-- TODO: Add clause about long strings. (Maybe >7 chars?)
--if type(value) == 'table' then print(value, is_short_table(value)) end
return SIMPLE_VALUE_TYPES[ type(value) ]
or type(value) == 'table' and is_short_table(value)
end
local function contains_non_simple_key_or_value (t)
for k, v in pairs(t) do
if not is_simple_value(k) or not is_simple_value(v) then
return true
end
end
return false
end
local function get_table_type (value) local function get_table_type (value)
-- Determines table type: -- Determines table type:
-- * Sequence: All keys are integer in the range 1..#value -- * Sequence: All keys are integer in the range 1..#value
@ -321,21 +291,6 @@ local function get_table_type (value)
end end
end end
local function is_single_line_table (value)
-- In this context, a single-line table, is:
-- A) Either a sequence or a pure map.
-- B) Has no non-simple keys or values
-- C 1) If sequence, has at most SINGLE_LINE_SEQ_MAX_ELEMENTS elements.
-- C 2) If map, has at most SINGLE_LINE_MAP_MAX_ELEMENTS elements.
local table_type = get_table_type(value)
return not contains_non_simple_key_or_value(value)
and SINGLE_LINE_TABLE_TYPES[table_type]
and #value <= SINGLE_LINE_SEQ_MAX_ELEMENTS
and nr_elements_in_map(value) <= SINGLE_LINE_MAP_MAX_ELEMENTS
end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Formatting stuff -- Formatting stuff
@ -353,7 +308,7 @@ end
local function format_key_and_value_arbitr_map (l, key, value, options, depth) local function format_key_and_value_arbitr_map (l, key, value, options, depth)
local index_before_key = #l+1 local index_before_key = #l+1
l[#l+1] = '[' l[#l+1] = '['
format_value(key, options, 'max', l) format_value(key, options, 'max', l) -- TODO: Outphase the usage of the "max" depth thingy.
l[#l+1] = ']' l[#l+1] = ']'
l[#l+1] = { width_of_strings_in_l(l, index_before_key), 'key' } l[#l+1] = { width_of_strings_in_l(l, index_before_key), 'key' }
l[#l+1] = ' = ' l[#l+1] = ' = '
@ -375,7 +330,7 @@ local TABLE_TYPE_TO_PAIR_FORMAT = {
-- Formatting tables -- Formatting tables
local function format_single_line_map (t, options, l) local function format_map (t, options, depth, l)
-- NOTE: Assumes that the input table was pre-checked with `is_single_line_table()` -- NOTE: Assumes that the input table was pre-checked with `is_single_line_table()`
local table_type = l.info[t] and l.info[t].type or get_table_type(t) -- FIXME: This is a temp fix local table_type = l.info[t] and l.info[t].type or get_table_type(t) -- FIXME: This is a temp fix
@ -385,48 +340,33 @@ local function format_single_line_map (t, options, l)
end end
local pair_format_func = TABLE_TYPE_TO_PAIR_FORMAT[table_type] local pair_format_func = TABLE_TYPE_TO_PAIR_FORMAT[table_type]
local start_of_table_i = #l + 1
l[#l+1] = '{' l[#l+1] = '{'
local top_before = #l l[#l+1] = {'indent'}
l[#l+1] = {'seperator'}
for _, pair in ipairs(key_value_pairs) do for _, pair in ipairs(key_value_pairs) do
pair_format_func(l, pair[1], pair[2], options, 'max')
l[#l+1] = ', '
end
-- Ignore the "width of key"-shit
ignore_alignment_info(l, top_before, #l)
if l[#l] == ', ' then l[#l] = nil end
l[#l+1] = ' }'
end
local function format_map (t, options, depth, l)
-- TODO: l.
local table_type = l.info[t] and l.info[t].type or get_table_type(t) -- FIXME: This is a temp fix
local key_value_pairs = get_key_value_pairs_in_proper_order(t)
if table_type == TABLE_TYPE.SEQUENCE and l.info[t].has_holes then
fill_holes_in_key_value_pairs(key_value_pairs)
end
local pair_format_func = TABLE_TYPE_TO_PAIR_FORMAT[table_type]
l[#l+1] = '{\n'
local top_before = #l
for _, pair in pairs(key_value_pairs) do
l[#l+1] = options.indent:rep(depth + 1)
pair_format_func(l, pair[1], pair[2], options, depth + 1) pair_format_func(l, pair[1], pair[2], options, depth + 1)
l[#l+1] = ',\n' l[#l+1] = ','
l[#l+1] = {'seperator'}
end end
-- Fix whitespace alignment if l[#l][1] == 'seperator' then l[#l-1], l[#l] = nil, nil end
fix_alignment(l, top_before, #l) l[#l+1] = {'unindent'}
l[#l+1] = {'seperator'}
-- Fix and cleanup
if l[#l] == ',\n' then l[#l] = nil end
l[#l+1] = '\n'
l[#l+1] = options.indent:rep(depth)
l[#l+1] = '}' l[#l+1] = '}'
local table_width = width_of_strings_in_l(l, start_of_table_i)
if table_width <= MAX_WIDTH_FOR_SINGLE_LINE_TABLE then
-- Is short table: Ignore the "width of key"-shit
replace_seperator_info(l, ' ', '', 0, start_of_table_i)
ignore_alignment_info(l, start_of_table_i)
else
-- Is long table: Fix whitespace alignment
replace_seperator_info(l, '\n', options.indent, depth, start_of_table_i)
fix_alignment(l, start_of_table_i)
end
end end
function format_table (t, options, depth, l) function format_table (t, options, depth, l)
@ -444,9 +384,6 @@ function format_table (t, options, depth, l)
l.visited[t] = true l.visited[t] = true
-- Single line?
if is_single_line_table(t) then return format_single_line_map(t, options, l) end
if depth == 'max' then l[#l+1] = '{...}'; return end if depth == 'max' then l[#l+1] = '{...}'; return end
-- Normal table -- Normal table

View File

@ -84,7 +84,7 @@ do
adv_getlocal = true, adv_getlocal = true,
input = { a = function () SOME_RANDOM_UPVALUE = true end }, input = { a = function () SOME_RANDOM_UPVALUE = true end },
options = { more_function_info = true }, options = { more_function_info = true },
expect = '{\n\ta = function () ... end\n}', expect = '{ a = function () ... end }',
} }
@ -232,7 +232,7 @@ format_test {
format_test { format_test {
input = { math.cos, math.sin, math.abs }, input = { math.cos, math.sin, math.abs },
options = { short_builtins = true }, options = { short_builtins = true },
expect = '{\n\tmath.cos,\n\tmath.sin,\n\tmath.abs\n}', expect = '{ math.cos, math.sin, math.abs }',
} }
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View File

@ -217,7 +217,7 @@ format_test { -- Can include very small tables
format_test { format_test {
input = { {1, 2, 3}, {4, 5, 6} }, input = { {1, 2, 3}, {4, 5, 6} },
expect = '{\n\t{ 1, 2, 3 },\n\t{ 4, 5, 6 }\n}', expect = '{ { 1, 2, 3 }, { 4, 5, 6 } }',
} }
format_test { format_test {
@ -232,17 +232,17 @@ format_test {
format_test { format_test {
input = { { {} } }, input = { { {} } },
expect = '{\n\t{ {} }\n}', expect = '{ { {} } }',
} }
format_test { format_test {
input = { [{ 1, 2 }] = { 2, 1 } }, input = { [{ 1, 2 }] = { 2, 1 } },
expect = '{\n\t[{ 1, 2 }] = { 2, 1 }\n}', expect = '{ [{ 1, 2 }] = { 2, 1 } }',
} }
format_test { format_test {
input = { { {1, 2}, {3, 4} }, {5, 6} }, input = { { {1, 2}, {3, 4} }, {5, 6} },
expect = '{\n\t{\n\t\t{ 1, 2 },\n\t\t{ 3, 4 }\n\t},\n\t{ 5, 6 }\n}', expect = '{ { { 1, 2 }, { 3, 4 } }, { 5, 6 } }',
} }
format_test { format_test {
@ -254,24 +254,24 @@ format_test {
format_test { format_test {
input = { { {1, 2}, {3, 4} }, {5, 6} }, input = { { {1, 2}, {3, 4} }, {5, 6} },
options = { max_depth = 1 }, options = { max_depth = 1 },
expect = '{\n\t{...},\n\t{...}\n}', expect = '{ {...}, {...} }',
} }
format_test { format_test {
input = { { {1, 2}, {3, 4} }, {5, 6} }, input = { { {1, 2}, {3, 4} }, {5, 6} },
options = { max_depth = 2 }, options = { max_depth = 2 },
expect = '{\n\t{\n\t\t{...},\n\t\t{...}\n\t},\n\t{ 5, 6 }\n}', expect = '{ { {...}, {...} }, { 5, 6 } }',
} }
format_test { format_test {
input = { { {1, 2}, {3, 4} }, {5, 6} }, input = { { {1, 2}, {3, 4} }, {5, 6} },
options = { max_depth = 3 }, options = { max_depth = 3 },
expect = '{\n\t{\n\t\t{ 1, 2 },\n\t\t{ 3, 4 }\n\t},\n\t{ 5, 6 }\n}', expect = '{ { { 1, 2 }, { 3, 4 } }, { 5, 6 } }',
} }
format_test { format_test {
input = { [{ {1,2}, {3,4} }] = 'Hello World' }, input = { [{ {1,2}, {3,4} }] = 'Hello World' },
expect = '{\n\t[{...}] = \'Hello World\'\n}', expect = '{ [{...}] = \'Hello World\' }',
} }
format_test { format_test {
@ -281,13 +281,13 @@ format_test {
format_test { format_test {
input = { [true] = 1, [1] = false }, input = { [true] = 1, [1] = false },
expect = '{\n\t[1] = false,\n\t[true] = 1\n}', expect = '{ [1] = false, [true] = 1 }',
} }
format_test { format_test {
-- Proper indent -- Proper indent
input = { [1] = 1, ['whatever'] = false }, input = { [1] = 1, ['whatever'] = false },
expect = '{\n\t[1] = 1,\n\t[\'whatever\'] = false\n}', expect = '{ [1] = 1, [\'whatever\'] = false }',
} }
format_test { format_test {

View File

@ -46,9 +46,8 @@ end)
SUITE:addTest('no_std_lib', function () SUITE:addTest('no_std_lib', function ()
-- This tests whether one could load the library with an empty env, without -- This tests whether one could load the library with an empty env, without
-- an error. -- an error.
local chunk = loadfile('./library.lua') local env = {}
setfenv(chunk, {}) local library = setfenv(loadfile('./library.lua'), env)()
local library = chunk()
for func, func_info in pairs(library) do for func, func_info in pairs(library) do
error(('For some reason %s is defined in the library'):format(func_info.name)) error(('For some reason %s is defined in the library'):format(func_info.name))
@ -58,13 +57,22 @@ end)
SUITE:addTest('a_very_small_part_of_math', function () SUITE:addTest('a_very_small_part_of_math', function ()
-- This tests whether one could load the library with an empty env, without -- This tests whether one could load the library with an empty env, without
-- an error. -- an error.
local chunk = loadfile('./library.lua') local env = { math = { abs = math.abs } }
setfenv(chunk, { math = { abs = math.abs } }) local library = setfenv(loadfile('./library.lua'), env)()
local library = chunk()
assert( library[math.abs], 'Why is math.abs not defined in the library?' ) assert( library[math.abs], 'Why is math.abs not defined in the library?' )
end) end)
SUITE:addTest('redefined part of math', function ()
-- This tests whether the library ignores a redefined function of an
-- builtin, when defining documentation.
local env = { setfenv = setfenv, loadfile = function() end }
local library = setfenv(loadfile('./library.lua'), env)()
assert( library[env.setfenv], 'Why is setfenv not defined in the library?' )
assert( not library[env.loadfile], 'Why is loadfile defined in the library?' )
end)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
return SUITE return SUITE