Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
CorsixTH/CorsixTH/Src/persist_lua.cpp
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1311 lines (1199 sloc)
40.8 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| Copyright (c) 2010 Peter "Corsix" Cawley | |
| 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. | |
| */ | |
| #include "persist_lua.h" | |
| #include <errno.h> | |
| #include <array> | |
| #include <cmath> | |
| #include <cstdio> | |
| #include <cstdlib> | |
| #include <cstring> | |
| #include <string> | |
| #include <vector> | |
| #include "th_lua.h" | |
| #ifdef _MSC_VER | |
| #pragma warning( \ | |
| disable : 4996) // Disable "std::strcpy unsafe" warnings under MSVC | |
| #endif | |
| namespace { | |
| enum persist_type { | |
| // LUA_TNIL = 0, | |
| // LUA_TBOOLEAN, // Used for false | |
| // LUA_TLIGHTUSERDATA, // Not currently persisted | |
| // LUA_TNUMBER, | |
| // LUA_TSTRING, | |
| // LUA_TTABLE, // Used for tables without metatables | |
| // LUA_TFUNCTION, // Used for Lua closures | |
| // LUA_TUSERDATA, | |
| // LUA_TTHREAD, // Not currently persisted | |
| PERSIST_TPERMANENT = LUA_TTHREAD + 1, | |
| PERSIST_TTRUE, | |
| PERSIST_TTABLE_WITH_META, | |
| PERSIST_TINTEGER, | |
| PERSIST_TPROTOTYPE, | |
| PERSIST_TRESERVED1, // Not currently used | |
| PERSIST_TRESERVED2, // Not currently used | |
| PERSIST_TCOUNT, // must equal 16 (for compatibility) | |
| }; | |
| template <class T> | |
| int l_crude_gc(lua_State* L) { | |
| // This __gc metamethod does not verify that the given value is the correct | |
| // type of userdata, or that the value is userdata at all. | |
| reinterpret_cast<T*>(lua_touserdata(L, 1))->~T(); | |
| return 0; | |
| } | |
| } // namespace | |
| //! Structure for loading multiple strings as a Lua chunk, avoiding | |
| //! concatenation | |
| /*! | |
| luaL_loadbuffer() is a good way to load a string as a Lua chunk. If there | |
| are several strings which need to be concatenated before being loaded, then | |
| it can be more efficient to use this structure, which can load them without | |
| concatenating them. Sample usage is: | |
| ``` | |
| load_multi_buffer ls; | |
| ls.s[0] = lua_tolstring(L, -2, &ls.i[0]); | |
| ls.s[1] = lua_tolstring(L, -1, &ls.i[1]); | |
| lua_load(L, LoadMultiBuffer_t::load_fn, &ls, "chunk name"); | |
| ``` | |
| */ | |
| class load_multi_buffer { | |
| public: | |
| const char* s[3]; | |
| size_t i[3]; | |
| int n; | |
| load_multi_buffer() { | |
| s[0] = s[1] = s[2] = nullptr; | |
| i[0] = i[1] = i[2] = 0; | |
| n = 0; | |
| } | |
| static const char* load_fn(lua_State* L, void* ud, size_t* size) { | |
| load_multi_buffer* pThis = reinterpret_cast<load_multi_buffer*>(ud); | |
| for (; pThis->n < 3; ++pThis->n) { | |
| if (pThis->i[pThis->n] != 0) { | |
| *size = pThis->i[pThis->n]; | |
| return pThis->s[pThis->n++]; | |
| } | |
| } | |
| *size = 0; | |
| return nullptr; | |
| } | |
| }; | |
| //! Basic implementation of persistence interface | |
| /*! | |
| self - Instance of lua_persist_basic_writer allocated as a Lua userdata | |
| self metatable: | |
| `__gc` - ~lua_persist_basic_writer (via l_crude_gc) | |
| `<file>:<line>` - index of function prototype in already written data | |
| [1] - pre-populated prototype persistence names | |
| `<file>:<line>` - `<name>` | |
| err - an object which could not be persisted | |
| self environment: | |
| `<object>` - index of object in already written data | |
| [1] - permanents table | |
| self environment metatable | |
| */ | |
| class lua_persist_basic_writer : public lua_persist_writer { | |
| public: | |
| lua_persist_basic_writer(lua_State* L) : L(L), data() {} | |
| ~lua_persist_basic_writer() override = default; | |
| lua_State* get_stack() override { return L; } | |
| void init() { | |
| lua_createtable(L, 1, 8); // Environment | |
| lua_pushvalue(L, 2); // Permanent objects | |
| lua_rawseti(L, -2, 1); | |
| lua_createtable(L, 1, 0); // Environment metatable | |
| lua_setmetatable(L, -2); | |
| lua_setfenv(L, 1); | |
| lua_createtable(L, 1, 4); // Metatable | |
| luaT_pushcclosure(L, l_crude_gc<lua_persist_basic_writer>, 0); | |
| lua_setfield(L, -2, "__gc"); | |
| lua_pushvalue(L, luaT_upvalueindex(1)); // Prototype persistence names | |
| lua_rawseti(L, -2, 1); | |
| lua_setmetatable(L, 1); | |
| next_index = 1; | |
| data_size = 0; | |
| had_error = false; | |
| } | |
| int finish() { | |
| if (get_error() != nullptr) { | |
| lua_pushnil(L); | |
| lua_pushstring(L, get_error()); | |
| lua_getmetatable(L, 1); | |
| lua_getfield(L, -1, "err"); | |
| lua_replace(L, -2); | |
| return 3; | |
| } else { | |
| lua_pushlstring(L, data.c_str(), data.length()); | |
| return 1; | |
| } | |
| } | |
| void fast_write_stack_object(int iIndex) override { | |
| if (lua_type(L, iIndex) != LUA_TUSERDATA) { | |
| write_stack_object(iIndex); | |
| return; | |
| } | |
| // Convert index from relative to absolute | |
| if (iIndex < 0 && iIndex > LUA_REGISTRYINDEX) | |
| iIndex = lua_gettop(L) + 1 + iIndex; | |
| // Check for no cycle | |
| lua_getfenv(L, 1); | |
| lua_pushvalue(L, iIndex); | |
| lua_rawget(L, -2); | |
| lua_rawgeti(L, -2, 1); | |
| lua_pushvalue(L, iIndex); | |
| lua_gettable(L, -2); | |
| lua_replace(L, -2); | |
| if (!lua_isnil(L, -1) || !lua_isnil(L, -2)) { | |
| lua_pop(L, 3); | |
| write_stack_object(iIndex); | |
| return; | |
| } | |
| lua_pop(L, 2); | |
| // Save the index to the cache | |
| lua_pushvalue(L, iIndex); | |
| lua_pushnumber(L, (lua_Number)(next_index++)); | |
| lua_settable(L, -3); | |
| if (!check_that_userdata_can_be_depersisted(iIndex)) return; | |
| // Write type, metatable, and then environment | |
| uint8_t iType = LUA_TUSERDATA; | |
| write_byte_stream(&iType, 1); | |
| write_stack_object(-1); | |
| lua_getfenv(L, iIndex); | |
| write_stack_object(-1); | |
| lua_pop(L, 1); | |
| // Write the raw data | |
| if (lua_type(L, -1) == LUA_TTABLE) { | |
| lua_getfield(L, -1, "__persist"); | |
| if (lua_isnil(L, -1)) | |
| lua_pop(L, 1); | |
| else { | |
| lua_pushvalue(L, iIndex); | |
| lua_checkstack(L, 20); | |
| lua_CFunction fn = lua_tocfunction(L, -2); | |
| fn(L); | |
| lua_pop(L, 2); | |
| } | |
| } | |
| write_uint((uint64_t)0x42); // sync marker | |
| lua_pop(L, 1); | |
| } | |
| void write_stack_object(int iIndex) override { | |
| // Convert index from relative to absolute | |
| if (iIndex < 0 && iIndex > LUA_REGISTRYINDEX) | |
| iIndex = lua_gettop(L) + 1 + iIndex; | |
| if (lua_type(L, 2) != LUA_TTABLE) { | |
| luaL_error(L, "Permanents table was lost!"); | |
| } | |
| // Basic types always have their value written | |
| int iType = lua_type(L, iIndex); | |
| if (iType == LUA_TNIL || iType == LUA_TNONE) { | |
| uint8_t iByte = LUA_TNIL; | |
| write_byte_stream(&iByte, 1); | |
| } else if (iType == LUA_TBOOLEAN) { | |
| uint8_t iByte; | |
| if (lua_toboolean(L, iIndex)) | |
| iByte = PERSIST_TTRUE; | |
| else | |
| iByte = LUA_TBOOLEAN; | |
| write_byte_stream(&iByte, 1); | |
| } else if (iType == LUA_TNUMBER) { | |
| double fValue = lua_tonumber(L, iIndex); | |
| if (floor(fValue) == fValue && 0.0 <= fValue && fValue <= 16383.0) { | |
| // Small integers are written as just a few bytes | |
| // NB: 16383 = 2^14-1, which is the maximum value which | |
| // can fit into two bytes of VUInt. | |
| uint8_t iByte = PERSIST_TINTEGER; | |
| write_byte_stream(&iByte, 1); | |
| uint16_t iValue = (uint16_t)fValue; | |
| write_uint(iValue); | |
| } else { | |
| // Other numbers are written as an 8 byte double | |
| uint8_t iByte = LUA_TNUMBER; | |
| write_byte_stream(&iByte, 1); | |
| write_byte_stream(reinterpret_cast<uint8_t*>(&fValue), sizeof(double)); | |
| } | |
| } else { | |
| // Complex values are cached, and are only written once (if this | |
| // weren't done, then cycles in the object graph would break | |
| // things). | |
| lua_getfenv(L, 1); | |
| lua_pushvalue(L, iIndex); | |
| lua_rawget(L, -2); | |
| uint64_t iValue = static_cast<uint64_t>(lua_tonumber(L, -1)); | |
| if (iValue != 0) { | |
| lua_pop(L, 2); | |
| // If the value has not previously been written, then | |
| // write_object_raw would have been called, and the appropriate | |
| // data written, and 0 would be returned. Otherwise, the index | |
| // would be returned, which we offset by the number of types, | |
| // and then write. | |
| write_uint(iValue + PERSIST_TCOUNT - 1); | |
| } else { | |
| lua_pop(L, 1); | |
| // Save the index to the cache | |
| lua_pushvalue(L, iIndex); | |
| lua_pushnumber(L, static_cast<lua_Number>(next_index++)); | |
| lua_rawset(L, -3); | |
| // Remove the fenv and add the item back to the top | |
| lua_pop(L, 1); | |
| lua_pushvalue(L, iIndex); | |
| // write the item and restore the stack to the start state | |
| write_object_raw(); | |
| lua_pop(L, 1); | |
| } | |
| } | |
| } | |
| void write_object_raw() { | |
| int top = lua_gettop(L); | |
| int item_index = top; // same position in write_stack_object | |
| int self_index = 1; // index 1 in write_stack_object | |
| int permanents_index = 2; // also in fenv self [1] | |
| uint8_t item_type = lua_type(L, item_index); | |
| lua_checkstack(L, top + 5); | |
| // Lookup the object in the permanents table | |
| lua_pushvalue(L, item_index); | |
| lua_gettable(L, permanents_index); | |
| if (lua_type(L, -1) != LUA_TNIL) { | |
| // Object is in the permanents table. | |
| uint8_t item_type = PERSIST_TPERMANENT; | |
| write_byte_stream(&item_type, 1); | |
| // Write the key corresponding to the permanent object | |
| write_stack_object(-1); | |
| lua_pop(L, 1); | |
| } else { | |
| // Object is not in the permanents table. | |
| lua_pop(L, 1); | |
| switch (item_type) { | |
| // LUA_TNIL handled in write_stack_object | |
| // LUA_TBOOLEAN handled in write_stack_object | |
| // LUA_TNUMBER handled in write_stack_object | |
| case LUA_TSTRING: { | |
| write_byte_stream(&item_type, 1); | |
| // Strings are simple: length and then bytes (not null | |
| // terminated) | |
| size_t iLength; | |
| const char* sString = lua_tolstring(L, item_index, &iLength); | |
| write_uint(iLength); | |
| write_byte_stream(reinterpret_cast<const uint8_t*>(sString), iLength); | |
| break; | |
| } | |
| case LUA_TTABLE: { | |
| // Save env and insert prior to table | |
| lua_getfenv(L, self_index); | |
| lua_insert(L, item_index); | |
| int table_env_index = item_index; | |
| item_index += 1; | |
| // Handle the metatable | |
| if (lua_getmetatable(L, item_index)) { | |
| item_type = PERSIST_TTABLE_WITH_META; | |
| write_byte_stream(&item_type, 1); | |
| write_stack_object(-1); | |
| lua_pop(L, 1); | |
| } else { | |
| write_byte_stream(&item_type, 1); | |
| } | |
| // Write the children as key, value pairs | |
| lua_pushnil(L); | |
| while (lua_next(L, item_index)) { | |
| write_stack_object(-2); // write the key | |
| write_stack_object(-1); // write the value | |
| lua_pop(L, 1); // remove the value | |
| // key is passed back to lua_next | |
| } | |
| // Write a nil to mark the end of the children (as nil is | |
| // the only value which cannot be used as a key in a table). | |
| uint8_t nil_type = LUA_TNIL; | |
| write_byte_stream(&nil_type, 1); | |
| lua_remove(L, table_env_index); | |
| } break; | |
| case LUA_TFUNCTION: | |
| if (lua_iscfunction(L, item_index)) { | |
| set_error_object(item_index, self_index); | |
| set_error("Cannot persist C functions"); | |
| break; | |
| } | |
| write_byte_stream(&item_type, 1); | |
| // Write the prototype (the part of a function which is | |
| // common across multiple closures - see LClosure / | |
| // Proto in Lua's lobject.h). | |
| lua_Debug proto_info; | |
| lua_pushvalue(L, item_index); | |
| lua_getinfo(L, ">Su", &proto_info); | |
| write_prototype(&proto_info, item_index); | |
| // Write the values of the upvalues | |
| // If available, also write the upvalue IDs (so that in | |
| // the future, we could hypothetically rejoin shared | |
| // upvalues). An ID is just an opaque sequence of bytes. | |
| write_uint(proto_info.nups); | |
| #if LUA_VERSION_NUM >= 502 | |
| write_uint(sizeof(void*)); | |
| #else | |
| write_uint(0); | |
| #endif | |
| for (int i = 1; i <= proto_info.nups; ++i) { | |
| lua_getupvalue(L, item_index, i); | |
| write_stack_object(-1); | |
| #if LUA_VERSION_NUM >= 502 | |
| void* pUpvalueID = lua_upvalueid(L, item_index, i); | |
| write_byte_stream((uint8_t*)&pUpvalueID, sizeof(void*)); | |
| #endif | |
| lua_pop(L, 1); | |
| } | |
| // Write the environment table | |
| lua_getfenv(L, item_index); | |
| write_stack_object(-1); | |
| lua_pop(L, 1); | |
| break; | |
| case LUA_TUSERDATA: | |
| if (!check_that_userdata_can_be_depersisted(item_index)) { | |
| break; | |
| } | |
| // Write type, metatable, and then environment | |
| write_byte_stream(&item_type, 1); | |
| write_stack_object(-1); | |
| lua_getfenv(L, item_index); | |
| write_stack_object(-1); | |
| lua_pop(L, 1); | |
| // Write the raw data | |
| if (lua_type(L, -1) == LUA_TTABLE) { | |
| lua_getfield(L, -1, "__persist"); | |
| if (lua_isnil(L, -1)) { | |
| lua_pop(L, 1); | |
| } else { | |
| lua_pushvalue(L, item_index); | |
| lua_pushvalue(L, self_index); | |
| lua_pushvalue(L, permanents_index); | |
| lua_call(L, 3, 0); | |
| } | |
| } | |
| lua_pop(L, 1); // remove userdata metatable | |
| write_uint((uint64_t)0x42); // sync marker | |
| break; | |
| default: | |
| set_error(lua_pushfstring(L, "Cannot persist %s values", | |
| luaL_typename(L, item_type))); | |
| } | |
| } | |
| if (had_error) { | |
| luaL_error(L, get_error()); | |
| } | |
| } | |
| // Checks if userdata can be persisted to file, that is if it has a size | |
| // of zero, or has a metatable with a __depersist_size, __persist, and | |
| // __depersist function. The metatable is pushed to the top of the stack. | |
| bool check_that_userdata_can_be_depersisted(int iIndex) { | |
| if (lua_getmetatable(L, iIndex)) { | |
| lua_getfield(L, -1, "__depersist_size"); | |
| if (lua_isnumber(L, -1)) { | |
| if (static_cast<int>(lua_tointeger(L, -1)) != | |
| static_cast<int>(lua_objlen(L, iIndex))) { | |
| set_error(lua_pushfstring(L, | |
| "__depersist_size is " | |
| "incorrect (%d vs. %d)", | |
| (int)lua_objlen(L, iIndex), | |
| (int)lua_tointeger(L, -1))); | |
| return false; | |
| } | |
| if (lua_objlen(L, iIndex) != 0) { | |
| lua_getfield(L, -2, "__persist"); | |
| lua_getfield(L, -3, "__depersist"); | |
| if (lua_isnil(L, -1) || lua_isnil(L, -2)) { | |
| set_error( | |
| "Can only persist non-empty userdata" | |
| " if they have __persist and __depersist " | |
| "metamethods"); | |
| return false; | |
| } | |
| lua_pop(L, 2); | |
| } | |
| } else { | |
| if (lua_objlen(L, iIndex) != 0) { | |
| set_error( | |
| "Can only persist non-empty userdata if " | |
| "they have a __depersist_size metafield"); | |
| return false; | |
| } | |
| } | |
| lua_pop(L, 1); | |
| } else { | |
| if (lua_objlen(L, iIndex) != 0) { | |
| set_error( | |
| "Can only persist userdata without a metatable" | |
| " if their size is zero"); | |
| return false; | |
| } | |
| lua_pushnil(L); | |
| } | |
| return true; | |
| } | |
| void write_prototype(lua_Debug* pProtoInfo, int iInstanceIndex) { | |
| // Sanity checks | |
| if (pProtoInfo->source[0] != '@') { | |
| // @ denotes that the source was a file | |
| // (http://www.lua.org/manual/5.1/manual.html#lua_Debug) | |
| set_error("Can only persist Lua functions defined in source files"); | |
| return; | |
| } | |
| if (std::strcmp(pProtoInfo->what, "Lua") != 0) { | |
| // what == "C" should have been caught by write_object_raw(). | |
| // what == "tail" should be impossible. | |
| // Hence "Lua" and "main" should be the only values seen. | |
| // NB: Chunks are not functions defined *in* source files, because | |
| // chunks *are* source files. | |
| set_error(lua_pushfstring(L, "Cannot persist entire Lua chunks (%s)", | |
| pProtoInfo->source + 1)); | |
| lua_pop(L, 1); | |
| return; | |
| } | |
| // Attempt cached lookup (prototypes are not publicly visible Lua | |
| // objects, and hence cannot be cached in the normal way of self's | |
| // environment). | |
| lua_getmetatable(L, 1); | |
| lua_pushfstring(L, "%s:%d", pProtoInfo->source + 1, | |
| pProtoInfo->linedefined); | |
| lua_pushvalue(L, -1); | |
| lua_rawget(L, -3); | |
| if (!lua_isnil(L, -1)) { | |
| uint64_t iValue = (uint64_t)lua_tonumber(L, -1); | |
| lua_pop(L, 3); | |
| write_uint(iValue + PERSIST_TCOUNT - 1); | |
| return; | |
| } | |
| lua_pop(L, 1); | |
| lua_pushvalue(L, -1); | |
| lua_pushnumber(L, (lua_Number)next_index++); | |
| lua_rawset(L, -4); | |
| uint8_t iType = PERSIST_TPROTOTYPE; | |
| write_byte_stream(&iType, 1); | |
| // Write upvalue names | |
| write_uint(pProtoInfo->nups); | |
| for (int i = 1; i <= pProtoInfo->nups; ++i) { | |
| lua_pushstring(L, lua_getupvalue(L, iInstanceIndex, i)); | |
| write_stack_object(-1); | |
| lua_pop(L, 2); | |
| } | |
| // Write the function's persist name | |
| lua_rawgeti(L, -2, 1); | |
| lua_replace(L, -3); | |
| lua_rawget(L, -2); | |
| if (lua_isnil(L, -1)) { | |
| set_error(lua_pushfstring( | |
| L, | |
| "Lua functions must be given a unique " | |
| "persistable name in order to be persisted (attempt to " | |
| "persist" | |
| " %s:%d)", | |
| pProtoInfo->source + 1, pProtoInfo->linedefined)); | |
| lua_pop(L, 2); | |
| return; | |
| } | |
| write_stack_object(-1); | |
| lua_pop(L, 2); | |
| } | |
| void write_byte_stream(const uint8_t* pBytes, size_t iCount) override { | |
| if (had_error) { | |
| // If an error occurred, then silently fail to write any | |
| // data. | |
| return; | |
| } | |
| data.append(reinterpret_cast<const char*>(pBytes), iCount); | |
| } | |
| void set_error(const char* sError) override { | |
| // If multiple errors occur, only record the first. | |
| if (had_error) { | |
| return; | |
| } | |
| had_error = true; | |
| // Use the written data buffer to store the error message | |
| data.assign(sError); | |
| } | |
| void set_error_object(int iStackObject, int self_index) { | |
| if (had_error) return; | |
| lua_pushvalue(L, iStackObject); | |
| lua_getmetatable(L, self_index); | |
| lua_insert(L, -2); | |
| lua_setfield(L, -2, "err"); | |
| lua_pop(L, 1); | |
| } | |
| const char* get_error() { | |
| if (had_error) | |
| return data.c_str(); | |
| else | |
| return nullptr; | |
| } | |
| private: | |
| lua_State* L; | |
| uint64_t next_index; | |
| std::string data; | |
| size_t data_size; | |
| bool had_error; | |
| }; | |
| //! Basic implementation of depersistence interface | |
| /*! | |
| self - Instance of lua_persist_basic_reader allocated as a Lua userdata | |
| self environment: | |
| [-3] - self | |
| [-2] - pre-populated prototype persistence code | |
| `<name>` - `<code>` | |
| [-1] - pre-populated prototype persistence filenames | |
| `<name>` - `<filename>` | |
| [ 0] - permanents table | |
| `<index>` - object already depersisted | |
| self metatable: | |
| `__gc` - ~lua_persist_basic_reader (via l_crude_gc) | |
| `<N>` - userdata to have second `__depersist` call | |
| */ | |
| class lua_persist_basic_reader : public lua_persist_reader { | |
| public: | |
| lua_persist_basic_reader(lua_State* L) : L(L), string_buffer() {} | |
| ~lua_persist_basic_reader() override = default; | |
| lua_State* get_stack() override { return L; } | |
| void set_error(const char* sError) override { | |
| had_error = true; | |
| string_buffer.assign(sError); | |
| } | |
| void init(const uint8_t* pData, size_t iLength) { | |
| data = pData; | |
| data_buffer_size = iLength; | |
| next_index = 1; | |
| had_error = false; | |
| lua_createtable(L, 32, 0); // Environment | |
| lua_pushvalue(L, 2); | |
| lua_rawseti(L, -2, 0); | |
| lua_pushvalue(L, luaT_upvalueindex(1)); | |
| lua_rawseti(L, -2, -1); | |
| lua_pushvalue(L, luaT_upvalueindex(2)); | |
| lua_rawseti(L, -2, -2); | |
| lua_pushvalue(L, 1); | |
| lua_rawseti(L, -2, -3); | |
| lua_setfenv(L, 1); | |
| lua_createtable(L, 0, 1); // Metatable | |
| luaT_pushcclosure(L, l_crude_gc<lua_persist_basic_reader>, 0); | |
| lua_setfield(L, -2, "__gc"); | |
| lua_setmetatable(L, 1); | |
| } | |
| bool read_stack_object() override { | |
| uint64_t iIndex; | |
| if (!read_uint(iIndex)) { | |
| set_error("Expected stack object"); | |
| return false; | |
| } | |
| if (lua_type(L, 1) != LUA_TTABLE) { | |
| // Ensure that index #1 is self environment | |
| lua_getfenv(L, 1); | |
| lua_replace(L, 1); | |
| } | |
| if (iIndex >= PERSIST_TCOUNT) { | |
| iIndex += 1 - PERSIST_TCOUNT; | |
| if (iIndex < (uint64_t)INT_MAX) | |
| lua_rawgeti(L, 1, (int)iIndex); | |
| else { | |
| lua_pushnumber(L, (lua_Number)iIndex); | |
| lua_rawget(L, 1); | |
| } | |
| if (lua_isnil(L, -1)) { | |
| set_error( | |
| "Cycle while depersisting permanent object key or " | |
| "userdata metatable"); | |
| return false; | |
| } | |
| } else { | |
| uint8_t iType = (uint8_t)iIndex; | |
| switch (iType) { | |
| case LUA_TNIL: | |
| lua_pushnil(L); | |
| break; | |
| case PERSIST_TPERMANENT: { | |
| uint64_t iOldIndex = next_index; | |
| ++next_index; // Temporary marker | |
| lua_rawgeti(L, 1, 0); // Permanents table | |
| if (!read_stack_object()) return false; | |
| lua_gettable(L, -2); | |
| lua_replace(L, -2); | |
| // Replace marker with actual object | |
| uint64_t iNewIndex = next_index; | |
| next_index = iOldIndex; | |
| save_stack_object(); | |
| next_index = iNewIndex; | |
| break; | |
| } | |
| case LUA_TBOOLEAN: | |
| lua_pushboolean(L, 0); | |
| break; | |
| case PERSIST_TTRUE: | |
| lua_pushboolean(L, 1); | |
| break; | |
| case LUA_TSTRING: { | |
| size_t iLength; | |
| if (!read_uint(iLength)) return false; | |
| if (!read_byte_stream(string_buffer, iLength)) return false; | |
| lua_pushlstring(L, string_buffer.c_str(), string_buffer.length()); | |
| save_stack_object(); | |
| break; | |
| } | |
| case LUA_TTABLE: | |
| lua_newtable(L); | |
| save_stack_object(); | |
| if (!lua_checkstack(L, 8)) return false; | |
| if (!read_table_contents()) return false; | |
| break; | |
| case PERSIST_TTABLE_WITH_META: | |
| lua_newtable(L); | |
| save_stack_object(); | |
| if (!lua_checkstack(L, 8)) return false; | |
| if (!read_stack_object()) return false; | |
| lua_setmetatable(L, -2); | |
| if (!read_table_contents()) return false; | |
| break; | |
| case LUA_TNUMBER: { | |
| double fValue; | |
| if (!read_byte_stream(reinterpret_cast<uint8_t*>(&fValue), | |
| sizeof(double))) | |
| return false; | |
| lua_pushnumber(L, fValue); | |
| break; | |
| } | |
| case LUA_TFUNCTION: { | |
| if (!lua_checkstack(L, 8)) return false; | |
| uint64_t iOldIndex = next_index; | |
| ++next_index; // Temporary marker | |
| if (!read_stack_object()) return false; | |
| lua_call(L, 0, 2); | |
| // Replace marker with closure | |
| uint64_t iNewIndex = next_index; | |
| next_index = iOldIndex; | |
| save_stack_object(); | |
| next_index = iNewIndex; | |
| // Set upvalues | |
| lua_insert(L, -2); | |
| int iNups, i; | |
| if (!read_uint(iNups)) return false; | |
| size_t iIDSize; | |
| if (!read_uint(iIDSize)) return false; | |
| for (i = 0; i < iNups; ++i) { | |
| if (!read_stack_object()) return false; | |
| // For now, just skip over the upvalue IDs. In the | |
| // future, the ID may be used to rejoin shared upvalues. | |
| if (!read_byte_stream(nullptr, iIDSize)) return false; | |
| } | |
| lua_call(L, iNups, 0); | |
| // Read environment | |
| if (!read_stack_object()) return false; | |
| lua_setfenv(L, -2); | |
| break; | |
| } | |
| case PERSIST_TPROTOTYPE: { | |
| if (!lua_checkstack(L, 8)) return false; | |
| uint64_t iOldIndex = next_index; | |
| ++next_index; // Temporary marker | |
| int iNups; | |
| if (!read_uint(iNups)) return false; | |
| if (iNups == 0) | |
| lua_pushliteral(L, "return function() end,"); | |
| else { | |
| lua_pushliteral(L, "local "); | |
| lua_checkstack(L, (iNups + 1) * 2); | |
| for (int i = 0; i < iNups; ++i) { | |
| if (i != 0) lua_pushliteral(L, ","); | |
| if (!read_stack_object()) return false; | |
| if (lua_type(L, -1) != LUA_TSTRING) { | |
| set_error("Upvalue name not a string"); | |
| return false; | |
| } | |
| } | |
| lua_concat(L, iNups * 2 - 1); | |
| lua_pushliteral(L, ";return function(...)"); | |
| lua_pushvalue(L, -2); | |
| lua_pushliteral(L, "=...end,"); | |
| lua_concat(L, 5); | |
| } | |
| // Fetch name and then lookup filename and code | |
| if (!read_stack_object()) return false; | |
| lua_pushliteral(L, "@"); | |
| lua_rawgeti(L, 1, -1); | |
| lua_pushvalue(L, -3); | |
| lua_gettable(L, -2); | |
| lua_replace(L, -2); | |
| if (lua_isnil(L, -1)) { | |
| set_error(lua_pushfstring(L, | |
| "Unable to depersist prototype" | |
| " \'%s\'", | |
| lua_tostring(L, -3))); | |
| return false; | |
| } | |
| lua_concat(L, 2); // Prepend the @ to the filename | |
| lua_rawgeti(L, 1, -2); | |
| lua_pushvalue(L, -3); | |
| lua_gettable(L, -2); | |
| lua_replace(L, -2); | |
| lua_remove(L, -3); | |
| // Construct the closure factory | |
| load_multi_buffer ls; | |
| ls.s[0] = lua_tolstring(L, -3, &ls.i[0]); | |
| ls.s[1] = lua_tolstring(L, -1, &ls.i[1]); | |
| if (luaT_load(L, load_multi_buffer::load_fn, &ls, lua_tostring(L, -2), | |
| "bt") != 0) { | |
| // Should never happen | |
| lua_error(L); | |
| return false; | |
| } | |
| lua_replace(L, -4); | |
| lua_pop(L, 2); | |
| // Replace marker with closure factory | |
| uint64_t iNewIndex = next_index; | |
| next_index = iOldIndex; | |
| save_stack_object(); | |
| next_index = iNewIndex; | |
| break; | |
| } | |
| case LUA_TUSERDATA: { | |
| bool bHasSetMetatable = false; | |
| uint64_t iOldIndex = next_index; | |
| ++next_index; // Temporary marker | |
| // Read metatable | |
| if (!read_stack_object()) return false; | |
| lua_getfield(L, -1, "__depersist_size"); | |
| if (!lua_isnumber(L, -1)) { | |
| set_error("Userdata missing __depersist_size metafield"); | |
| return false; | |
| } | |
| lua_newuserdata(L, (size_t)lua_tonumber(L, -1)); | |
| lua_replace(L, -2); | |
| // Replace marker with userdata | |
| uint64_t iNewIndex = next_index; | |
| next_index = iOldIndex; | |
| save_stack_object(); | |
| next_index = iNewIndex; | |
| // Perform immediate initialisation | |
| lua_getfield(L, -2, "__pre_depersist"); | |
| if (lua_isnil(L, -1)) | |
| lua_pop(L, 1); | |
| else { | |
| // Set metatable now, as pre-depersister may expect it | |
| // NB: Setting metatable if there isn't a | |
| // pre-depersister is not a good idea, as if there is an | |
| // error while the environment table is being | |
| // de-persisted, then the __gc handler of the userdata | |
| // will eventually be called with the userdata's | |
| // contents still being uninitialised. | |
| lua_pushvalue(L, -3); | |
| lua_setmetatable(L, -3); | |
| bHasSetMetatable = true; | |
| lua_pushvalue(L, -2); | |
| lua_call(L, 1, 0); | |
| } | |
| // Read environment | |
| if (!read_stack_object()) return false; | |
| lua_setfenv(L, -2); | |
| // Set metatable and read the raw data | |
| if (!bHasSetMetatable) { | |
| lua_pushvalue(L, -2); | |
| lua_setmetatable(L, -2); | |
| } | |
| lua_getfield(L, -2, "__depersist"); | |
| if (lua_isnil(L, -1)) | |
| lua_pop(L, 1); | |
| else { | |
| lua_pushvalue(L, -2); | |
| lua_rawgeti(L, 1, -3); | |
| lua_call(L, 2, 1); | |
| if (lua_toboolean(L, -1) != 0) { | |
| lua_pop(L, 1); | |
| lua_rawgeti(L, 1, -3); | |
| lua_getmetatable(L, -1); | |
| lua_replace(L, -2); | |
| lua_pushvalue(L, -2); | |
| lua_rawseti(L, -2, (int)lua_objlen(L, -2) + 1); | |
| } | |
| lua_pop(L, 1); | |
| } | |
| lua_replace(L, -2); | |
| uint64_t iSyncMarker; | |
| if (!read_uint(iSyncMarker)) return false; | |
| if (iSyncMarker != 0x42) { | |
| set_error("sync fail"); | |
| return false; | |
| } | |
| break; | |
| } | |
| case PERSIST_TINTEGER: { | |
| uint16_t iValue; | |
| if (!read_uint(iValue)) return false; | |
| lua_pushinteger(L, iValue); | |
| break; | |
| } | |
| default: | |
| lua_pushliteral(L, "Unable to depersist values of type \'"); | |
| if (iType <= LUA_TTHREAD) | |
| lua_pushstring(L, lua_typename(L, iType)); | |
| else { | |
| switch (iType) { | |
| case PERSIST_TPERMANENT: | |
| lua_pushliteral(L, "permanent"); | |
| break; | |
| case PERSIST_TTRUE: | |
| lua_pushliteral(L, "boolean-true"); | |
| break; | |
| case PERSIST_TTABLE_WITH_META: | |
| lua_pushliteral(L, "table-with-metatable"); | |
| break; | |
| case PERSIST_TINTEGER: | |
| lua_pushliteral(L, "integer"); | |
| break; | |
| case PERSIST_TPROTOTYPE: | |
| lua_pushliteral(L, "prototype"); | |
| break; | |
| case PERSIST_TRESERVED1: | |
| lua_pushliteral(L, "reserved1"); | |
| break; | |
| case PERSIST_TRESERVED2: | |
| lua_pushliteral(L, "reserved2"); | |
| break; | |
| } | |
| } | |
| lua_pushliteral(L, "\'"); | |
| lua_concat(L, 3); | |
| set_error(lua_tostring(L, -1)); | |
| lua_pop(L, 1); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| void save_stack_object() { | |
| if (next_index < (uint64_t)INT_MAX) { | |
| lua_pushvalue(L, -1); | |
| lua_rawseti(L, 1, (int)next_index); | |
| } else { | |
| lua_pushnumber(L, (lua_Number)next_index); | |
| lua_pushvalue(L, -2); | |
| lua_rawset(L, 1); | |
| } | |
| ++next_index; | |
| } | |
| bool read_table_contents() { | |
| while (true) { | |
| if (!read_stack_object()) return false; | |
| if (lua_type(L, -1) == LUA_TNIL) { | |
| lua_pop(L, 1); | |
| return true; | |
| } | |
| if (!read_stack_object()) return false; | |
| // NB: lua_rawset used rather than lua_settable to avoid invoking | |
| // any metamethods, as they may not have been designed to be called | |
| // during depersistence. | |
| lua_rawset(L, -3); | |
| } | |
| } | |
| bool finish() { | |
| // Ensure that all data has been read | |
| if (data_buffer_size != 0) { | |
| set_error(lua_pushfstring(L, "%d bytes of data remain unpersisted", | |
| (int)data_buffer_size)); | |
| return false; | |
| } | |
| // Ensure that index #1 is self environment | |
| if (lua_type(L, 1) != LUA_TTABLE) { | |
| lua_getfenv(L, 1); | |
| lua_replace(L, 1); | |
| } | |
| // Ensure that index #1 is self metatable | |
| lua_rawgeti(L, 1, -3); | |
| lua_getmetatable(L, -1); | |
| lua_replace(L, 1); | |
| lua_pop(L, 1); | |
| // Call all the __depersist functions which need a 2nd call | |
| int iNumCalls = (int)lua_objlen(L, 1); | |
| for (int i = 1; i <= iNumCalls; ++i) { | |
| lua_rawgeti(L, 1, i); | |
| luaL_getmetafield(L, -1, "__depersist"); | |
| lua_insert(L, -2); | |
| lua_call(L, 1, 0); | |
| } | |
| return true; | |
| } | |
| bool read_byte_stream(uint8_t* pBytes, size_t iCount) override { | |
| if (iCount > data_buffer_size) { | |
| set_error(lua_pushfstring( | |
| L, "End of input reached while attempting to read %d byte%s", | |
| (int)iCount, iCount == 1 ? "" : "s")); | |
| lua_pop(L, 1); | |
| return false; | |
| } | |
| if (pBytes != nullptr) std::memcpy(pBytes, data, iCount); | |
| data += iCount; | |
| data_buffer_size -= iCount; | |
| return true; | |
| } | |
| bool read_byte_stream(std::string& bytes, size_t iCount) { | |
| if (iCount > data_buffer_size) { | |
| set_error(lua_pushfstring( | |
| L, "End of input reached while attempting to read %d byte%s", | |
| (int)iCount, iCount == 1 ? "" : "s")); | |
| lua_pop(L, 1); | |
| return false; | |
| } | |
| bytes.assign(reinterpret_cast<const char*>(data), iCount); | |
| data += iCount; | |
| data_buffer_size -= iCount; | |
| return true; | |
| } | |
| const uint8_t* get_pointer() { return data; } | |
| uint64_t get_object_count() { return next_index; } | |
| const char* get_error() { | |
| if (had_error) | |
| return string_buffer.c_str(); | |
| else | |
| return nullptr; | |
| } | |
| private: | |
| lua_State* L; | |
| uint64_t next_index; | |
| const uint8_t* data; | |
| size_t data_buffer_size; | |
| std::string string_buffer; | |
| bool had_error; | |
| }; | |
| namespace { | |
| int l_dump_toplevel(lua_State* L) { | |
| luaL_checktype(L, 2, LUA_TTABLE); | |
| lua_settop(L, 2); | |
| lua_pushvalue(L, 1); | |
| lua_persist_basic_writer* pWriter = | |
| new (lua_newuserdata(L, sizeof(lua_persist_basic_writer))) | |
| lua_persist_basic_writer(L); | |
| lua_replace(L, 1); | |
| pWriter->init(); | |
| pWriter->write_stack_object(3); | |
| return pWriter->finish(); | |
| } | |
| int l_load_toplevel(lua_State* L) { | |
| size_t iDataLength; | |
| const uint8_t* pData = luaT_checkfile(L, 1, &iDataLength); | |
| luaL_checktype(L, 2, LUA_TTABLE); | |
| lua_settop(L, 2); | |
| lua_pushvalue(L, 1); | |
| lua_persist_basic_reader* pReader = | |
| new (lua_newuserdata(L, sizeof(lua_persist_basic_reader))) | |
| lua_persist_basic_reader(L); | |
| lua_replace(L, 1); | |
| pReader->init(pData, iDataLength); | |
| if (!pReader->read_stack_object() || !pReader->finish()) { | |
| int iNumObjects = (int)pReader->get_object_count(); | |
| int iNumBytes = (int)(pReader->get_pointer() - pData); | |
| lua_pushnil(L); | |
| lua_pushfstring(L, "%s after %d objects (%d bytes)", | |
| pReader->get_error() ? pReader->get_error() | |
| : "Error while depersisting", | |
| iNumObjects, iNumBytes); | |
| return 2; | |
| } else { | |
| return 1; | |
| } | |
| } | |
| int calculate_line_number(const char* sStart, const char* sPosition) { | |
| int iLine = 1; | |
| for (; sStart != sPosition; ++sStart) { | |
| switch (sStart[0]) { | |
| case '\0': | |
| return -1; // error return value | |
| case '\n': | |
| ++iLine; | |
| if (sStart[1] == '\r') ++sStart; | |
| break; | |
| case '\r': | |
| ++iLine; | |
| if (sStart[1] == '\n') ++sStart; | |
| break; | |
| } | |
| } | |
| return iLine; | |
| } | |
| const char* find_function_end(lua_State* L, const char* sStart) { | |
| const char* sEnd = sStart; | |
| while (sEnd) { | |
| sEnd = std::strstr(sEnd, "end"); | |
| if (sEnd) { | |
| sEnd += 3; | |
| load_multi_buffer ls; | |
| ls.s[0] = "return function"; | |
| ls.i[0] = sizeof("return function") - 1; | |
| ls.s[1] = sStart; | |
| ls.i[1] = sEnd - sStart; | |
| if (luaT_load(L, load_multi_buffer::load_fn, &ls, "", "bt") == 0) { | |
| lua_pop(L, 1); | |
| return sEnd; | |
| } | |
| lua_pop(L, 1); | |
| } | |
| } | |
| return nullptr; | |
| } | |
| int l_persist_dofile(lua_State* L) { | |
| const char* sFilename = luaL_checkstring(L, 1); | |
| lua_settop(L, 1); | |
| // Read entire file into memory | |
| std::FILE* fFile = std::fopen(sFilename, "r"); | |
| if (fFile == nullptr) { | |
| const char* sError = std::strerror(errno); | |
| return luaL_error(L, "cannot open %s: %s", sFilename, sError); | |
| } | |
| size_t iBufferSize = lua_objlen(L, luaT_upvalueindex(1)); | |
| size_t iBufferUsed = 0; | |
| while (!std::feof(fFile)) { | |
| iBufferUsed += std::fread( | |
| reinterpret_cast<char*>(lua_touserdata(L, luaT_upvalueindex(1))) + | |
| iBufferUsed, | |
| 1, iBufferSize - iBufferUsed, fFile); | |
| if (iBufferUsed == iBufferSize) { | |
| iBufferSize *= 2; | |
| std::memcpy(lua_newuserdata(L, iBufferSize), | |
| lua_touserdata(L, luaT_upvalueindex(1)), iBufferUsed); | |
| lua_replace(L, luaT_upvalueindex(1)); | |
| } else | |
| break; | |
| } | |
| int iStatus = std::ferror(fFile); | |
| std::fclose(fFile); | |
| if (iStatus) { | |
| const char* sError = std::strerror(errno); | |
| return luaL_error(L, "cannot read %s: %s", sFilename, sError); | |
| } | |
| // Check file | |
| char* sFile = | |
| reinterpret_cast<char*>(lua_touserdata(L, luaT_upvalueindex(1))); | |
| sFile[iBufferUsed] = 0; | |
| if (sFile[0] == '#') { | |
| do { | |
| ++sFile; | |
| --iBufferUsed; | |
| } while (sFile[0] != 0 && sFile[0] != '\r' && sFile[0] != '\n'); | |
| } | |
| if (sFile[0] == LUA_SIGNATURE[0]) { | |
| return luaL_error(L, "cannot load %s: compiled files not permitted", | |
| sFilename); | |
| } | |
| // Load and do file | |
| lua_pushliteral(L, "@"); | |
| lua_pushvalue(L, 1); | |
| lua_concat(L, 2); | |
| if (luaL_loadbuffer(L, sFile, iBufferUsed, lua_tostring(L, -1)) != 0) | |
| return lua_error(L); | |
| lua_remove(L, -2); | |
| int iBufferCopyIndex = lua_gettop(L); | |
| std::memcpy(lua_newuserdata(L, iBufferUsed + 1), sFile, iBufferUsed + 1); | |
| lua_insert(L, -2); | |
| lua_call(L, 0, LUA_MULTRET); | |
| sFile = reinterpret_cast<char*>(lua_touserdata(L, luaT_upvalueindex(1))); | |
| std::memcpy(sFile, lua_touserdata(L, iBufferCopyIndex), iBufferUsed + 1); | |
| lua_remove(L, iBufferCopyIndex); | |
| // Extract persistable functions | |
| const char* sPosition = sFile; | |
| while (true) { | |
| sPosition = std::strstr(sPosition, "--[[persistable:"); | |
| if (!sPosition) break; | |
| sPosition += 16; | |
| const char* sNameEnd = std::strstr(sPosition, "]]"); | |
| if (sNameEnd) { | |
| int iLineNumber = calculate_line_number(sFile, sNameEnd); | |
| const char* sFunctionArgs = std::strchr(sNameEnd + 2, '('); | |
| const char* sFunctionEnd = find_function_end(L, sFunctionArgs); | |
| if ((sNameEnd - sPosition) == 1 && *sPosition == ':') { | |
| // --[[persistable::]] means take the existing name of the | |
| // function | |
| sPosition = std::strstr(sNameEnd, "function") + 8; | |
| sPosition += std::strspn(sPosition, " \t"); | |
| sNameEnd = sFunctionArgs; | |
| while (sNameEnd[-1] == ' ') --sNameEnd; | |
| } | |
| if (iLineNumber != -1 && sFunctionArgs && sFunctionEnd) { | |
| // Save <filename>:<line> => <name> | |
| lua_pushfstring(L, "%s:%d", sFilename, iLineNumber); | |
| lua_pushvalue(L, -1); | |
| lua_gettable(L, luaT_upvalueindex(2)); | |
| if (lua_isnil(L, -1)) { | |
| lua_pop(L, 1); | |
| lua_pushlstring(L, sPosition, sNameEnd - sPosition); | |
| lua_settable(L, luaT_upvalueindex(2)); | |
| } else { | |
| return luaL_error(L, | |
| "Multiple persistable functions defined" | |
| "on the same line (%s:%d)", | |
| sFilename, iLineNumber); | |
| } | |
| // Save <name> => <filename> | |
| lua_pushlstring(L, sPosition, sNameEnd - sPosition); | |
| lua_pushvalue(L, -1); | |
| lua_gettable(L, luaT_upvalueindex(3)); | |
| if (lua_isnil(L, -1)) { | |
| lua_pop(L, 1); | |
| lua_pushvalue(L, 1); | |
| lua_settable(L, luaT_upvalueindex(3)); | |
| } else { | |
| return luaL_error(L, | |
| "Persistable function name \'%s\' is" | |
| " not unique (defined in both %s and %s)", | |
| lua_tostring(L, -2), lua_tostring(L, -1), | |
| sFilename); | |
| } | |
| // Save <name> => <code> | |
| lua_pushlstring(L, sPosition, sNameEnd - sPosition); | |
| lua_pushliteral(L, "\n"); | |
| lua_getfield(L, -1, "rep"); | |
| lua_insert(L, -2); | |
| lua_pushinteger(L, iLineNumber - 1); | |
| lua_call(L, 2, 1); | |
| lua_pushliteral(L, "function"); | |
| lua_pushlstring(L, sFunctionArgs, sFunctionEnd - sFunctionArgs); | |
| lua_concat(L, 3); | |
| lua_settable(L, luaT_upvalueindex(4)); | |
| } | |
| } | |
| } | |
| // Finish | |
| return lua_gettop(L) - 1; | |
| } | |
| int l_errcatch(lua_State* L) { | |
| // Dummy function for debugging - place a breakpoint on the following | |
| // return statement to inspect the full C call stack when a Lua error | |
| // occurs (assuming that l_errcatch is used as the error catch handler). | |
| return 1; | |
| } | |
| // Due to the various required upvalues, functions are registered manually, but | |
| // we still need a dummy to pass to luaL_register. | |
| constexpr std::array<luaL_Reg, 2> persist_lib{ | |
| {{"errcatch", l_errcatch}, {nullptr, nullptr}}}; | |
| } // namespace | |
| int luaopen_persist(lua_State* L) { | |
| luaT_register(L, "persist", persist_lib); | |
| lua_newuserdata(L, 512); // buffer for dofile | |
| lua_newtable(L); | |
| lua_newtable(L); | |
| lua_newtable(L); | |
| lua_pushvalue(L, -3); | |
| luaT_pushcclosure(L, l_dump_toplevel, 1); | |
| lua_setfield(L, -6, "dump"); | |
| lua_pushvalue(L, -2); | |
| lua_pushvalue(L, -2); | |
| luaT_pushcclosure(L, l_load_toplevel, 2); | |
| lua_setfield(L, -6, "load"); | |
| luaT_pushcclosure(L, l_persist_dofile, 4); | |
| lua_setfield(L, -2, "dofile"); | |
| return 1; | |
| } |