diff --git a/README.md b/README.md index 1c50b5e..66bb82c 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,12 @@ Contrast the two following pieces of code: } ``` -This project came out of the frustration with existing pretty printers, which -would often employ simpler heuristics, each good at displaying some specific -structure, but bad at others. See below for other pretty printers. +This project is the outcome of my frustration with existing pretty printers, and +a desire to expand upon the pretty printer I developed for +[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 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. - Attempt to fit output within a predefined width limit. Default to 80(?) - Find a better name than `pretty`. +- Add options for colored output, maybe even support multiple? ## Other pretty printers diff --git a/function.lua b/function.lua index 94d7faf..92b8f1f 100644 --- a/function.lua +++ b/function.lua @@ -85,7 +85,8 @@ local function get_function_info (f) end 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' end @@ -99,6 +100,9 @@ local function get_function_info (f) end local function get_line_index (str, line_nr) + assert(type(str) == 'string') + assert(type(line_nr) == 'number') + local index = 0 for _ = 2, line_nr do index = str:find('\n', index, true) @@ -108,23 +112,40 @@ local function get_line_index (str, line_nr) return index 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 -- 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 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') - return 'function '..function_body..'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') + --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 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 str = file:read('*all') file:close() return get_full_function_str(str, start_line, end_line) end + local function width_of_strings_in_l (l, start_i, end_i) -- FIXME: Copy of the one in pretty.lua local width = 0 @@ -187,15 +208,15 @@ return function (value, depth, l, format_value) return format_function_with_closure(value, depth, l, format_value) 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. - l[#l+1] = get_full_function_str(info.source, info.linedefined, info.lastlinedefined) - return; + function_params, function_body = get_function_paramlist_and_body(info.source, info.linedefined, info.lastlinedefined) end if info.builtin and l.options.short_builtins then - l[#l+1] = info.name - return; + return l(info.name); end -- 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]} -- Build rest of function signature - l[#l+1] = 'function (' - local top_before = #l - 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] = ')' + l[#l+1] = 'function ' + local top_before = #l + if function_params then + l[#l+1] = function_params + 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) } -- Cleanup and finish + print(not l.options.more_function_info, depth ~= 0) if not l.options.more_function_info or depth ~= 0 then - l[#l+1] = ' ... end' - return; + l[#l+1] = (function_body:sub(1,1) == '\n') and '' or ' ' + 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 -- More info! -- diff --git a/pretty.lua b/pretty.lua index 0593c06..48c0f77 100644 --- a/pretty.lua +++ b/pretty.lua @@ -249,7 +249,8 @@ local function fix_seperator_info (l, indent_char, max_depth) if type(l[i]) ~= 'table' then -- Do nothing 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 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))) @@ -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 for _, pair in ipairs(key_value_pairs) do pair_format_func(l, pair[1], pair[2], depth + 1) - l[#l+1] = ',' - l[#l+1] = {'seperator'} + l[#l+1] = {'seperator', ','} 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', '}'} + --require 'fun' () -- DEBUG! + --DEBUG = map(operator.identity, l) -- DEBUG! + -- Decide for short or long table formatting. local table_width = width_of_strings_in_l(l, start_of_table_i) if table_width <= MAX_WIDTH_FOR_SINGLE_LINE_TABLE then @@ -471,6 +474,7 @@ local KNOWN_OPTIONS = { math_shorthand = { type = 'boolean', default = false }, soft_numbers = { type = 'boolean', default = true }, -- TODO: Add support for maximally precise numbers. max_depth = { type = 'number', default = math.huge }, + embed_loaded_funcs = { type = 'boolean', default = false }, more_function_info = { type = 'boolean', default = false }, recursion = { type = 'string', default = 'ignore', accepted = {['ignore'] = true, ['marked'] = true} }, short_builtins = { type = 'boolean', default = false }, diff --git a/test/test_function.lua b/test/test_function.lua index d45e6fb..6f8c3b9 100644 --- a/test/test_function.lua +++ b/test/test_function.lua @@ -135,18 +135,21 @@ end format_test { adv_getlocal = true, + options = { embed_loaded_funcs = true }, input = loadstring('return function () end')(), expect = 'function () end', } format_test { adv_getlocal = true, + options = { embed_loaded_funcs = true }, input = loadstring('return function () return function () end end')(), expect = 'function () return function () end end', } format_test { adv_getlocal = true, + options = { embed_loaded_funcs = true }, input = loadstring('return function () return function () end\nend')()(), expect = 'function () end', } @@ -154,6 +157,7 @@ format_test { format_test { -- NOTE: This is HARD to fix. It's thus longerterm adv_getlocal = true, + options = { embed_loaded_funcs = true }, input = loadstring('return function () return function () end end')()(), expect = 'function () end', } @@ -161,14 +165,14 @@ format_test { format_test { -- 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')(), - options = { more_function_info = true }, + options = { embed_loaded_funcs = true }, expect = 'function (a, b) return a + b end', } format_test { -- 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')(), - options = { more_function_info = true }, + options = { embed_loaded_funcs = true }, 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}', } +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 { -- No special indent if no special function modifier. adv_getlocal = true,