1
0

Improved alignment of functions.

This commit is contained in:
Jon Michael Aanes 2017-06-11 13:53:06 +02:00
parent 1bfd4abb96
commit 2cc9301c58
4 changed files with 83 additions and 25 deletions

View File

@ -26,9 +26,12 @@ Contrast the two following pieces of code:
} }
``` ```
This project came out of the frustration with existing pretty printers, which This project is the outcome of my frustration with existing pretty printers, and
would often employ simpler heuristics, each good at displaying some specific a desire to expand upon the pretty printer I developed for
structure, but bad at others. See below for other pretty printers. [Xenoterm](https://gitfub.space/takunomi/Xenoterm). The default Xenoterm pretty
printer is much simpler than `pretty`, but the enhancements compared to other
pretty printers, inspired me to create `pretty`. See the bottom of the page for
other pretty printers.
Another aspect where `pretty` shines is in exploratory programming, when Another aspect where `pretty` shines is in exploratory programming, when
attempting to avoid reliance on outside documentation. attempting to avoid reliance on outside documentation.
@ -77,6 +80,7 @@ I'm looking into implementing following features:
non-breaking strings. Maybe even attempt to break near whitespace. non-breaking strings. Maybe even attempt to break near whitespace.
- Attempt to fit output within a predefined width limit. Default to 80(?) - Attempt to fit output within a predefined width limit. Default to 80(?)
- Find a better name than `pretty`. - Find a better name than `pretty`.
- Add options for colored output, maybe even support multiple?
## Other pretty printers ## Other pretty printers

View File

@ -85,7 +85,8 @@ local function get_function_info (f)
end end
if info.source:sub(1,1) == '=' then info.defined_how = 'C' if info.source:sub(1,1) == '=' then info.defined_how = 'C'
elseif info.source:sub(1,1) == '@' then info.defined_how = 'file' elseif info.source:sub(1,1) == '@' then info.defined_how = 'file'
elseif info.source:find'^%w+.lua$' then info.defined_how = 'file' -- Hotfix for Love2d boot.lua issue.
else info.defined_how = 'string' else info.defined_how = 'string'
end end
@ -99,6 +100,9 @@ local function get_function_info (f)
end end
local function get_line_index (str, line_nr) local function get_line_index (str, line_nr)
assert(type(str) == 'string')
assert(type(line_nr) == 'number')
local index = 0 local index = 0
for _ = 2, line_nr do for _ = 2, line_nr do
index = str:find('\n', index, true) index = str:find('\n', index, true)
@ -108,23 +112,40 @@ local function get_line_index (str, line_nr)
return index return index
end end
local function get_full_function_str (str, start_line, end_line) local function get_function_paramlist_and_body (str, start_line, end_line)
-- Will attempt to find a string which refer to a function starting on -- Will attempt to find a string which refer to a function starting on
-- line `start_line` and ending on line `end_line`. -- line `start_line` and ending on line `end_line`.
assert(type(str) == 'string')
assert(type(start_line) == 'number')
assert(type(end_line) == 'number')
local start_line_index = get_line_index(str, start_line) local start_line_index = get_line_index(str, start_line)
local end_line_index = get_line_index(str, end_line + 1) local end_line_index = get_line_index(str, end_line + 1)
local function_body = str:sub(start_line_index, end_line_index):match('function%s*[a-zA-Z0-9_.]*%s*(.+)end') local function_params, function_body = str:sub(start_line_index, end_line_index):match('function%s*[a-zA-Z0-9_.]*%s*(%([a-zA-Z0-9_,. \t]*%))[ \t]*(.-)[ \t]*end')
return 'function '..function_body..'end' --print(function_params, function_body)
assert(type(function_params) == 'string' and type(function_body) == 'string')
return function_params, function_body
end
local function get_full_function_str (...)
local function_params, function_body = get_function_paramlist_and_body(...)
return 'function '..function_params..' '..function_body..' end'
end end
local function get_function_str_from_file (filename, start_line, end_line) local function get_function_str_from_file (filename, start_line, end_line)
assert(type(filename) == 'string')
assert(type(start_line) == 'number')
assert(type(end_line) == 'number')
local file = io.open(filename, 'r') local file = io.open(filename, 'r')
local str = file:read('*all') local str = file:read('*all')
file:close() file:close()
return get_full_function_str(str, start_line, end_line) return get_full_function_str(str, start_line, end_line)
end 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)
-- FIXME: Copy of the one in pretty.lua -- FIXME: Copy of the one in pretty.lua
local width = 0 local width = 0
@ -187,15 +208,15 @@ return function (value, depth, l, format_value)
return format_function_with_closure(value, depth, l, format_value) return format_function_with_closure(value, depth, l, format_value)
end end
if info.defined_how == 'string' then local function_params, function_body = nil, '...'
if info.defined_how == 'string' and l.options.embed_loaded_funcs then
-- Function was defined as a string. -- Function was defined as a string.
l[#l+1] = get_full_function_str(info.source, info.linedefined, info.lastlinedefined) function_params, function_body = get_function_paramlist_and_body(info.source, info.linedefined, info.lastlinedefined)
return;
end end
if info.builtin and l.options.short_builtins then if info.builtin and l.options.short_builtins then
l[#l+1] = info.name return l(info.name);
return;
end end
-- Include function modifier, and alignment info. -- Include function modifier, and alignment info.
@ -203,17 +224,26 @@ return function (value, depth, l, format_value)
l[#l+1] = { 'align', 'func_mod', #l[#l]} l[#l+1] = { 'align', 'func_mod', #l[#l]}
-- Build rest of function signature -- Build rest of function signature
l[#l+1] = 'function (' l[#l+1] = 'function '
local top_before = #l local top_before = #l
for _, param in ipairs(info.params) do l[#l+1], l[#l+2] = param, ', ' end if function_params then
if l[#l] == ', ' then l[#l] = nil end l[#l+1] = function_params
l[#l+1] = ')' else
l[#l+1] = '('
for _, param in ipairs(info.params) do l[#l+1], l[#l+2] = param, ', ' end
if l[#l] == ', ' then l[#l] = nil end
l[#l+1] = ')'
end
l[#l+1] = { 'align', 'func_def', width_of_strings_in_l(l, top_before) } l[#l+1] = { 'align', 'func_def', width_of_strings_in_l(l, top_before) }
-- Cleanup and finish -- Cleanup and finish
print(not l.options.more_function_info, depth ~= 0)
if not l.options.more_function_info or depth ~= 0 then if not l.options.more_function_info or depth ~= 0 then
l[#l+1] = ' ... end' l[#l+1] = (function_body:sub(1,1) == '\n') and '' or ' '
return; l[#l+1] = function_body
l[#l+1] = { 'align', 'func_end', #function_body }
l[#l+1] = (function_body:sub(-1) == '\n' or function_body == '') and '' or ' '
return l 'end'
end end
-- More info! -- -- More info! --

View File

@ -249,7 +249,8 @@ local function fix_seperator_info (l, indent_char, max_depth)
if type(l[i]) ~= 'table' then if type(l[i]) ~= 'table' then
-- Do nothing -- Do nothing
elseif l[i][1] == 'seperator' then elseif l[i][1] == 'seperator' then
l[i] = inline_depth and ' ' or ('\n' .. indent_char:rep(depth)) assert(l[i][2] == nil or type(l[i][2]) == 'string')
l[i] = (l[i][2] or '') .. (inline_depth and ' ' or ('\n' .. indent_char:rep(depth)))
elseif l[i][1] == 'indent' then elseif l[i][1] == 'indent' then
depth, inline_depth = depth + 1, inline_depth or l[i][3] == 'inline' and depth + 1 or nil depth, inline_depth = depth + 1, inline_depth or l[i][3] == 'inline' and depth + 1 or nil
l[i] = l[i][2] .. (inline_depth and ' ' or ('\n' .. indent_char:rep(depth))) l[i] = l[i][2] .. (inline_depth and ' ' or ('\n' .. indent_char:rep(depth)))
@ -340,12 +341,14 @@ function format_table (t, depth, l)
if l.options._table_addr_comment then l[#l+1], l[#l+2] = '--[['..table_info.address..']]', {'seperator'} end if l.options._table_addr_comment then l[#l+1], l[#l+2] = '--[['..table_info.address..']]', {'seperator'} end
for _, pair in ipairs(key_value_pairs) do for _, pair in ipairs(key_value_pairs) do
pair_format_func(l, pair[1], pair[2], depth + 1) pair_format_func(l, pair[1], pair[2], depth + 1)
l[#l+1] = ',' l[#l+1] = {'seperator', ','}
l[#l+1] = {'seperator'}
end end
if l[#l][1] == 'seperator' then l[#l-1], l[#l] = nil, nil end if l[#l][1] == 'seperator' then l[#l] = nil end
l[#l+1] = {'unindent', '}'} l[#l+1] = {'unindent', '}'}
--require 'fun' () -- DEBUG!
--DEBUG = map(operator.identity, l) -- DEBUG!
-- Decide for short or long table formatting. -- Decide for short or long table formatting.
local table_width = width_of_strings_in_l(l, start_of_table_i) local table_width = width_of_strings_in_l(l, start_of_table_i)
if table_width <= MAX_WIDTH_FOR_SINGLE_LINE_TABLE then if table_width <= MAX_WIDTH_FOR_SINGLE_LINE_TABLE then
@ -471,6 +474,7 @@ local KNOWN_OPTIONS = {
math_shorthand = { type = 'boolean', default = false }, math_shorthand = { type = 'boolean', default = false },
soft_numbers = { type = 'boolean', default = true }, -- TODO: Add support for maximally precise numbers. soft_numbers = { type = 'boolean', default = true }, -- TODO: Add support for maximally precise numbers.
max_depth = { type = 'number', default = math.huge }, max_depth = { type = 'number', default = math.huge },
embed_loaded_funcs = { type = 'boolean', default = false },
more_function_info = { type = 'boolean', default = false }, more_function_info = { type = 'boolean', default = false },
recursion = { type = 'string', default = 'ignore', accepted = {['ignore'] = true, ['marked'] = true} }, recursion = { type = 'string', default = 'ignore', accepted = {['ignore'] = true, ['marked'] = true} },
short_builtins = { type = 'boolean', default = false }, short_builtins = { type = 'boolean', default = false },

View File

@ -135,18 +135,21 @@ end
format_test { format_test {
adv_getlocal = true, adv_getlocal = true,
options = { embed_loaded_funcs = true },
input = loadstring('return function () end')(), input = loadstring('return function () end')(),
expect = 'function () end', expect = 'function () end',
} }
format_test { format_test {
adv_getlocal = true, adv_getlocal = true,
options = { embed_loaded_funcs = true },
input = loadstring('return function () return function () end end')(), input = loadstring('return function () return function () end end')(),
expect = 'function () return function () end end', expect = 'function () return function () end end',
} }
format_test { format_test {
adv_getlocal = true, adv_getlocal = true,
options = { embed_loaded_funcs = true },
input = loadstring('return function () return function () end\nend')()(), input = loadstring('return function () return function () end\nend')()(),
expect = 'function () end', expect = 'function () end',
} }
@ -154,6 +157,7 @@ format_test {
format_test { format_test {
-- NOTE: This is HARD to fix. It's thus longerterm -- NOTE: This is HARD to fix. It's thus longerterm
adv_getlocal = true, adv_getlocal = true,
options = { embed_loaded_funcs = true },
input = loadstring('return function () return function () end end')()(), input = loadstring('return function () return function () end end')()(),
expect = 'function () end', expect = 'function () end',
} }
@ -161,14 +165,14 @@ format_test {
format_test { format_test {
-- More function info allows one to even get the function whole, if it was defined in a string. -- More function info allows one to even get the function whole, if it was defined in a string.
input = loadstring('return function (a, b) return a + b end')(), input = loadstring('return function (a, b) return a + b end')(),
options = { more_function_info = true }, options = { embed_loaded_funcs = true },
expect = 'function (a, b) return a + b end', expect = 'function (a, b) return a + b end',
} }
format_test { format_test {
-- More function info allows one to even get the function whole, if it was defined in a string. -- More function info allows one to even get the function whole, if it was defined in a string.
input = loadstring('return function (a, b)\n return a + b\nend')(), input = loadstring('return function (a, b)\n return a + b\nend')(),
options = { more_function_info = true }, options = { embed_loaded_funcs = true },
expect = 'function (a, b)\n return a + b\nend', expect = 'function (a, b)\n return a + b\nend',
} }
@ -362,6 +366,22 @@ format_test {
expect = '{\n abs = function (x) ... end,\n random = builtin function ([m [, n]) ... end\n}', expect = '{\n abs = function (x) ... end,\n random = builtin function ([m [, n]) ... end\n}',
} }
format_test {
-- The function part should align, even if one is loaded from a string.
adv_getlocal = true,
input = { random = math.random, abs = loadstring('return function () return 1 end')() },
options = { embed_loaded_funcs = true },
expect = '{\n abs = function () return 1 end,\n random = builtin function ([m [, n]) ... end\n}',
}
format_test {
-- The end part should align when both are loaded from strings.
adv_getlocal = true,
input = { a = loadstring'return function(a) return a end'(), b = loadstring'return function (...) return ... end'() },
options = { embed_loaded_funcs = true },
expect = '{\n a = function (a) return a end,\n b = function (...) return ... end\n}',
}
format_test { format_test {
-- No special indent if no special function modifier. -- No special indent if no special function modifier.
adv_getlocal = true, adv_getlocal = true,