diff --git a/function.lua b/function.lua index 5120489..268ca63 100644 --- a/function.lua +++ b/function.lua @@ -99,14 +99,14 @@ local function get_function_info (f) 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:find'^%w+.lua$' then info.defined_how = 'file' -- Hotfix for Love2d boot.lua issue. + elseif info.source:find'^%w+.lua$' then info.defined_how = 'file' -- XXX: Hotfix for Love2d boot.lua issue. else info.defined_how = 'string' end if info.builtin and LIBRARY[f] then info.name = LIBRARY[f].name info.params[1] = LIBRARY[f].para - info.doc = LIBRARY[f].docs + info.docs = LIBRARY[f].docs end return info @@ -125,9 +125,41 @@ local function get_line_index (str, line_nr) return index end -local function get_function_paramlist_and_body (info) - -- Will attempt to find a string which refer to the function. This will - -- possibly require opening a file. +local function get_docs_from_function_body (body, max_index) + -- Finds the documentation lines of a function. + -- Also returns the remaining non-documentation lines. + + assert(type(body) == 'string') + + local doc_lines = {} + for line in body:sub(1, max_index):gmatch('[^\n]+', true) do + if not line:match '^%s*$' then + local line_text = line:match '^%s*%-%-%s*(.*)%s*$' + doc_lines[#doc_lines+1] = line_text + if not line_text then break end + end + end + return table.concat(doc_lines, '\n'):match '^%s*(.-)%s*$' +end + +local function get_docs_split_index (body) + local index = 1 + while index <= #body do + local next_newline = body:find('\n', index + 1) or -1 + if body:sub(index, next_newline):match('^%s*$') or body:sub(index, next_newline):match('^%s*%-%-%s*(.*)%s*$') then + index = next_newline + else + return index + end + end + return -1 +end + +local function get_function_body_info (info) + -- Will attempt to expand `info` with extra info found by looking at the + -- source code. This could require opening a file. + -- There is no guarentee that the function body it finds is correct, or even + -- that it finds any. -- Error check assert(type(info) == 'table') @@ -149,19 +181,29 @@ local function get_function_paramlist_and_body (info) local start_line_index = get_line_index(str, info.linedefined) local end_line_index = get_line_index(str, info.lastlinedefined + 1) - -- Now find the function parameters and the function body. + -- Now find some info about the function. + -- NOTE: function_params is currently not used for anything. local function_name, function_params, function_body = str:sub(start_line_index, end_line_index):match(FUNCTION_DEFINITION_MATCH) - -- TODO: Use function_name for something. - if type(function_params) ~= 'string' or type(function_body) ~= 'string' then + + if type(function_body) ~= 'string' then error(('[pretty.function/internal]: Could not find the function defined on lines %i-%i (indices %i-%i) for string:\n\n%s\n'):format(info.linedefined, info.lastlinedefined, start_line_index, end_line_index, str)) end - -- And return them. - return function_params, function_body:match('^%s*(.-)%s*$'), function_name:match('^%s*(.-)%s*$') + + local function_body = function_body:match '^%s*(.-)%s*$' + local pivot_index = get_docs_split_index (function_body) + + info.name = info.name or function_name:match '^%s*(.-)%s*$' + info.docs = info.docs or get_docs_from_function_body(function_body, pivot_index) + info.body = info.body or function_body:sub(pivot_index, -1):match '^%s*(.-)%s*$' + + if info.name == '' then info.name = nil end + if info.docs == '' then info.docs = nil end + + return info end -local function get_function_string (...) - return string.format('function %s %s end', get_function_paramlist_and_body(...)) -end +-------------------------------------------------------------------------------- +-- Text handling local function width_of_strings_in_l (l, start_i, end_i) -- FIXME: Copy of the one in pretty.lua @@ -179,29 +221,6 @@ local function add_indent_to_string (str, indent) return indent .. str:gsub('\n', '\n'..indent) end -local function get_docs_from_function_body (func_body) - -- Finds the documentation lines of a function. - -- Also returns the remaining non-documentation lines. - - assert(type(func_body) == 'string') - - local doc_lines, not_doc_lines, still_docs = {}, {}, true - for line in func_body:gmatch('[^\n]+', true) do - if still_docs and not line:match('^%s*$') then - local line_text = line:match('^%s*%-%-%s*(.*)%s*$') - if line_text then - doc_lines[#doc_lines+1] = line_text - else - still_docs = false - end - end - if not still_docs then - not_doc_lines[#not_doc_lines+1] = line - end - end - return table.concat(doc_lines, '\n'), table.concat(not_doc_lines, '\n') -end - local function wrap_text (text, max_width) local l, i, last_i = {}, max_width, 1 repeat @@ -220,52 +239,20 @@ end -------------------------------------------------------------------------------- -local function format_function_with_closure (value, depth, l, format_value) - assert(type(value) == 'function') - assert(type(depth) == 'number' and type(l) == 'table' and type(format_value) == 'function') - - local info = get_function_info(value) - - local function_str = get_function_string(info) - - if info.nups > 0 then l[#l+1] = '(function () ' end - -- Upvalues - for k, v in pairs(info.ups) do - l[#l+1] = 'local ' - l[#l+1] = tostring(k) - l[#l+1] = ' = ' - format_value(v, depth + 1, l) - l[#l+1] = '; ' - end - -- Return function - if info.nups > 0 then l[#l+1] = 'return ' end - l[#l+1] = function_str - -- - if info.nups > 0 then l[#l+1] = ' end)()' end -end - return function (value, depth, l, format_value) assert(type(value) == 'function') assert(type(depth) == 'number' and type(l) == 'table' and type(format_value) == 'function') local info = get_function_info(value) - if l.options._include_closure and not info.builtin then - return format_function_with_closure(value, depth, l, format_value) - end - local function_params, function_body = nil, '...' - if info.defined_how == 'string' or not info.doc then - local _, body, name = get_function_paramlist_and_body(info) - local docs, body = get_docs_from_function_body(body) - body = body:match('^%s*(.-)%s*$') - if #body <= NR_CHARS_IN_LONG_FUNCTION_BODY and not body:find '\n' and not body:find(FUNCTION_KEYWORD_MATCH) then - if info.defined_how == 'string' then function_body = body end - end + if not info.docs then + info = get_function_body_info(info) - info.doc = not info.doc and docs and docs ~= '' and docs - info.name = not info.name and name and name ~= '' and name + if #info.body <= NR_CHARS_IN_LONG_FUNCTION_BODY and not info.body:find '\n' and not info.body:find(FUNCTION_KEYWORD_MATCH) then + if info.defined_how == 'string' then function_body = info.body end + end end if info.builtin and l.options.short_builtins then @@ -311,15 +298,15 @@ return function (value, depth, l, format_value) end -- Doc - if info.doc then + if info.docs then l[#l+1] = '\n' local indent = l.options.indent .. '-- ' - local docs = not info.builtin and info.doc or wrap_text(info.doc, 80 - #indent) + local docs = not info.builtin and info.docs or wrap_text(info.docs, 80 - #indent) l[#l+1] = add_indent_to_string(docs, indent) end -- source - if info.doc or info.name then -- Do nothing + if info.docs or info.name then -- Do nothing elseif info.defined_how == 'string' then l[#l+1] = indent l[#l+1] = '-- Loaded from string' @@ -334,39 +321,12 @@ return function (value, depth, l, format_value) end -- upvalues - if info.nups > 0 and (not info.builtin and not info.doc) then + if info.nups > 0 and (not info.builtin and not info.docs) then l[#l+1] = indent l[#l+1] = '-- Up values: ' format_value(info.ups, depth + 1, l) end - if l.options._all_function_info then - -- NOTE: This is for testing/debugging/experimentation purposes, and is - -- not designed to be pretty. - - -- Native - l[#l+1] = indent - l[#l+1] = '-- Native Representation: ' - l[#l+1] = tostring(value) - - -- Function body - if info.defined_how ~= 'C' then - l[#l+1] = indent - l[#l+1] = '--[[ Function Body:\n\t' - l[#l+1] = add_indent_to_string(get_function_string(info), l.options.indent) - l[#l+1] = indent - l[#l+1] = '--]]' - end - - -- Full info - l[#l+1] = indent - l[#l+1] = '--[[ full_info:\n' - info.env = nil - format_value(info, depth + 1, l) - l[#l+1] = indent - l[#l+1] = '--]]' - end - -- Ignore spacing and function body if it's a Λ string. if function_body ~= '' then l[#l+1] = '\n' diff --git a/pretty.lua b/pretty.lua index 3471ae5..385ab1d 100644 --- a/pretty.lua +++ b/pretty.lua @@ -470,8 +470,6 @@ local DEBUG_OPTION_USED = { } local KNOWN_OPTIONS = { _table_addr_comment = { type = 'boolean', default = false, debug = 'debug' }, - _all_function_info = { type = 'boolean', default = false, debug = 'debug' }, - _include_closure = { type = 'boolean', default = false, debug = 'experimental' }, cut_strings = { type = 'boolean', default = false }, indent = { type = 'string', default = ' ' }, diff --git a/test/test_function.lua b/test/test_function.lua index 61e664e..2956023 100644 --- a/test/test_function.lua +++ b/test/test_function.lua @@ -442,128 +442,5 @@ if HAS_UNICODE_IDEN then end -------------------------------------------------------------------------------- --- Closure creation --- _include_closure option: If a function uses upvalues, return code that creates --- a closure for that function, including the function code. - --- NOTE: Without AST traversal, it's impossible to garentee that values refered --- by multiple recursive closures are the same, because they can actually refer --- to different variables with the same name, as the following example shows: --- --- local val_a = 1 --- local func_a = function () return val_a end --- local val_a = 2 --- local func_c = function () return func_a() + val_a end - --- NOTE: The following tests are all EXPERIMENTAL! All of the tests use features --- which may change at any time. - -format_test { - name = 'Closures do not affect non-upvalue function', - adv_getlocal = true, - options = { _include_closure = true }, - input = function (a, b) return (a > b) and a or b end, - expect = 'function (a, b) return (a > b) and a or b end', -} - -format_test { - name = 'Closures do not affect builtins', - input = math.abs, - options = { _include_closure = true }, - expect = 'builtin function (x)\n -- math.abs\n -- Returns the absolute value of x.\n\n ...\nend', -} - -do - local input_func = (function () - local i = 0 - return function (a) i = i + a; return i end - end)() - for i = 1, 10 do input_func(1) end - - format_test { - adv_getlocal = true, - options = { _include_closure = true }, - input = input_func, - expect = '(function () local i = 10; return function (a) i = i + a; return i end end)()', - } -end - -do - local input_func = loadstring([[ - return function () - local i = 0 - return function (a) i = i + a; return i end - end]])()() - for i = 1, 10 do input_func(1) end - - format_test { - name = 'Can include variables in closure', - adv_getlocal = true, - options = { _include_closure = true }, - input = input_func, - expect = '(function () local i = 10; return function (a) i = i + a; return i end end)()', - } -end - -format_test { - name = 'Can include functions in closure', - adv_getlocal = true, - options = { _include_closure = true }, - input = (function () - local custom_max = function (a, b) return (a > b) and a or b end - return function (a, b) return custom_max(a, b) + 2 end - end)(), - expect = '(function () local custom_max = function (a, b) return (a > b) and a or b end; return function (a, b) return custom_max(a, b) + 2 end end)()', -} - -format_test { - name = 'Can include functions defined with `function () ...` style', - adv_getlocal = true, - options = { _include_closure = true }, - input = (function () - local function custom_max (a, b) return (a > b) and a or b end - return function (a, b) return custom_max(a, b) + 2 end - end)(), - expect = '(function () local custom_max = function (a, b) return (a > b) and a or b end; return function (a, b) return custom_max(a, b) + 2 end end)()', -} - -do - local custom_max = function (a, b) return (a > b) and a or b end - format_test { - name = 'Can include functions from outside scope into closure', - adv_getlocal = true, - options = { _include_closure = true }, - input = (function () - return function (a, b) return custom_max(a, b) + 2 end - end)(), - expect = '(function () local custom_max = function (a, b) return (a > b) and a or b end; return function (a, b) return custom_max(a, b) + 2 end end)()', - } -end - - - -do - local a_func = function (x) return x + 2 end - local b_func = function (x) return a_func(x) * a_func(x) end - local c_func = function (x) return b_func(a_func(x)) end - format_test { - name = 'Can support recursive closures', - adv_getlocal = true, - options = { _include_closure = true }, - input = c_func, - expect = '(function () local b_func = (function () local a_func = function (x) return x + 2 end; return function (x) return a_func(x) * a_func(x) end end)(); local a_func = function (x) return x + 2 end; return function (x) return b_func(a_func(x)) end end)()', - } -end - --------------------------------------------------------------------------------- - ---[[ -format_test { - adv_getlocal = true, - options = { _all_function_info = true, max_depth = 2 }, - input = function() end, - expect = '', -} ---]] return SUITE