From 8fa89822600fcec8a9e816c73e8e24a82cdfb12c Mon Sep 17 00:00:00 2001 From: nitrocaster Date: Sat, 12 Dec 2015 18:58:15 +0300 Subject: [PATCH] Implement script bingings dumper. --- src/xrScriptEngine/BindingsDumper.cpp | 397 ++++++++++++++++++ src/xrScriptEngine/BindingsDumper.hpp | 53 +++ src/xrScriptEngine/script_engine.cpp | 88 ++-- src/xrScriptEngine/script_engine.hpp | 1 + src/xrScriptEngine/xrScriptEngine.vcxproj | 2 + .../xrScriptEngine.vcxproj.filters | 6 + 6 files changed, 511 insertions(+), 36 deletions(-) create mode 100644 src/xrScriptEngine/BindingsDumper.cpp create mode 100644 src/xrScriptEngine/BindingsDumper.hpp diff --git a/src/xrScriptEngine/BindingsDumper.cpp b/src/xrScriptEngine/BindingsDumper.cpp new file mode 100644 index 00000000000..3e41b4bbdf7 --- /dev/null +++ b/src/xrScriptEngine/BindingsDumper.cpp @@ -0,0 +1,397 @@ +#include "pch.hpp" +#include "BindingsDumper.hpp" +#include + +int BindingsDumper::GetIdentSize() const +{ return options.ShiftWidth*shiftLevel; } + +void BindingsDumper::Print(const char *s) +{ writer->w(s, xr_strlen(s)); } + +void BindingsDumper::Print(const char *s, int len) +{ writer->w(s, len); } + +void BindingsDumper::Printf(const char *format, ...) +{ + va_list args; + va_start(args, format); + writer->VPrintf(format, args); + va_end(args); +} + +void BindingsDumper::PrintIndented(const char *s) +{ writer->w_printf("%*s" "%s", GetIdentSize(), "", s); } + +void BindingsDumper::PrintfIndented(const char *format, ...) +{ + writer->w_printf("%*s", GetIdentSize(), ""); + va_list args; + va_start(args, format); + writer->VPrintf(format, args); + va_end(args); +} + +namespace +{ +luabind::detail::function_object* get_upvalue_function(lua_State *ls, int n) +{ + using namespace luabind::detail; + function_object *f = nullptr; + if (lua_getupvalue(ls, -1, n)) + { + int ltype = lua_type(ls, -1); + if (ltype==LUA_TFUNCTION) + { + if (lua_getupvalue(ls, -1, 1)) + { + if (lua_type(ls, -1)==LUA_TUSERDATA) + f = *static_cast(lua_touserdata(ls, -1)); + lua_pop(ls, 1); // upvalue + } + } + lua_pop(ls, 1); // upvalue + } + return f; +} + +void ReplaceArg(lua_State *ls, int argIndex, const char *subst) +{ + lua_pushstring(ls, subst); + lua_replace(ls, argIndex-1); +} + +int StripArg(lua_State *ls, int argIndex, bool commaOnly = true) +{ + int removed = 1; + lua_remove(ls, argIndex); + const char *comma = lua_tostring(ls, argIndex+1); + if (!commaOnly || !xr_strcmp(comma, ",")) + { + lua_remove(ls, argIndex+1); + removed++; + } + return removed; +} +} + +void BindingsDumper::PrintFunction(SignatureFormatter formatter, const void *fcontext /*= nullptr*/) +{ + using namespace luabind::detail; + bool done = false; + int cfunc = lua_iscfunction(ls, -1); + int luabindFunc = is_luabind_function(ls, -1); + bool hasupvalue = lua_getupvalue(ls, -1, 1)!=nullptr; + if (hasupvalue) + { + if (luabindFunc) + { + if (lua_type(ls, -1)==LUA_TUSERDATA) + { + auto fobj = *static_cast(lua_touserdata(ls, -1)); + if (formatter) + { + SignatureFormatterParams params; + params.Function = fobj; + params.Context = fcontext; + (this->*formatter)(params); + } + else + { + int signatureLen = fobj->format_signature(ls, fobj->name.c_str()); + auto signature = lua_tostring(ls, -1); + PrintfIndented("%s;\n", signature); + lua_pop(ls, signatureLen); + } + done = true; + } + } + lua_pop(ls, 1); // pop upvalue + } + if (cfunc && !done && hasupvalue) // property + { + const char *propName = lua_tostring(ls, -2); + function_object *getter = get_upvalue_function(ls, 1); + function_object *setter = get_upvalue_function(ls, 2); + R_ASSERT(getter); + int signatureLen = getter->format_signature(ls, "#"); + const char *signature = lua_tostring(ls, -1); + int typeLen = std::strchr(signature, '#')-signature-1; + PrintIndented(""); + Print(signature, typeLen); + Printf(" %s { get;", propName); + if (setter) + Print(" set;"); + Print(" }\n"); + lua_pop(ls, signatureLen); + } +} + +void BindingsDumper::FormatStaticFunction(const SignatureFormatterParams ¶ms) +{ + using namespace luabind::detail; + function_object *fobj = params.Function; + int signatureLen = fobj->format_signature(ls, fobj->name.c_str()); + const char *signature = lua_tostring(ls, -1); + PrintfIndented("static %s;\n", signature); + lua_pop(ls, signatureLen); +}; + +void BindingsDumper::PrintStaticFunction() +{ PrintFunction(&BindingsDumper::FormatStaticFunction); } + +void BindingsDumper::FormatMemberFunction(const SignatureFormatterParams ¶ms) +{ + auto refClassName = static_cast(params.Context); + bool stripReturnValue = false; // for constructors and operators + xr_string funcName; + auto refFuncName = params.Function->name; + if (refFuncName=="__init") + { + funcName = refClassName; + stripReturnValue = true; + } + else // check operators + { + auto it = operatorSubst.find(refFuncName); + if (it!=operatorSubst.end()) + { + funcName = it->second; + stripReturnValue = true; + } + else + funcName = refFuncName.c_str(); + } + bool concat = !(options.IgnoreDerived || options.StripThis || stripReturnValue); + int signLen = params.Function->format_signature(ls, funcName.c_str(), concat); + if (!concat) + { + int argCount = (signLen-4)/2; + R_ASSERT(argCount>0); + // -n+0 -n+1 -n+2 -n+3 -n+4 -1 + // [return_type][ ][func_name][(][arg1][,][arg2]...[)] + int offset = 0; + if (stripReturnValue) + { + offset = StripArg(ls, -signLen, false); + signLen -= offset; + } + int argIndex = -signLen+4-offset; + xr_string arg = lua_tostring(ls, argIndex); + xr_string className = refClassName; + // check if arg matches 'className[ const]{*|&}' + std::regex matcher(className+"( const)?(\\*|&)$"); + if (std::regex_match(arg, matcher)) // non-derived member function + { + if (options.StripThis) + signLen -= StripArg(ls, argIndex); + } + else + { + // check special cases: opertators and constructors + // void __tostring(lua_State*, ClassName&); // operator + // void __init(luabind::argument const&, int); // constructor + if (arg=="lua_State*" && argCount>1) // operator? + { + // 1] check next argument: + int nextArgIndex = argIndex+2; + const char *nextArg = lua_tostring(ls, nextArgIndex); + if (className.append("&")!=nextArg) // derived? + { + // if next!=className && ignoreDerived => ignore + if (options.IgnoreDerived) + { + lua_pop(ls, signLen); + return; + } + } + // 2] remove arg+comma + signLen -= StripArg(ls, argIndex); + // 3] if stripThis => remove next + argIndex = nextArgIndex; + } + else if (arg=="luabind::argument const&") // constructor? + { + if (!options.StripThis) + ReplaceArg(ls, argIndex, className.append("&").c_str()); + } + else if (options.IgnoreDerived) // some derived function, ignore? + { + lua_pop(ls, signLen); + return; + } + if (options.StripThis) + signLen -= StripArg(ls, argIndex); + } + lua_concat(ls, signLen); + } + const char *signature = lua_tostring(ls, -1); + PrintfIndented("%s;\n", signature); + lua_pop(ls, 1); // pop concatenated signature +}; + +void BindingsDumper::PrintMemberFunction(const char *className) +{ PrintFunction(&BindingsDumper::FormatMemberFunction, className); } + +void BindingsDumper::PrintFunction() +{ PrintFunction(nullptr); } + +void BindingsDumper::PrintIntConstant(const char *name, int value) +{ PrintfIndented("const int %s = %d;\n", name, value); } + +void BindingsDumper::PrintClass() +{ + using namespace luabind; + using namespace luabind::detail; + auto crep = static_cast(lua_touserdata(ls, -1)); + bool cppClass = crep->get_class_type()==class_rep::cpp_class; + PrintIndented(cppClass ? "[cpp]\n" : "[lua]\n"); + PrintfIndented("class %s", crep->name()); + const auto &bases = crep->bases(); + size_t baseCount = bases.size(); + if (baseCount) + Print(" : "); + for (u32 i = 0; iname(); + if (!*baseName) + baseName = ""; + Print(baseName); + } + Print("\n"); + PrintIndented("{\n"); + shiftLevel++; + // print static members (static functions + nested classes) + crep->get_default_table(ls); + object staticMembers(from_stack(ls, -1)); + for (luabind::iterator it(staticMembers), end; it!=end; it++) + { + auto proxy = *it; + int prev = lua_gettop(ls); + proxy.push(ls); + if (is_class_rep(ls, -1)) + PrintClass(); + else if (is_luabind_function(ls, -1, false)) + PrintStaticFunction(); + lua_pop(ls, 1); + R_ASSERT(lua_gettop(ls)==prev); + } + lua_pop(ls, 1); // pop default table + // print constants + auto &constants = crep->static_constants(); + for (auto &c : constants) + PrintIntConstant(c.first, c.second); + // print functions and properties + crep->get_table(ls); + object members(from_stack(ls, -1)); + for (luabind::iterator it(members), end; it!=end; it++) + { + auto proxy = *it; + int prev = lua_gettop(ls); + proxy.push(ls); + int ltype = luabind::type(proxy); + if (ltype==LUA_TFUNCTION) // XXX: print class members in reverse order + PrintMemberFunction(crep->name()); + lua_pop(ls, 1); + R_ASSERT(lua_gettop(ls)==prev); + } + lua_pop(ls, 1); // pop table + shiftLevel--; + PrintIndented("}\n"); +} + +void BindingsDumper::PrintNamespace(luabind::object &namesp) +{ + using namespace luabind; + using namespace luabind::detail; + int scopeFunctions = 0, scopeClasses = 0, scopeNamespaces = 0; + for (luabind::iterator it(namesp), end; it!=end; it++) + { + auto proxy = *it; + int ltype = luabind::type(proxy); + switch (ltype) + { + case LUA_TFUNCTION: // free function + scopeFunctions++; + functions.push(it); + break; + case LUA_TUSERDATA: // class + scopeClasses++; + classes.push(it); + break; + case LUA_TTABLE: // namespace + scopeNamespaces++; + namespaces.push(it); + break; + default: + PrintfIndented("[?] ltype = %s\n", lua_typename(ls, ltype)); + break; + } + } + for (int i = 0; i subst[] = + { + {"__add", "operator+"}, + {"__sub", "operator-"}, + {"__mul", "operator*"}, + {"__div", "operator/"}, + {"__pow", "operator^"}, + {"__lt", "operator<"}, + {"__le", "operator<="}, + {"__gt", "operator>"}, + {"__ge", "operator>="}, + {"__eq", "operator=="}, + {"__tostring", "operator string"} + }; + const u32 substCount = sizeof(subst)/sizeof(*subst); + for (u32 i = 0; i functions; + xr_stack classes; + xr_stack namespaces; + xr_map operatorSubst; + +private: + struct SignatureFormatterParams + { + luabind::detail::function_object *Function; + const void *Context; + }; + + using SignatureFormatter = void(BindingsDumper::*)(const SignatureFormatterParams ¶ms); + + int GetIdentSize() const; + void Print(const char *s); + void Print(const char *s, int len); + void Printf(const char *format, ...); + void PrintIndented(const char *s); + void PrintfIndented(const char *format, ...); + void PrintFunction(SignatureFormatter formatter, const void *fcontext = nullptr); + void FormatStaticFunction(const SignatureFormatterParams ¶ms); + void PrintStaticFunction(); + void FormatMemberFunction(const SignatureFormatterParams ¶ms); + void PrintMemberFunction(const char* className); + void PrintFunction(); + void PrintIntConstant(const char *name, int value); + void PrintClass(); + void PrintNamespace(luabind::object &namesp); + +public: + BindingsDumper(); + void Dump(lua_State *luaState, IWriter *outStream, const Options &opt); +}; diff --git a/src/xrScriptEngine/script_engine.cpp b/src/xrScriptEngine/script_engine.cpp index 7453d0926e7..6dcbc770c29 100644 --- a/src/xrScriptEngine/script_engine.cpp +++ b/src/xrScriptEngine/script_engine.cpp @@ -17,6 +17,7 @@ #endif #include "Include/xrAPI/xrAPI.h" #include "ScriptExporter.hpp" +#include "BindingsDumper.hpp" #ifdef USE_DEBUGGER #include "script_debugger.hpp" #endif @@ -251,36 +252,6 @@ void CScriptEngine::reinit() return; } RegisterState(m_virtual_machine, this); - // initialize lua standard library functions - struct luajit - { - static void open_lib(lua_State *L, pcstr module_name, lua_CFunction function) - { - lua_pushcfunction(L, function); - lua_pushstring(L, module_name); - lua_call(L, 1, 0); - } - }; - - luajit::open_lib(lua(), "", luaopen_base); - luajit::open_lib(lua(), LUA_LOADLIBNAME, luaopen_package); - luajit::open_lib(lua(), LUA_TABLIBNAME, luaopen_table); - luajit::open_lib(lua(), LUA_IOLIBNAME, luaopen_io); - luajit::open_lib(lua(), LUA_OSLIBNAME, luaopen_os); - luajit::open_lib(lua(), LUA_MATHLIBNAME, luaopen_math); - luajit::open_lib(lua(), LUA_STRLIBNAME, luaopen_string); -#ifdef DEBUG - luajit::open_lib(lua(), LUA_DBLIBNAME, luaopen_debug); -#endif - if (!strstr(Core.Params, "-nojit")) - { - luajit::open_lib(lua(), LUA_JITLIBNAME, luaopen_jit); -#ifndef DEBUG - put_function(lua(), opt_lua_binary, sizeof(opt_lua_binary), "jit.opt"); - put_function(lua(), opt_inline_lua_binary, sizeof(opt_lua_binary), "jit.opt_inline"); - dojitopt(lua(), "2"); -#endif - } if (strstr(Core.Params, "-_g")) file_header = file_header_new; else @@ -1032,6 +1003,57 @@ void CScriptEngine::init(ExporterFunc exporterFunc, bool loadGlobalNamespace) m_lua_studio_world->remove(lua()); #endif reinit(); + luabind::open(lua()); + // XXX: temporary workaround to preserve backwards compatibility with game scripts + luabind::disable_super_deprecation(); + setup_callbacks(); + if (exporterFunc) + exporterFunc(lua()); + if (std::strstr(Core.Params, "-dump_bindings") && !bindingsDumped) + { + bindingsDumped = true; + static int dumpId = 1; + string_path filePath; + xr_sprintf(filePath, "ScriptBindings_%d.txt", dumpId++); + FS.update_path(filePath, "$app_data_root$", filePath); + IWriter *writer = FS.w_open(filePath); + BindingsDumper dumper; + BindingsDumper::Options options = {}; + options.ShiftWidth = 4; + options.IgnoreDerived = true; + options.StripThis = true; + dumper.Dump(lua(), writer, options); + FS.w_close(writer); + } + // initialize lua standard library functions + struct luajit + { + static void open_lib(lua_State *L, pcstr module_name, lua_CFunction function) + { + lua_pushcfunction(L, function); + lua_pushstring(L, module_name); + lua_call(L, 1, 0); + } + }; + luajit::open_lib(lua(), "", luaopen_base); + luajit::open_lib(lua(), LUA_LOADLIBNAME, luaopen_package); + luajit::open_lib(lua(), LUA_TABLIBNAME, luaopen_table); + luajit::open_lib(lua(), LUA_IOLIBNAME, luaopen_io); + luajit::open_lib(lua(), LUA_OSLIBNAME, luaopen_os); + luajit::open_lib(lua(), LUA_MATHLIBNAME, luaopen_math); + luajit::open_lib(lua(), LUA_STRLIBNAME, luaopen_string); +#ifdef DEBUG + luajit::open_lib(lua(), LUA_DBLIBNAME, luaopen_debug); +#endif + if (!strstr(Core.Params, "-nojit")) + { + luajit::open_lib(lua(), LUA_JITLIBNAME, luaopen_jit); +#ifndef DEBUG + put_function(lua(), opt_lua_binary, sizeof(opt_lua_binary), "jit.opt"); + put_function(lua(), opt_inline_lua_binary, sizeof(opt_lua_binary), "jit.opt_inline"); + dojitopt(lua(), "2"); +#endif + } #ifdef USE_LUA_STUDIO if (m_lua_studio_world || strstr(Core.Params, "-lua_studio")) { @@ -1045,12 +1067,6 @@ void CScriptEngine::init(ExporterFunc exporterFunc, bool loadGlobalNamespace) } } #endif - luabind::open(lua()); - // XXX: temporary workaround to preserve backwards compatibility with game scripts - luabind::disable_super_deprecation(); - setup_callbacks(); - if (exporterFunc) - exporterFunc(lua()); setup_auto_load(); #ifdef DEBUG m_stack_is_ready = true; diff --git a/src/xrScriptEngine/script_engine.hpp b/src/xrScriptEngine/script_engine.hpp index f060855910b..c5897d2ae7e 100644 --- a/src/xrScriptEngine/script_engine.hpp +++ b/src/xrScriptEngine/script_engine.hpp @@ -81,6 +81,7 @@ class XRSCRIPTENGINE_API CScriptEngine u32 m_last_no_file_length; static string4096 g_ca_stdout; bool logReenterability = false; + bool bindingsDumped = false; protected: CScriptProcessStorage m_script_processes; diff --git a/src/xrScriptEngine/xrScriptEngine.vcxproj b/src/xrScriptEngine/xrScriptEngine.vcxproj index 579c1ce6a0b..fee3c4c060d 100644 --- a/src/xrScriptEngine/xrScriptEngine.vcxproj +++ b/src/xrScriptEngine/xrScriptEngine.vcxproj @@ -217,6 +217,7 @@ + @@ -248,6 +249,7 @@ + Create diff --git a/src/xrScriptEngine/xrScriptEngine.vcxproj.filters b/src/xrScriptEngine/xrScriptEngine.vcxproj.filters index fb5586e4cd5..260f0c20c80 100644 --- a/src/xrScriptEngine/xrScriptEngine.vcxproj.filters +++ b/src/xrScriptEngine/xrScriptEngine.vcxproj.filters @@ -117,6 +117,9 @@ Engine + + Engine + @@ -158,5 +161,8 @@ LuaStudio + + Engine + \ No newline at end of file