-- Import local LIBRARY do local thispath = ... and select('1', ...):match('.+%.') or '' local was_loaded, library = pcall(require, thispath..'library') LIBRARY = was_loaded and library or {} end -- Constants -------------------------------------------------------------------------------- -- Util local function get_function_info (f) -- NOTE: Works best in LuaJIT or Lua 5.2+ -- Regarding get-info: -- * No need to includ 'f'. Function is already known -- * No need to include 'L' (active lines) option. Ignored -- * No need to include 'n' (name and namewhat). Won't work. local info = debug.getinfo(f, 'Su') info.params = {} info.ups = {} info.env = debug.getfenv and debug.getfenv(f) info.builtin = (info.source == '=[C]') for i = 1, info.nparams or 0 do info.params[i] = debug.getlocal(f, i) end if info.isvararg or not info.nparams then info.params[#info.params+1] = '...' end -- Get upvalues for i = 1, info.nups do local k, v = debug.getupvalue(f, i) if k == '_ENV' and not debug.getfenv then info.env = v else info.ups[k] = v end end if info.source:sub(1,1) == '=' then info.defined_how = 'C' elseif info.source:sub(1,1) == '@' then info.defined_how = 'file' 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 end return info end local function get_line_index (str, line_nr) local index = 0 for _ = 2, line_nr do index = str:find('\n', index, true) if not index then return #str end index = index + 1 end return index end local function get_full_function_str (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`. 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' end local function get_function_str_from_file (filename, start_line, end_line) 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 for i = start_i or 1, (end_i or #l) do width = width + #l[i] end return width end local function add_indent_to_string (str, indent) local l = {} for line in string.gmatch(str, '\n', true) do l[#l+1] = indent l[#l+1] = line end return table.concat(l, '\n') end -------------------------------------------------------------------------------- local function format_function_with_closure (value, options, depth, l, format_value) local info = get_function_info(value) local function_str = nil if (info.defined_how == 'string') then function_str = get_full_function_str(info.source, info.linedefined, info.lastlinedefined) else function_str = get_function_str_from_file(info.short_src, info.linedefined, info.lastlinedefined) end 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, options, 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, options, depth, l, format_value) local info = get_function_info(value) if options.include_closure and not info.builtin then return format_function_with_closure(value, options, depth, l, format_value) end if info.defined_how == 'string' then -- Function was defined as a string. l[#l+1] = get_full_function_str(info.source, info.linedefined, info.lastlinedefined) return; end if info.builtin and options.short_builtins then l[#l+1] = info.name return; end -- Include function modifier, and alignment info. l[#l+1] = info.builtin and 'builtin ' or '' l[#l+1] = { #l[#l], 'func_mod'} -- 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] = { width_of_strings_in_l(l, top_before), 'func_def' } -- Cleanup and finish if not options.more_function_info or depth ~= 0 then l[#l+1] = ' ... end' return; end -- More info! -- local indent = '\n' .. options.indent -- Name if info.name then l[#l+1] = indent l[#l+1] = '-- ' l[#l+1] = info.name end -- Doc if info.doc then for doc_line in info.doc:gmatch('[^\n]+') do l[#l+1] = indent l[#l+1] = '-- ' l[#l+1] = doc_line end end -- source if not info.builtin then l[#l+1] = indent l[#l+1] = ('-- source_file: \'%s\' '):format(info.short_src) if info.linedefined == info.lastlinedefined then l[#l+1] = ('[Line: %i]'):format(info.linedefined) else l[#l+1] = ('[Lines: %i - %i]'):format(info.linedefined, info.lastlinedefined) end end -- upvalues if info.nups > 0 and not info.builtin then l[#l+1] = indent l[#l+1] = '-- up_values: ' format_value(info.ups, options, depth + 1, l) end if options._all_function_info then -- NOTE: This is for testing/debugging/experimentation purposes. l[#l+1] = indent l[#l+1] = '--[[ Function Body:\n\t' l[#l+1] = add_indent_to_string(get_function_str_from_file(info.short_src, info.linedefined, info.lastlinedefined), options.indent) l[#l+1] = indent l[#l+1] = '--]]' l[#l+1] = indent l[#l+1] = '--[[\n\tNative repr:' l[#l+1] = tostring(value) l[#l+1] = indent format_value(info, options, depth + 1, l) l[#l+1] = indent l[#l+1] = '--]]' end l[#l+1] = '\n' l[#l+1] = indent l[#l+1] = '...\nend' end