diff --git a/luprex/build.bat b/luprex/build.bat index e1f9d63f..48836a16 100644 --- a/luprex/build.bat +++ b/luprex/build.bat @@ -1 +1 @@ -g++ -std=c++17 -g -o main syscpp/util.cpp syscpp/main.cpp syscpp/lpx-table.cpp syscpp/lpx-classdb.cpp -Iinc -Llib lib/libluajit-dbg.a -Isyscpp +g++ -std=c++17 -g -o main syscpp/util.cpp syscpp/main.cpp syscpp/luastack.cpp syscpp/source.cpp syscpp/table.cpp syscpp/globaldb.cpp -Iinc -Llib lib/libluajit-dbg.a -Isyscpp diff --git a/luprex/syscpp/globaldb.cpp b/luprex/syscpp/globaldb.cpp new file mode 100644 index 00000000..ef12b476 --- /dev/null +++ b/luprex/syscpp/globaldb.cpp @@ -0,0 +1,43 @@ +#include "globaldb.hpp" +#include "table.hpp" + +// Get a table from the global database. +// +// GLOBALNAME +// if globalname is already present, and is a table, return it. +// if globalname is already present, and not a table, error. +// if globalname is not present, create and initialize it. +// +int lpx_globaldb_global(lua_State *L) { + LuaArg globalname; + LuaRet globaltab; + LuaVar globaldb; + LuaStack LS(L, globalname, globaltab, globaldb); + + LS.checktype(globalname, LUA_TSTRING); + + // Get a pointer to the globaldb. + LS.getfield(globaldb, LUA_REGISTRYINDEX, "globaldb"); + if (!LS.istable(globaldb)) { + LS.newtable(globaldb); + LS.setfield(LUA_REGISTRYINDEX, "globaldb", globaldb); + } + + // Get the globaltab from the globaldb, sanity check it. + LS.rawget(globaltab, globaldb, globalname); + 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()); + } + + // Create a new globaltab and store it in the globaldb. + LS.newtable(globaltab); + LS.rawset(globaldb, globalname, globaltab); + LS.setfield(globaltab, "__global", globalname); + return LS.result(); +} + +void luaopen_lpx_globaldb(lua_State *L) { + LuaStack::reg(L, 0, "global", LuaArgCheck<1, lpx_globaldb_global>); +} diff --git a/luprex/syscpp/globaldb.hpp b/luprex/syscpp/globaldb.hpp new file mode 100644 index 00000000..6d37f194 --- /dev/null +++ b/luprex/syscpp/globaldb.hpp @@ -0,0 +1,19 @@ +// 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. +// + +#ifndef GLOBALDB_HPP +#define GLOBALDB_HPP + +#include "luastack.hpp" + +int lpx_globaldb_global(lua_State *L); + +void luaopen_lpx_globaldb(lua_State *L); + +#endif // GLOBALDB_HPP + + diff --git a/luprex/syscpp/lpx-classdb.cpp b/luprex/syscpp/lpx-classdb.cpp deleted file mode 100644 index fade9951..00000000 --- a/luprex/syscpp/lpx-classdb.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "lpx-classdb.hpp" -#include "lpx-table.hpp" - -// Get an entry from the class database. -// -// DB, 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. -// -int lpx_classdb_get(lua_State *L) { - LpxArg db, classname; - LpxRet classtab; - LpxVar action; - LpxStackManager LSM(L, db, classname, classtab, action); - - LSM.checknometa(db); - luaL_checktype(L, classname, LUA_TSTRING); - LSM.rawget(classtab, db, classname); - if (lua_istable(L, classtab)) { - return LSM.retval(); - } else if (!lua_isnil(L, classtab)) { - luaL_error(L, "%s is not a class", lua_tostring(L, classname)); - } - LSM.newtable(classtab); - LSM.rawset(db, classname, classtab); - LSM.setfield(classtab, "__index", classtab); - LSM.setfield(classtab, "__class", classname); - LSM.newtable(action); - LSM.setfield(classtab, "action", action); - return LSM.retval(); -} - -// Curried version of classdb.get where db is stored in an upvalue. -// -static int lpx_classdb_class(lua_State *L) { - lua_pushvalue(L, lua_upvalueindex(1)); - lua_insert(L, -2); - return lpx_classdb_get(L); -} - -int lpx_classdb_accessor(lua_State *L) { - luaL_checktype(L, -1, LUA_TTABLE); - lua_pushcclosure(L, LpxArgCheck<1, lpx_classdb_class>, 1); - return 1; -} - -// Reset a class database: -// -// 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. -// -// Caution: do not reset the global class database! -// -int lpx_classdb_reset(lua_State *L) { - LpxArg db; - LpxVar classname, classtab, action, key; - LpxStackManager LSM(L, db, classname, classtab, action, key); - - luaL_checktype(L, db, LUA_TTABLE); - LSM.setnil(classname); - while (LSM.next(db, classname, classtab) != 0) { - if (lua_istable(L, classtab)) { - LSM.setstring(key, "action"); - LSM.rawget(action, classtab, key); - if (lua_istable(L, action)) { - lua_pushvalue(L, action); - lpx_table_clear(L); - } else { - LSM.newtable(action); - } - lua_pushvalue(L, classtab); - lpx_table_clear(L); - LSM.setfield(classtab, "__index", classtab); - LSM.setfield(classtab, "__class", classname); - LSM.setfield(classtab, "action", action); - } - } - return LSM.retval(); -} - -// Create a classdb. That's just an empty table. -int lpx_classdb_create(lua_State *L) { - lua_newtable(L); - return 1; -} - -void luaopen_lpx_classdb(lua_State *L) { - LpxStackManager::reg(L, "classdb", "get", LpxArgCheck<2, lpx_classdb_get>); - LpxStackManager::reg(L, "classdb", "accessor", LpxArgCheck<1, lpx_classdb_accessor>); - LpxStackManager::reg(L, "classdb", "reset", LpxArgCheck<1, lpx_classdb_reset>); - LpxStackManager::reg(L, "classdb", "create", LpxArgCheck<0, lpx_classdb_create>); - - // Insert the global function 'class', which is a closure with an upvalue. - lua_pushstring(L, "class"); - lua_pushvalue(L, LUA_GLOBALSINDEX); - lpx_classdb_accessor(L); - lua_rawset(L, LUA_GLOBALSINDEX); -} diff --git a/luprex/syscpp/lpx-classdb.hpp b/luprex/syscpp/lpx-classdb.hpp deleted file mode 100644 index 59047cc3..00000000 --- a/luprex/syscpp/lpx-classdb.hpp +++ /dev/null @@ -1,22 +0,0 @@ -// 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. -// - -#ifndef LPX_CLASSDB_HPP -#define LPX_CLASSDB_HPP - -#include "lpx-stack-manager.hpp" - -int lpx_classdb_get(lua_State *L); -int lpx_classdb_reset(lua_State *L); -int lpx_classdb_accessor(lua_State *L); -int lpx_classdb_create(lua_State *L); - -void luaopen_lpx_classdb(lua_State *L); - -#endif // LPX_CLASSDB_HPP - - diff --git a/luprex/syscpp/lpx-stack-manager.hpp b/luprex/syscpp/lpx-stack-manager.hpp deleted file mode 100644 index 9f735bd0..00000000 --- a/luprex/syscpp/lpx-stack-manager.hpp +++ /dev/null @@ -1,269 +0,0 @@ - - -#ifndef LPX_STACK_MANAGER_HPP -#define LPX_STACK_MANAGER_HPP - -extern "C" { -#include "lua.h" -#include "lauxlib.h" -#include "lualib.h" -#include "luajit.h" -} - -// LpxArgCheck -// -// This is a template that, when given a lua_CFunction that doesn't -// verify the number of arguments on the stack, returns a new lua_CFunction -// that *does* verify the number of arguments on the stack. -// -// In general, your lua_CFunctions should not verify the number of -// arguments on the stack. Instead, they should assume that the stack -// contains arbitrary stuff underneath the arguments. That makes your -// lua_CFunctions callable from both C and Lua. But it is desirable to -// check arguments (only when calling from lua), so it is useful to use -// LpxArgCheck when turning a lua_CFunction into a lua closure. -// -template -int LpxArgCheck(lua_State *L) { - if (lua_gettop(L) != N) { - lua_pushfstring(L, "expected %d arguments, got %d", N, lua_gettop(L)); - lua_error(L); - } - FN(L); -} - -// LpxSlot -// -// An LpxSlot contains a lua stack index. It is initialized by the -// LpxStackManager and contains the same index until the LpxStackManager -// is destroyed. You can convert an LpxSlot into a lua stack index -// by simply coercing it to 'int'. -// -// There are three variants of LpxSlot that you can use: LpxArg (function -// argument), LpxRet (function return value), and LpxVar (function local -// variable). -// -class LpxSlot { -private: - int index_; - LpxSlot *next_; - -public: - inline operator int() const { - return index_; - } - - LpxSlot() { - index_ = 0; - next_ = 0; - } - friend class LpxStackManager; -}; - -class LpxArg : public LpxSlot {}; -class LpxRet : public LpxSlot {}; -class LpxVar : public LpxSlot {}; - -class LpxStackManager { -private: - using SSList = LpxSlot*; - - SSList arg_; - SSList ret_; - SSList var_; - - int argtop_; - int gaptop_; - int vartop_; - int rettop_; - int finaltop_; - - int narg_; - int ngap_; - int nvar_; - int nret_; - - lua_State *L_; - - static int sscount(SSList l) { - int total = 0; - while (l != 0) { - total += 1; - l = l->next_; - } - return total; - } - - void ck() { - if (lua_gettop(L_) != rettop_) { - lua_pushstring(L_, "LpxStackManager: stack is not right"); - lua_error(L_); - } - } - - template - void record(LpxArg &v, SS & ... stackslots) - { - v.next_ = arg_; arg_ = &v; - record(stackslots...); - } - - template - void record(LpxRet &v, SS & ... stackslots) - { - v.next_ = ret_; ret_ = &v; - record(stackslots...); - } - - template - void record(LpxVar &v, SS & ... stackslots) - { - v.next_ = var_; var_ = &v; - record(stackslots...); - } - - void record() - { - } - -public: - template - LpxStackManager(lua_State *L, SS & ... stackslots) { - L_ = L; - arg_ = 0; - ret_ = 0; - var_ = 0; - - record(stackslots...); - - int narg = sscount(arg_); - int nvar = sscount(var_); - int nret = sscount(ret_); - int ngap = (nret - nvar - narg); - if (ngap < 0) ngap = 0; - - argtop_ = lua_gettop(L_); - gaptop_ = argtop_ + ngap; - vartop_ = gaptop_ + nvar; - rettop_ = vartop_ + nret; - finaltop_ = argtop_ - narg + nret; - - int i = argtop_; - for (LpxSlot *v = arg_; v != 0; v = v->next_) { - v->index_ = i; - i -= 1; - } - i = vartop_; - for (LpxSlot *v = var_; v != 0; v = v->next_) { - v->index_ = i; - i -= 1; - } - i = rettop_; - for (LpxSlot *v = ret_; v != 0; v = v->next_) { - v->index_ = i; - i -= 1; - } - - // Initialize vars and rets to NIL. - // I could conceivably do a Lpx_settop instead. - for (int i = argtop_; i < rettop_; i++) { - lua_pushnil(L_); - } - } - - ~LpxStackManager() { - ck(); - int i = finaltop_; - for (LpxSlot *v = ret_; v != 0; v = v->next_) { - lua_replace(L_, i); - i -= 1; - } - lua_settop(L_, finaltop_); - } - - int retval() { - return rettop_ - vartop_; - } - - static void reg(lua_State *L, const char *classname, const char *funcname, lua_CFunction fn) { - luaL_Reg reg; - reg.name = funcname; - reg.func = fn; - luaL_register(L, classname, ®); - } - -public: - // The following functions are 'register-machine' variants - // of the core lua functions. They do not read from the stack, - // or leave results on the stack. - - void setnil(int target) { - lua_pushnil(L_); - lua_replace(L_, target); - } - - void setstring(int target, const char *str) { - lua_pushstring(L_, str); - lua_replace(L_, target); - } - - void setlstring(int target, const char *str, int len) { - lua_pushlstring(L_, str, len); - lua_replace(L_, target); - } - - void setnumber(int target, double value) { - lua_pushnumber(L_, value); - lua_replace(L_, target); - } - - int next(int tab, int key, int value) { - lua_pushvalue(L_, key); - int ret = lua_next(L_, tab); - if (ret != 0) { - lua_replace(L_, value); - lua_replace(L_, key); - } - return ret; - } - - void rawget(int target, int tab, int key) { - lua_pushvalue(L_, key); - lua_rawget(L_, tab); - lua_replace(L_, target); - } - - void rawset(int tab, int key, int value) { - lua_pushvalue(L_, key); - lua_pushvalue(L_, value); - lua_rawset(L_, tab); - } - - void setfield(int tab, const char *field, int value) { - lua_pushvalue(L_, value); - lua_setfield(L_, tab, field); - } - - void getfield(int target, int tab, const char *field) { - lua_getfield(L_, tab, field); - lua_replace(L_, target); - } - - void newtable(int target) { - lua_newtable(L_); - lua_replace(L_, target); - } - - void checknometa(int index) { - if (lua_istable(L_, index)) { - if (!lua_getmetatable(L_, index)) { - return; - } - } - luaL_error(L_, "expected simple table with no metatable"); - } -}; - - - -#endif // LPX_STACK_MANAGER_HPP diff --git a/luprex/syscpp/lpx-table.cpp b/luprex/syscpp/lpx-table.cpp deleted file mode 100644 index 377a8f49..00000000 --- a/luprex/syscpp/lpx-table.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "lpx-table.hpp" - -// Clear the table. Removes metatable and all key-value pairs. -int lpx_table_clear(lua_State *L) { - LpxArg tab; - LpxStackManager LSM(L, tab); - - luaL_checktype(L, tab, LUA_TTABLE); - - // Clear the metatable. - lua_pushnil(L); - lua_setmetatable(L, tab); - - // Clear the elements. - lua_pushnil(L); - while (lua_next(L, tab) != 0) { - lua_pop(L, 1); // Pop the old value. - lua_pushvalue(L, -1); // Clone the key - lua_pushnil(L); // Push the new value. - lua_settable(L, tab); - } - - return LSM.retval(); -} - -int lpx_table_coerce(lua_State *L) { - if (!lua_istable(L, -1)) { - lua_pop(L, 1); - lua_newtable(L); - } - return 1; -} - -void luaopen_lpx_table (lua_State *L) { - LpxStackManager::reg(L, "table", "clear", LpxArgCheck<1, lpx_table_clear>); - LpxStackManager::reg(L, "table", "coerce", LpxArgCheck<1, lpx_table_coerce>); -} diff --git a/luprex/syscpp/luastack.cpp b/luprex/syscpp/luastack.cpp new file mode 100644 index 00000000..73ab49ff --- /dev/null +++ b/luprex/syscpp/luastack.cpp @@ -0,0 +1,92 @@ +#include "luastack.hpp" + +LuaSlot LuaRegistry(LUA_REGISTRYINDEX); +LuaSlot LuaGlobals(LUA_GLOBALSINDEX); + +void LuaStack::count_slots_finalize(int narg, int nvar, int nret) { + narg_ = narg; + nret_ = nret; + nvar_ = nvar; + ngap_ = nret - nvar - narg; + if (ngap_ < 0) ngap_ = 0; + + int argtop = lua_gettop(L_); + argpos_ = argtop + 1 - narg_; + gappos_ = argpos_ + narg_; + varpos_ = gappos_ + ngap_; + retpos_ = varpos_ + nvar_; + + rettop_ = retpos_ + nret_ - 1; + finaltop_ = argpos_ + nret_ - 1; +} + +void LuaStack::clear_frame() { + lua_settop(L_, varpos_ - 1); + for (int i = 0; i < nvar_ + nret_; i++) { + lua_pushnil(L_); + } +} + +int LuaStack::result() { + lua_settop(L_, rettop_); + int i = finaltop_; + for (int j = 0; j < nret_; j++) { + lua_replace(L_, i); + i -= 1; + } + lua_settop(L_, finaltop_); + return nret_; +} + +void LuaStack::reg(lua_State *L, const char *classname, const char *funcname, lua_CFunction fn) { + int top = lua_gettop(L); + lua_pushvalue(L, LUA_GLOBALSINDEX); + luaL_Reg reg; + reg.name = funcname; + reg.func = fn; + luaL_register(L, classname, ®); + lua_settop(L, top); +} + +void LuaStack::setnil(LuaSlot target) const { + lua_pushnil(L_); + lua_replace(L_, target); +} + +void LuaStack::setboolean(LuaSlot target, bool b) const { + lua_pushboolean(L_, b ? 1 : 0); + lua_replace(L_, target); +} + +void LuaStack::setboolean(LuaSlot target, int b) const { + lua_pushboolean(L_, b); + lua_replace(L_, target); +} + +void LuaStack::setstring(LuaSlot target, const char *str) const { + lua_pushstring(L_, str); + lua_replace(L_, target); +} + +void LuaStack::setstring(LuaSlot target, const std::string &str) const { + lua_pushlstring(L_, str.c_str(), str.size()); + lua_replace(L_, target); +} + +void LuaStack::setnumber(LuaSlot target, double value) const { + lua_pushnumber(L_, value); + lua_replace(L_, target); +} + +std::string LuaStack::tostring(LuaSlot s) { + size_t len; + const char *str = lua_tolstring(L_, s, &len); + return std::string(str, len); +} + +std::string LuaStack::checkstring(LuaSlot s) { + size_t len; + const char *str = luaL_checklstring(L_, s, &len); + return std::string(str, len); +} + diff --git a/luprex/syscpp/luastack.hpp b/luprex/syscpp/luastack.hpp new file mode 100644 index 00000000..a1a53a28 --- /dev/null +++ b/luprex/syscpp/luastack.hpp @@ -0,0 +1,264 @@ + + +#ifndef LUASTACK_HPP +#define LUASTACK_HPP + +extern "C" { +#include "lua.h" +#include "lauxlib.h" +#include "lualib.h" +#include "luajit.h" +} + +#include + +// LuaArgCheck +// +// This is a template that, when given a lua_CFunction that doesn't +// verify the number of arguments on the stack, returns a new lua_CFunction +// that *does* verify the number of arguments on the stack. +// +// In general, your lua_CFunctions should not verify the number of +// arguments on the stack. Instead, they should assume that the stack +// contains arbitrary stuff underneath the arguments. That makes your +// lua_CFunctions callable from both C and Lua. But it is desirable to +// check arguments (only when calling from lua), so it is useful to use +// LuaArgCheck when turning a lua_CFunction into a lua closure. +// +template +int LuaArgCheck(lua_State *L) { + if (lua_gettop(L) != N) { + lua_pushfstring(L, "expected %d arguments, got %d", N, lua_gettop(L)); + lua_error(L); + } + FN(L); +} + +// 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_; +private: + inline operator int() const { + return index_; + } +public: + LuaSlot(int n) { + index_ = n; + } + + LuaSlot() { + index_ = 0; + } + + int index() const { + return index_; + } + + friend class LuaStack; +}; + +class LuaArg : public LuaSlot {}; +class LuaRet : public LuaSlot {}; +class LuaVar : public LuaSlot {}; + +extern LuaSlot LuaRegistry; +extern LuaSlot LuaGlobals; + +class LuaUpvalue : public LuaSlot { + public: + LuaUpvalue(int n) { + index_ = lua_upvalueindex(n); + } +}; + +class LuaStack { +private: + int narg_; + int ngap_; + int nvar_; + int nret_; + + int argpos_; + int gappos_; + int varpos_; + int retpos_; + + int rettop_; + int finaltop_; + + lua_State *L_; + + template + void count_slots(LuaArg &v, SS & ... stackslots) + { + count_slots(stackslots...); + } + + template + void count_slots(LuaVar &v, SS & ... stackslots) + { + count_slots(stackslots...); + } + template + void count_slots(LuaRet &v, SS & ... stackslots) + { + count_slots(stackslots...); + } + + template + void count_slots() { + count_slots_finalize(NARG, NVAR, NRET); + } + + void count_slots_finalize(int narg, int nvar, int nret); + + template + void assign_slots(int argp, int varp, int retp, LuaArg &v, SS & ... stackslots) { + v.index_ = argp; + assign_slots(argp + 1, varp, retp, stackslots...); + } + + template + void assign_slots(int argp, int varp, int retp, LuaVar &v, SS & ... stackslots) { + v.index_ = varp; + assign_slots(argp, varp+1, retp, stackslots...); + } + + template + void assign_slots(int argp, int varp, int retp, LuaRet &v, SS & ... stackslots) { + v.index_ = retp; + assign_slots(argp, varp, retp+1, stackslots...); + } + + void assign_slots(int argp, int varp, int retp) {} + + void clear_frame(); + +public: + template + LuaStack(lua_State *L, SS & ... stackslots) { + L_ = L; + count_slots<0, 0, 0>(stackslots...); + assign_slots(argpos_, varpos_, retpos_, stackslots...); + clear_frame(); + } + + ~LuaStack() {}; + + int result(); + +public: + static void reg(lua_State *L, const char *classname, const char *funcname, lua_CFunction fn); + + void setnil(LuaSlot target) const; + void setboolean(LuaSlot target, bool b) const; + void setboolean(LuaSlot target, int b) const; + void setstring(LuaSlot target, const char *str) const; + void setstring(LuaSlot target, const std::string &str) const; + void setnumber(LuaSlot target, double value) const; + + bool isboolean(LuaSlot s) { return lua_isboolean(L_, s); } + bool iscfunction(LuaSlot s) { return lua_iscfunction(L_, s); } + bool isfunction(LuaSlot s) { return lua_isfunction(L_, s); } + bool islightuserdata(LuaSlot s) { return lua_islightuserdata(L_, s); } + bool isnil(LuaSlot s) { return lua_isnil(L_, s); } + bool isnumber(LuaSlot s) { return lua_isnumber(L_, s); } + bool isstring(LuaSlot s) { return lua_isstring(L_, s); } + bool istable(LuaSlot s) { return lua_istable(L_, s); } + bool isthread(LuaSlot s) { return lua_isthread(L_, s); } + bool isuserdata(LuaSlot s) { return lua_isuserdata(L_, s); } + + bool toboolean(LuaSlot s) { return lua_toboolean(L_, s); } + int tointeger(LuaSlot s) { return lua_tointeger(L_, s); } + double tonumber(LuaSlot s) { return lua_tonumber(L_, s); } + std::string tostring(LuaSlot s); + lua_State *tothread(LuaSlot s) { return lua_tothread(L_, s); } + + int checkint(LuaSlot s) { return luaL_checkint(L_, s); } + long checklong(LuaSlot s) { return luaL_checklong(L_, s); } + double checknumber(LuaSlot s) { return luaL_checknumber(L_, s); } + std::string checkstring(LuaSlot s); + void checktype(LuaSlot s, int t) { return luaL_checktype(L_, s, t); } + + void clearmetatable(LuaSlot tab) { + lua_pushnil(L_); + lua_setmetatable(L_, tab); + } + + void setmetatable(LuaSlot tab, LuaSlot mt) { + lua_pushvalue(L_, mt); + lua_setmetatable(L_, tab); + } + + void rawget(LuaSlot target, LuaSlot tab, LuaSlot key) const { + lua_pushvalue(L_, key); + lua_rawget(L_, tab); + lua_replace(L_, target); + } + + void rawset(LuaSlot tab, LuaSlot key, LuaSlot value) const { + lua_pushvalue(L_, key); + lua_pushvalue(L_, value); + lua_rawset(L_, tab); + } + + + int next(LuaSlot tab, LuaSlot key, LuaSlot value) const { + lua_pushvalue(L_, key); + int ret = lua_next(L_, tab); + if (ret != 0) { + lua_replace(L_, value); + lua_replace(L_, key); + } + return ret; + } + + + void setfield(LuaSlot tab, const char *field, LuaSlot value) const { + lua_pushvalue(L_, value); + lua_setfield(L_, tab, field); + } + + void setfield(LuaSlot tab, const char *field, const char *value) const { + lua_pushstring(L_, value); + lua_setfield(L_, tab, field); + } + + void setfield(LuaSlot tab, const char *field, const std::string &value) const { + lua_pushlstring(L_, value.c_str(), value.size()); + lua_setfield(L_, tab, field); + } + + void getfield(LuaSlot target, LuaSlot tab, const char *field) const { + lua_getfield(L_, tab, field); + lua_replace(L_, target); + } + + void newtable(LuaSlot target) const { + lua_newtable(L_); + lua_replace(L_, target); + } + + void checknometa(LuaSlot index) const { + if (lua_istable(L_, index)) { + if (!lua_getmetatable(L_, index)) { + return; + } + } + luaL_error(L_, "expected simple table with no metatable"); + } +}; + + +#endif // LUASTACK_HPP diff --git a/luprex/syscpp/main.cpp b/luprex/syscpp/main.cpp index d924296b..ab119ba6 100644 --- a/luprex/syscpp/main.cpp +++ b/luprex/syscpp/main.cpp @@ -12,10 +12,11 @@ #include #include #include -#include "lpx-stack-manager.hpp" +#include "luastack.hpp" #include "util.hpp" -#include "lpx-table.hpp" -#include "lpx-classdb.hpp" +#include "table.hpp" +#include "globaldb.hpp" +#include "source.hpp" // Add another error status. @@ -174,7 +175,7 @@ static void dotty(lua_State *L) report(L, status); if (status == LUA_OK && lua_gettop(L) > 0) { /* any result to print? */ - lua_getglobal(L, "iprint"); + lua_getglobal(L, "pprint"); if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_getglobal(L, "print"); @@ -193,26 +194,6 @@ static void dotty(lua_State *L) fflush(stdout); } -static void loadmain(lua_State *L) -{ - util::stringvec filenames = util::trim_and_uncomment(util::read_lines("syslua/control.lst")); - if (filenames.empty()) { - lua_pushfstring(L, "Cannot read syslua/control.lst"); - lua_error(L); - } - - for (const auto &fn : filenames) { - std::string full = "syslua/" + fn; - int status = luaL_loadfilex(L, full.c_str(), NULL); - if (status == LUA_OK) { - status = docall(L, 0, 0); - } - if (status != LUA_OK) { - lua_error(L); - } - } -} - static int pmain(lua_State *L) { globalL = L; @@ -231,11 +212,10 @@ static int pmain(lua_State *L) // luaopen_debug(L); // Not safe for the sandbox. luaopen_bit(L); // luaopen_jit(L); // Don't know what it's for. - luaopen_lpx_classdb(L); + luaopen_lpx_globaldb(L); + luaopen_lpx_source(L); lua_gc(L, LUA_GCRESTART, -1); - loadmain(L); - dotty(L); return 0; } diff --git a/luprex/syscpp/source.cpp b/luprex/syscpp/source.cpp new file mode 100644 index 00000000..7524bfb7 --- /dev/null +++ b/luprex/syscpp/source.cpp @@ -0,0 +1,225 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "util.hpp" +#include "table.hpp" +#ifndef WIN32 +#include +#endif +#include "luastack.hpp" +#ifdef WIN32 +#define stat _stat +#endif + + +//////////////////////////////////////////////////////////// +// +// 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 +// mtime: 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. +// +//////////////////////////////////////////////////////////// + +static std::string get_mtime(const std::string &fn) { + struct stat result; + if(stat(fn.c_str(), &result)==0) + { + std::stringstream ss; + ss << result.st_mtime; + return ss.str(); + } + return ""; +} + +static std::string get_code(const std::string &fn) { + std::ifstream fs(fn); + std::stringstream buffer; + buffer << fs.rdbuf(); + return buffer.str(); +} + +int lpx_source_updatefile(lua_State *L) { + LuaArg source, fn; + LuaRet info; + LuaVar mtime, null; + LuaStack LS(L, source, fn, info, mtime, null); + + // Get the existing info table from the source DB. + if (LS.istable(source)) { + LS.rawget(info, source, fn); + if (!LS.istable(info)) { + LS.newtable(info); + } + } else { + LS.newtable(info); + } + + // If the file modification is wrong, update + // these fields: code, mtime, closure, error + // Otherwise, update nothing. + std::string cfn = LS.tostring(fn); + LS.getfield(mtime, info, "mtime"); + std::string old_mtime; + if (LS.isstring(mtime)) { + old_mtime = LS.tostring(mtime); + } + std::cerr << "Probing " << cfn << std::endl; + std::string new_mtime = get_mtime("syslua/" + cfn); + LS.setnil(null); + if ((old_mtime == "") || (old_mtime != new_mtime)) { + std::cerr << "Rereading " << cfn << std::endl; + std::string ccode = get_code("syslua/" + cfn); + LS.setfield(info, "name", fn); + LS.setfield(info, "mtime", new_mtime); + LS.setfield(info, "code", ccode); + if ((new_mtime == "")||(ccode == "")) { + LS.setfield(info, "error", "cannot read source file"); + LS.setfield(info, "closure", null); + } else if (luaL_loadbuffer(L, ccode.c_str(), ccode.size(), cfn.c_str()) == 0) { + lua_setfield(L, info.index(), "closure"); + LS.setfield(info, "error", null); + } else { + lua_setfield(L, info.index(), "error"); + LS.setfield(info, "closure", null); + } + } + return LS.result(); +} + +int lpx_source_updateall(lua_State *L) { + LuaArg source; + LuaRet newdb; + LuaVar info, fn, seq; + LuaStack LS(L, source, newdb, info, fn, seq); + + // Read the list of filenames. + std::string ctrl = "syslua/control.lst"; + util::stringvec filenames = util::trim_and_uncomment(util::read_lines(ctrl)); + if (filenames.empty()) { + luaL_error(L, "cannot read source database control.lst"); + } + + // Process the files one by one. + LS.newtable(newdb); + for (int i = 0; i < filenames.size(); i++) { + LS.setstring(fn, filenames[i]); + + // Call source_updatefile to get the updated info for one file. + lua_pushvalue(L, source.index()); + lua_pushvalue(L, fn.index()); + lpx_source_updatefile(L); + lua_replace(L, info.index()); + + // Insert the sequence number and put finalized info into the new database. + LS.setnumber(seq, i + 1); + LS.setfield(info, "sequence", seq); + LS.rawset(newdb, fn, info); + } + return LS.result(); +} + +// Get a class from the class database. +// +// 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. +// +int lpx_source_class(lua_State *L) { + LuaArg classname; + LuaRet classtab; + LuaVar classdb, action; + LuaStack LS(L, classname, classtab, classdb, action); + + LS.checktype(classname, LUA_TSTRING); + + // Get a pointer to the classdb. + LS.getfield(classdb, LUA_REGISTRYINDEX, "classdb"); + if (!LS.istable(classdb)) { + LS.newtable(classdb); + LS.setfield(LUA_REGISTRYINDEX, "classdb", classdb); + } + + // Get the classtab from the classdb, sanity check it. + LS.rawget(classtab, classdb, classname); + if (LS.istable(classtab)) { + return LS.result(); + } else if (!LS.isnil(classtab)) { + luaL_error(L, "%s is not a class", LS.tostring(classname).c_str()); + } + + // Create a new classtab and store it in the classdb. + LS.newtable(classtab); + LS.rawset(classdb, classname, classtab); + LS.setfield(classtab, "__index", classtab); + LS.setfield(classtab, "__class", classname); + LS.newtable(action); + LS.setfield(classtab, "action", action); + return LS.result(); +} + +// Reset a class database: +// +// 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. +// +int lpx_source_resetclasses(lua_State *L) { + LuaVar classdb, classname, classtab, action, key; + LuaStack LS(L, classname, classtab, classdb, action, key); + + // Get a pointer to the classdb. + LS.getfield(classdb, LUA_REGISTRYINDEX, "classdb"); + if (!LS.istable(classdb)) { + LS.newtable(classdb); + LS.setfield(LUA_REGISTRYINDEX, "classdb", classdb); + } + + // Iterate over the classdb, clearing it. + LS.setnil(classname); + while (LS.next(classdb, classname, classtab) != 0) { + if (LS.istable(classtab)) { + LS.setstring(key, "action"); + LS.rawget(action, classtab, key); + if (LS.istable(action)) { + lua_pushvalue(L, action.index()); + lpx_table_clear(L); + } else { + LS.newtable(action); + } + lua_pushvalue(L, classtab.index()); + lpx_table_clear(L); + LS.setfield(classtab, "__index", classtab); + LS.setfield(classtab, "__class", classname); + LS.setfield(classtab, "action", action); + } + } + return LS.result(); +} + +void luaopen_lpx_source(lua_State *L) { + LuaStack::reg(L, "source", "updatefile", LuaArgCheck<2, lpx_source_updatefile>); + LuaStack::reg(L, "source", "updateall", LuaArgCheck<1, lpx_source_updateall>); + LuaStack::reg(L, "source", "class", LuaArgCheck<1, lpx_source_class>); + LuaStack::reg(L, "source", "resetclasses", LuaArgCheck<0, lpx_source_resetclasses>); + LuaStack::reg(L, 0 , "class", LuaArgCheck<1, lpx_source_class>); +} diff --git a/luprex/syscpp/source.hpp b/luprex/syscpp/source.hpp new file mode 100644 index 00000000..96108fbf --- /dev/null +++ b/luprex/syscpp/source.hpp @@ -0,0 +1,21 @@ +// 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. +// + +#ifndef SOURCE_HPP +#define SOURCE_HPP + +#include "luastack.hpp" + +int lpx_source_update(lua_State *L); +int lpx_source_class(lua_State *L); +int lpx_source_resetclasses(lua_State *L); + +void luaopen_lpx_source(lua_State *L); + +#endif // SOURCE_HPP + + diff --git a/luprex/syscpp/table.cpp b/luprex/syscpp/table.cpp new file mode 100644 index 00000000..4edce9f5 --- /dev/null +++ b/luprex/syscpp/table.cpp @@ -0,0 +1,34 @@ +#include "table.hpp" + +// Clear the table. Removes metatable and all key-value pairs. +int lpx_table_clear(lua_State *L) { + LuaArg tab; + LuaStack LS(L, tab); + + LS.checktype(tab, LUA_TTABLE); + + LS.clearmetatable(tab); + + lua_pushnil(L); + while (lua_next(L, tab.index()) != 0) { + lua_pop(L, 1); // Pop the old value. + lua_pushvalue(L, -1); // Clone the key + lua_pushnil(L); // Push the new value. + lua_settable(L, tab.index()); + } + + return LS.result(); +} + +int lpx_table_coerce(lua_State *L) { + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + lua_newtable(L); + } + return 1; +} + +void luaopen_lpx_table (lua_State *L) { + LuaStack::reg(L, "table", "clear", LuaArgCheck<1, lpx_table_clear>); + LuaStack::reg(L, "table", "coerce", LuaArgCheck<1, lpx_table_coerce>); +} diff --git a/luprex/syscpp/lpx-table.hpp b/luprex/syscpp/table.hpp similarity index 53% rename from luprex/syscpp/lpx-table.hpp rename to luprex/syscpp/table.hpp index 70f008c3..6546f4ec 100644 --- a/luprex/syscpp/lpx-table.hpp +++ b/luprex/syscpp/table.hpp @@ -1,11 +1,11 @@ -#ifndef LPX_TABLE_HPP -#define LPX_TABLE_HPP +#ifndef TABLE_HPP +#define TABLE_HPP -#include "lpx-stack-manager.hpp" +#include "luastack.hpp" int lpx_table_clear(lua_State *L); int lpx_table_coerce(lua_State *L); void luaopen_lpx_table (lua_State *L); -#endif // LPX_TABLE_HPP +#endif // TABLE_HPP diff --git a/luprex/syscpp/util.hpp b/luprex/syscpp/util.hpp index d0ea96a8..b3d0089e 100644 --- a/luprex/syscpp/util.hpp +++ b/luprex/syscpp/util.hpp @@ -2,11 +2,13 @@ #define UTIL_HPP #include +#include #include namespace util { using stringvec = std::vector; +using stringset = std::set; // trim from start static inline std::string ltrim(std::string s) { diff --git a/luprex/syslua/control.lst b/luprex/syslua/control.lst index 5535c598..19bb28db 100644 --- a/luprex/syslua/control.lst +++ b/luprex/syslua/control.lst @@ -4,7 +4,6 @@ # inspect.lua -globaldb.lua -model.lua + diff --git a/luprex/syslua/globaldb.lua b/luprex/syslua/globaldb.lua deleted file mode 100644 index 1eff00c9..00000000 --- a/luprex/syslua/globaldb.lua +++ /dev/null @@ -1,22 +0,0 @@ -local globaldb=class('globaldb') - -function globaldb.get(db, key) - local result = db[key] - if type(result) == 'table' then - return result - else - result = {} - db[key] = result - end - return result -end - -function globaldb.accessor(db) - return function(key) - return globaldb.get(db, key) - end -end - -function globaldb.create() - return {} -end diff --git a/luprex/syslua/inspect.lua b/luprex/syslua/inspect.lua index 5b9d36d8..169994f1 100644 --- a/luprex/syslua/inspect.lua +++ b/luprex/syslua/inspect.lua @@ -333,7 +333,7 @@ function inspect.inspect(root, options) return table.concat(inspector.buffer) end -function inspect.iprint(...) +function inspect.pprint(...) local n = select("#", ...) for i = 1,n do local v = select(i, ...) @@ -341,4 +341,4 @@ function inspect.iprint(...) end end -_G.iprint = inspect.iprint +_G.pprint = inspect.pprint diff --git a/luprex/syslua/model.lua b/luprex/syslua/model.lua deleted file mode 100644 index 17e98980..00000000 --- a/luprex/syslua/model.lua +++ /dev/null @@ -1,41 +0,0 @@ -local model=class('model') -local globaldb=class('globaldb') - --- The global environment contents for a model. A list of the builtin --- functions and classes that get installed in the global environment of --- a world model. --- --- This barely counts as 'sandboxing' - it's more just a mechanism to --- keep the user from unintentionally thinking that some functionality is --- available when it's not. For true sandboxing, you need to not import --- functions into the lua interpreter at all. --- --- Omitted from this list: debug, dofile, getfenv, io, jit, --- load, loadfile, loadstring, module, newproxy, os, package, --- pcall, require, xpcall --- - -model.global_contents = { "assert", "bit", "error", "getmetatable", "inspect", - "ipairs", "math", "next", "pairs", "print", "rawequal", "rawget", "rawset", - "select", "setmetatable", "tonumber", "tostring", "type", "unpack" } - --- make a world model. --- -function model.make() - genv = {} - genv._G = genv - meta = {} - meta.class_db = classdb.create() - meta.global_db = globaldb.create() - meta.source_db = {} - meta.tangible_db = {} - meta.__newindex = function() error("world model global environment is read-only") end - -- meta.__metatable = false - genv.class = classdb.accessor(meta.class_db) - genv.global = make_global_accessor(meta.global_db) - for i,name in ipairs(world.global_contents) do - genv[name] = _G[name] - end - setmetatable(genv, meta) - return genv -end