diff --git a/luprex/build.bat b/luprex/build.bat index 4cc92dd6..916327dd 100644 --- a/luprex/build.bat +++ b/luprex/build.bat @@ -1,2 +1,2 @@ clear -g++ -std=c++17 -Wall -g -o main syscpp/util.cpp syscpp/main.cpp syscpp/luastack.cpp syscpp/source.cpp syscpp/table.cpp syscpp/idalloc.cpp syscpp/globaldb.cpp -Iinc -Llib lib/libluajit-dbg.a -Isyscpp +g++ -std=c++17 -Wall -g -o main syscpp/util.cpp syscpp/main.cpp syscpp/cellgrid.cpp syscpp/traceback.cpp syscpp/luastack.cpp syscpp/source.cpp syscpp/table.cpp syscpp/idalloc.cpp syscpp/globaldb.cpp -Iinc -Llib lib/libluajit-dbg.a -Isyscpp diff --git a/luprex/syscpp/cellgrid.cpp b/luprex/syscpp/cellgrid.cpp new file mode 100644 index 00000000..b046106b --- /dev/null +++ b/luprex/syscpp/cellgrid.cpp @@ -0,0 +1,150 @@ +#include "luastack.hpp" +#include "cellgrid.hpp" +#include "table.hpp" +#include +#include + +LuaDefine(cellgrid_initregistry, "a") { + LuaVar grid; + LuaStack LS(L, grid); + + LS.getfield(grid, LuaRegistry, "cellgrid_cells"); + if (LS.isnil(grid)) { + LS.newtable(grid); + LS.setfield(LuaRegistry, "cellgrid_cells", grid); + } + return LS.result(); +} + +LuaDefine(cellgrid_addplane, "c") { + LuaArg grid, plane; + LuaVar planegrid; + LuaStack LS(L, grid, plane, planegrid); + + // Get the grid. + if (LS.isnil(grid)) LS.getfield(grid, LuaRegistry, "cellgrid_cells"); + LS.checktable(grid); + + // Check for the plane. If not present, add it. + LS.checkstring(plane); + LS.rawget(planegrid, grid, plane); + if (LS.isnil(planegrid)) { + LS.rawset(grid, plane, LuaNewTable); + } + return LS.result(); +} + +#define CELL_SCALE 10.0 +#define CELL_INVALID 0 + +LuaDefine(cellgrid_scale, "c") { + LuaRet scale; + LuaStack LS(L, scale); + LS.set(scale, CELL_SCALE); + return LS.result(); +} + +lua_Number calc_cellid(lua_Number x, lua_Number y, lua_Number z) { + lua_Number cellx = floor(x / CELL_SCALE); + lua_Number celly = floor(y / CELL_SCALE); + lua_Number cellz = floor(z / CELL_SCALE); + if ((cellx > 32767)||(celly > 32767)||(cellz > 32767)) { + return CELL_INVALID; + } + if ((cellx < -32767)||(celly < -32767)||(cellz < -32767)) { + return CELL_INVALID; + } + int64_t icellx = int64_t(cellx) & 0xFFFF; + int64_t icelly = int64_t(celly) & 0xFFFF; + int64_t icellz = int64_t(cellz) & 0xFFFF; + return lua_Number(0x0001000000000000 | (icellx << 32) | (icelly << 16) | (icellz << 0)); +} + +LuaDefine(cellgrid_cellid, "c") { + LuaArg x, y, z; + LuaRet cellid; + LuaStack LS(L, x, y, z, cellid); + lua_Number nx = LS.cknumber(x); + lua_Number ny = LS.cknumber(y); + lua_Number nz = LS.cknumber(z); + LS.set(cellid, calc_cellid(nx, ny, nz)); + return LS.result(); +} + +LuaDefine(cellgrid_setpos, "c") { + LuaArg grid, sprite, x, y, z, nplane; + LuaVar smt, oplane, planegrid, cell; + LuaStack LS(L, grid, sprite, x, y, z, nplane, smt, oplane, planegrid, cell); + + // Get the grid. + if (LS.isnil(grid)) LS.getfield(grid, LuaRegistry, "cellgrid_cells"); + LS.checktable(grid); + + // Calculate the new plane and cell ID. + lua_Number nx, ny, nz; + nx = LS.cknumber(x); + ny = LS.cknumber(y); + nz = LS.cknumber(z); + if (!LS.isnil(nplane)) LS.checkstring(nplane); + lua_Number ncellid = calc_cellid(nx, ny, nz); + + // Check that the new plane is an existing plane. + LS.rawget(planegrid, grid, nplane); + if (!LS.istable(planegrid)) { + luaL_error(L, "invalid plane"); + } + + // Get the sprite metatable. + LS.checktable(sprite); + LS.getmetatable(smt, sprite); + LS.checktable(smt); + + // Get the sprite's old plane and cell ID. + lua_Number ox, oy, oz; + LS.getfield(LuaAcceptNil(ox), smt, "x"); + LS.getfield(LuaAcceptNil(oy), smt, "y"); + LS.getfield(LuaAcceptNil(oz), smt, "z"); + LS.getfield(oplane, smt, "plane"); + lua_Number ocellid = calc_cellid(ox, oy, oz); + + // The sprite 'moved' if it's in a new cell. + bool moved = (!LS.equal(nplane, oplane)) || (ncellid != ocellid); + + // Change the sprite position. + LS.rawset(smt, "x", x); + LS.rawset(smt, "y", y); + LS.rawset(smt, "z", z); + LS.rawset(smt, "plane", nplane); + + // Remove sprite from the old cell. + if (moved && LS.isstring(oplane) && (ocellid != CELL_INVALID)) { + LS.rawget(planegrid, grid, oplane); + if (LS.istable(planegrid)) { + LS.rawget(cell, planegrid, ocellid); + if (LS.istable(cell)) { + LS.call(LuaDiscard, table_findremove, cell, sprite); + if (LS.isemptytable(cell)) { + LS.rawset(planegrid, ocellid, LuaNil); + } + } + } + } + + // Insert sprite into the new cell + if (moved && LS.isstring(nplane) && (ncellid != CELL_INVALID)) { + LS.rawget(planegrid, grid, nplane); + if (LS.istable(planegrid)) { + LS.rawget(cell, planegrid, ncellid); + if (!LS.istable(cell)) { + LS.newtable(cell); + LS.rawset(planegrid, ncellid, cell); + } + LS.call(table_append, cell, sprite); + } + } + + return LS.result(); +} + +int cellgrid_scanradius(lua_State *L); +int cellgrid_scanregion(lua_State *L); diff --git a/luprex/syscpp/cellgrid.hpp b/luprex/syscpp/cellgrid.hpp new file mode 100644 index 00000000..19dcff43 --- /dev/null +++ b/luprex/syscpp/cellgrid.hpp @@ -0,0 +1,38 @@ +// +// Cellgrid: stores sprites in a grid. +// +// To create a grid, just create an empty table. +// In actual usage, the cellgrid will be stored in the registry +// in the field "cellgrid_cells". If you pass grid=nil to +// any of these routines, the system will automatically use +// the table in the registry. +// +// For purposes of this module, a "sprite" is any table +// having a metatable. The (X,Y,Z,Plane) of the sprite are +// stored as hidden fields inside the metatable. +// +// We preserve the invariant that sprites whose plane is nil +// are not stored in the grid. All sprites whose plane +// is a string are stored in the grid. +// +// When creating a new sprite, initialize plane to nil. That +// fits the invariant that sprites whose plane is nil are +// not stored in the grid. If you ever want to change the +// sprite's position, use cellgrid_setpos: that will +// preserve the invariant. +// + +#ifndef CELLGRID_HPP +#define CELLGRID_HPP + +#include "luastack.hpp" + +int cellgrid_initgrid(lua_State *L); +int cellgrid_addplane(lua_State *L); +int cellgrid_setpos(lua_State *L); +int cellgrid_scanradius(lua_State *L); +int cellgrid_scanregion(lua_State *L); + +#endif // CELLGRID_HPP + + diff --git a/luprex/syscpp/globaldb.cpp b/luprex/syscpp/globaldb.cpp index e4c7149b..9f80219a 100644 --- a/luprex/syscpp/globaldb.cpp +++ b/luprex/syscpp/globaldb.cpp @@ -9,14 +9,14 @@ // if globalname is already present, and not a table, error. // if globalname is not present, create and initialize it. // -LuaDefineGlobalFunction(globaldb_global) { +LuaDefine(globaldb_global, "f") { LuaArg globalname; LuaRet globaltab; LuaVar globaldb; LuaStack LS(L, globalname, globaltab, globaldb); - LS.checktype(globalname, LUA_TSTRING); - + LS.checkstring(globalname); + // Get a pointer to the globaldb. LS.getfield(globaldb, LuaRegistry, "globaldb"); if (!LS.istable(globaldb)) { @@ -29,7 +29,7 @@ LuaDefineGlobalFunction(globaldb_global) { if (LS.istable(globaltab)) { return LS.result(); } else if (!LS.isnil(globaltab)) { - luaL_error(L, "%s is not a global", LS.tostring(globalname).c_str()); + luaL_error(L, "%s is not a global", LS.ckstring(globalname).c_str()); } // Create a new globaltab and store it in the globaldb. diff --git a/luprex/syscpp/idalloc.cpp b/luprex/syscpp/idalloc.cpp index 3afd215a..97b5f7f8 100644 --- a/luprex/syscpp/idalloc.cpp +++ b/luprex/syscpp/idalloc.cpp @@ -1,19 +1,237 @@ #include "luastack.hpp" +#include "table.hpp" #include "idalloc.hpp" -LuaDefineGlobalMethod(idalloc_getnextid) { - LuaRet value; - LuaStack LS(L, value); - double id = lua_getnextid(L); - LS.set(value, id); +LuaDefine(idalloc_initmaster, "c") { + LuaArg allocator, queuefill; + LuaVar salvqueue; + LuaStack LS(L, allocator, queuefill, salvqueue); + + // Use the registry by default. + if (LS.isnil(allocator)) LS.set(allocator, LuaRegistry); + + LS.checktable(allocator); + LS.checknumber(queuefill); + LS.call(salvqueue, queue_create); + LS.setfield(allocator, "id_salvaged", salvqueue); + LS.setfield(allocator, "id_nextbatch", 0x0001000000000000); + LS.setfield(allocator, "id_nextid", 0x0010000000000000); + LS.setfield(allocator, "id_queuefill", queuefill); return LS.result(); } -LuaDefineGlobalMethod(idalloc_setnextid) { - LuaArg value; - LuaStack LS(L, value); - double id = LS.tonumber(value); - lua_setnextid(L, int64_t(id)); +LuaDefine(idalloc_initsynch, "c") { + LuaArg allocator, queuefill; + LuaStack LS(L, allocator, queuefill); + + // Use the registry by default. + if (LS.isnil(allocator)) LS.set(allocator, LuaRegistry); + + LS.checktable(allocator); + LS.checknumber(queuefill); + LS.setfield(allocator, "id_salvaged", LuaNil); + LS.setfield(allocator, "id_nextbatch", LuaNil); + LS.setfield(allocator, "id_nextid", 0x001E000000000000); + LS.setfield(allocator, "id_queuefill", queuefill); return LS.result(); } +LuaDefine(idalloc_refill, "c") { + LuaArg allocator, queue; + LuaVar salvaged, batch; + lua_Integer qhead, qtail, shead, stail, nextb, queuefill; + LuaStack LS(L, allocator, queue, salvaged, batch); + + // Use the registry by default. + if (LS.isnil(allocator)) LS.set(allocator, LuaRegistry); + + // Get salvaged batch table. If there is none, we're in donotpredict mode. + LS.getfield(salvaged, allocator, "id_salvaged"); + if (LS.isnil(salvaged)) { + return LS.result(); + } + + // Get the head and tail of the queue. + LS.checktable(queue); + LS.getfield(qhead, queue, "head"); + LS.getfield(qtail, queue, "tail"); + lua_Integer oqhead = qhead; + + // Try using salvaged batches. + LS.getfield(queuefill, allocator, "id_queuefill"); + if (qhead - qtail < queuefill) { + // Grab the head and tail of the salvaged batches. + LS.checktable(salvaged); + LS.getfield(shead, salvaged, "head"); + LS.getfield(stail, salvaged, "tail"); + + // Get salvaged batches where possible. + while ((stail < shead) && (qhead - qtail < queuefill)) { + LS.rawget(batch, salvaged, stail); + LS.rawset(salvaged, stail, LuaNil); + stail += 1; + LS.checknumber(batch); + LS.rawset(queue, qhead, batch); + qhead += 1; + } + + // Update the head and tail of the salvaged batches. + LS.setfield(salvaged, "head", shead); + LS.setfield(salvaged, "tail", stail); + } + + // Try using newly-created batches. + if (qhead - qtail < queuefill) { + // Grab the next batch counter. + LS.getfield(nextb, allocator, "id_nextbatch"); + + // Get newly-allocated batches to fill the rest. + while (qhead - qtail < queuefill) { + LS.rawset(queue, qhead, nextb); + nextb += 256; + qhead += 1; + } + + // Update the counter in the registry. + LS.setfield(allocator, "id_nextbatch", nextb); + } + + // Update the head of the queue. + if (oqhead != qhead) { + LS.setfield(queue, "head", qhead); + } + + return LS.result(); +} + +LuaDefine(idalloc_unqueue, "c") { + LuaArg allocator, queue; + LuaVar salvaged, batch; + lua_Integer qhead, qtail, shead, stail; + LuaStack LS(L, allocator, queue, salvaged, batch); + + // Use the registry by default. + if (LS.isnil(allocator)) LS.set(allocator, LuaRegistry); + + // Get the head and tail of the queue. + LS.checktable(queue); + LS.getfield(qhead, queue, "head"); + LS.getfield(qtail, queue, "tail"); + + // Grab the table of salvaged batches. + // If there is none, we're in donotpredict mode. In that + // case, just empty the queue and dump the batches. + LS.getfield(salvaged, allocator, "id_salvaged"); + if (LS.isnil(salvaged)) { + while (qhead > qtail) { + LS.rawset(queue, qtail, LuaNil); + qtail += 1; + } + LS.setfield(queue, "tail", qtail); + return LS.result(); + } + + // We're in master mode. Transfer batches from the queue + // into the salvaged batches table. + LS.checktable(salvaged); + LS.getfield(shead, salvaged, "head"); + LS.getfield(stail, salvaged, "tail"); + + while (qhead > qtail) { + LS.rawget(batch, queue, qtail); + LS.rawset(queue, qtail, LuaNil); + qtail += 1; + + LS.checknumber(batch); + LS.rawset(salvaged, shead, batch); + shead += 1; + } + + // Update the queue pointers. + LS.setfield(queue, "tail", qtail); + LS.setfield(salvaged, "head", shead); + + return LS.result(); +} + +LuaDefine(idalloc_preparethread, "c") { + LuaArg queue, thread; + LuaVar batch; + LuaStack LS(L, queue, thread, batch); + + // Get the thread. + lua_State *TH = LS.ckthread(thread); + + // Pop a batch from the queue. If there's nothing in + // the queue, just leave the thread unprepped. + LS.call(batch, queue_pop, queue); + if (LS.isnil(batch)) { + return LS.result(); + } + + // Store the batch into the thread. + lua_setnextid(TH, LS.ckinteger(batch)); + return LS.result(); +} + +LuaDefine(idalloc_salvagethread, "c") { + LuaArg allocator, thread; + LuaVar salvaged; + LuaStack LS(L, allocator, thread, salvaged); + + lua_State *TH = LS.ckthread(thread); + lua_Integer idbatch = lua_getnextid(TH); + lua_setnextid(TH, 0); + if (idbatch == 0) { + return LS.result(); + } + if ((idbatch & 0xFF) >= 128) { + return LS.result(); + } + + // Use the registry by default. + if (LS.isnil(allocator)) LS.set(allocator, LuaRegistry); + + // Push the batch onto the queue of salvaged batches. + // If the table of salvaged batches is nil, we're in donotpredict + // mode. In that case, don't bother salvaging. + LS.getfield(salvaged, allocator, "id_salvaged"); + if (LS.isnil(salvaged)) { + return LS.result(); + } + LS.call(queue_push, salvaged, idbatch); + return LS.result(); +} + +LuaDefine(idalloc_allocid, "c") { + LuaArg allocator; + LuaRet result; + LuaStack LS(L, allocator, result); + + lua_Integer id = lua_getnextid(L); + if (id == 0) { + // Use the registry by default. + if (LS.isnil(allocator)) LS.set(allocator, LuaRegistry); + LS.getfield(id, allocator, "id_nextid"); + LS.setfield(allocator, "id_nextid", id + 1); + } else { + lua_Integer next = id + 1; + if ((next & 0xFF) == 0) { + next = 0; + } + lua_setnextid(L, next); + } + LS.set(result, id); + return LS.result(); +} + +LuaDefine(idalloc_getthreadbatch, "c") { + LuaArg thread; + LuaRet result; + LuaStack LS(L, thread, result); + + lua_State *TH = LS.ckthread(thread); + lua_Integer id = lua_getnextid(TH); + LS.set(result, id); + return LS.result(); +} diff --git a/luprex/syscpp/idalloc.hpp b/luprex/syscpp/idalloc.hpp index 34a3ba04..521d4eb9 100644 --- a/luprex/syscpp/idalloc.hpp +++ b/luprex/syscpp/idalloc.hpp @@ -1,12 +1,64 @@ - +// The ID allocator. +// +// This ID allocator attempts to allocate IDs in such a way that the +// synchronous model gets the same IDs as the master model. +// +// Every logged-in player maintains a "batch queue". That's a fifo queue +// of ID batches. An ID batch is a contiguous range of IDs, containing +// between 128 and 256 contiguous IDs. The batch queue is difference +// transmitted, to ensure that the synchronous model has the same +// batches as the master model. +// +// When a player creates a thread, that thread gets an ID batch from the +// player's batch queue. When that thread allocates IDs, +// it uses the batch it was allocated. If the batch is used up, then the +// thread falls back to using a global fallback allocator. Such fallback IDs +// are not likely to be predicted correctly. +// +// When a player creates a thread, he uses up one of his batches. In the +// master model, the batch queue is 'refilled' by creating a new batch. +// In the synchronous model, the batch queue is not directly refilled, +// but the difference transmitter effectively refills it. It is imperative +// that this difference transmission happen before the player's asynchronous +// model runs out of batches, otherwise we'll get prediction failures. +// +// It is common that a thread will only use 0, 1, or 2 IDs. If a thread +// exits without using up most of its IDs, then the batch it contains is +// still a pretty usable batch. The master model will reclaim that batch, +// putting it into a global salvage pool. The salvage batches are later +// used when refilling batch queues. +// +// ID ranges are assigned as follows: +// +// 0x0000+ : reserved for future expansion. +// 0x0001+ : used by master model to allocate batches. +// 0x0010+ : used by master model to allocate unpredictable IDs. +// 0x001E+ : used by synchronous model to allocate stopgap IDs. +// +// The operations in this class are: +// +// idalloc.initmaster() - reinitialize the ID allocator in master mode. +// idalloc.initsynch() - reinitialize the ID allocator in synchronous mode. +// idalloc.setqueuefill() - override the default queue fill level. +// idalloc.refill(bq) - get batches from the global pool until the batch queue is full. +// idalloc.unqueue(bq) - push batches from the batch queue back into the global pool. +// idalloc.preparethread(bq, co) - transfer a batch from the batch queue to the coroutine +// idalloc.salvagethread(co) - push any batch in the dead coroutine back into the global pool +// +// idalloc.allocid() - get an ID using either the current thread's pool or the global pool #ifndef IDALLOC_HPP #define IDALLOC_HPP #include "luastack.hpp" -int idalloc_getnextid(lua_State *L); -int idalloc_setnextid(lua_State *L); +int idalloc_initmaster(lua_State *L); +int idalloc_initsynch(lua_State *L); +int idalloc_refill(lua_State *L); +int idalloc_unqueue(lua_State *L); +int idalloc_preparethread(lua_State *L); +int idalloc_salvagethread(lua_State *L); +int idalloc_allocid(lua_State *L); #endif // IDALLOC_HPP diff --git a/luprex/syscpp/luastack.cpp b/luprex/syscpp/luastack.cpp index 595aa9ce..f99c6431 100644 --- a/luprex/syscpp/luastack.cpp +++ b/luprex/syscpp/luastack.cpp @@ -3,6 +3,26 @@ LuaSpecial LuaRegistry(LUA_REGISTRYINDEX); LuaSpecial LuaGlobals(LUA_GLOBALSINDEX); LuaNilMarker LuaNil; +LuaNewTableMarker LuaNewTable; +LuaDiscardMarker LuaDiscard; + +LuaFunctionReg *LuaFunctionReg::LuaFunctionRegistry; + +LuaFunctionReg::LuaFunctionReg(const char *m, const char *n, lua_CFunction f) { + mode_ = m; + name_ = n; + func_ = f; + next_ = LuaFunctionRegistry; + LuaFunctionRegistry = this; +} + +LuaFunctionReg::List LuaFunctionReg::all() { + LuaFunctionReg::List result; + for (const LuaFunctionReg *r = LuaFunctionRegistry; r != 0; r = r->next_) { + result.push_back(r); + } + return result; +} void LuaStack::count_slots_finalize(int narg, int nvar, int nret) { narg_ = narg; @@ -39,33 +59,45 @@ int LuaStack::result() { return nret_; } -LuaFunctionReg *LuaFunctionReg::LuaFunctionRegistry; - -LuaFunctionReg::LuaFunctionReg(int m, const char *n, lua_CFunction f) { - mode_ = m; - name_ = n; - func_ = f; - next_ = LuaFunctionRegistry; - LuaFunctionRegistry = this; +void LuaStack::pop_any_value(std::string &s) const { + size_t len; + const char *str = luaL_cklstring(L_, -1, &len); + s = std::string(str, len); + lua_pop(L_, 1); } -LuaFunctionReg::List LuaFunctionReg::all() { - LuaFunctionReg::List result; - for (const LuaFunctionReg *r = LuaFunctionRegistry; r != 0; r = r->next_) { - result.push_back(r); +void LuaStack::pop_any_value(LuaAcceptNilNumber &s) const { + if (lua_isnil(L_, -1)) { + s.v = 0.0; + } else { + s.v = luaL_cknumber(L_, -1); } - return result; + lua_pop(L_, 1); } -std::string LuaStack::tostring(LuaSlot s) const { - size_t len; - const char *str = lua_tolstring(L_, s, &len); - return std::string(str, len); +void LuaStack::pop_any_value(LuaAcceptNilInteger &s) const { + if (lua_isnil(L_, -1)) { + s.v = 0; + } else { + s.v = luaL_ckinteger(L_, -1); + } + lua_pop(L_, 1); } -std::string LuaStack::checkstring(LuaSlot s) const { +void LuaStack::pop_any_value(LuaAcceptNilString &s) const { + if (lua_isnil(L_, -1)) { + s.v = ""; + } else { + size_t len; + const char *str = luaL_cklstring(L_, -1, &len); + s.v = std::string(str, len); + } + lua_pop(L_, 1); +} + +std::string LuaStack::ckstring(LuaSlot s) const { size_t len; - const char *str = luaL_checklstring(L_, s, &len); + const char *str = luaL_cklstring(L_, s, &len); return std::string(str, len); } @@ -79,6 +111,11 @@ void LuaStack::setmetatable(LuaSlot tab, LuaSlot mt) const { lua_setmetatable(L_, tab); } +void LuaStack::getmetatable(LuaSlot mt, LuaSlot tab) const { + lua_getmetatable(L_, tab); + lua_replace(L_, mt); +} + void LuaStack::checknometa(LuaSlot index) const { if (lua_istable(L_, index)) { if (!lua_getmetatable(L_, index)) { @@ -98,6 +135,17 @@ int LuaStack::next(LuaSlot tab, LuaSlot key, LuaSlot value) const { return ret; } +bool LuaStack::isemptytable(LuaSlot tab) const { + if (lua_istable(L_, tab)) { + lua_pushnil(L_); + if (lua_next(L_, tab) == 0) { + return true; + } + lua_pop(L_, 2); + } + return false; +} + void LuaStack::newtable(LuaSlot target) const { lua_newtable(L_); lua_replace(L_, target); diff --git a/luprex/syscpp/luastack.hpp b/luprex/syscpp/luastack.hpp index 9ceadb8e..66250d34 100644 --- a/luprex/syscpp/luastack.hpp +++ b/luprex/syscpp/luastack.hpp @@ -1,3 +1,155 @@ +///////////////////////////////////////////////////////// +// +// +// LuaStack +// +// Class LuaStack lets you create "lua local variables." These are +// variables that seem to store lua values. Class LuaStack also provides +// accessors like "rawget" that reference lua local variables instead of +// using the lua stack. +// +// Of course, this is all using the lua stack under the covers. Lua +// local variables are actually just lua stack addresses. But that's +// all kept fairly well hidden. When you use Lua local variables, and +// the accessors inside class LuaStack, it appears that you're +// manipulating data using local variables instead of using a stack. +// For people like me, that's easier to think about. +// +// Here's an example. +// +// let's say you have a function that takes two arguments +// ARG1 and ARG2, has a single return value RET1, and needs two local +// variables LOC1 and LOC2. We would declare it like this: +// +// int myfunc(lua_State *L) { +// +// LuaArg arg1, arg2; // Declare local variables to hold the arguments. +// LuaRet ret1; // Declare local variables to hold the return values. +// LuaVar loc1, loc2, loc3; // Declare local variables for other purposes. +// +// // Assign every local var a stack index. +// LuaStack LS(L, arg1, arg2, ret1, loc1, loc2, loc3); +// +// // manipulate the data in the lua local variables... +// LS.rawget(loc1, arg1, arg2); +// ... etc ... +// } +// +// Class LuaArg, LuaRet, and LuaVar are all lua local variables. +// The luastack constructor assigns each one of them a position on +// the lua stack. It also makes sure that the arguments are in +// the LuaArg variables, and it makes sure that the LuaRet values +// are the only thing left on the stack at return time. +// +// Class LuaStack provides a complete catalog of accessors +// like 'rawget' - roughly speaking, it provides equivalents to +// every major accessor in the lua API. However, the accessors +// provided by LuaStack take input and output from lua locals, not +// from the stack. For example, consider this: +// +// LS.rawget(value, tab, key); +// +// In the above, value, tab, and key should be lua local variables. +// This does a rawget on 'table', with the specified 'key', and +// stores the result in 'value'. Nothing is added to or removed +// from the lua stack. In general, none of the accessors in class +// LuaStack add anything to the stack, or pop anything from the +// stack. +// +// Class LuaStack can also do automatic type conversions. For +// example, suppose you do this: +// +// LS.rawget(value, tab, key); +// +// Nominally, you would expect value, tab, and key to be lua local +// variables. But if you pass a std::string for key, then LuaStack will +// automatically convert it. In general, class LuaStack can +// convert lua_Integer, lua_Number, std::string, bool, and LuaNil. +// +// On output, LuaStack can convert lua_Integers, lua_Numbers, and +// std::strings. In this case, strict type checking is done. If +// there is a type mismatch, a lua error is thrown. +// +// You can use the operator 'set' to assign a value to a lua local +// variable: +// +// LS.set(val1, val2); +// +// This is actually a copy operation that copies from one lua local +// variable to another. But using type conversions, it can also be +// used to assign arbitrary values to lua local variables, or to +// get values from lua local variables. +// +// Passing LuaNewTable as an input will cause a new table to be +// created before calling the specified operation. +// +// +///////////////////////////////////////////////////////// +// +// +// LuaStack type checking +// +// LuaStack contains accessors for type checking. These include: +// +// bool LuaStack::isnumber(LuaSlot s) +// bool LuaStack::isinteger(LuaSlot s) +// bool LuaStack::isstring(LuaSlot s) +// etc... +// +// And it also contains operations that throw errors: +// +// void LuaStack::checknumber(LuaSlot s) +// void LuaStack::checkinteger(LuaSlot s) +// void LuaStack::checkstring(LuaSlot s) +// etc... +// +// These are different from the lua builtins in that they are strict. +// For example, 'isnumber' only returns true if the value in the +// lua local variable is already a number. No conversions are done. +// +// These functions do checking and also conversion at the same time: +// +// lua_Integer LuaStack::ckinteger(LuaSlot s) +// lua_Number LuaStack::cknumber(LuaSlot s) +// std::string LuaStack::ckstring(LuaSlot s) +// lua_State *LuaStack::ckthread(LuaSlot s) +// +// Like the other operations, they are strict. +// +// +///////////////////////////////////////////////////////// +// +// +// LuaDefine +// +// LuaDefine is a macro that helps you define lua functions, but +// it also puts the function into a global function registry. +// You use it like so: +// +// LuaDefine(function_name, "modebits") { +// ... +// } +// +// This macroexpands into a function definition and a function +// registration. The function definition looks like this: +// +// int function_name(lua_State *L) { +// ... +// } +// +// The macro expansion generates this function definition, but it +// also generates a "registration object" whose constructor puts +// this function into a global registry of lua-callable C functions. +// This global registry is later used to inject these C functions +// into the lua intepreter. The mode is a string that contain +// the following characters: +// +// c - create a class, and put a function into it. +// f - create a global function not inside a class. +// a - autoexec - run this function automatically on model creation. +// +// +///////////////////////////////////////////////////////// #ifndef LUASTACK_HPP @@ -13,17 +165,6 @@ extern "C" { #include #include -// LuaSlot -// -// An LuaSlot contains a lua stack index. It is initialized by the -// LuaStack and contains the same index until the LuaStack -// is destroyed. You can convert an LuaSlot into a lua stack index -// by simply coercing it to 'int'. -// -// There are three variants of LuaSlot that you can use: LuaArg (function -// argument), LuaRet (function return value), and LuaVar (function local -// variable). -// class LuaSlot { protected: int index_; @@ -67,6 +208,20 @@ class LuaUpvalue : public LuaSlot { class LuaNilMarker {}; extern LuaNilMarker LuaNil; +class LuaNewTableMarker {}; +extern LuaNewTableMarker LuaNewTable; + +class LuaDiscardMarker {}; +extern LuaDiscardMarker LuaDiscard; + +struct LuaAcceptNilNumber { lua_Number v; }; +struct LuaAcceptNilInteger { lua_Integer v; }; +struct LuaAcceptNilString { std::string v; }; + +inline LuaAcceptNilNumber &LuaAcceptNil(lua_Number &x) { return *(LuaAcceptNilNumber *)(&x); } +inline LuaAcceptNilInteger &LuaAcceptNil(lua_Integer &x) { return *(LuaAcceptNilInteger *)(&x); } +inline LuaAcceptNilString &LuaAcceptNil(std::string &x) { return *(LuaAcceptNilString *)(&x); } + class LuaStack { private: int narg_; @@ -130,6 +285,75 @@ private: void clear_frame(); +private: + // Push any value on the stack, by type. + void push_any_value(LuaNewTableMarker s) const { lua_newtable(L_); } + void push_any_value(LuaNilMarker s) const { lua_pushnil(L_); } + void push_any_value(LuaSlot s) const { lua_pushvalue(L_, s); } + void push_any_value(const std::string &s) const { lua_pushlstring(L_, s.c_str(), s.size()); } + void push_any_value(const char *s) const { lua_pushstring(L_, s); } + void push_any_value(float s) const { lua_pushnumber(L_, s); } + void push_any_value(double s) const { lua_pushnumber(L_, s); } + void push_any_value(int s) const { lua_pushinteger(L_, s); } + void push_any_value(lua_Integer s) const { lua_pushinteger(L_, s); } + void push_any_value(bool b) const { lua_pushboolean(L_, b ? 1:0); } + + // Pop any value off the stack, by type. + void pop_any_value(LuaSlot &s) const { lua_replace(L_, s); } + void pop_any_value(lua_Integer &s) const { s = luaL_ckinteger(L_, -1); lua_pop(L_, 1); } + void pop_any_value(lua_Number &s) const { s = luaL_cknumber(L_, -1); lua_pop(L_, 1); } + void pop_any_value(std::string &s) const; + void pop_any_value(LuaAcceptNilNumber &s) const; + void pop_any_value(LuaAcceptNilInteger &s) const; + void pop_any_value(LuaAcceptNilString &s) const; + void pop_any_value(LuaDiscardMarker &s) const { lua_pop(L_, 1); } + + // Push multiple values on the stack, in order, by type. + template + void push_any_values(T0 arg0, T... args) { + push_any_value(arg0); + push_any_values(args...); + } + void push_any_values() { + } + + // Call the CFunction, pushing and popping arguments appropriately. + template + void call_cfunction(int otop, LuaSlot s, T... args) { + call_cfunction(otop, args...); + pop_any_value(s); + } + template + void call_cfunction(int otop, lua_Integer &s, T... args) { + call_cfunction(otop, args...); + pop_any_value(s); + } + template + void call_cfunction(int otop, lua_Number &s, T... args) { + call_cfunction(otop, args...); + pop_any_value(s); + } + template + void call_cfunction(int otop, std::string &s, T... args) { + call_cfunction(otop, args...); + pop_any_value(s); + } + template + void call_cfunction(int otop, LuaDiscardMarker &s, T... args) { + call_cfunction(otop, args...); + pop_any_value(s); + } + template + void call_cfunction(int otop, lua_CFunction fn, T... args) { + push_any_values(args...); + int nret = fn(L_); + check_nret(NRET, otop, nret); + } + + // Check number of return values: xpected number of return values, + // original stack top, and number of declared return values. + void check_nret(int xnret, int otop, int nret) const; + public: template LuaStack(lua_State *L, SS & ... stackslots) { @@ -145,100 +369,57 @@ public: ~LuaStack() {}; int result(); - -private: - // Push any value on the stack, by type. - void push_any_value(LuaNilMarker s) const { lua_pushnil(L_); } - void push_any_value(LuaSlot s) const { lua_pushvalue(L_, s); } - void push_any_value(const std::string &s) const { lua_pushlstring(L_, s.c_str(), s.size()); } - void push_any_value(const char *s) const { lua_pushstring(L_, s); } - void push_any_value(double s) const { lua_pushnumber(L_, s); } - void push_any_value(int s) const { lua_pushnumber(L_, s); } - void push_any_value(bool b) const { lua_pushboolean(L_, b ? 1:0); } - // Push multiple values on the stack, in order, by type. - template - void push_any_values(T0 arg0, T... args) { - push_any_value(arg0); - push_any_values(args...); - } - void push_any_values() { - } - -private: - // Push everything after the CFunction. Used in the implementation - // of the 'call' template function. - template - void push_after_cfunction(LuaSlot s, T... args) { - push_after_cfunction(args...); - } - template - void push_after_cfunction(lua_CFunction f, T... args) { - push_any_values(args...); - } - - // Call the CFunction, verify that the number of return values is - // correct, and pop everything before the CFunction. Used in the - // implementation of the 'call' template function. - template - void call_cfunction_and_pop(int otop, LuaSlot s, T... args) { - call_cfunction_and_pop(otop, args...); - lua_replace(L_, s); - } - template - void call_cfunction_and_pop(int otop, lua_CFunction fn, T... args) { - int nret = fn(L_); - check_nret(NRET, otop, nret); - } - - // Check number of return values: xpected number of return values, - // original stack top, and number of declared return values. - void check_nret(int xnret, int otop, int nret) const; public: - template - void set(LuaSlot target, T value) const { - push_any_value(value); - lua_replace(L_, target); - } - int type(LuaSlot s) const { return lua_type(L_, s); } + void checktype(LuaSlot s, int type) const { luaL_checktype(L_, s, type); } - bool isboolean(LuaSlot s) const { return lua_isboolean(L_, s); } - bool iscfunction(LuaSlot s) const { return lua_iscfunction(L_, s); } - bool isfunction(LuaSlot s) const { return lua_isfunction(L_, s); } - bool islightuserdata(LuaSlot s) const { return lua_islightuserdata(L_, s); } - bool isnil(LuaSlot s) const { return lua_isnil(L_, s); } - bool isnumber(LuaSlot s) const { return lua_type(L_, s) == LUA_TNUMBER; } + bool istable(LuaSlot s) const { return lua_type(L_, s) == LUA_TTABLE; } bool isstring(LuaSlot s) const { return lua_type(L_, s) == LUA_TSTRING; } - bool istable(LuaSlot s) const { return lua_istable(L_, s); } - bool isthread(LuaSlot s) const { return lua_isthread(L_, s); } - bool isuserdata(LuaSlot s) const { return lua_isuserdata(L_, s); } + bool isnumber(LuaSlot s) const { return lua_type(L_, s) == LUA_TNUMBER; } + bool isthread(LuaSlot s) const { return lua_type(L_, s) == LUA_TTHREAD; } + bool isfunction(LuaSlot s) const { return lua_type(L_, s) == LUA_TFUNCTION; } + bool isboolean(LuaSlot s) const { return lua_type(L_, s) == LUA_TBOOLEAN; } + bool isnil(LuaSlot s) const { return lua_type(L_, s) == LUA_TNIL; } - bool toboolean(LuaSlot s) const { return lua_toboolean(L_, s); } - int tointeger(LuaSlot s) const { return lua_tointeger(L_, s); } - double tonumber(LuaSlot s) const { return lua_tonumber(L_, s); } - std::string tostring(LuaSlot s) const; - lua_State *tothread(LuaSlot s) const { return lua_tothread(L_, s); } - - int checkint(LuaSlot s) const { return luaL_checkint(L_, s); } - long checklong(LuaSlot s) const { return luaL_checklong(L_, s); } - double checknumber(LuaSlot s) const { return luaL_checknumber(L_, s); } - std::string checkstring(LuaSlot s) const; - void checktype(LuaSlot s, int t) const { return luaL_checktype(L_, s, t); } + void checktable(LuaSlot index) const { checktype(index, LUA_TTABLE); } + void checkstring(LuaSlot index) const { checktype(index, LUA_TSTRING); } + void checknumber(LuaSlot index) const { checktype(index, LUA_TNUMBER); } + void checkthread(LuaSlot index) const { checktype(index, LUA_TTHREAD); } + void checkfunction(LuaSlot index) const { checktype(index, LUA_TFUNCTION); } + void checkboolean(LuaSlot index) const { checktype(index, LUA_TBOOLEAN); } + void checknil(LuaSlot index) const { checktype(index, LUA_TNIL); } + lua_Integer ckinteger(LuaSlot s) const { return luaL_ckinteger(L_, s); } + double cknumber(LuaSlot s) const { return luaL_cknumber(L_, s); } + std::string ckstring(LuaSlot s) const; + lua_State *ckthread(LuaSlot s) const { return luaL_ckthread(L_, s); } + void clearmetatable(LuaSlot tab) const; void setmetatable(LuaSlot tab, LuaSlot mt) const; + void getmetatable(LuaSlot mt, LuaSlot tab) const; void checknometa(LuaSlot index) const; void newtable(LuaSlot target) const; int next(LuaSlot tab, LuaSlot key, LuaSlot value) const; + bool isemptytable(LuaSlot s) const; - template - void rawget(LuaSlot target, LuaSlot tab, KT key) const { + bool equal(LuaSlot v1, LuaSlot v2) { + return lua_equal(L_, v1, v2); + } + + template + void set(T1 &target, T2 value) const { + push_any_value(value); + pop_any_value(target); + } + + template + void rawget(RT &target, LuaSlot tab, KT key) const { push_any_value(key); lua_rawget(L_, tab); - lua_replace(L_, target); + pop_any_value(target); } template @@ -254,25 +435,24 @@ public: lua_setfield(L_, tab, field); } - void getfield(LuaSlot target, LuaSlot tab, const char *field) const { + template + void getfield(RT &target, LuaSlot tab, const char *field) const { lua_getfield(L_, tab, field); - lua_replace(L_, target); + pop_any_value(target); } // Call invokes any C function. It pushes the arguments on the stack, // calls the cfunction, verifies that the number of return values is as // expected, and pops the return values into LuaVars. template - void call(T... args) { - int otop = lua_gettop(L_); - push_after_cfunction(args...); - call_cfunction_and_pop<0>(otop, args...); + void call(T&... args) { + call_cfunction<0>(lua_gettop(L_), args...); } }; class LuaFunctionReg { private: - int mode_; + const char *mode_; const char *name_; lua_CFunction func_; LuaFunctionReg *next_; @@ -282,20 +462,15 @@ private: public: using List = std::vector; - LuaFunctionReg(int m, const char *n, lua_CFunction f); + LuaFunctionReg(const char *mode, const char *n, lua_CFunction f); static List all(); - int get_mode() const { return mode_; } + const char *get_mode() const { return mode_; } const char *get_name() const { return name_; } lua_CFunction get_func() const { return func_; } }; -#define LuaDefineHidden(name) LuaDefineCore(name, 0) -#define LuaDefineGlobalFunction(name) LuaDefineCore(name, 1) -#define LuaDefineGlobalMethod(name) LuaDefineCore(name, 2) -#define LuaDefineClassMethod(name) LuaDefineCore(name, 3) - -#define LuaDefineCore(name, mode) \ +#define LuaDefine(name, mode) \ int name(lua_State *L); \ LuaFunctionReg reg_##name(mode, #name, name); \ int name(lua_State *L) diff --git a/luprex/syscpp/main.cpp b/luprex/syscpp/main.cpp index 17c82afc..16698f4f 100644 --- a/luprex/syscpp/main.cpp +++ b/luprex/syscpp/main.cpp @@ -15,6 +15,7 @@ #include "luastack.hpp" #include "util.hpp" #include "source.hpp" +#include "traceback.hpp" // Add another error status. @@ -61,30 +62,11 @@ static int report(lua_State *L, int status) return status; } -static int traceback(lua_State *L) +static int docall(lua_State *L, int narg, int nret) { - if (!lua_isstring(L, 1)) - { /* Non-string error object? Try metamethod. */ - if (lua_isnoneornil(L, 1) || - !luaL_callmeta(L, 1, "__tostring") || - !lua_isstring(L, -1)) - return 1; /* Return non-string error object. */ - lua_remove(L, 1); /* Replace object by result of __tostring metamethod. */ - } - luaL_traceback(L, L, lua_tostring(L, 1), 1); - return 1; -} - -static int docall(lua_State *L, int narg, int clear) -{ - int status; - int base = lua_gettop(L) - narg; /* function index */ - lua_pushcfunction(L, traceback); /* push traceback function */ - lua_insert(L, base); /* put it under chunk and args */ signal(SIGINT, laction); - status = lua_pcall(L, narg, (clear ? 0 : LUA_MULTRET), base); + int status = traceback_pcall(L, narg, nret); signal(SIGINT, SIG_DFL); - lua_remove(L, base); /* remove traceback function */ /* force a complete garbage collection in case of errors */ if (status != LUA_OK) lua_gc(L, LUA_GCCOLLECT, 0); @@ -169,7 +151,7 @@ static void dotty(lua_State *L) { int status = read_and_load(L); if (status == LUA_EOF) break; - if (status == LUA_OK) status = docall(L, 0, 0); + if (status == LUA_OK) status = docall(L, 0, LUA_MULTRET); report(L, status); if (status == LUA_OK && lua_gettop(L) > 0) { /* any result to print? */ @@ -198,12 +180,21 @@ static int pmain(lua_State *L) LUAJIT_VERSION_SYM(); /* Linker-enforced version check. */ + // Initialize the builtins, then copy a snapshot of the + // builtins to the registry. This will allow us to restore + // the builtins during source_rebuild operations. + source_load_builtins(L); + source_snapshot_builtins(L); + // Load the lua source. source_update(L); - - // Rebuild the global environment and class database. + + // Rebuild the global environment. source_rebuild(L); + // Run all the autoinit functions. + source_autoinit(L); + dotty(L); return 0; } diff --git a/luprex/syscpp/source.cpp b/luprex/syscpp/source.cpp index 60ec2e95..da070262 100644 --- a/luprex/syscpp/source.cpp +++ b/luprex/syscpp/source.cpp @@ -1,3 +1,4 @@ + #include #include #include @@ -10,32 +11,10 @@ #include "luastack.hpp" #include "table.hpp" #include "source.hpp" +#include "traceback.hpp" - -//////////////////////////////////////////////////////////// -// -// The source database is a lua table that maps filenames -// to file info. A file info is a lua table containing: -// -// name: filename as a string -// fingerprint: file modification time as human-readable string -// code: the entire contents of the source file as a string -// error: a syntax error message, or nil -// hash: 128-bit hash of the code -// closure: a lua closure, the compiled file -// sequence: the position of the file in control.lst -// -// Operations on a source DB: -// -// source.peek() -// - peek at the source database, for inspection purposes. -// source.read() -// - return a new source, reusing info from old. -// -//////////////////////////////////////////////////////////// - -LuaDefineHidden(source_updatefile) { +LuaDefine(source_updatefile, "") { LuaArg source, fn; LuaRet info; LuaVar fingerprint, null; @@ -54,11 +33,11 @@ LuaDefineHidden(source_updatefile) { // If the file modification is wrong, update // these fields: code, fingerprint, closure, error // Otherwise, update nothing. - std::string cfn = LS.tostring(fn); + std::string cfn = LS.ckstring(fn); LS.getfield(fingerprint, info, "fingerprint"); std::string old_fingerprint; if (LS.isstring(fingerprint)) { - old_fingerprint = LS.tostring(fingerprint); + old_fingerprint = LS.ckstring(fingerprint); } std::cerr << "Probing " << cfn << std::endl; std::string new_fingerprint = util::get_file_fingerprint("syslua/" + cfn); @@ -80,7 +59,7 @@ LuaDefineHidden(source_updatefile) { return LS.result(); } -LuaDefineHidden(source_update) { +LuaDefine(source_update, "c") { LuaVar sourcedb, newdb, info, fn, seq; LuaStack LS(L, newdb, sourcedb, info, fn, seq); @@ -116,98 +95,158 @@ LuaDefineHidden(source_update) { return LS.result(); } -// Get a class from the class database. +// Make a class. A class is a table in the global environment. // -// CLASSNAME -// if classname is already present, and is a table, return it. -// if classname is already present, and not a table, error. -// if classname is not present, create and initialize it. +// The global environment is protected, but this function can +// override that protection. // -LuaDefineGlobalFunction(source_class) { +LuaDefine(source_makeclass, "f") { LuaArg classname; + LuaVar action, gname; LuaRet classtab; - LuaVar classdb, action; - LuaStack LS(L, classname, classtab, classdb, action); + LuaStack LS(L, classname, classtab, action, gname); - LS.checktype(classname, LUA_TSTRING); + LS.checkstring(classname); - // Get a pointer to the classdb. - LS.getfield(classdb, LuaRegistry, "classdb"); - if (!LS.istable(classdb)) { - LS.newtable(classdb); - LS.setfield(LuaRegistry, "classdb", classdb); - } - - // Get the classtab from the classdb, sanity check it. - LS.rawget(classtab, classdb, classname); - if (LS.istable(classtab)) { + // Special case: if the classname is _G, return global env. + LS.set(gname, "_G"); + if (LS.equal(classname, gname)) { + LS.set(classtab, LuaGlobals); return LS.result(); - } else if (!LS.isnil(classtab)) { - luaL_error(L, "%s is not a class", LS.tostring(classname).c_str()); + } + + // Get the classtab from the global environment. + // Create it if it doesn't exist. + LS.rawget(classtab, LuaGlobals, classname); + if (LS.isnil(classtab)) { + LS.newtable(classtab); + LS.rawset(LuaGlobals, classname, classtab); } - // Create a new classtab and store it in the classdb. - LS.newtable(classtab); - LS.rawset(classdb, classname, classtab); + // If the name isn't bound to a table, abort. + if (!LS.istable(classtab)) { + luaL_error(L, "%s is not a class", LS.ckstring(classname).c_str()); + } + + // Repair the special fields. LS.setfield(classtab, "__index", classtab); LS.setfield(classtab, "__class", classname); - LS.newtable(action); - LS.setfield(classtab, "action", action); + + // Repair the action table. + LS.getfield(action, classtab, "action"); + if (!LS.istable(action)) { + LS.setfield(classtab, "action", LuaNewTable); + } + return LS.result(); } -// Reset a class database: +// Clear the global environment. // -// Clear out all classes. Instead of replacing the class tables, -// it simply deletes all keys. That way, if somebody has a pointer -// to a class, the pointer is not invalidated. +// Clears out almost everything from the global +// environment. However, does not delete class tables. // -LuaDefineHidden(source_reset_classes) { - LuaVar classdb, classname, classtab, action, key; - LuaStack LS(L, classname, classtab, classdb, action, key); +// This is used during source_rebuild operations. +// +LuaDefine(source_clear_globals, "") { + LuaVar classname, classtab, action, key; + LuaStack LS(L, classname, classtab, action, key); - // Get a pointer to the classdb. - LS.getfield(classdb, LuaRegistry, "classdb"); - if (!LS.istable(classdb)) { - LS.newtable(classdb); - LS.setfield(LuaRegistry, "classdb", classdb); - } - - // Iterate over the classdb, clearing it. + LS.setfield(LuaGlobals, "_G", LuaNil); LS.set(classname, LuaNil); - while (LS.next(classdb, classname, classtab) != 0) { + while (LS.next(LuaGlobals, classname, classtab) != 0) { if (LS.istable(classtab)) { - LS.set(key, "action"); - LS.rawget(action, classtab, key); + LS.getfield(action, classtab, "action"); if (LS.istable(action)) { LS.call(table_clear, action); } else { LS.newtable(action); } LS.call(table_clear, classtab); - LS.setfield(classtab, "__index", classtab); - LS.setfield(classtab, "__class", classname); - LS.setfield(classtab, "action", action); + } else { + LS.rawset(LuaGlobals, classname, LuaNil); + } + } + LS.setfield(LuaGlobals, "_G", LuaGlobals); + return LS.result(); +} + +// Restore the lua builtins from the backup snapshot. +// +// The global environment is expected to be clean, but +// the subtables are expected to still be present, in +// accordance with how 'source_clear_globals' works. +// +LuaDefine(source_restore_builtins, "g") { + LuaVar snapshot, key, value, skey, svalue, subglobal; + LuaStack LS(L, snapshot, key, value, skey, svalue, subglobal); + + LS.getfield(snapshot, LuaRegistry, "source_snapshot_builtins"); + LS.setfield(LuaGlobals, "_G", LuaGlobals); + LS.set(key, LuaNil); + while (LS.next(snapshot, key, value) != 0) { + LS.checktable(value); + LS.call(subglobal, source_makeclass, key); + LS.set(skey, LuaNil); + while (LS.next(value, skey, svalue) != 0) { + LS.rawset(subglobal, skey, svalue); } } return LS.result(); } -LuaDefineHidden(source_load_builtins) { - luaopen_base(L); - luaopen_table(L); - luaopen_string(L); - luaopen_math(L); - luaopen_bit(L); - // luaopen_package(L); // Omitted because we use our own package system. - // luaopen_io(L); // Not safe for the sandbox. - // luaopen_os(L); // Not safe for the sandbox. - // luaopen_debug(L); // Not safe for the sandbox. - // luaopen_jit(L); // Don't know what it's for. +// Snapshot all the lua builtin functions. +// +// Precondition: this is meant to be used on a pristine lua +// intepreter, before the user dumps a bunch of crap into the +// global environment. It won't work if the global environment +// contains anything other than the lua builtins. +// +// Note: the global environment contains _G. This routine +// perceives this as a subtable, so it backs up the top level +// as well. +// +LuaDefine(source_snapshot_builtins, "c") { + LuaVar key, value, skey, svalue, snapshot, ssnapshot; + LuaStack LS(L, snapshot, key, value, skey, svalue, ssnapshot); + + LS.newtable(snapshot); + LS.set(key, LuaNil); + while (LS.next(LuaGlobals, key, value) != 0) { + if (LS.istable(value)) { + LS.newtable(ssnapshot); + LS.rawset(snapshot, key, ssnapshot); + LS.set(skey, LuaNil); + while (LS.next(value, skey, svalue) != 0) { + if (LS.isfunction(svalue) || LS.isstring(svalue) || LS.isnumber(svalue)) { + LS.rawset(ssnapshot, skey, svalue); + } + } + } + } + LS.setfield(LuaRegistry, "source_snapshot_builtins", snapshot); + return LS.result(); +} + +static void load_builtin(lua_State *L, const char *name, lua_CFunction func) { + lua_pushcfunction(L, func); + lua_pushstring(L, name); + lua_call(L, 1, 0); +} + +LuaDefine(source_load_builtins, "") { + LuaStack LS(L); + load_builtin(L, "base", luaopen_base); + load_builtin(L, "table", luaopen_table); + load_builtin(L, "string", luaopen_string); + load_builtin(L, "math", luaopen_math); + load_builtin(L, "bit", luaopen_math); + load_builtin(L, "debug", luaopen_debug); + // Do not load: package, io, os, debug, jit return 0; } -LuaDefineHidden(source_load_cfunctions) { +LuaDefine(source_load_cfunctions, "") { auto regs = LuaFunctionReg::all(); for (const LuaFunctionReg *r : regs) { const std::string &name = r->get_name(); @@ -221,27 +260,14 @@ LuaDefineHidden(source_load_cfunctions) { classname = name.substr(0, upos); } lua_CFunction func = r->get_func(); - int mode = r->get_mode(); - if (mode == 3) { // Class Method + std::string mode = r->get_mode(); + if (mode.find('c') != std::string::npos) { // Insert into class lua_pushlstring(L, classname.c_str(), classname.size()); - source_class(L); + source_makeclass(L); lua_pushcfunction(L, func); lua_setfield(L, -2, funcname.c_str()); } - if ((mode == 1) || (mode == 2)) { // Global function or global method - int top = lua_gettop(L); - lua_getglobal(L, classname.c_str()); - if (!lua_istable(L, -1)) { - lua_pop(L, 1); - lua_newtable(L); - lua_pushvalue(L, -1); - lua_setglobal(L, classname.c_str()); - } - lua_pushcfunction(L, func); - lua_setfield(L, -2, funcname.c_str()); - lua_settop(L, top); - } - if (mode == 1) { // Global function + if (mode.find('f') != std::string::npos) { // Make global function lua_pushcfunction(L, func); lua_setglobal(L, funcname.c_str()); } @@ -254,7 +280,7 @@ LuaDefineHidden(source_load_cfunctions) { // Returns a single string, which is a bunch of concatenated error // messages. // -LuaDefineHidden(source_load_lfunctions) { +LuaDefine(source_load_lfunctions, "") { LuaRet errors; LuaVar sourcedb, key, info, seq, closure, err; LuaStack LS(L, sourcedb, errors, key, info, seq, closure, err); @@ -271,7 +297,7 @@ LuaDefineHidden(source_load_lfunctions) { LS.set(key, LuaNil); while (LS.next(sourcedb, key, info) != 0) { LS.getfield(seq, info, "sequence"); - indices[LS.tointeger(seq)] = LS.tostring(key); + indices[LS.ckinteger(seq)] = LS.ckstring(key); } // Now call the closures in the proper order. @@ -282,31 +308,41 @@ LuaDefineHidden(source_load_lfunctions) { // If there's already an error in the sourcedb, collect it. if (!LS.isfunction(closure)) { - errss << LS.tostring(closure); + errss << LS.ckstring(closure) << "\n"; continue; } // Call the closure. If there's an error, collect it. lua_pushvalue(L, closure.index()); - if (lua_pcall(L, 0, 0, 0) != 0) { + if (traceback_pcall(L, 0, 0) != 0) { lua_replace(L, err.index()); - errss << LS.tostring(err); + errss << LS.ckstring(err); } } LS.set(errors, errss.str()); return LS.result(); } -LuaDefineGlobalMethod(source_rebuild) { +LuaDefine(source_rebuild, "c") { LuaVar errs; LuaStack LS(L, errs); - source_reset_classes(L); - source_load_builtins(L); + source_clear_globals(L); + source_restore_builtins(L); source_load_cfunctions(L); source_load_lfunctions(L); lua_replace(L, errs.index()); - std::string errstr = LS.tostring(errs); + std::string errstr = LS.ckstring(errs); std::cerr << errstr; return LS.result(); } +LuaDefine(source_autoinit, "") { + auto regs = LuaFunctionReg::all(); + for (const LuaFunctionReg *r : regs) { + std::string mode = r->get_mode(); + if (mode.find('a') != std::string::npos) { + r->get_func()(L); + } + } + return 0; +} diff --git a/luprex/syscpp/source.hpp b/luprex/syscpp/source.hpp index 5b106f21..de72ace2 100644 --- a/luprex/syscpp/source.hpp +++ b/luprex/syscpp/source.hpp @@ -1,16 +1,26 @@ -// ClassDB - code to manipulate class databases. +//////////////////////////////////////////////////////////// // -// It would have been easier to write this in Lua, but since every -// lua module in the system depends on it, it's safer to have it -// preloaded before we even open any of the lua files. // +// The source database is a lua table that maps filenames +// to file info. The source database is stored in the registry. +// +// In the source database, the keys are filenames, and the values +// are tables containing the following fields: +// +// name: filename as a string +// fingerprint: file modification, file length, as a string +// code: the entire contents of the source file as a string +// loadresult: a lua closure, or, an error message +// sequence: the position of the file in control.lst +// +// +//////////////////////////////////////////////////////////// + #ifndef SOURCE_HPP #define SOURCE_HPP #include "luastack.hpp" -#include - // Get a class from the class database. int source_class(lua_State *L); @@ -18,9 +28,18 @@ int source_class(lua_State *L); // Update the source database from disk. No parameters, no return values. int source_update(lua_State *L); +// Load the builtins into the global environment using lua_openlibs +int source_load_builtins(lua_State *L); + +// Back up the pristine global environment to the registry. +int source_snapshot_builtins(lua_State *L); + // Rebuild the class database from the source database. No parameters, no return values. int source_rebuild(lua_State *L); +// Run all 'autoinit' functions. +int source_autoinit(lua_State *L); + #endif // SOURCE_HPP diff --git a/luprex/syscpp/table.cpp b/luprex/syscpp/table.cpp index 46f7a6a1..73be7213 100644 --- a/luprex/syscpp/table.cpp +++ b/luprex/syscpp/table.cpp @@ -1,13 +1,131 @@ #include "table.hpp" #include "source.hpp" -// Clear the table. Removes metatable and all key-value pairs. -LuaDefineGlobalMethod(table_clear) { +LuaDefine(table_equal, "c") { + LuaArg t1, t2; + LuaRet eql; + LuaStack LS(L, t1, t2, eql); + LS.checktable(t1); + LS.checktable(t2); + lua_pushnil(L); + int total1 = 0; + while (lua_next(L, t1.index()) != 0) { + lua_pushvalue(L, -2); // k v1 k + lua_rawget(L, t2.index()); // k v1 v2 + if (!lua_equal(L, -1, -2)) { + LS.set(eql, false); + return LS.result(); + } + lua_pop(L, 2); + total1 += 1; + } + int total2 = 0; + lua_pushnil(L); + while (lua_next(L, t2.index()) != 0) { + lua_pop(L, 1); + total2 += 1; + } + LS.set(eql, total1 == total2); + return LS.result(); +} + +LuaDefine(table_append, "c") { + luaL_checktype(L, -2, LUA_TTABLE); + int len = lua_objlen(L, -2); + lua_pushinteger(L, len+1); + lua_pushvalue(L, -2); + lua_rawset(L, -4); + lua_pop(L, 2); + return 0; +} + +LuaDefine(table_findremove, "c") { + luaL_checktype(L, -2, LUA_TTABLE); + int src = 1; + int dst = 1; + while (true) { + lua_pushinteger(L, src); + lua_rawget(L, -3); + if (lua_equal(L, -1, -2)) { + src++; + lua_pop(L, 1); + } else if (lua_isnil(L, -1)) { + lua_pop(L, 1); + int removed = src - dst; + while (src > dst) { + lua_pushinteger(L, dst); + lua_pushnil(L); + lua_rawset(L, -4); + dst++; + } + lua_pop(L, 2); + lua_pushinteger(L, removed); + return 1; + } else { + if (src > dst) { + lua_pushinteger(L, dst); + lua_insert(L, lua_gettop(L) - 1); + lua_rawset(L, -4); + } else { + lua_pop(L, 1); + } + src++; + dst++; + } + } +} + +LuaDefine(table_find, "c") { + luaL_checktype(L, -2, LUA_TTABLE); + for (int i = 1; ; i++) { + lua_pushinteger(L, i); + lua_rawget(L, -3); + if (lua_equal(L, -1, -2)) { + lua_pop(L, 3); + lua_pushinteger(L, i); + return 1; + } else if (lua_isnil(L, -1)) { + lua_pop(L, 3); + lua_pushnil(L); + return 1; + } else { + lua_pop(L, 1); + } + } +} + +LuaDefine(table_empty, "c") { + luaL_checktype(L, -1, LUA_TTABLE); + lua_pushnil(L); + if (lua_next(L, -2) != 0) { + lua_pop(L, 3); + lua_pushboolean(L, 0); + return 1; + } else { + lua_pop(L, 1); + lua_pushboolean(L, 1); + return 1; + } +} + +LuaDefine(table_count, "c") { + luaL_checktype(L, -1, LUA_TTABLE); + lua_pushnil(L); + lua_Integer total = 0; + while (lua_next(L, -2) != 0) { + total += 1; + lua_pop(L, 1); + } + lua_pop(L, 1); + lua_pushinteger(L, total); + return 1; +} + +LuaDefine(table_clear, "c") { LuaArg tab; LuaStack LS(L, tab); - LS.checktype(tab, LUA_TTABLE); - + LS.checktable(tab); LS.clearmetatable(tab); lua_pushnil(L); @@ -21,7 +139,7 @@ LuaDefineGlobalMethod(table_clear) { return LS.result(); } -LuaDefineGlobalMethod(table_coerce) { +LuaDefine(table_coerce, "c") { if (!lua_istable(L, -1)) { lua_pop(L, 1); lua_newtable(L); @@ -29,3 +147,77 @@ LuaDefineGlobalMethod(table_coerce) { return 1; } +LuaDefine(queue_create, "c") { + LuaRet queue; + LuaStack LS(L, queue); + + LS.newtable(queue); + LS.setfield(queue, "head", 1000000); + LS.setfield(queue, "tail", 1000000); + return LS.result(); +} + +LuaDefine(queue_push, "c") { + LuaArg queue, elt; + lua_Integer head; + LuaStack LS(L, queue, elt); + + LS.getfield(head, queue, "head"); + LS.rawset(queue, head, elt); + LS.setfield(queue, "head", head+1); + return LS.result(); +} + +LuaDefine(queue_pop, "c") { + LuaArg queue; + LuaRet elt; + lua_Integer head, tail; + LuaStack LS(L, queue, elt); + + LS.getfield(tail, queue, "tail"); + LS.getfield(head, queue, "head"); + if (head == tail) { + LS.set(elt, LuaNil); + } else { + LS.rawget(elt, queue, tail); + LS.rawset(queue, tail, LuaNil); + LS.setfield(queue, "tail", tail + 1); + } + return LS.result(); +} + +LuaDefine(queue_size, "c") { + LuaArg queue; + LuaRet size; + lua_Number head, tail; + LuaStack LS(L, queue, size); + + LS.getfield(head, queue, "head"); + LS.getfield(tail, queue, "tail"); + LS.set(size, head - tail); + return LS.result(); +} + +LuaDefine(queue_nth, "c") { + LuaArg queue, n; + LuaRet elt; + lua_Integer nth, head, tail; + LuaStack LS(L, queue, n, elt); + + nth = LS.ckinteger(n) - 1; + LS.getfield(head, queue, "head"); + LS.getfield(tail, queue, "tail"); + if ((nth < 0) || (nth + tail >= head)) { + luaL_error(L, "index out of range"); + } + LS.rawget(elt, queue, tail + nth); + return LS.result(); +} + +LuaDefine(table_getregistry, "f") { + LuaArg key; + LuaRet result; + LuaStack LS(L, key, result); + LS.rawget(result, LuaRegistry, key); + return LS.result(); +} diff --git a/luprex/syscpp/table.hpp b/luprex/syscpp/table.hpp index a8a36bf9..0654b720 100644 --- a/luprex/syscpp/table.hpp +++ b/luprex/syscpp/table.hpp @@ -3,11 +3,18 @@ #include "luastack.hpp" -// Clear a table. Takes the table as a parameter. +int table_equal(lua_State *L); +int table_findremove(lua_State *L); +int table_append(lua_State *L); +int table_find(lua_State *L); +int table_empty(lua_State *L); +int table_count(lua_State *L); int table_clear(lua_State *L); - -// Takes an object O. If O is a table, returns it, otherwise -// returns a new table. int table_coerce(lua_State *L); +int queue_create(lua_State *L); +int queue_push(lua_State *L); +int queue_pop(lua_State *L); +int queue_size(lua_State *L); +int queue_nth(lua_State *L); #endif // TABLE_HPP diff --git a/luprex/syscpp/traceback.cpp b/luprex/syscpp/traceback.cpp new file mode 100644 index 00000000..74279c94 --- /dev/null +++ b/luprex/syscpp/traceback.cpp @@ -0,0 +1,89 @@ +#include "traceback.hpp" + +#define TRACEBACK_LEVELS1 12 +#define TRACEBACK_LEVELS2 10 + + +static int traceback_general(lua_State *L, lua_State *L1, int msgindex) { + int top = lua_gettop(L); + + // Convert message to a string and push the string. + if (lua_tostring(L, msgindex)) { + lua_pushvalue(L, msgindex); + } else { + luaL_callmeta(L, msgindex, "__tostring"); + } + + // If we didn't end up with exactly one string on + // the stack, then clear the stack and push 'unknown error'. + if ((lua_gettop(L) != top + 1) || (!lua_tostring(L, -1))) { + lua_settop(L, top); + lua_pushstring(L, "unknown error"); + } + + // Append the traceback. + lua_Debug ar; + int level = 1; + int firstpart = 1; + while (lua_getstack(L1, level++, &ar)) { + if (level > TRACEBACK_LEVELS1 && firstpart) { + /* no more than `LEVELS2' more levels? */ + if (!lua_getstack(L1, level + TRACEBACK_LEVELS2, &ar)) + level--; /* keep going */ + else { + lua_pushliteral(L, "\n\t..."); /* too many levels */ + while (lua_getstack(L1, level + TRACEBACK_LEVELS2, &ar)) /* find last levels */ + level++; + } + firstpart = 0; + continue; + } + lua_getinfo(L1, "Snl", &ar); + if ((ar.currentline > 0) || (*ar.namewhat != 0) || (*ar.what != 'C')) { + lua_pushliteral(L, "\n\t"); + lua_pushfstring(L, "%s:", ar.short_src); + if (ar.currentline > 0) + lua_pushfstring(L, "%d:", ar.currentline); + if (*ar.namewhat != '\0') /* is there a name? */ + lua_pushfstring(L, " in function " LUA_QS, ar.name); + else { + if (*ar.what == 'm') /* main? */ + lua_pushfstring(L, " in main chunk"); + else if (*ar.what == 'C' || *ar.what == 't') + lua_pushliteral(L, " ?"); /* C function or tail call */ + else + lua_pushfstring(L, " in function <%s:%d>", + ar.short_src, ar.linedefined); + } + if (lua_gettop(L) - top > 5) { + lua_concat(L, lua_gettop(L) - top); + } + } + } + lua_pushstring(L, "\n"); + if (lua_gettop(L) - top > 1) { + lua_concat(L, lua_gettop(L) - top); + } + return 1; +} + +LuaDefine(traceback_handler, "c") { + return traceback_general(L, L, lua_gettop(L)); +} + +LuaDefine(traceback_coroutine, "c") { + LuaArg thread, message; + LuaStack LS(L, thread, message); + lua_State *L1 = LS.ckthread(thread); + return traceback_general(L, L1, message.index()); +} + +int traceback_pcall(lua_State *L, int narg, int nret) { + int status; + int base = lua_gettop(L) - narg; /* function index */ + lua_pushcfunction(L, traceback_handler); /* push traceback function */ + lua_insert(L, base); /* put it under chunk and args */ + status = lua_pcall(L, narg, nret, base); + lua_remove(L, base); /* remove traceback function */ + return status; +} diff --git a/luprex/syscpp/traceback.hpp b/luprex/syscpp/traceback.hpp new file mode 100644 index 00000000..d6ce6e1a --- /dev/null +++ b/luprex/syscpp/traceback.hpp @@ -0,0 +1,27 @@ +///////////////////////////////////////////////////////////////// +// +// +// Traceback routines. +// +// traceback_handler: a traceback routine meant to be used +// as a message handler routine for 'pcall'. +// +// traceback_coroutine: takes a coroutine and an error message. +// Returns a traceback of the coroutine. +// +// traceback_pcall: same as lua_pcall, except that it supplies +// the default traceback_handler as a message handler. +// +// +///////////////////////////////////////////////////////////////// + +#ifndef TRACEBACK_HPP +#define TRACEBACK_HPP + +#include "luastack.hpp" + +int traceback_coroutine(lua_State *L); +int traceback_handler(lua_State *L); +int traceback_pcall(lua_State *L, int narg, int nret); + +#endif // TRACEBACK_HPP diff --git a/luprex/syslua/control.lst b/luprex/syslua/control.lst index 19bb28db..392d9a30 100644 --- a/luprex/syslua/control.lst +++ b/luprex/syslua/control.lst @@ -3,7 +3,10 @@ # in the order that they're supposed to be loaded. # +utils.lua inspect.lua - - +ut-table.lua +ut-idalloc.lua +ut-globaldb.lua +ut-cellgrid.lua diff --git a/luprex/syslua/inspect.lua b/luprex/syslua/inspect.lua index 169994f1..3c94fc3c 100644 --- a/luprex/syslua/inspect.lua +++ b/luprex/syslua/inspect.lua @@ -341,4 +341,4 @@ function inspect.pprint(...) end end -_G.pprint = inspect.pprint +pprint = inspect.pprint diff --git a/luprex/syslua/ut-cellgrid.lua b/luprex/syslua/ut-cellgrid.lua new file mode 100644 index 00000000..57f4b147 --- /dev/null +++ b/luprex/syslua/ut-cellgrid.lua @@ -0,0 +1,12 @@ +local ut = {} + +function ut.cellid() + local sc = cellgrid.scale() + local cid = cellgrid.cellid(0,0,0) + assert(cellgrid.cellid(0,0,0) == 0x0001000000000000) + assert(cellgrid.cellid( 1*sc, 2*sc, 3*sc) == 0x0001000100020003) + assert(cellgrid.cellid(-1*sc, -2*sc, -3*sc) == 0x0001FFFFFFFEFFFD) + assert(cellgrid.cellid(10000000, 0, 0) == 0) +end + +rununittests(ut) diff --git a/luprex/syslua/ut-globaldb.lua b/luprex/syslua/ut-globaldb.lua new file mode 100644 index 00000000..8d5c1ae5 --- /dev/null +++ b/luprex/syslua/ut-globaldb.lua @@ -0,0 +1,15 @@ + +local ut = {} + +function ut.globaldb() + local g1a = global("unittest-g1") + local g2a = global("unittest-g2") + local g1b = global("unittest-g1") + local g2b = global("unittest-g2") + assert(g1a == g1b) + assert(g2a == g2b) + assert(g1a.__global == "unittest-g1") + assert(g2a.__global == "unittest-g2") +end + +rununittests(ut) diff --git a/luprex/syslua/ut-idalloc.lua b/luprex/syslua/ut-idalloc.lua new file mode 100644 index 00000000..b6bd44fc --- /dev/null +++ b/luprex/syslua/ut-idalloc.lua @@ -0,0 +1,95 @@ + +local ut = {} + +local function nthbatch(i) + return 0x0001000000000000 + 256 * (i - 1) +end + +local function equalbatches(q, batches) + if queue.size(q) ~= #batches then + return false + end + for i = 1, #batches do + if queue.nth(q, i) ~= nthbatch(batches[i]) then + return false + end + end + return true +end + +function ut.idalloc_synch() + local allocator = {} + idalloc.initsynch(allocator, 10) + assert(allocator.id_salvaged == nil) + assert(allocator.id_nextbatch == nil) + assert(allocator.id_nextid == 0x001E000000000000) + assert(allocator.id_queuefill == 10) + + local q1 = queue.create() + idalloc.refill(allocator, q1) + assert(queue.size(q1) == 0) +end + +function ut.idalloc_master() + local allocator = {} + idalloc.initmaster(allocator, 3) + assert(queue.size(allocator.id_salvaged) == 0) + assert(allocator.id_nextbatch == nthbatch(1)) + assert(allocator.id_nextid == 0x0010000000000000) + assert(allocator.id_queuefill == 3) + + local q1 = queue.create() + local q2 = queue.create() + local q3 = queue.create() + local salv = allocator.id_salvaged + + idalloc.refill(allocator, q1) + idalloc.refill(allocator, q2) + assert(equalbatches(q1, { 1, 2, 3 })) + assert(equalbatches(q2, { 4, 5, 6 })) + idalloc.unqueue(allocator, q1) + assert(equalbatches(salv, { 1, 2, 3 })) + queue.pop(q2) + assert(equalbatches(q2, { 5, 6 })) + idalloc.refill(allocator, q2) + assert(equalbatches(q2, { 5, 6, 1 })) + assert(equalbatches(salv, { 2, 3 })) + idalloc.refill(allocator, q3) + assert(equalbatches(q3, { 2, 3, 7 })) +end + +function ut.idalloc_thread() + local allocator = {} + idalloc.initmaster(allocator, 3) + local salv = allocator.id_salvaged + local q = queue.create() + idalloc.refill(allocator, q) + + local id = 0 + local function useids() + for i = 1,10000 do + id = idalloc.allocid(allocator) + coroutine.yield() + end + end + + local co = coroutine.create(useids) + coroutine.resume(co) + assert(id == 0x0010000000000000) + coroutine.resume(co) + assert(id == 0x0010000000000001) + idalloc.preparethread(q, co) + coroutine.resume(co) + assert(id == nthbatch(1) + 0) + coroutine.resume(co) + assert(id == nthbatch(1) + 1) + idalloc.salvagethread(allocator, co) + coroutine.resume(co) + assert(id == 0x0010000000000002) + + assert(queue.size(salv) == 1) + assert(queue.nth(salv, 1) == nthbatch(1) + 2) +end + +rununittests(ut) + diff --git a/luprex/syslua/ut-table.lua b/luprex/syslua/ut-table.lua new file mode 100644 index 00000000..09c6c077 --- /dev/null +++ b/luprex/syslua/ut-table.lua @@ -0,0 +1,96 @@ + +local ut = {} + +function ut.table_count() + assert(table.count({}) == 0) + assert(table.count({a=1,b=2}) == 2) + assert(table.count({[2]=5,[5]=3}) == 2) +end + +function ut.table_coerce() + local t = {} + local t1 = table.coerce(t) + assert(t1==t) + t1 = table.coerce(0) + assert(type(t1) == "table") + t1 = table.coerce(nil) + assert(type(t1) == "table") +end + +function ut.table_clear() + local t = { a = 1, b = 2 } + table.clear(t) + assert(t.a == nil) + assert(t.b == nil) + assert(table.count(t) == 0) + setmetatable(t, t) + table.clear(t) + assert(getmetatable(t) == nil) +end + +function ut.table_empty() + assert(table.empty({}) == true) + assert(table.empty({1}) == false) + assert(table.empty({a=1}) == false) +end + +function ut.table_equal() + assert(table.equal({},{})) + assert(not table.equal({}, {1})) + assert(not table.equal({1}, {})) + assert(table.equal({1,2,3}, {1,2,3})) + assert(not table.equal({1,2,3}, {1,5,3})) + assert(not table.equal({1,2}, {1,2,3})) + assert(not table.equal({1,2,3}, {1,2})) + assert(table.equal({a=1,b=2},{a=1,b=2})) + assert(not table.equal({a=1,b=3},{a=1,b=2})) +end + +function ut.table_append() + t = {} + table.append(t, 1) + assert(table.equal(t, {1})) + table.append(t, 2) + assert(table.equal(t, {1,2})) + table.append(t, 3) + assert(table.equal(t, {1,2,3})) +end + +function ut.table_findremove() + t = {1,2,3,4,5,1,2,3,4,5} + table.findremove(t, 2) + assert(table.equal(t, {1,3,4,5,1,3,4,5})) + table.findremove(t, 5) + assert(table.equal(t, {1,3,4,1,3,4})) + table.findremove(t, 1) + assert(table.equal(t, {3,4,3,4})) +end + +function ut.queues() + local q = queue.create() + assert(q.head == 1000000) + assert(q.tail == 1000000) + assert(queue.size(q) == 0) + assert(table.count(q) == 2) + queue.push(q, 27) + assert(queue.size(q) == 1) + queue.push(q, 45) + assert(queue.nth(q, 1) == 27) + assert(queue.nth(q, 2) == 45) + assert(queue.size(q) == 2) + assert(table.count(q) == 4) + assert(queue.pop(q) == 27) + assert(queue.size(q) == 1) + assert(table.count(q) == 3) + assert(queue.pop(q) == 45) + assert(queue.size(q) == 0) + assert(table.count(q) == 2) + assert(queue.pop(q) == nil) + assert(table.count(q) == 2) + assert(q.head == 1000002) + assert(q.tail == 1000002) + assert(queue.size(q) == 0) +end + +rununittests(ut) + diff --git a/luprex/syslua/utils.lua b/luprex/syslua/utils.lua new file mode 100644 index 00000000..8ca5c813 --- /dev/null +++ b/luprex/syslua/utils.lua @@ -0,0 +1,20 @@ + + +function rununittests(tab) + for k, v in pairs(tab) do + v() + end +end + +function crash2() + local tab = nil + tab[3] = 1 +end + +function crash1() + crash2() +end + +function doyield() + coroutine.yield() +end