From 58327525051d0e282b4bc2d0d7ddd1bbad2e9b65 Mon Sep 17 00:00:00 2001 From: Pixel Date: Wed, 26 Oct 2011 15:06:49 -0700 Subject: Adding Lua. --- LuaJIT | 2 +- Makefile | 16 +- includes/BLua.h | 335 +++++++++++++++++++++ src/BLua.cc | 854 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/test-Lua.cc | 33 +++ 5 files changed, 1234 insertions(+), 6 deletions(-) create mode 100644 includes/BLua.h create mode 100644 src/BLua.cc create mode 100644 tests/test-Lua.cc diff --git a/LuaJIT b/LuaJIT index e80478c..1d190c9 160000 --- a/LuaJIT +++ b/LuaJIT @@ -1 +1 @@ -Subproject commit e80478c44b7e4bf32a509c480edb39bd39ede51b +Subproject commit 1d190c99a2547b44deb8f5e483452d9f51925fb2 diff --git a/Makefile b/Makefile index 85dbab5..0cc7064 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ CPPFLAGS += -g -DDEBUG LDFLAGS += -g endif -INCLUDES = includes libcoro libeio libev +INCLUDES = includes libcoro libeio libev LuaJIT/src LIBS = ifeq ($(SYSTEM),Darwin) @@ -91,7 +91,7 @@ endif ifeq ($(SYSTEM),Linux) CPPFLAGS += -fPIC LDFLAGS += -fPIC - LIBS += pthread + LIBS += pthread dl CONFIG_H = linux-config.h ARCH_FLAGS = -march=i686 -m32 ASFLAGS = -march=i686 --32 @@ -123,6 +123,8 @@ Socket.cc \ \ Task.cc \ TaskMan.cc \ +\ +BLua.cc \ ifeq ($(SYSTEM),MINGW32) WIN32_SOURCES = \ @@ -159,6 +161,7 @@ test-Tasks.cc \ test-Threads.cc \ test-Handles.cc \ test-Sockets.cc \ +test-Lua.cc \ LIB = libBalau.a @@ -186,11 +189,14 @@ strip: $(TESTS) lib: $(LIB) -libBalau.a: $(BALAU_OBJECTS) +LuaJIT: + $(MAKE) -C LuaJIT CC="$(CC) $(ARCH_FLAGS)" BUILDMODE=static + +libBalau.a: LuaJIT $(BALAU_OBJECTS) $(AR) libBalau.a $(BALAU_OBJECTS) %.$(BINEXT) : %.o $(LIB) - $(LD) $(LDFLAGS) -o $@ $< ./$(LIB) $(LDLIBS) + $(LD) $(LDFLAGS) -o $@ $< ./$(LIB) ./LuaJIT/src/libluajit.a $(LDLIBS) dep: $(ALL_DEPS) @@ -205,4 +211,4 @@ dep: $(ALL_DEPS) clean: rm -f $(ALL_OBJECTS) $(TESTS) $(LIB) $(ALL_DEPS) -.PHONY: lib tests clean strip +.PHONY: lib tests clean strip LuaJIT diff --git a/includes/BLua.h b/includes/BLua.h new file mode 100644 index 0000000..addd081 --- /dev/null +++ b/includes/BLua.h @@ -0,0 +1,335 @@ +#pragma once + +#include + +extern "C" { +#include +#include +} + +#include +#include + +namespace Balau { + +class Lua; + +class LuaExport { + public: + virtual ~LuaExport() { } +}; + +class LuaObject { + public: + LuaObject() : m_wantsDestruct(false), m_pushed(false) { } + virtual void push(Lua & L) throw (GeneralException); + void pushDestruct(Lua & L) throw (GeneralException); + template + static T * getMe(Lua & L, int idx = 1); + protected: + virtual void pushMembers(Lua & L) = 0; + void pushMe(Lua & L, void * obj = 0, const char * name = NULL, bool isObj = true); + static void pushIt(Lua & L, const char * name, lua_CFunction func); + static void pushMeta(Lua & L, const char * name, lua_CFunction func); + static void * getMeInternal(Lua & L, int idx); + friend class Lua; + private: + bool m_wantsDestruct, m_pushed; +}; + +typedef int (*openlualib_t)(lua_State * L); + +class Lua { + public: + Lua(); + Lua(lua_State * __L) : L(__L) { } + Lua(const Lua &) throw (GeneralException) { throw GeneralException("Error: can't duplicate a Lua object."); } + + typedef int (*lua_CallWrapper)(lua_State *, lua_CFunction); + + int ref(int t = -2) { luaL_ref(L, t); } + void unref(int ref, int t = -1) { luaL_unref(L, t, ref); } + + void open_base(); + void open_table(); + void open_string(); + void open_math(); + void open_debug(); + void open_bit(); + void open_jit(); + int wrap_open(openlualib_t open) { int n = gettop(); int r = open(L); while (n < gettop()) remove(n); } + void openlib(const String & libname, const struct luaL_reg *l, int nup) { luaL_openlib(L, libname.to_charp(), l, nup); } + + void setCallWrap(lua_CallWrapper wrapper); + void declareFunc(const char * funcName, lua_CFunction f, int tableIdx = LUA_GLOBALSINDEX); + + void call(const char * funcName, int tableIdx = LUA_GLOBALSINDEX, int nArgs = 0); + void call(int nArgs = 0) { resume(nArgs); } + + void push() { checkstack(); lua_pushnil(L); } + void push(lua_Number n) { checkstack(); lua_pushnumber(L, n); } + void push(const String & s) { checkstack(); lua_pushlstring(L, s.to_charp(), s.strlen()); } + void push(bool b) { checkstack(); lua_pushboolean(L, b); } + void push(const char * str, int size = -1) { if (size < 0) size = strlen(str); checkstack(); lua_pushlstring(L, str, size); } + void push(void * p) { checkstack(); lua_pushlightuserdata(L, p); } + void push(lua_CFunction f, int n = 0) { checkstack(); lua_pushcclosure(L, f, n); } + void pop(int idx = 1) { lua_pop(L, idx); } + int checkstack(int extra = 1) { return lua_checkstack(L, extra); } + + int next(int t = -2) { return lua_next(L, t); } + void copy(int n = -1) { checkstack(); lua_pushvalue(L, n); } + void remove(int n = 1) { lua_remove(L, n); } + void insert(int n = 1) { checkstack(); lua_insert(L, n); } + void replace(int n = 1) { lua_replace(L, n); } + void newtable() { checkstack(); lua_newtable(L); } + void * newuser(size_t s) { checkstack(); return lua_newuserdata(L, s); } + void settable(int tableIdx = -3, bool raw = false); + void gettable(int tableIdx = -2, bool raw = false); + void rawseti(int idx, int tableIdx = -2) { lua_rawseti(L, tableIdx, idx); } + void rawgeti(int idx, int tableIdx = -1) { lua_rawgeti(L, tableIdx, idx); } + void setvar() { lua_settable(L, LUA_GLOBALSINDEX); } + int gettop() { return lua_gettop(L); } + void getglobal(const char * name) throw (GeneralException); + void pushLuaContext(); + void error(const char * msg); + void error(const String & msg) { error(msg.to_charp()); } + + int type(int i = -1) { return lua_type(L, i); } + bool isnil(int i = -1) { return lua_isnil(L, i); } + bool isboolean(int i = -1) { return lua_isboolean(L, i); } + bool isnumber(int i = -1) { return lua_isnumber(L, i); } + bool isstring(int i = -1) { return lua_isstring(L, i); } + bool istable(int i = -1) { return lua_istable(L, i); } + bool isfunction(int i = -1) { return lua_isfunction(L, i); } + bool iscfunction(int i = -1) { return lua_iscfunction(L, i); } + bool isuserdata(int i = -1) { return lua_isuserdata(L, i); } + bool islightuserdata(int i = -1) { return lua_islightuserdata(L, i); } + bool isobject(int i = -1); + + bool toboolean(int i = -1) { return lua_toboolean(L, i); } + lua_Number tonumber(int i = -1) { return lua_tonumber(L, i); } + String tostring(int i = -1); + lua_CFunction tocfunction(int i = -1) { return lua_tocfunction(L, i); } + void * touserdata(int i = -1) { return lua_touserdata(L, i); } + + String escapeString(const String &); + void load(IO in, bool docall = true) throw (GeneralException); + void load(const String &, bool docall = true) throw (GeneralException); + void dumpvars(IO out, const String & prefix, int idx = -1); + Lua thread(bool saveit = true); + Lua thread(const String &, int nargs = 0, bool saveit = true); + Lua thread(IO in, int nargs = 0, bool saveit = true); + int yield(int nresults = 0) { return lua_yield(L, nresults); } + bool resume(int nargs = 0) throw (GeneralException); + bool resume(const String &, int nargs = 0); + bool resume(IO in, int nargs = 0); + void showstack(int level = M_INFO); + void showerror(); + int getmetatable(int i = -1) { checkstack(); return lua_getmetatable(L, i); } + int setmetatable(int i = -2) { return lua_setmetatable(L, i); } + int sethook(lua_Hook func, int mask, int count) { return lua_sethook(L, func, mask, count); } + void weaken(); + + template + T * recast(int n = 1) { + LuaExport * b; + T * r; + + b = (LuaExport *) LuaObject::getMeInternal(*this, n); + if (!b) + error("LuaExport base object required; got null."); + + r = dynamic_cast(b); + + if (!r) + error(String("Object not compatible; expecting ") + typeid(r).name() + " but got *" + typeid(*b).name() + " instead."); + + return r; + } + + lua_State * getState() { return L; } + protected: + + private: + void dumpvars_r(IO out, int idx, int depth = 0) throw (GeneralException); + + lua_State * L; + + friend class LuaStatics; +}; + +class LuaException : public GeneralException { + public: + LuaException(String fn) : GeneralException(fn) { } + protected: + LuaException() { } +}; + +enum Lua_types_t { + BLUA_OBJECT = 0x01, + BLUA_TABLE = 0x02, + BLUA_BOOLEAN = 0x04, + BLUA_NUMBER = 0x08, + BLUA_STRING = 0x10, + BLUA_FUNCTION = 0x20, + BLUA_NIL = 0x40, + BLUA_USERDATA = 0x80, + BLUA_ANY = 0xff, +}; + +#define MAX_TYPE 8 + +#define MAXARGS 32 + +struct lua_functypes_t { + int number; + const char * name; + int minargs, maxargs; + int argtypes[MAXARGS]; +}; + +#define DECLARE_METHOD(classname, enumvar) static int method_##enumvar(lua_State * L) { \ + return LuaHelpers::method_multiplex( \ + enumvar, \ + L, \ + sLua_##classname::classname##_proceed, \ + 0, \ + classname##_methods, \ + true); \ + } + +#define DECLARE_FUNCTION(classname, enumvar) static int function_##enumvar(lua_State * L) { \ + return LuaHelpers::method_multiplex( \ + enumvar, \ + L, \ + 0, \ + sLua_##classname::classname##_proceed_statics, \ + classname##_functions, \ + false); \ + } + +#define PUSH_METHOD(classname, enumvar) pushit( \ + L, \ + classname##_methods[enumvar].name, \ + sLua_##classname::method_##enumvar) + +#define PUSH_METAMETHOD(classname, enumvar) pushmeta( \ + L, \ + String("__") + classname##_methods[enumvar].name, \ + sLua_##classname::method_##enumvar) + +#define PUSH_FUNCTION(classname, enumvar) L->declarefunc( \ + classname##_functions[enumvar].name, \ + sLua_##classname::function_##enumvar) + +#define PUSH_SUBFUNCTION(classname, enumvar, array) L->declarefunc( \ + classname##_functions[enumvar].name, \ + sLua_##classname::function_##enumvar, \ + array) + + +#define CHECK_METHODS(classname) { \ + int i = 0; \ + while (classname##_methods[i].number != -1) { \ + if (i != classname##_methods[i].number) { \ + throw GeneralException("Data of " #classname "_methods inconsistants!"); \ + } \ + i++; \ + } \ +} + +#define CHECK_FUNCTIONS(classname) { \ + int i = 0; \ + while (classname##_functions[i].number != -1) { \ + if (i != classname##_functions[i].number) { \ + throw GeneralException("Data of " #classname "_functions inconsistants!"); \ + } \ + i++; \ + } \ +} + +template +T * LuaObject::getMe(Lua & L, int idx) { return L.recast(idx); } + +template +class LuaHelpers { + public: + static int method_multiplex(int caller, lua_State * __L, int (*proceed)(Lua & L, int n, T * obj, int caller), int (*proceed_static)(Lua & L, int n, int caller), lua_functypes_t * tab, bool method) { + Lua L(__L); + int add = method ? 1 : 0; + int n = L.gettop() - add; + T * obj = 0; + int i, j, mask; + bool invalid = false, arg_valid; + + if (method) + obj = LuaObject::getMe(L); + + if ((n < tab[caller].minargs) || (n > tab[caller].maxargs)) { + invalid = true; + } else { + for (i = 0; i < tab[caller].maxargs && !invalid; i++) { + if (n >= (i + 1)) { + arg_valid = false; + for (j = 0; j < MAX_TYPE && !arg_valid; j++) { + mask = 1 << j; + if (tab[caller].argtypes[i] & mask) { + switch(mask) { + case BLUA_OBJECT: + if (L.istable(i + 1 + add)) { + L.push("__obj"); + L.gettable(i + 1 + add); + arg_valid = L.isuserdata(); + L.pop(); + } else { + arg_valid = L.isnil(i + 1 + add); + } + break; + case BLUA_TABLE: + arg_valid = L.istable(i + 1 + add); + break; + case BLUA_BOOLEAN: + arg_valid = L.isboolean(i + 1 + add); + break; + case BLUA_NUMBER: + arg_valid = L.isnumber(i + 1 + add); + break; + case BLUA_STRING: + arg_valid = L.isstring(i + 1 + add); + break; + case BLUA_FUNCTION: + arg_valid = L.isfunction(i + 1 + add); + break; + case BLUA_NIL: + arg_valid = L.isnil(i + 1 + add); + break; + case BLUA_USERDATA: + arg_valid = L.isuserdata(i + 1 + add) || L.islightuserdata(i + 1 + add); + break; + } + } + } + invalid = !arg_valid; + } + } + } + + if (invalid) { + if (method) { + L.error(String("Invalid arguments to method `") + typeid(T).name() + "::" + tab[caller].name + "'"); + } else { + L.error(String("Invalid arguments to function `") + typeid(T).name() + " " + tab[caller].name + "'"); + } + } + + if (method) { + return proceed(L, n, obj, caller); + } else { + return proceed_static(L, n, caller); + } + } +}; + +template T * lua_recast(Lua & L, int n = 1) { return L.recast(n); } + +}; diff --git a/src/BLua.cc b/src/BLua.cc new file mode 100644 index 0000000..9cfccf1 --- /dev/null +++ b/src/BLua.cc @@ -0,0 +1,854 @@ +#include +#include "BLua.h" +#include "Printer.h" +#include "Input.h" + +extern "C" { +#include +#include +} + +#ifndef BUFFERSIZE +#define BUFFERSIZE 2048 +#endif + +namespace Balau { + +class LuaStatics { + public: + static const char * getF(lua_State * L, void *, size_t *); + + static int callwrap(lua_State * L, lua_CFunction); + static int collector(lua_State * L); + static int destructor(lua_State * L); + + static int dumpvars(lua_State * L); + + static int iconv(lua_State * L); + static int mkdir(lua_State * L); + static int time(lua_State * L); + static int getenv(lua_State * L); + static int setenv(lua_State * L); + static int unsetenv(lua_State * L); + + static int hex(lua_State * L); + + static int globalindex(lua_State * L); + + static int print(lua_State * L); +}; + +}; + +int Balau::LuaStatics::hex(lua_State * __L) { + Lua L(__L); + int n = L.gettop(); + int x; + String r; + + if (((n != 1) && (n != 2)) || !L.isnumber(1) || ((n == 2) && !L.isstring(2))) + L.error("Incorrect arguments to function `hex'"); + + x = L.tonumber(1); + String fmt = n == 2 ? L.tostring() : "%02x"; + r.set(fmt.to_charp(), x); + + L.push(r); + + return 1; +} + +int Balau::LuaStatics::dumpvars(lua_State * __L) { + Lua L(__L); + int n = L.gettop(); + String prefix; + String varname; + + if ((n > 3) || (n < 2) || !L.isobject(1) || + ((n == 2) && !L.isstring(2)) || + ((n == 3) && !L.isstring(3) && !L.istable(2) && !L.isstring(2))) + L.error("Incorrect arguments to function `dumpvars'"); + + prefix = L.tostring(n); + + if (n == 3) + L.pop(); + + if (L.isstring(2)) + L.getglobal(L.tostring(2).to_charp()); + + IO h(L.recast()); + + L.dumpvars(h, prefix); + + return 0; +} + +int Balau::LuaStatics::iconv(lua_State * __L) { + Lua L(__L); + int n = L.gettop(); + String str, from, to; + + if ((n != 3) || !L.isstring(1) || !L.isstring(2) || !L.isstring(3)) + L.error("Incorrect arguments to function `string.iconv'"); + + str = L.tostring(1); + from = L.tostring(2); + to = L.tostring(3); + + str.iconv(from, to); + + L.push(str); + + return 1; +} + +int Balau::LuaStatics::mkdir(lua_State * __L) { + Lua L(__L); + int n = L.gettop(); + String dirname; + + if (n != 1) + L.error("Incorrect arguments to function `mkdir'"); + + dirname = L.tostring(1); + + Balau::FileSystem::mkdir(dirname.to_charp()); + + return 0; +} + +int Balau::LuaStatics::time(lua_State * __L) { + Lua L(__L); + + L.push((lua_Number) ::time(NULL)); + + return 1; +} + +int Balau::LuaStatics::getenv(lua_State * __L) { + Lua L(__L); + int n = L.gettop(); + + if (n != 1) + L.error("Incorrect arguments to function `getenv'"); + +#ifdef _WIN32 + char buffer[BUFSIZ + 1]; + if (GetEnvironmentVariable(L.tostring(1).to_charp(), buffer, BUFSIZ)) { + L.push(buffer); + } else { + L.push(); + } +#else + char * var = ::getenv(L.tostring(1).to_charp()); + if (var) { + L.push(var); + } else { + L.push(); + } +#endif + + return 1; +} + +int Balau::LuaStatics::setenv(lua_State * __L) { + Lua L(__L); + int n = L.gettop(); + + if (n != 2) { + L.error("Incorrect arguments to function `setenv'"); + } + +#ifdef _WIN32 + SetEnvironmentVariable(L.tostring(1).to_charp(), L.tostring(2).to_charp()); +#else + ::setenv(L.tostring(1).to_charp(), L.tostring(2).to_charp(), 1); +#endif + + return 0; +} + +int Balau::LuaStatics::unsetenv(lua_State * __L) { + Lua L(__L); + + int n = L.gettop(); + if (n != 1) + L.error("Incorrect arguments to function `unsetenv'"); + +#ifdef _WIN32 + SetEnvironmentVariable(L.tostring(1).to_charp(), NULL); +#else + ::unsetenv(L.tostring(1).to_charp()); +#endif + + return 0; +} + +int Balau::LuaStatics::print(lua_State * __L) { + Lua L(__L); + + int n = L.gettop(); + int i; + for (i = 1; i <= n; i++) { + const char *s; + s = lua_tostring(__L, i); + if (s == NULL) + L.error("`tostring' must return a string to `print'"); + if (i > 1) + Printer::print("\t"); + Printer::print(s); + L.pop(); + } + Printer::print("\n"); + return 0; +} + +int Balau::LuaStatics::callwrap(lua_State * __L, lua_CFunction func) { + Lua L(__L); + + try { + return func(__L); + } + catch (LuaException e) { + L.error(String("LuaException: ") + e.getMsg()); + } + catch (Balau::GeneralException e) { + L.error(String("GeneralException: ") + e.getMsg()); + } + catch (...) { + L.error("Unknown C++ exception"); + } + + return 0; +} + +struct ObjData { + void * ptr; + bool isObj; +}; + +int Balau::LuaStatics::collector(lua_State * __L) { + Lua L(__L); + ObjData * u = (ObjData *) L.touserdata(); + if (u->isObj) { + LuaExport * obj = (LuaExport *) u->ptr; + delete obj; + } else { + free(u->ptr); + } + u->ptr = NULL; + return 0; +} + +int Balau::LuaStatics::destructor(lua_State * __L) { + Lua L(__L); + L.push("__obj"); + L.gettable(-2, true); + ObjData * u = (ObjData *) L.touserdata(); + if (u->isObj) { + LuaExport * obj = (LuaExport *) u->ptr; + delete obj; + } else { + free(u->ptr); + } + u->ptr = NULL; + L.pop(); + return 0; +} + +void Balau::Lua::setCallWrap(lua_CallWrapper wrapper) { + push((void *) wrapper); + luaJIT_setmode(L, -1, LUAJIT_MODE_WRAPCFUNC | LUAJIT_MODE_ON); + pop(); +} + +Balau::Lua::Lua() : L(lua_open()) { + setCallWrap(LuaStatics::callwrap); + declareFunc("hex", LuaStatics::hex); + declareFunc("dumpvars", LuaStatics::dumpvars); + declareFunc("print", LuaStatics::print); + push("BLUA_THREADS"); + newtable(); + settable(LUA_REGISTRYINDEX); +} + +#define IntPoint(p) ((unsigned int)(lu_mem)(p)) +typedef size_t lu_mem; + +void Balau::Lua::weaken() { + push("BLUA_THREADS"); // -1 = "BLUA_THREADS" + gettable(LUA_REGISTRYINDEX); // -1 = BLUA_THREADS + push((lua_Number) IntPoint(L)); // -2 = BLUA_THREADS, -1 = key-Lt + push(); // -3 = BLUA_THREADS, -2 = key-Lt, -1 = nil + settable(); // -1 = BLUA_THREADS + pop(); +} + +void Balau::Lua::open_base() { + int n = gettop(); + luaopen_base(L); + push("mkdir"); + push(LuaStatics::mkdir); + settable(); + push("time"); + push(LuaStatics::time); + settable(); + push("getenv"); + push(LuaStatics::getenv); + settable(); + push("setenv"); + push(LuaStatics::setenv); + settable(); + push("unsetenv"); + push(LuaStatics::unsetenv); + settable(); + push("print"); + push(LuaStatics::print); + settable(); + while (n < gettop()) remove(n); + push("mkdir"); + push(LuaStatics::mkdir); + settable(LUA_GLOBALSINDEX); + push("time"); + push(LuaStatics::time); + settable(LUA_GLOBALSINDEX); + push("getenv"); + push(LuaStatics::getenv); + settable(LUA_GLOBALSINDEX); + push("setenv"); + push(LuaStatics::setenv); + settable(LUA_GLOBALSINDEX); + push("unsetenv"); + push(LuaStatics::unsetenv); + settable(LUA_GLOBALSINDEX); + push("print"); + push(LuaStatics::print); + settable(LUA_GLOBALSINDEX); +} + +void Balau::Lua::open_table() { + int n = gettop(); + luaopen_table(L); + while (n < gettop()) remove(n); +} + +void Balau::Lua::open_string() { + int n = gettop(); + luaopen_string(L); + push("iconv"); + push(LuaStatics::iconv); + settable(); + while (n < gettop()) remove(n); +} + +void Balau::Lua::open_math() { + int n = gettop(); + luaopen_math(L); + while (n < gettop()) remove(n); +} + +void Balau::Lua::open_debug() { + int n = gettop(); + luaopen_debug(L); + while (n < gettop()) remove(n); +} + +void Balau::Lua::open_jit() { + int n = gettop(); + luaopen_jit(L); + while (n < gettop()) remove(n); +} + +void Balau::Lua::open_bit() { + int n = gettop(); + luaopen_bit(L); + while (n < gettop()) remove(n); +} + +void Balau::Lua::declareFunc(const char * name, lua_CFunction f, int i) { + checkstack(2); + lua_pushstring(L, name); + lua_pushcfunction(L, f); + lua_settable(L, i); +} + +void Balau::Lua::call(const char * f, int i, int nargs) { + checkstack(1); + lua_pushstring(L, f); + lua_gettable(L, i); + lua_insert(L, -1 - nargs); + call(nargs); +} + +void Balau::Lua::settable(int i, bool raw) { + if (raw) { + lua_rawset(L, i); + } else { + lua_settable(L, i); + } +} + +void Balau::Lua::gettable(int i, bool raw) { + if (raw) { + lua_rawget(L, i); + } else { + lua_gettable(L, i); + } +} + +void Balau::Lua::getglobal(const char * name) throw (GeneralException) { + push(name); + gettable(LUA_GLOBALSINDEX); +} + +void Balau::Lua::pushLuaContext() { + String whole_msg; + struct lua_Debug ar; + bool got_error = false; + int level = 0; + + do { + if (lua_getstack(L, level, &ar) == 1) { + if (lua_getinfo(L, "nSl", &ar) != 0) { + push(String("at ") + ar.source + ":" + ar.currentline + " (" + (ar.name ? ar.name : "[top]") + ")"); + } else { + got_error = true; + } + } else { + got_error = true; + } + level++; + } while (!got_error); +} + +void Balau::Lua::error(const char * msg) { + push(msg); + + lua_error(L); +} + +bool Balau::Lua::isobject(int i) { + bool r = false; + if (istable(i)) { + push("__obj"); + gettable(i); + r = isuserdata(); + pop(); + } else { + r = isnil(i); + } + return r; +} + +Balau::String Balau::Lua::tostring(int i) { + const char * r = 0; + size_t l = -1; + switch (type(i)) { + case LUA_TNIL: + r = "(nil)"; + break; + case LUA_TBOOLEAN: + r = toboolean(i) ? "true" : "false"; + break; + case LUA_TNUMBER: + return String(tonumber(i)); + break; + default: + r = lua_tolstring(L, i, &l); + } + return String(r ? r : "", l); +} + +struct LoadF { + Balau::IO f; + char buff[BUFFERSIZE]; +}; + +const char * Balau::LuaStatics::getF(lua_State * L, void * ud, size_t * size) { + LoadF * lf = (LoadF *)ud; + + *size = lf->f->read(lf->buff, BUFFERSIZE); + return (*size > 0) ? lf->buff : NULL; +} + +Balau::String Balau::Lua::escapeString(const String & s) { + String r = ""; + int i; + for (i = 0; i < s.strlen(); i++) { + switch(s[i]) { + case '"': case '\\': + r += '\\'; + r += s[i]; + break; + case '\n': + r += "\\n"; + break; + case '\r': + r += "\\r"; + break; + case '\0': + r += "\\000"; + break; + default: + r += s[i]; + } + } + return r; +} + +void Balau::Lua::load(IO h, bool docall) throw (GeneralException) { + LoadF lf; + int status; + + lf.f = h; + + checkstack(); + String name = h->getName(); + IO i = h; + if (!i.isNull()) + name = String("@") + i->getFName(); + status = lua_load(L, LuaStatics::getF, &lf, name.to_charp()); + + if (status) { + pushLuaContext(); + showerror(); + throw LuaException(String("Error loading lua chunk from Handle `") + h->getName() + "'"); + } + + if (docall) + call(); +} + +void Balau::Lua::load(const String & s, bool docall) throw (GeneralException) { + const char * buf = s.to_charp(); + int status; + + status = luaL_loadbuffer(L, buf, s.strlen(), buf); + + if (status) { + pushLuaContext(); + showerror(); + throw LuaException(String("Error loading lua string `") + s + "'"); + } + + if (docall) + call(); +} + +void Balau::Lua::dumpvars(IO h, const String & prefix, int i) { + h->writeString(prefix); + h->writeString(" = {\n"); + dumpvars_r(h, i); + h->writeString("}\n"); +} + +void Balau::Lua::dumpvars_r(IO h, int i, int depth) throw (GeneralException) { + int j; + String t; + bool dump_value; + + if (lua_type(L, i) != LUA_TTABLE) + throw LuaException("Error dumping variables: variable isn't a table."); + + push(); + + if (i < 0) + i--; + + depth++; + + checkstack(); + while(lua_next(L, i) != 0) { + if (lua_type(L, -2) == LUA_TSTRING) + if (String(lua_tostring(L, -2)) == String("_G")) + continue; + for (j = 0; j < depth; j++) + h->writeString(" "); + + dump_value = true; + + // first, let's dump the key. + switch(lua_type(L, -2)) { + case LUA_TNONE: + throw LuaException("Internal error: got invalid index for key while dumpvars."); + case LUA_TNIL: + throw LuaException("Internal error: got nil index for key while dumpvars."); + case LUA_TNUMBER: + t.set("[%.14g] = ", lua_tonumber(L, -2)); + break; + case LUA_TBOOLEAN: + t = String("[") + (lua_toboolean(L, -2) ? "true" : "false") + "] = "; + break; + case LUA_TSTRING: + t = String("[\"") + escapeString(lua_tostring(L, -2)) + String("\"] = "); + break; + case LUA_TTABLE: + t = "-- [a table]\n"; + dump_value = false; + break; + case LUA_TFUNCTION: + t = "-- [function() ... end]\n"; + dump_value = false; + break; + case LUA_TUSERDATA: + t = "-- [userdata]\n"; + dump_value = false; + break; + case LUA_TTHREAD: + t = "-- [thread]\n"; + dump_value = false; + break; + default: + throw LuaException("Internal error: got unknow index for key while dumpvars."); + } + + // Seems that we can't dump that key. + if (!dump_value) { + pop(); + h->writeString(t); + continue; + } + + // let's look at the value: if it's a function, a userdata or a thread, we can't dump it. + if ((lua_type(L, -1) == LUA_TFUNCTION) || + (lua_type(L, -1) == LUA_TUSERDATA) || + (lua_type(L, -1) == LUA_TTHREAD)) + h->writeString("-- "); + + h->writeString(t); + + // Finally, let's dump the value. + switch(lua_type(L, -1)) { + case LUA_TNONE: + throw LuaException("Internal error: got invalid index for value while dumpvars."); + case LUA_TNIL: + h->writeString("nil,\n"); + case LUA_TNUMBER: + t.set("%.14g,\n", lua_tonumber(L, -1)); + h->writeString(t); + break; + case LUA_TBOOLEAN: + h->writeString(lua_toboolean(L, -1) ? "true" : "false"); + h->writeString(",\n"); + break; + case LUA_TSTRING: + h->writeString("\""); + h->writeString(escapeString(lua_tostring(L, -1))); + h->writeString("\",\n"); + break; + case LUA_TTABLE: + h->writeString("{\n"); + dumpvars_r(h, -1, depth); + for (j = 0; j < depth; j++) + h->writeString(" "); + h->writeString("},\n"); + break; + case LUA_TFUNCTION: + h->writeString("function() ... end\n"); + break; + case LUA_TUSERDATA: + h->writeString("userdata ...\n"); + break; + case LUA_TTHREAD: + h->writeString("thread ...\n"); + break; + default: + throw LuaException("Internal error: got unknow index for value while dumpvars."); + } + + pop(); + } +} + +Balau::Lua Balau::Lua::thread(bool saveit) { + checkstack(); + lua_State * L1 = lua_newthread(L); + if (saveit) { // -1 = thread + push("BLUA_THREADS"); // -2 = thread, -1 = "BLUA_THREADS" + gettable(LUA_REGISTRYINDEX); // -2 = thread, -1 = BLUA_THREADS + push((lua_Number) IntPoint(L1)); // -3 = thread, -2 = BLUA_THREADS, -1 = key-Lt + copy(-3); // -4 = thread, -3 = BLUA_THREADS, -2 = key-Lt, -1 = thread + settable(); // -2 = thread, -1 = BLUA_THREADS + pop(); // -1 = thread + } + return Lua(L1); +} + +Balau::Lua Balau::Lua::thread(const String & code, int nargs, bool saveit) { + Lua L1; + L1 = thread(saveit); + L1.resume(code, nargs); + return L1; +} + +Balau::Lua Balau::Lua::thread(IO h, int nargs, bool saveit) { + Lua L1; + L1 = thread(saveit); + L1.resume(h, nargs); + return L1; +} + +bool Balau::Lua::resume(int nargs) throw (GeneralException) { + int r; + + r = lua_resume(L, nargs); + + if ((r == 0) || (r == LUA_YIELD)) + return 0; + + pushLuaContext(); + showerror(); + + switch(r) { + case 0: + return false; + case LUA_YIELD: + return true; + case LUA_ERRRUN: + throw LuaException("Runtime error while running LUA code."); + case LUA_ERRMEM: + throw LuaException("Memory allocation error while running LUA code."); + case LUA_ERRERR: + throw LuaException("Error in Error function."); + case LUA_ERRSYNTAX: + throw LuaException("Syntax error in Lua code."); + default: + throw LuaException(String("Unknow error while running LUA code (err code: ") + String(r) + ")"); + } +} + +bool Balau::Lua::resume(IO h, int nargs) { + bool r; + + load(h, false); + r = resume(nargs); + + return r; +} + +bool Balau::Lua::resume(const String & s, int nargs) { + bool r; + + load(s, false); + r = resume(nargs); + + return r; +} + +void Balau::Lua::showstack(int level) { + int n = lua_gettop(L); + int i; + String t; + + if (n == 0) { + Printer::log(level, "Stack empty\n"); + return; + } + + for (i = 1; i <= n; i++) { + switch(lua_type(L, i)) { + case LUA_TNONE: + t = "Invalid"; + break; + case LUA_TNIL: + t = "(Nil)"; + break; + case LUA_TNUMBER: + t.set("(Number) %f", lua_tonumber(L, i)); + break; + case LUA_TBOOLEAN: + t = String("(Bool) ") + (lua_toboolean(L, i) ? "true" : "false"); + break; + case LUA_TSTRING: + t = String("(String) ") + lua_tostring(L, i); + break; + case LUA_TTABLE: + t = "(Table)"; + break; + case LUA_TFUNCTION: + t = "(Function)"; + break; + default: + t = "Unknown"; + } + + Printer::log(level, String(i) + ": " + t + "\n"); + } +} + +void Balau::Lua::showerror() { + Printer::log(M_ERROR, "Lua object: Got an LUA error, inspecting stack.\n"); + + showstack(M_ERROR); +} + +void Balau::LuaObject::push(Lua & L) throw (GeneralException) { + if (m_pushed && m_wantsDestruct) + throw GeneralException("Error: object is owned by the LUA script and can not be pushed."); + L.newtable(); + pushMembers(L); + m_pushed = true; +} + +void Balau::LuaObject::pushMe(Lua & L, void * o, const char * objname, bool obj) { + ObjData * u; + L.push("__obj"); + u = (ObjData *) L.newuser(sizeof(ObjData)); + u->ptr = o; + u->isObj = obj; + L.settable(-3, true); + if (objname && *objname) { + L.push("__objname"); + L.push(objname); + L.settable(-3, true); + } +} + +void * Balau::LuaObject::getMeInternal(Lua & L, int i) { + ObjData * u = NULL; + + if (L.istable(i)) { + L.push("__obj"); + L.gettable(i, true); + if (!(u = (ObjData *) L.touserdata())) + L.error("Table is not an object."); + if (!u->ptr) + L.error("Object already destroyed."); + L.pop(); + } else if (L.isnil(i)) { + u = NULL; + } else { + L.error("Not an object (not even a table)."); + } + + return u ? u->ptr : NULL; +} + +void Balau::LuaObject::pushIt(Lua & L, const char * s, lua_CFunction f) { + L.push(s); + L.push(f); + L.settable(-3, true); +} + +void Balau::LuaObject::pushMeta(Lua & L, const char * s, lua_CFunction f) { + if (!L.getmetatable()) + L.newtable(); + L.push(s); + L.push(f); + L.settable(); + L.setmetatable(); +} + +void Balau::LuaObject::pushDestruct(Lua & L) throw (GeneralException) { + if (m_pushed) + throw GeneralException("Error: can't push destructor, object already pushed"); + push(L); + L.push("__obj"); + L.gettable(-2, true); + pushMeta(L, "__gc", LuaStatics::collector); + L.pop(); + pushIt(L, "destroy", LuaStatics::destructor); + + m_wantsDestruct = true; +} diff --git a/tests/test-Lua.cc b/tests/test-Lua.cc new file mode 100644 index 0000000..86baffb --- /dev/null +++ b/tests/test-Lua.cc @@ -0,0 +1,33 @@ +#include +#include + +BALAU_STARTUP; + +using namespace Balau; + +void MainTask::Do() { + Printer::log(M_STATUS, "Test::Lua running."); + + Lua L; + + // yeah, they really should be the same thing. + Assert(sizeof(L) == sizeof(lua_State *)); + + L.open_base(); + L.open_table(); + L.open_string(); + L.open_math(); + L.open_debug(); + L.open_bit(); + L.open_jit(); + + Assert(L.gettop() == 0); + L.load("return 42"); + Assert(L.gettop() == 1); + int r = L.tonumber(); + Assert(r == 42); + L.pop(); + Assert(L.gettop() == 0); + + Printer::log(M_STATUS, "Test::Lua passed."); +} -- cgit v1.2.3