/////////////////////////////////////////////////////////////////////////////// // This source file is part of the LuaPlus source distribution and is Copyright // 2001-2013 by Joshua C. Jensen (jjensen@workspacewhiz.com). // // The latest version may be obtained from http://luaplus.org/. // // The code presented in this file may be used in any environment it is // acceptable to use Lua. /////////////////////////////////////////////////////////////////////////////// #define LUA_CORE #include "LuaPlus.h" #include using namespace LuaPlus; #if LUA_VERSION_NUM >= 502 static int luaL_typerror (lua_State *L, int narg, const char *tname) { const char *msg = lua_pushfstring(L, "%s expected, got %s", tname, luaL_typename(L, narg)); return luaL_argerror(L, narg, msg); } #endif /* macro to `unsign' a character */ #define uchar(c) ((unsigned char)(c)) extern "C" int str_format_helper (luaL_Buffer* b, lua_State *L, int arg); namespace LuaPlus { static void luaI_addquotednonwidebinary (LuaStateOutFile& file, const char* s, size_t l) { file.Print("%c", '"'); while (l--) { switch (*s) { case '"': case '\\': file.Print("\\%c", *s); break; case '\a': file.Print("\\a"); break; case '\b': file.Print("\\b"); break; case '\f': file.Print("\\f"); break; case '\n': file.Print("\\n"); break; case '\r': file.Print("\\r"); break; case '\t': file.Print("\\t"); break; case '\v': file.Print("\\v"); break; default: if (isprint((unsigned char)*s)) file.Print("%c", *s); else file.Print("\\x%02x", (unsigned int)(unsigned char)*s); } s++; } file.Print("%c", '"'); } #if LUA_VERSION_NUM <= 501 #define L_ESC '%' /* maximum size of each formatted item (> len(format('%99.99f', -1e308))) */ #define MAX_ITEM 512 /* valid flags in a format specification */ #define FLAGS "-+ #0" /* ** maximum size of each format specification (such as '%-099.99d') ** (+10 accounts for %99.99x plus margin of error) */ #define MAX_FORMAT (sizeof(FLAGS) + sizeof(LUA_INTFRMLEN) + 10) static const char *scanformat (lua_State *L, const char *strfrmt, char *form) { const char *p = strfrmt; while (*p != '\0' && strchr(FLAGS, *p) != NULL) p++; /* skip flags */ if ((size_t)(p - strfrmt) >= sizeof(FLAGS)) luaL_error(L, "invalid format (repeated flags)"); if (isdigit(uchar(*p))) p++; /* skip width */ if (isdigit(uchar(*p))) p++; /* (2 digits at most) */ if (*p == '.') { p++; if (isdigit(uchar(*p))) p++; /* skip precision */ if (isdigit(uchar(*p))) p++; /* (2 digits at most) */ } if (isdigit(uchar(*p))) luaL_error(L, "invalid format (width or precision too long)"); *(form++) = '%'; strncpy(form, strfrmt, p - strfrmt + 1); form += p - strfrmt + 1; *form = '\0'; return p; } static void addquoted (lua_State *L, luaL_Buffer *b, int arg) { size_t l; const char *s = luaL_checklstring(L, arg, &l); luaL_addchar(b, '"'); while (l--) { switch (*s) { case '"': case '\\': case '\n': { luaL_addchar(b, '\\'); luaL_addchar(b, *s); break; } case '\r': { luaL_addlstring(b, "\\r", 2); break; } case '\0': { luaL_addlstring(b, "\\000", 4); break; } default: { luaL_addchar(b, *s); break; } } s++; } luaL_addchar(b, '"'); } void addquotedbinary (lua_State *L, luaL_Buffer *b, int arg) { size_t l; { const char *s = luaL_checklstring(L, arg, &l); luaL_addchar(b, '"'); while (l--) { switch (*s) { case '"': case '\\': luaL_addchar(b, '\\'); luaL_addchar(b, *s); break; case '\a': luaL_addchar(b, '\\'); luaL_addchar(b, 'a'); break; case '\b': luaL_addchar(b, '\\'); luaL_addchar(b, 'b'); break; case '\f': luaL_addchar(b, '\\'); luaL_addchar(b, 'f'); break; case '\n': luaL_addchar(b, '\\'); luaL_addchar(b, 'n'); break; case '\r': luaL_addchar(b, '\\'); luaL_addchar(b, 'r'); break; case '\t': luaL_addchar(b, '\\'); luaL_addchar(b, 't'); break; case '\v': luaL_addchar(b, '\\'); luaL_addchar(b, 'v'); break; default: if (isprint((unsigned char)*s)) { luaL_addchar(b, *s); } else { char str[10]; sprintf(str, "\\x%02x", (unsigned int)(unsigned char)*s); luaL_addstring(b, str); } } s++; } luaL_addchar(b, '"'); } } static void addintlen (char *form) { size_t l = strlen(form); char spec = form[l - 1]; strcpy(form + l - 1, LUA_INTFRMLEN); form[l + sizeof(LUA_INTFRMLEN) - 2] = spec; form[l + sizeof(LUA_INTFRMLEN) - 1] = '\0'; } int str_format_helper (luaL_Buffer *b, lua_State *L, int arg) { int top = lua_gettop(L); size_t sfl; const char *strfrmt = luaL_checklstring(L, arg, &sfl); const char *strfrmt_end = strfrmt+sfl; luaL_buffinit(L, b); while (strfrmt < strfrmt_end) { if (*strfrmt != L_ESC) luaL_addchar(b, *strfrmt++); else if (*++strfrmt == L_ESC) luaL_addchar(b, *strfrmt++); /* %% */ else { /* format item */ char form[MAX_FORMAT]; /* to store the format (`%...') */ char buff[MAX_ITEM]; /* to store the formatted item */ if (++arg > top) luaL_argerror(L, arg, "no value"); strfrmt = scanformat(L, strfrmt, form); switch (*strfrmt++) { case 'c': { sprintf(buff, form, (int)luaL_checknumber(L, arg)); break; } case 'd': case 'i': { addintlen(form); sprintf(buff, form, (LUA_INTFRM_T)luaL_checknumber(L, arg)); break; } case 'o': case 'u': case 'x': case 'X': { addintlen(form); sprintf(buff, form, (unsigned LUA_INTFRM_T)luaL_checknumber(L, arg)); break; } case 'e': case 'E': case 'f': case 'g': case 'G': { sprintf(buff, form, (double)luaL_checknumber(L, arg)); break; } case 'q': { addquoted(L, b, arg); continue; /* skip the 'addsize' at the end */ } case 's': { size_t l; const char *s = luaL_checklstring(L, arg, &l); if (!strchr(form, '.') && l >= 100) { /* no precision and string is too long to be formatted; keep original string */ lua_pushvalue(L, arg); luaL_addvalue(b); continue; /* skip the `addsize' at the end */ } else { sprintf(buff, form, s); break; } } default: { /* also treat cases `pnLlh' */ return luaL_error(L, "invalid option " LUA_QL("%%%c") " to " LUA_QL("format"), *(strfrmt - 1)); } case 'B': { if (!lua_isboolean(L, arg) && !lua_isnil(L, arg)) luaL_typerror(L, arg, lua_typename(L, LUA_TBOOLEAN)); strcpy(buff, lua_toboolean(L, arg) ? "true" : "false"); break; } case 'Q': { addquotedbinary(L, b, arg); continue; /* skip the `addsize' at the end */ } } luaL_addlstring(b, buff, strlen(buff)); } } return 1; } #define bufflen(B) ((B)->p - (B)->buffer) #else /* ** {====================================================== ** STRING FORMAT ** ======================================================= */ /* ** LUA_INTFRMLEN is the length modifier for integer conversions in ** 'string.format'; LUA_INTFRM_T is the integer type corresponding to ** the previous length */ #if !defined(LUA_INTFRMLEN) /* { */ #if defined(LUA_USE_LONGLONG) #define LUA_INTFRMLEN "ll" #define LUA_INTFRM_T long long #else #define LUA_INTFRMLEN "l" #define LUA_INTFRM_T long #endif #endif /* } */ /* ** LUA_FLTFRMLEN is the length modifier for float conversions in ** 'string.format'; LUA_FLTFRM_T is the float type corresponding to ** the previous length */ #if !defined(LUA_FLTFRMLEN) #define LUA_FLTFRMLEN "" #define LUA_FLTFRM_T double #endif /* maximum size of each formatted item (> len(format('%99.99f', -1e308))) */ #define MAX_ITEM 512 /* valid flags in a format specification */ #define FLAGS "-+ #0" /* ** maximum size of each format specification (such as '%-099.99d') ** (+10 accounts for %99.99x plus margin of error) */ #define MAX_FORMAT (sizeof(FLAGS) + sizeof(LUA_INTFRMLEN) + 10) static void addquoted (lua_State *L, luaL_Buffer *b, int arg) { size_t l; const char *s = luaL_checklstring(L, arg, &l); luaL_addchar(b, '"'); while (l--) { if (*s == '"' || *s == '\\' || *s == '\n') { luaL_addchar(b, '\\'); luaL_addchar(b, *s); } else if (*s == '\0' || iscntrl(uchar(*s))) { char buff[10]; if (!isdigit(uchar(*(s+1)))) sprintf(buff, "\\%d", (int)uchar(*s)); else sprintf(buff, "\\%03d", (int)uchar(*s)); luaL_addstring(b, buff); } else luaL_addchar(b, *s); s++; } luaL_addchar(b, '"'); } static const char *scanformat (lua_State *L, const char *strfrmt, char *form) { const char *p = strfrmt; while (*p != '\0' && strchr(FLAGS, *p) != NULL) p++; /* skip flags */ if ((size_t)(p - strfrmt) >= sizeof(FLAGS)/sizeof(char)) luaL_error(L, "invalid format (repeated flags)"); if (isdigit(uchar(*p))) p++; /* skip width */ if (isdigit(uchar(*p))) p++; /* (2 digits at most) */ if (*p == '.') { p++; if (isdigit(uchar(*p))) p++; /* skip precision */ if (isdigit(uchar(*p))) p++; /* (2 digits at most) */ } if (isdigit(uchar(*p))) luaL_error(L, "invalid format (width or precision too long)"); *(form++) = '%'; memcpy(form, strfrmt, (p - strfrmt + 1) * sizeof(char)); form += p - strfrmt + 1; *form = '\0'; return p; } /* ** add length modifier into formats */ static void addlenmod (char *form, const char *lenmod) { size_t l = strlen(form); size_t lm = strlen(lenmod); char spec = form[l - 1]; strcpy(form + l - 1, lenmod); form[l + lm - 1] = spec; form[l + lm] = '\0'; } #define L_ESC '%' static int str_format_helper (luaL_Buffer* b, lua_State *L, int arg) { int top = lua_gettop(L); size_t sfl; const char *strfrmt = luaL_checklstring(L, arg, &sfl); const char *strfrmt_end = strfrmt+sfl; luaL_buffinit(L, b); while (strfrmt < strfrmt_end) { if (*strfrmt != L_ESC) luaL_addchar(b, *strfrmt++); else if (*++strfrmt == L_ESC) luaL_addchar(b, *strfrmt++); /* %% */ else { /* format item */ char form[MAX_FORMAT]; /* to store the format (`%...') */ char *buff = luaL_prepbuffsize(b, MAX_ITEM); /* to put formatted item */ int nb = 0; /* number of bytes in added item */ if (++arg > top) luaL_argerror(L, arg, "no value"); strfrmt = scanformat(L, strfrmt, form); switch (*strfrmt++) { case 'c': { nb = sprintf(buff, form, luaL_checkinteger(L, arg)); break; } case 'd': case 'i': { lua_Number n = luaL_checknumber(L, arg); LUA_INTFRM_T ni = (LUA_INTFRM_T)n; lua_Number diff = n - (lua_Number)ni; luaL_argcheck(L, -1 < diff && diff < 1, arg, "not a number in proper range"); addlenmod(form, LUA_INTFRMLEN); nb = sprintf(buff, form, ni); break; } case 'o': case 'u': case 'x': case 'X': { lua_Number n = luaL_checknumber(L, arg); unsigned LUA_INTFRM_T ni = (unsigned LUA_INTFRM_T)n; lua_Number diff = n - (lua_Number)ni; luaL_argcheck(L, -1 < diff && diff < 1, arg, "not a non-negative number in proper range"); addlenmod(form, LUA_INTFRMLEN); nb = sprintf(buff, form, ni); break; } case 'e': case 'E': case 'f': #if defined(LUA_USE_AFORMAT) case 'a': case 'A': #endif case 'g': case 'G': { addlenmod(form, LUA_FLTFRMLEN); nb = sprintf(buff, form, (LUA_FLTFRM_T)luaL_checknumber(L, arg)); break; } case 'q': { addquoted(L, b, arg); break; } case 's': { size_t l; const char *s = luaL_tolstring(L, arg, &l); if (!strchr(form, '.') && l >= 100) { /* no precision and string is too long to be formatted; keep original string */ luaL_addvalue(b); break; } else { nb = sprintf(buff, form, s); lua_pop(L, 1); /* remove result from 'luaL_tolstring' */ break; } } default: { /* also treat cases `pnLlh' */ return luaL_error(L, "invalid option " LUA_QL("%%%c") " to " LUA_QL("format"), *(strfrmt - 1)); } } luaL_addsize(b, nb); } } luaL_pushresult(b); return 1; } #define bufflen(B) ((B)->n) #endif static int LS_LuaFilePrint(LuaState* state) { LuaStateOutFile* file = (LuaStateOutFile*)state->Stack(1).GetUserdata(); luaL_Buffer b; ::str_format_helper(&b, *state, 2); size_t l = bufflen(&b); if (l != 0) { { luaL_addchar(&b, 0); #if LUA_VERSION_NUM <= 501 file->Print(b.buffer); #else file->Print(b.b); #endif } } return 0; } static int LS_LuaFileIndent(LuaState* state) { LuaStateOutFile* file = (LuaStateOutFile*)state->Stack(1).GetUserdata(); int indentLevel = (int)state->Stack(2).GetInteger(); file->Indent((unsigned int)indentLevel); return 0; } #if LUAPLUS_DUMPOBJECT bool LuaState::CallFormatting(LuaObject& tableObj, LuaStateOutFile& file, int indentLevel, bool writeAll, bool alphabetical, bool writeTablePointers, unsigned int maxIndentLevel) { LuaObject metatableObj = tableObj.GetMetatable(); if (metatableObj.IsNil()) return false; LuaObject formattedWriteObj = metatableObj["FormattedWrite"]; if (!formattedWriteObj.IsFunction()) return false; LuaState* state = tableObj.GetState(); { LuaObject funcObj = state->GetGlobals()["LuaFilePrint"]; if (funcObj.IsNil()) { state->GetGlobals().Register("LuaFilePrint", LS_LuaFilePrint); } funcObj = state->GetGlobals()["LuaFileIndent"]; if (funcObj.IsNil()) { state->GetGlobals().Register("LuaFileIndent", LS_LuaFileIndent); } } LuaCall call = formattedWriteObj; call << &file << tableObj << alphabetical << indentLevel << maxIndentLevel << writeAll << writeTablePointers << LuaRun(); return true; } struct KeyValue { LuaObject key; LuaObject value; inline bool operator<(const KeyValue& right) const { if (key.Type() == right.key.Type()) { if (key.IsBoolean() && right.key.IsBoolean()) return !key.GetBoolean(); return key < right.key; } if (key.IsNumber()) return true; if (key.IsString() && !right.key.IsNumber()) return true; return false; } }; static void WriteKey(LuaStateOutFile& file, LuaObject& key) { if (key.IsNumber()) { char keyName[255]; sprintf(keyName, "[%.16g]", key.GetNumber()); file.Print("%s", keyName); } else if (key.IsString()) { size_t keyLen = key.StrLen(); const char* ptr = key.GetString(); const char* endPtr = ptr + keyLen; bool isAlphaNumeric = true; if (isdigit((unsigned char)*ptr)) isAlphaNumeric = false; while (ptr < endPtr) { if (!isalnum((unsigned char)*ptr) && *ptr != '_') { isAlphaNumeric = false; break; } ptr++; } if (isAlphaNumeric) file.Print("%s", key.GetString()); else { file.Print("["); luaI_addquotednonwidebinary(file, key.GetString(), key.StrLen()); file.Print("]"); } } else if (key.IsBoolean()) { file.Print(key.GetBoolean() ? "[true]" : "[false]"); } } static bool KeyValueCompare(const KeyValue& left, const KeyValue &right) { return left < right; } template class SimpleList { public: SimpleList() : m_pHead(NULL) , m_pTail(NULL) { } ~SimpleList() throw() { while (m_pHead) { CNode* pKill = m_pHead; m_pHead = m_pHead->m_pNext; delete pKill; } m_pHead = NULL; m_pTail = NULL; } void AddTail(E& element) { CNode* pNewNode = new CNode(element); pNewNode->m_pPrev = m_pTail; pNewNode->m_pNext = NULL; if (m_pTail) m_pTail->m_pNext = pNewNode; else m_pHead = pNewNode; m_pTail = pNewNode; } void* GetHeadPosition() const throw() { return m_pHead; } E& GetNext( void*& pos ) throw() { CNode* pNode = (CNode*)pos; pos = (void*)pNode->m_pNext; return pNode->m_element; } // Algorithm taken from http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html by Simon Tatham. template void Sort(CompareT Compare) { CNode *q, *e; if (m_pHead == NULL) return; int insize = 1; while (1) { CNode* p = m_pHead; m_pHead = NULL; m_pTail = NULL; int nmerges = 0; /* count number of merges we do in this pass */ while (p) { nmerges++; /* there exists a merge to be done */ /* step `insize' places along from p */ q = p; int psize = 0; for (int i = 0; i < insize; i++) { psize++; q = q->m_pNext; if (!q) break; } /* if q hasn't fallen off end, we have two lists to merge */ int qsize = insize; /* now we have two lists; merge them */ while (psize > 0 || (qsize > 0 && q)) { /* decide whether next element of merge comes from p or q */ if (psize == 0) { /* p is empty; e must come from q. */ e = q; q = q->m_pNext; qsize--; } else if (qsize == 0 || !q) { /* q is empty; e must come from p. */ e = p; p = p->m_pNext; psize--; } else if (Compare(p->m_element, q->m_element)) { // p < q /* First element of p is lower (or same); * e must come from p. */ e = p; p = p->m_pNext; psize--; } else { /* First element of q is lower; e must come from q. */ e = q; q = q->m_pNext; qsize--; } /* add the next element to the merged list */ if (m_pTail) m_pTail->m_pNext = e; else m_pHead = e; e->m_pPrev = m_pTail; m_pTail = e; } /* now p has stepped `insize' places along, and q has too */ p = q; } m_pTail->m_pNext = NULL; /* If we have done only one merge, we're finished. */ if (nmerges <= 1) /* allow for nmerges==0, the empty list case */ { return; } /* Otherwise repeat, merging lists twice the size */ insize *= 2; } } private: class CNode { public: CNode( E& element ) : m_element( element ) { } CNode* m_pNext; CNode* m_pPrev; E m_element; private: CNode( const CNode& ) throw(); }; CNode* m_pHead; CNode* m_pTail; SimpleList( const SimpleList& ) throw(); SimpleList& operator=( const SimpleList& ) throw(); }; /** Writes a Lua object to a text file. **/ bool LuaState::DumpObject(LuaStateOutFile& file, LuaObject& key, LuaObject& value, unsigned int flags, int indentLevel, unsigned int maxIndentLevel) { bool alreadyDumpedKey = (flags & 0xF0000000) != 0; flags &= ~0xF0000000; // If the value is nil, don't write it. if (value.IsNil()) return false; // Indent the line the number of spaces for the current indentation level. const unsigned int INDENT_SIZE = 1; const unsigned int indentSpaces = (indentLevel == -1 ? 0 : indentLevel) * INDENT_SIZE; // If the variable is user data or a function... if (!alreadyDumpedKey && (value.IsUserdata() || value.IsFunction() || value.IsCFunction())) { // ... only write it if they requested it be written. But only do // it as a comment. if (flags & DUMP_WRITEALL) { if ((unsigned int)indentLevel < maxIndentLevel) file.Indent(indentSpaces); else file.Print(" "); if (value.IsUserdata()) { file.Print("-- "); if (!key.IsNil()) { WriteKey(file, key); file.Print(" = "); } file.Print("'userdata: %p'", value.GetUserdata()); } else if (value.IsCFunction()) { file.Print("-- "); if (!key.IsNil()) { WriteKey(file, key); file.Print(" = "); } file.Print("'cfunction: %p'", value.GetCFunction()); } else if (value.IsFunction()) { lua_Debug ar; value.Push(this); lua_getinfo(*this, ">S", &ar); // printf("%d\n", ar.linedefined); file.Print("-- "); if (!key.IsNil()) { WriteKey(file, key); file.Print(" = "); } file.Print("'function: %s %d'", ar.source, ar.linedefined); } return true; } return false; } if (!alreadyDumpedKey) { if ((unsigned int)indentLevel < maxIndentLevel) file.Indent(indentSpaces); else file.Print(" "); // If the object has a name, write it out. if (!key.IsNil()) { WriteKey(file, key); file.Print(" = "); } } // If the object's value is a number, write it as a number. if (value.IsBoolean()) file.Print("%s", value.GetBoolean() ? "true" : "false"); else if (value.IsNumber()) file.Print(LUA_NUMBER_FMT, value.GetNumber()); // Or if the object's value is a string, write it as a quoted string. else if (value.IsString()) { luaI_addquotednonwidebinary(file, value.GetString(), value.StrLen()); } // Otherwise, see if the object's value is a table. else if (value.IsTable()) { bool calledFormatting = CallFormatting(value, file, indentLevel, (flags & DUMP_WRITEALL) != 0, (flags & DUMP_ALPHABETICAL) != 0, (flags & DUMP_WRITETABLEPOINTERS) != 0, maxIndentLevel); if (!calledFormatting) { // Write the table header. if (indentLevel != -1) { if ((unsigned int)indentLevel + 1 < maxIndentLevel) { file.Print("\n"); file.Indent(indentSpaces); } if (flags & DUMP_WRITETABLEPOINTERS) file.Print("{ --%8x\n", value.GetLuaPointer()); else file.Print("{"); if ((unsigned int)indentLevel + 1 < maxIndentLevel) { file.Print("\n"); } } // Rename, just for ease of reading. LuaObject& table = value; // upperIndex is the upper index value of a sequential numerical array // items. int upperIndex = 1; bool wroteSemi = false; bool hasSequential = false; // Block to search for array items. { // Grab index 1 and index 2 of the table. LuaObject value1 = table.RawGetByIndex(1); LuaObject value2 = table.RawGetByIndex(2); // If they both exist, then there is a sequential list. if (!value1.IsNil()) { // Cycle through the list. bool headSequential = true; for (; ; ++upperIndex) { // Try retrieving the table entry at upperIndex. LuaObject value = table.RawGetByIndex(upperIndex); // If it doesn't exist, then exit the loop. if (value.IsNil()) break; // Only add the comma and return if not on the head item. if (!headSequential && indentLevel != -1) { file.Print(","); if ((unsigned int)indentLevel + 1 < maxIndentLevel) { file.Print("\n"); } } // Write the object as an unnamed entry. LuaObject nilObj(this); DumpObject(file, nilObj, value, flags, indentLevel + 1, maxIndentLevel); // We've definitely passed the head item now. headSequential = false; } } } // Did we find any sequential table values? if (upperIndex > 1) { hasSequential = true; } if (flags & DUMP_ALPHABETICAL) { SimpleList keys; // Cycle through the table. for (LuaTableIterator it(table); it; ++it) { // Retrieve the table entry's key and value. LuaObject& key = it.GetKey(); // Is the key a number? if (key.IsNumber()) { // Yes, were there sequential array items in this table? if (hasSequential) { // Is the array item's key an integer? lua_Number realNum = key.GetNumber(); int intNum = (int)realNum; if (realNum == (lua_Number)intNum) { // Yes. Is it between 1 and upperIndex? if (intNum >= 1 && intNum < upperIndex) { // We already wrote it as part of the sequential // list. continue; } } } } KeyValue info; info.key = key; info.value = it.GetValue(); keys.AddTail(info); } keys.Sort(KeyValueCompare); if (keys.GetHeadPosition() != NULL) { // If we wrote a sequential list, the value we're about to write // is not nil, and we haven't written the semicolon to separate // the sequential table entries from the keyed table entries... if (hasSequential && indentLevel != -1) { // Then add a comma (for good measure). file.Print(", "); if ((unsigned int)indentLevel + 1 < maxIndentLevel) { file.Print("\n"); } wroteSemi = true; } } for (void* keysIt = keys.GetHeadPosition(); keysIt; ) { KeyValue& info = keys.GetNext(keysIt); // Write the table entry. bool ret = DumpObject(file, info.key, info.value, flags, indentLevel + 1, maxIndentLevel); // Add a comma after the table entry. if (indentLevel != -1 && ret) { file.Print(","); if ((unsigned int)indentLevel + 1 < maxIndentLevel) { file.Print("\n"); } } } } else { // Cycle through the table. for (LuaTableIterator it(table); it; ++it) { // Retrieve the table entry's key and value. LuaObject& key = it.GetKey(); // Is the key a number? if (key.IsNumber()) { // Yes, were there sequential array items in this table? if (hasSequential) { // Is the array item's key an integer? lua_Number realNum = key.GetNumber(); int intNum = (int)realNum; if (realNum == (lua_Number)intNum) { // Yes. Is it between 1 and upperIndex? if (intNum >= 1 && intNum < upperIndex) { // We already wrote it as part of the sequential // list. continue; } } } } // If we wrote a sequential list, the value we're about to write // is not nil, and we haven't written the semicolon to separate // the sequential table entries from the keyed table entries... if (hasSequential && !value.IsNil() && !wroteSemi) { // Then add a comma (for good measure). if (indentLevel != -1) { file.Print(", "); if ((unsigned int)indentLevel + 1 < maxIndentLevel) { file.Print("\n"); } } wroteSemi = true; } // Write the table entry. bool ret = DumpObject(file, key, it.GetValue(), flags, indentLevel + 1, maxIndentLevel); // Add a comma after the table entry. if (ret && indentLevel != -1) { file.Print(","); if ((unsigned int)indentLevel + 1 < maxIndentLevel) { file.Print("\n"); } } } } // If we wrote a sequential list and haven't written a semicolon, then // there were no keyed table entries. Just write the final comma. if (hasSequential && !wroteSemi && indentLevel != -1) { file.Print(","); if ((unsigned int)indentLevel + 1 < maxIndentLevel) { file.Print("\n"); } } // Indent, with the intent of closing up the table. file.Indent(indentSpaces); // If the indentation level is 0, then we're at the root position. if (indentLevel == 0) { // Add a couple extra returns for readability's sake. file.Print("}"); if ((unsigned int)indentLevel + 1 < maxIndentLevel) { file.Print("\n\n"); } } else if (indentLevel > 0) { // Close the table. The comma is written when WriteObject() // returns from the recursive call. file.Print("}"); } } } // If the indentation level is at the root, then add a return to separate // the lines. if (indentLevel == 0) { if ((unsigned int)indentLevel < maxIndentLevel) { file.Print("\n"); } } return true; } /** Writes a Lua object to a text file. **/ bool LuaState::DumpObject(LuaStateOutFile& file, const char* name, LuaObject& value, unsigned int flags, int indentLevel, unsigned int maxIndentLevel) { // Yes, this is hack-ish. // If the value is nil, don't write it. if (value.IsNil()) return false; const unsigned int INDENT_SIZE = 1; const unsigned int indentSpaces = (indentLevel == -1 ? 0 : indentLevel) * INDENT_SIZE; // If the variable is user data or a function... if (value.IsUserdata() || value.IsFunction() || value.IsCFunction()) { // ... only write it if they requested it be written. But only do // it as a comment. if ((flags & DUMP_WRITEALL) && name) { if (value.IsUserdata()) { file.Print("-- %s", name); file.Print(" = '!!!USERDATA!!!'\n"); } else if (value.IsFunction()) { lua_Debug ar; value.Push(this); lua_getinfo(*this, ">S", &ar); // printf("%d\n", ar.linedefined); file.Print("-- %s", name); file.Print(" = '!!!FUNCTION!!! %s %d'\n", ar.source, ar.linedefined); } else { file.Print("-- %s", name); file.Print(" = '!!!CFUNCTION!!!'\n"); } return true; } return false; } if ((unsigned int)indentLevel < maxIndentLevel) file.Indent(indentSpaces); else file.Print(" "); // If the object has a name, write it out. if (name) { file.Print("%s = ", name); } LuaObject key(this); bool ret = DumpObject(file, key, value, flags | 0xF0000000, indentLevel, maxIndentLevel); file.Print("\n"); return ret; } /** Save the complete script state. **/ bool LuaState::DumpObject(const char* filename, LuaObject& key, LuaObject& value, unsigned int flags, int indentLevel, unsigned int maxIndentLevel) { if (!key.IsString()) { // Open the text file to write the script state to. LuaStateOutFile regFile; LuaStateOutFile* file = ®File; if (!file->Open(filename)) return false; return DumpObject(*file, key, value, flags, indentLevel, maxIndentLevel); } else { return DumpObject(filename, key.GetString(), value, flags, indentLevel, maxIndentLevel); } } /** Save the complete script state. **/ bool LuaState::DumpObject(const char* filename, const char* name, LuaObject& value, unsigned int flags, int indentLevel, unsigned int maxIndentLevel) { // Open the text file to write the script state to. LuaStateOutFile regFile; LuaStateOutFile* file = ®File; if (!file->Open(filename)) return false; // Yes, this is hack-ish. // If the value is nil, don't write it. if (value.IsNil()) return false; // If the variable is user data or a function... if (value.IsUserdata() || value.IsFunction() || value.IsCFunction()) { // ... only write it if they requested it be written. But only do // it as a comment. if ((flags & DUMP_WRITEALL) && name) { if (value.IsUserdata()) { file->Print("-- %s", name); file->Print(" = '!!!USERDATA!!!'\n"); } else if (value.IsFunction()) { lua_Debug ar; value.Push(this); lua_getinfo(*this, ">S", &ar); // printf("%d\n", ar.linedefined); file->Print("-- %s", name); file->Print(" = '!!!FUNCTION!!! %s %d'\n", ar.source, ar.linedefined); } else { file->Print("-- %s", name); file->Print(" = '!!!CFUNCTION!!!'\n"); } return true; } return false; } // Indent the line the number of spaces for the current indentation level. const unsigned int INDENT_SIZE = 1; const unsigned int indentSpaces = (indentLevel == -1 ? 0 : indentLevel) * INDENT_SIZE; if ((unsigned int)indentLevel < maxIndentLevel) file->Indent(indentSpaces); else file->Print(" "); // If the object has a name, write it out. if (name) { file->Print("%s = ", name); } LuaObject key(this); bool ret = DumpObject(*file, key, value, flags | 0xF0000000, indentLevel, maxIndentLevel); file->Print("\n"); return ret; } /** Adds [indentLevel] number of spaces to the file. **/ void LuaStateOutFile::Indent(unsigned int indentLevel) { // Write out indentation. char spaces[500]; unsigned int i; for (i = 0; i < indentLevel; ++i) spaces[i] = '\t'; spaces[i] = 0; Print(spaces); } #endif // LUAPLUS_DUMPOBJECT } // namespace LuaPlus extern "C" int str_format_helper (luaL_Buffer *b, lua_State *L, int arg) { return LuaPlus::str_format_helper(b, L, arg); } #if LUAPLUS_DUMPOBJECT extern "C" void luaplus_dumptable(lua_State* L, int index) { LuaPlus::LuaState* state = lua_State_to_LuaState(L); LuaPlus::LuaObject valueObj(state, index); LuaPlus::LuaStateOutString stringFile; state->DumpObject(stringFile, NULL, valueObj, LuaPlus::LuaState::DUMP_ALPHABETICAL | LuaPlus::LuaState::DUMP_WRITEALL, 0, -1); state->PushString(stringFile.GetBuffer()); } // LuaDumpObject(file, key, value, alphabetical, indentLevel, maxIndentLevel, writeAll) extern "C" int luaplus_ls_LuaDumpObject( lua_State* L ) { LuaPlus::LuaState* state = lua_State_to_LuaState( L ); LuaPlus::LuaStateOutFile file; LuaPlus::LuaStack args(state); LuaPlus::LuaStackObject fileObj = args[1]; if (fileObj.IsTable() && state->GetTop() == 1) { LuaPlus::LuaObject valueObj(fileObj); LuaPlus::LuaObject nameObj; LuaPlus::LuaStateOutString stringFile; state->DumpObject(stringFile, NULL, valueObj, LuaPlus::LuaState::DUMP_ALPHABETICAL, 0, -1); state->PushString(stringFile.GetBuffer()); return 1; } const char* fileName = NULL; if ( fileObj.IsUserdata() ) { FILE* stdioFile = (FILE *)fileObj.GetUserdata(); file.Assign( stdioFile ); } else if ( fileObj.IsString() ) { fileName = fileObj.GetString(); } LuaPlus::LuaObject nameObj = args[2]; LuaPlus::LuaObject valueObj = args[3]; LuaPlus::LuaStackObject alphabeticalObj = args[4]; LuaPlus::LuaStackObject indentLevelObj = args[5]; LuaPlus::LuaStackObject maxIndentLevelObj = args[6]; LuaPlus::LuaStackObject writeAllObj = args[7]; bool writeAll = writeAllObj.IsBoolean() ? writeAllObj.GetBoolean() : false; bool alphabetical = alphabeticalObj.IsBoolean() ? alphabeticalObj.GetBoolean() : true; unsigned int maxIndentLevel = maxIndentLevelObj.IsInteger() ? (unsigned int)maxIndentLevelObj.GetInteger() : 0xFFFFFFFF; unsigned int flags = (alphabetical ? LuaPlus::LuaState::DUMP_ALPHABETICAL : 0) | (writeAll ? LuaPlus::LuaState::DUMP_WRITEALL : 0); if (fileName) { if (strcmp(fileName, ":string") == 0) { LuaPlus::LuaStateOutString stringFile; state->DumpObject(stringFile, nameObj, valueObj, flags, indentLevelObj.GetInteger(), maxIndentLevel); state->PushString(stringFile.GetBuffer()); return 1; } else { state->DumpObject(fileName, nameObj, valueObj, flags, (unsigned int)indentLevelObj.GetInteger(), maxIndentLevel); } } else { state->DumpObject(file, nameObj, valueObj, flags, (unsigned int)indentLevelObj.GetInteger(), maxIndentLevel); } return 0; } #endif // LUAPLUS_DUMPOBJECT