From bac1a7b87677753ebbffc0de15cdd3973c511195 Mon Sep 17 00:00:00 2001 From: Josh Yelon Date: Wed, 8 Sep 2021 01:32:08 -0400 Subject: [PATCH] Implement C++ pretty-printer --- luprex/core/cpp/print.cpp | 244 +++++++++++++++++++++++- luprex/core/cpp/print.hpp | 17 +- luprex/core/cpp/table.cpp | 103 +++-------- luprex/core/cpp/table.hpp | 15 +- luprex/core/cpp/textgame.cpp | 14 +- luprex/core/cpp/util.cpp | 32 ++++ luprex/core/cpp/util.hpp | 3 + luprex/core/lua/control.lst | 1 - luprex/core/lua/inspect.lua | 347 ----------------------------------- 9 files changed, 322 insertions(+), 454 deletions(-) delete mode 100644 luprex/core/lua/inspect.lua diff --git a/luprex/core/cpp/print.cpp b/luprex/core/cpp/print.cpp index c71f5173..23bcc24d 100644 --- a/luprex/core/cpp/print.cpp +++ b/luprex/core/cpp/print.cpp @@ -1,5 +1,6 @@ #include "print.hpp" #include "util.hpp" +#include "table.hpp" #include #include @@ -25,8 +26,243 @@ LuaDefine(string_isidentifier, "c") { return LS.result(); } - -void pprint(LuaStack &LS0, LuaSlot val, int indent, int maxlen, std::ostream *os) { - - +bool string_quote(LuaStack &LS, LuaSlot val, std::ostream *os) { + switch (LS.type(val)) { + case LUA_TNIL: + (*os) << "nil"; + return true; + case LUA_TSTRING: + util::quote_string(LS.ckstring(val), os); + return true; + case LUA_TNUMBER: + (*os) << LS.ckinteger(val); + return true; + case LUA_TBOOLEAN: + (*os) << (LS.ckboolean(val) ? "true" : "false"); + return true; + default: + return false; + } +} + +LuaDefine(string_quote, "c") { + LuaArg val; + LuaRet result; + LuaStack LS(L, val, result); + std::ostringstream oss; + string_quote(LS, val, &oss); + LS.set(result, oss.str()); + return LS.result(); +} + +// Find tables recursively. +// +// Builds a table (tabcount) whose keys are tables. If a table +// is visited exactly once, then tabcount[t]=0. If a table is +// visited more than once, then tabcount[t]=-1. +// +static void findtables(LuaStack &LS0, LuaSlot root, LuaSlot tabcount) { + lua_State *L = LS0.state(); + LuaVar key, val, tab, count; + LuaStack LS(L, key, val, tab, count); + + LS.newtable(tabcount); + int top = lua_gettop(L); + if (LS.istable(root)) { + lua_pushvalue(L, root.index()); + } + while (lua_gettop(L) > top) { + lua_checkstack(L, 10); + lua_replace(L, tab.index()); + LS.rawget(count, tabcount, tab); + if (LS.isnil(count)) { + LS.rawset(tabcount, tab, 0); + LS.set(key, LuaNil); + while (LS.next(tab, key, val)) { + if (LS.istable(key)) { + lua_pushvalue(L, key.index()); + } + if (LS.istable(val)) { + lua_pushvalue(L, val.index()); + } + } + LS.getmetatable(val, tab); + if (LS.istable(val)) { + lua_pushvalue(L, val.index()); + } + } else { + LS.rawset(tabcount, tab, -1); + } + } + LS.result(); +} + +LuaDefine(table_findtables, "c") { + LuaArg root; + LuaRet tabcount; + LuaStack LS(L, root, tabcount); + findtables(LS, root, tabcount); + return LS.result(); +} + +struct Inspector { + lua_State *L; + LuaVar ids; + int nextid; + bool indent; + int maxlen; + bool anyindent; + std::ostream *stream; +}; + +static void tabify(Inspector &insp, int level) { + if (insp.indent) { + (*insp.stream) << std::endl; + for (int i = 0; i < level; i++) { + (*insp.stream) << " "; + } + insp.anyindent = true; + } else { + (*insp.stream) << " "; + } +} + +static void pprint_r(Inspector &insp, int level, LuaSlot root) { + LuaVar idv, pairs, key, val; + LuaStack LS(insp.L, idv, pairs, key, val); + + // If it's a simple type, print it quoted. + if (string_quote(LS, root, insp.stream)) { + LS.result(); + return; + } + + // If it's not a table, just print the typename. + int t = LS.type(root); + if (t != LUA_TTABLE) { + (*insp.stream) << "<" << lua_typename(insp.L, t) << ">"; + LS.result(); + return; + } + + // If the table ID is greater than zero, then we've already + // printed it. Just print the table ID. + LS.rawget(idv, insp.ids, root); + int id = LS.ckint(idv); + if (id > 0) { + if (lua_nkeys(insp.L, root.index())==0) { + (*insp.stream) << "{}"; + } else { + (*insp.stream) << "
{...}"; + } + LS.result(); + return; + } + + // Allocate an ID for the table, if necessary. + if (id < 0) { + id = insp.nextid++; + LS.rawset(insp.ids, root, id); + } + + // Print the table ID if greater than zero. + if (id > 0) { + (*insp.stream) << "
"; + } + + // State variables. + int nextseq = 1; + bool needcomma = false; + bool multiline = false; + + // Open the brackets. + (*insp.stream) << "{"; + + // Output the table keys. + table_getpairs(LS, root, pairs, true); + for (int i = 2; ; i+=2) { + LS.rawgeti(key, pairs, i); + if (LS.isnil(key)) break; + LS.rawgeti(val, pairs, i+1); + if (needcomma) (*insp.stream) << ","; + needcomma = true; + if (LS.isnumber(key) && (LS.ckint(key) == nextseq)) { + (*insp.stream) << " "; + pprint_r(insp, level + 1, val); + nextseq = nextseq + 1; + } else { + multiline = true; + tabify(insp, level + 1); + if (LS.isstring(key) && util::is_identifier(LS.ckstring(key))) { + (*insp.stream) << LS.ckstring(key); + } else { + (*insp.stream) << "["; + pprint_r(insp, level + 1, key); + (*insp.stream) << "]"; + } + (*insp.stream) << " = "; + pprint_r(insp, level + 1, val); + } + } + + // Output the metatable. + LS.getmetatable(val, root); + if (LS.istable(val)) { + multiline = true; + if (needcomma) (*insp.stream) << ","; + needcomma = true; + tabify(insp, level + 1); + (*insp.stream) << " = "; + + // if isclass(mt) then + // self:puts('') + // end + pprint_r(insp, level + 1, val); + } + + // Close the brackets. + if (multiline) { + tabify(insp, level); + } else if (nextseq > 1) { + (*insp.stream) << " "; + } + (*insp.stream) << "}"; + + LS.result(); +} + +void pprint(LuaStack &LS0, LuaSlot root, bool indent, std::ostream *os) { + Inspector insp; + LuaStack LS(LS0.state(), insp.ids); + findtables(LS, root, insp.ids); + insp.L = LS0.state(); + insp.nextid = 1; + insp.indent = indent; + insp.anyindent = false; + insp.stream = os; + pprint_r(insp, 0, root); + LS.result(); +} + +LuaDefine(pprint_pprint, "f") { + LuaStack LS(L); + for (int i = 1; i <= lua_gettop(L); i++) { + LuaSpecial root(i); + pprint(LS, root, true, &std::cout); + std::cout << std::endl; + } + return LS.result(); +} + +LuaDefine(string_pprint, "c") { + LuaArg root, indent; + LuaRet result; + LuaStack LS(L, root, indent, result); + bool ind = LS.ckboolean(indent); + std::ostringstream oss; + pprint(LS, root, ind, &oss); + LS.set(result, oss.str()); + return LS.result(); } diff --git a/luprex/core/cpp/print.hpp b/luprex/core/cpp/print.hpp index a4a712a7..e6660ab7 100644 --- a/luprex/core/cpp/print.hpp +++ b/luprex/core/cpp/print.hpp @@ -20,6 +20,15 @@ void luai_writestring(const char *s, size_t len); void luai_writeline(); } +// Output a simple value to a stream. +// +// If the value is a string, number, boolean, or nil, it is +// quoted and output to the stream, and this function returns +// true. Otherwise, this function returns false and nothing +// is sent to the stream. +// +bool string_quote(LuaStack &LS, LuaSlot val, std::ostream *os); + // Pretty print to a stream. // // If indent is >=0, the output is indented. If indent<0, then @@ -32,15 +41,15 @@ void pprint(LuaStack &LS, LuaSlot val, int indent, int maxlen, std::ostream *os) // The following lua interfaces to this code are included: // -// pprint(expr) +// pprint(expr, expr, expr...) // // - pretty print the specified expression to stdout. // -// string.pprint(expr, indent, maxlen) +// string.pprint(expr, indent) // // - pretty print the specified expression, return the result as a string. // -void pprint_pprint(lua_State *L); -void string_pprint(lua_State *L); +int lfn_pprint_pprint(lua_State *L); +int lfn_string_pprint(lua_State *L); #endif // PRINT_HPP \ No newline at end of file diff --git a/luprex/core/cpp/table.cpp b/luprex/core/cpp/table.cpp index 44091867..41ee306f 100644 --- a/luprex/core/cpp/table.cpp +++ b/luprex/core/cpp/table.cpp @@ -1,50 +1,6 @@ #include "table.hpp" #include "source.hpp" -void table_findtables_i(LuaStack &LS0, LuaSlot root, LuaSlot tabcount) { - lua_State *L = LS0.state(); - LuaVar key, val, tab, count; - LuaStack LS(L, key, val, tab, count); - - LS.newtable(tabcount); - int top = lua_gettop(L); - if (LS.istable(root)) { - lua_pushvalue(L, root.index()); - } - while (lua_gettop(L) > top) { - lua_checkstack(L, 10); - lua_replace(L, tab.index()); - LS.rawget(count, tabcount, tab); - if (LS.isnil(count)) { - LS.rawset(tabcount, tab, 1); - LS.set(key, LuaNil); - while (LS.next(tab, key, val)) { - if (LS.istable(key)) { - lua_pushvalue(L, key.index()); - } - if (LS.istable(val)) { - lua_pushvalue(L, val.index()); - } - } - LS.getmetatable(val, tab); - if (LS.istable(val)) { - lua_pushvalue(L, val.index()); - } - } else { - LS.rawset(tabcount, tab, LS.ckint(count) + 1); - } - } - LS.result(); -} - -LuaDefine(table_findtables, "c") { - LuaArg root; - LuaRet tabcount; - LuaStack LS(L, root, tabcount); - table_findtables_i(LS, root, tabcount); - return LS.result(); -} - LuaDefine(table_getregistry, "f") { LuaArg key; LuaRet result; @@ -575,28 +531,13 @@ static void auxsort (lua_State *L, int tab, int l, int u) { } /* repeat the routine for the larger one */ } -static int table_getpairs(lua_State *L, bool sort, bool *unsortable) { - lua_checkstack(L, 40); - LuaArg tab; +bool table_getpairs(LuaStack &LS0, LuaSlot tab, LuaSlot pairs, bool sort) { + lua_State *L = LS0.state(); LuaVar key, value; - LuaRet pairs; - LuaStack LS(L, tab, key, value, pairs); - if (unsortable != nullptr) { - *unsortable = false; - } - LS.checktable(tab); - // Count the total number of pairs. - // TODO: add lua_npairs to make this faster. - lua_pushnil(L); - int total = 0; - while (lua_next(L, tab.index()) != 0) { - int ktype = lua_type(L, -2); - if (ktype == LUA_TNUMBER || ktype == LUA_TSTRING || ktype == LUA_TBOOLEAN) { - total += 1; - } - lua_pop(L, 1); - } + LuaStack LS(L, key, value); + bool sorted = true; // Create the table, store the initial 1. + int total = lua_nkeys(L, tab.index()); LS.createtable(pairs, total * 2 + 1, 0); LS.rawseti(pairs, 1, 1); // Transfer the pairs into the sequence. @@ -604,21 +545,18 @@ static int table_getpairs(lua_State *L, bool sort, bool *unsortable) { int offset = 2; while (lua_next(L, tab.index()) != 0) { int ktype = lua_type(L, -2); - if (ktype == LUA_TNUMBER || ktype == LUA_TSTRING || ktype == LUA_TBOOLEAN) { - lua_pushvalue(L, -2); // K V K - lua_rawseti(L, pairs.index(), offset++); - lua_rawseti(L, pairs.index(), offset++); - } else { - if (unsortable != nullptr) { - *unsortable = true; - } - lua_pop(L, 1); + if (ktype != LUA_TNUMBER && ktype != LUA_TSTRING && ktype != LUA_TBOOLEAN) { + sorted = false; } + lua_pushvalue(L, -2); // K V K + lua_rawseti(L, pairs.index(), offset++); + lua_rawseti(L, pairs.index(), offset++); } if (sort) { auxsort(L, pairs.index(), 1, total); } - return LS.result(); + LS.result(); + return sorted; } ///////////////////////////////////////////////////////////// @@ -652,13 +590,20 @@ LuaDefine(table_sortedpairs, "c") { LuaArg tab; LuaRet closure, rtab, key; LuaStack LS(L, tab, closure, rtab, key); - lua_pushvalue(L, tab.index()); - bool unsortable; - table_getpairs(L, true, &unsortable); - if (unsortable) { + bool sorted = table_getpairs(LS, tab, rtab, true); + if (!sorted) { luaL_error(L, "Cannot iterate over a table with unsortable keys"); } - lua_replace(L, rtab.index()); + LS.set(closure, lfn_table_nextsortedpair); + LS.set(key, LuaNil); + return LS.result(); +} + +LuaDefine(table_semisortedpairs, "c") { + LuaArg tab; + LuaRet closure, rtab, key; + LuaStack LS(L, tab, closure, rtab, key); + table_getpairs(LS, tab, rtab, true); LS.set(closure, lfn_table_nextsortedpair); LS.set(key, LuaNil); return LS.result(); diff --git a/luprex/core/cpp/table.hpp b/luprex/core/cpp/table.hpp index f9a8f8eb..20d133a5 100644 --- a/luprex/core/cpp/table.hpp +++ b/luprex/core/cpp/table.hpp @@ -12,14 +12,6 @@ #include "luastack.hpp" -// Starting at the specified root, find tables recursively. -// -// Returns a table containing every table found, in tabcount. -// The value associated with the table is the number of times the -// table was found. -// -void table_findtables(LuaStack &LS0, LuaSlot root, LuaSlot tabcount); - // table_clear // // Remove all key/value pairs from the table. Does not remove @@ -33,6 +25,13 @@ void table_clear(LuaStack &LS0, LuaSlot tab); // bool table_equal(LuaStack &LS0, LuaSlot tab1, LuaSlot tab2); +// table_getpairs +// +// Get a table containing the key-value pairs in tab. Optionally sort +// the pairs. Return true if all keys were sortable. +// +bool table_getpairs(LuaStack &LS0, LuaSlot tab, LuaSlot pairs, bool sort); + // table_findremove // // Given a vector and a value, remove the specified value from diff --git a/luprex/core/cpp/textgame.cpp b/luprex/core/cpp/textgame.cpp index b73b913c..dd233022 100644 --- a/luprex/core/cpp/textgame.cpp +++ b/luprex/core/cpp/textgame.cpp @@ -14,6 +14,7 @@ #include "traceback.hpp" #include "textgame.hpp" #include "luaconsole.hpp" +#include "print.hpp" // Add another error status. @@ -54,17 +55,8 @@ void TextGame::do_lua(const std::string &exp) { signal(SIGINT, SIG_DFL); if (status == LUA_OK) { if (lua_gettop(L) > 0) { - lua_getglobal(L, "pprint"); - if (lua_isnil(L, -1)) { - lua_pop(L, 1); - lua_getglobal(L, "print"); - } - lua_insert(L, 1); - if (lua_pcall(L, lua_gettop(L) - 1, 0, 0) != 0) { - l_message( - lua_pushfstring(L, "error calling 'print' (%s)", - lua_tostring(L, -1))); - } + lfn_pprint_pprint(L); + lua_settop(L, 0); } } else { const char *msg = lua_tostring(L, -1); diff --git a/luprex/core/cpp/util.cpp b/luprex/core/cpp/util.cpp index 8c13803d..607bdbdb 100644 --- a/luprex/core/cpp/util.cpp +++ b/luprex/core/cpp/util.cpp @@ -35,6 +35,38 @@ bool is_identifier(const std::string &str) { return true; } +void quote_string(const std::string &s, std::ostream *os) { + bool usesinglequote = false; + for (char c : s) { + if (c == '"') { + usesinglequote = true; + break; + } + } + (*os) << (usesinglequote ? '\'' : '"'); + for (char c : s) { + if (c >= 32) { + if (c == '"') { + (*os) << (usesinglequote ? "\"" : "\\\""); + } else if (c == '\'') { + (*os) << (usesinglequote ? "\\'" : "'"); + } else { + (*os) << c; + } + } else { + switch (c) { + case '\n': (*os) << "\\n"; break; + case '\t': (*os) << "\\t"; break; + case '\r': (*os) << "\\r"; break; + default: + (*os) << "\\" << std::setw(3) << int(c); + break; + } + } + } + (*os) << (usesinglequote ? '\'' : '"'); +} + IdVector id_vector_create(int64_t id1, int64_t id2, int64_t id3, int64_t id4) { IdVector result; if (id1 >= 0) result.push_back(id1); diff --git a/luprex/core/cpp/util.hpp b/luprex/core/cpp/util.hpp index 9c20afd0..7864bee3 100644 --- a/luprex/core/cpp/util.hpp +++ b/luprex/core/cpp/util.hpp @@ -27,6 +27,9 @@ using IdVector = std::vector; // Return true if the string is a valid lua identifier. bool is_identifier(const std::string &str); +// Output a string to a stream using Lua string escaping and quoting. +void quote_string(const std::string &str, std::ostream *os); + // ID vector quick create. IdVector id_vector_create(int64_t id1=-1, int64_t id2=-1, int64_t id3=-1, int64_t id4=-1); diff --git a/luprex/core/lua/control.lst b/luprex/core/lua/control.lst index 89c34177..f645dd9d 100644 --- a/luprex/core/lua/control.lst +++ b/luprex/core/lua/control.lst @@ -3,7 +3,6 @@ # in the order that they're supposed to be loaded. # -inspect.lua ut-table.lua ut-globaldb.lua ut-tablecmp.lua diff --git a/luprex/core/lua/inspect.lua b/luprex/core/lua/inspect.lua deleted file mode 100644 index 2e30dcb2..00000000 --- a/luprex/core/lua/inspect.lua +++ /dev/null @@ -1,347 +0,0 @@ --- --- inspect.lua 3.1.0 --- http://github.com/kikito/inspect.lua --- human-readable representations of tables --- --- Modified by J. Yelon to work with our package and doc-gen system. --- --- MIT LICENSE --- Copyright (c) 2013 Enrique GarcĂ­a Cota --- --- Permission is hereby granted, free of charge, to any person obtaining a --- copy of this software and associated documentation files (the --- "Software"), to deal in the Software without restriction, including --- without limitation the rights to use, copy, modify, merge, publish, --- distribute, sublicense, and/or sell copies of the Software, and to --- permit persons to whom the Software is furnished to do so, subject to --- the following conditions: --- --- The above copyright notice and this permission notice shall be included --- in all copies or substantial portions of the Software. --- --- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS --- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF --- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. --- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY --- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, --- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE --- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --- - -local inspect = {} - -local tostring = tostring - -inspect.KEY = {} -inspect.METATABLE = {} - - --- Apostrophizes the string if it has quotes, but not aphostrophes --- Otherwise, it returns a regular quoted string -local function smartQuote(str) - if str:match('"') and not str:match("'") then - return "'" .. str .. "'" - end - return '"' .. str:gsub('"', '\\"') .. '"' -end - --- \a => '\\a', \0 => '\\0', 31 => '\31' -local shortControlCharEscapes = { - ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", - ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v" -} - -local longControlCharEscapes = {} -- \a => nil, \0 => \000, 31 => \031 -for i=0, 31 do - local ch = string.char(i) - if not shortControlCharEscapes[ch] then - shortControlCharEscapes[ch] = "\\"..i - longControlCharEscapes[ch] = string.format("\\%03d", i) - end -end - -local function escape(str) - return (str:gsub("\\", "\\\\") - :gsub("(%c)%f[0-9]", longControlCharEscapes) - :gsub("%c", shortControlCharEscapes)) -end - -local function isIdentifier(str) - return type(str) == 'string' and str:match( "^[_%a][_%a%d]*$" ) -end - -local function isSequenceKey(k, sequenceLength) - return type(k) == 'number' - and 1 <= k - and k <= sequenceLength - and math.floor(k) == k -end - -local defaultTypeOrders = { - ['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4, - ['function'] = 5, ['userdata'] = 6, ['thread'] = 7 -} - -local function sortKeys(a, b) - local ta, tb = type(a), type(b) - - -- strings and numbers are sorted numerically/alphabetically - if ta == tb and (ta == 'string' or ta == 'number') then return a < b end - - local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb] - -- Two default types are compared according to the defaultTypeOrders table - if dta and dtb then return defaultTypeOrders[ta] < defaultTypeOrders[tb] - elseif dta then return true -- default types before custom ones - elseif dtb then return false -- custom types after default ones - end - - -- custom types are sorted out alphabetically - return ta < tb -end - --- For implementation reasons, the behavior of rawlen & # is "undefined" when --- tables aren't pure sequences. So we implement our own # operator. -local function getSequenceLength(t) - local len = 1 - local v = rawget(t,len) - while v ~= nil do - len = len + 1 - v = rawget(t,len) - end - return len - 1 -end - -local function getNonSequentialKeys(t) - local keys, keysLength = {}, 0 - local sequenceLength = getSequenceLength(t) - for k,_ in rawpairs(t) do - if not isSequenceKey(k, sequenceLength) then - keysLength = keysLength + 1 - keys[keysLength] = k - end - end - table.sort(keys, sortKeys) - return keys, keysLength, sequenceLength -end - -local function countTableAppearances(t, tableAppearances) - tableAppearances = tableAppearances or {} - - if type(t) == 'table' then - if not tableAppearances[t] then - tableAppearances[t] = 1 - for k,v in rawpairs(t) do - countTableAppearances(k, tableAppearances) - countTableAppearances(v, tableAppearances) - end - countTableAppearances(getmetatable(t), tableAppearances) - else - tableAppearances[t] = tableAppearances[t] + 1 - end - end - - return tableAppearances -end - -local copySequence = function(s) - local copy, len = {}, #s - for i=1, len do copy[i] = s[i] end - return copy, len -end - -local function makePath(path, ...) - local keys = {...} - local newPath, len = copySequence(path) - for i=1, #keys do - newPath[len + i] = keys[i] - end - return newPath -end - -local function processRecursive(process, item, path, visited) - if item == nil then return nil end - if visited[item] then return visited[item] end - - local processed = process(item, path) - if type(processed) == 'table' then - local processedCopy = {} - visited[item] = processedCopy - local processedKey - - for k,v in rawpairs(processed) do - processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited) - if processedKey ~= nil then - processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited) - end - end - - local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited) - if type(mt) ~= 'table' then mt = nil end -- ignore not nil/table __metatable field - setmetatable(processedCopy, mt) - processed = processedCopy - end - return processed -end - - - -------------------------------------------------------------------- - -local Inspector = {} -local Inspector_mt = {__index = Inspector} - -function Inspector:puts(...) - local args = {...} - local buffer = self.buffer - local len = #buffer - for i=1, #args do - len = len + 1 - buffer[len] = args[i] - end -end - -function Inspector:down(f) - self.level = self.level + 1 - f() - self.level = self.level - 1 -end - -function Inspector:tabify() - self:puts(self.newline, string.rep(self.indent, self.level)) -end - -function Inspector:alreadyVisited(v) - return self.ids[v] ~= nil -end - -function Inspector:getId(v) - local id = self.ids[v] - if not id then - local tv = type(v) - id = (self.maxIds[tv] or 0) + 1 - self.maxIds[tv] = id - self.ids[v] = id - end - return tostring(id) -end - -function Inspector:putKey(k) - if isIdentifier(k) then return self:puts(k) end - self:puts("[") - self:putValue(k) - self:puts("]") -end - -function Inspector:putTable(t) - if t == inspect.KEY then - self:puts("inspect.KEY") - elseif t == inspect.METATABLE then - self:puts("inspect.METATABLE") - elseif self:alreadyVisited(t) then - self:puts('
') - elseif self.level >= self.depth then - self:puts('{...}') - else - if self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end - - local nonSequentialKeys, nonSequentialKeysLength, sequenceLength = getNonSequentialKeys(t) - local mt = getmetatable(t) - - self:puts('{') - self:down(function() - local count = 0 - for i=1, sequenceLength do - if count > 0 then self:puts(',') end - self:puts(' ') - self:putValue(t[i]) - count = count + 1 - end - - for i=1, nonSequentialKeysLength do - local k = nonSequentialKeys[i] - if count > 0 then self:puts(',') end - self:tabify() - self:putKey(k) - self:puts(' = ') - self:putValue(t[k]) - count = count + 1 - end - - if type(mt) == 'table' then - if count > 0 then self:puts(',') end - self:tabify() - self:puts(' = ') - if isclass(mt) then - self:puts('') - else - self:putValue(mt) - end - end - end) - - if nonSequentialKeysLength > 0 or type(mt) == 'table' then -- result is multi-lined. Justify closing } - self:tabify() - elseif sequenceLength > 0 then -- array tables have one extra space before closing } - self:puts(' ') - end - - self:puts('}') - end -end - -function Inspector:putValue(v) - local tv = type(v) - - if tv == 'string' then - self:puts(smartQuote(escape(v))) - elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or - tv == 'cdata' or tv == 'ctype' then - self:puts(tostring(v)) - elseif tv == 'table' then - self:putTable(v) - else - self:puts('<', tv, ' ', self:getId(v), '>') - end -end - -------------------------------------------------------------------- - -function inspect.inspect(root, options) - options = options or {} - - local depth = options.depth or math.huge - local newline = options.newline or '\n' - local indent = options.indent or ' ' - local process = options.process - - if process then - root = processRecursive(process, root, {}, {}) - end - - local inspector = setmetatable({ - depth = depth, - level = 0, - buffer = {}, - ids = {}, - maxIds = {}, - newline = newline, - indent = indent, - tableAppearances = countTableAppearances(root) - }, Inspector_mt) - - inspector:putValue(root) - - return table.concat(inspector.buffer) -end - -function inspect.pprint(...) - local n = select("#", ...) - for i = 1,n do - local v = select(i, ...) - print(inspect.inspect(v)) - end -end - -pprint = inspect.pprint