From 48e4717dd2cf6c0e2a6ae88ae7bde20e01c0b610 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 16 Apr 2012 10:59:55 +0400 Subject: [PATCH 1/5] Try working around some msvc problems. --- library/LuaTools.cpp | 4 ++-- library/PluginManager.cpp | 2 +- library/include/LuaTools.h | 8 +++++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index bf40e4041..b4f959446 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -981,7 +981,7 @@ void DFHack::Lua::InvokeEvent(color_ostream &out, lua_State *state, void *key, i set_dfhack_output(state, cur_out); } -void DFHack::Lua::CreateEvent(lua_State *state, void *key) +void DFHack::Lua::MakeEvent(lua_State *state, void *key) { lua_rawgetp(state, LUA_REGISTRYINDEX, key); @@ -1009,7 +1009,7 @@ void DFHack::Lua::Notification::bind(lua_State *state, void *key) void DFHack::Lua::Notification::bind(lua_State *state, const char *name) { - CreateEvent(state, this); + MakeEvent(state, this); if (handler) { diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index edc7fb9f8..1821e18d7 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -537,7 +537,7 @@ void Plugin::open_lua(lua_State *state, int table) { for (auto it = lua_events.begin(); it != lua_events.end(); ++it) { - Lua::CreateEvent(state, it->second); + Lua::MakeEvent(state, it->second); push_function(state, &it->second->handler); lua_rawsetp(state, -2, NULL); diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 549bdaaf0..7e38f5bb3 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -175,6 +175,7 @@ namespace DFHack {namespace Lua { /** * Push utility functions */ +#if 0 #define NUMBER_PUSH(type) inline void Push(lua_State *state, type value) { lua_pushnumber(state, value); } NUMBER_PUSH(char) NUMBER_PUSH(int8_t) NUMBER_PUSH(uint8_t) @@ -183,6 +184,11 @@ namespace DFHack {namespace Lua { NUMBER_PUSH(int64_t) NUMBER_PUSH(uint64_t) NUMBER_PUSH(float) NUMBER_PUSH(double) #undef NUMBER_PUSH +#else + template inline void Push(lua_State *state, T value) { + lua_pushnumber(state, lua_Number(value)); + } +#endif inline void Push(lua_State *state, bool value) { lua_pushboolean(state, value); } @@ -212,7 +218,7 @@ namespace DFHack {namespace Lua { DFHACK_EXPORT bool IsCoreContext(lua_State *state); DFHACK_EXPORT int NewEvent(lua_State *state); - DFHACK_EXPORT void CreateEvent(lua_State *state, void *key); + DFHACK_EXPORT void MakeEvent(lua_State *state, void *key); DFHACK_EXPORT void InvokeEvent(color_ostream &out, lua_State *state, void *key, int num_args); /** From 3e4863bc80de2844e795a086599c68b6506ca57f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 16 Apr 2012 14:45:04 +0400 Subject: [PATCH 2/5] Integrate coroutines with table-based error handling. Properly attach stack traces to errors passing the resume boundary. Replaces coroutine.resume and coroutine.wrap with appropriately modified versions, and adds a Lua::SafeResume function for C++. --- library/LuaTools.cpp | 268 +++++++++++++++++++++++++++++-------- library/include/LuaTools.h | 24 +++- 2 files changed, 236 insertions(+), 56 deletions(-) diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index b4f959446..564384ef3 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -70,6 +70,10 @@ inline void AssertCoreSuspend(lua_State *state) assert(!Lua::IsCoreContext(state) || DFHack::Core::getInstance().isSuspended()); } +/* + * Public DF object reference handling API + */ + void DFHack::Lua::PushDFObject(lua_State *state, type_identity *type, void *ptr) { push_object_internal(state, type, ptr, false); @@ -110,6 +114,10 @@ void *DFHack::Lua::CheckDFObject(lua_State *state, type_identity *type, int val_ return rv; } +/* + * Console I/O wrappers + */ + static int DFHACK_OSTREAM_TOKEN = 0; color_ostream *DFHack::Lua::GetOutput(lua_State *L) @@ -263,6 +271,10 @@ static int lua_dfhack_lineedit(lua_State *S) } } +/* + * Exception handling + */ + static int DFHACK_EXCEPTION_META_TOKEN = 0; static void error_tostring(lua_State *L, bool keep_old = false) @@ -304,8 +316,21 @@ static void report_error(lua_State *L, color_ostream *out = NULL) lua_pop(L, 1); } -static bool convert_to_exception(lua_State *L) +static bool convert_to_exception(lua_State *L, int slevel, lua_State *thread = NULL) { + if (!thread) + thread = L; + + if (thread == L) + lua_pushthread(L); + else + { + lua_pushthread(thread); + lua_xmove(thread, L, 1); + } + + lua_swap(L); + int base = lua_gettop(L); bool force_unknown = false; @@ -316,13 +341,33 @@ static bool convert_to_exception(lua_State *L) bool is_exception = lua_rawequal(L, -1, -2); lua_settop(L, base); - // If it is an exception, return as is if (is_exception) - return false; + { + // If it is an exception from the same thread, return as is + lua_getfield(L, base, "thread"); + bool same_thread = lua_rawequal(L, -1, base-1); + lua_settop(L, base); + + if (same_thread) + { + lua_remove(L, base-1); + return false; + } - force_unknown = true; + // Create a new exception for this thread + lua_newtable(L); + luaL_where(L, 1); + lua_pushstring(L, "coroutine resume failed"); + lua_concat(L, 2); + lua_setfield(L, -2, "message"); + lua_swap(L); + lua_setfield(L, -2, "cause"); + } + else + force_unknown = true; } + // Promote non-table to table, and do some sanity checks if (!lua_istable(L, base) || force_unknown) { lua_newtable(L); @@ -352,6 +397,10 @@ static bool convert_to_exception(lua_State *L) lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN); lua_setmetatable(L, base); + lua_swap(L); + lua_setfield(L, -2, "thread"); + luaL_traceback(L, thread, NULL, slevel); + lua_setfield(L, -2, "stacktrace"); return true; } @@ -360,13 +409,7 @@ static int dfhack_onerror(lua_State *L) luaL_checkany(L, 1); lua_settop(L, 1); - bool changed = convert_to_exception(L); - if (!changed) - return 1; - - luaL_traceback(L, L, NULL, 1); - lua_setfield(L, 1, "stacktrace"); - + convert_to_exception(L, 1); return 1; } @@ -403,14 +446,8 @@ static void push_simple_error(lua_State *L, const char *str) { lua_pushstring(L, str); - if (lua_checkstack(L, 5)) - convert_to_exception(L); - if (lua_checkstack(L, LUA_MINSTACK)) - { - luaL_traceback(L, L, NULL, 1); - lua_setfield(L, -2, "stacktrace"); - } + convert_to_exception(L, 0); } static bool do_finish_pcall(lua_State *L, bool success, int base = 1, int space = 2) @@ -481,6 +518,134 @@ bool DFHack::Lua::SafeCall(color_ostream &out, lua_State *L, int nargs, int nres return ok; } +// Copied from lcorolib.c, with error handling modifications +static int resume_helper(lua_State *L, lua_State *co, int narg, int nres) +{ + if (!co) { + lua_pop(L, narg); + push_simple_error(L, "coroutine expected in resume"); + return LUA_ERRRUN; + } + if (!lua_checkstack(co, narg)) { + lua_pop(L, narg); + push_simple_error(L, "too many arguments to resume"); + return LUA_ERRRUN; + } + if (lua_status(co) == LUA_OK && lua_gettop(co) == 0) { + lua_pop(L, narg); + push_simple_error(L, "cannot resume dead coroutine"); + return LUA_ERRRUN; + } + lua_xmove(L, co, narg); + int status = lua_resume(co, L, narg); + if (status == LUA_OK || status == LUA_YIELD) + { + int nact = lua_gettop(co); + if (nres == LUA_MULTRET) + nres = nact; + else if (nres < nact) + lua_settop(co, nact = nres); + if (!lua_checkstack(L, nres + 1)) { + lua_settop(co, 0); + push_simple_error(L, "too many results to resume"); + return LUA_ERRRUN; + } + int ttop = lua_gettop(L) + nres; + lua_xmove(co, L, nact); + lua_settop(L, ttop); + } + else + { + lua_xmove(co, L, 1); + + // A cross-thread version of dfhack_onerror + if (lua_checkstack(L, LUA_MINSTACK)) + convert_to_exception(L, 0, co); + } + return status; +} + +static int dfhack_coresume (lua_State *L) { + lua_State *co = lua_tothread(L, 1); + luaL_argcheck(L, !!co, 1, "coroutine expected"); + int r = resume_helper(L, co, lua_gettop(L) - 1, LUA_MULTRET); + bool ok = (r == LUA_OK || r == LUA_YIELD); + lua_pushboolean(L, ok); + lua_insert(L, 2); + return lua_gettop(L) - 1; +} + +static int dfhack_saferesume (lua_State *L) { + lua_State *co = lua_tothread(L, 1); + luaL_argcheck(L, !!co, 1, "coroutine expected"); + int r = resume_helper(L, co, lua_gettop(L) - 1, LUA_MULTRET); + bool ok = (r == LUA_OK || r == LUA_YIELD); + lua_pushboolean(L, ok); + lua_insert(L, 2); + if (!ok) + report_error(L); + return lua_gettop(L) - 1; +} + +static int dfhack_coauxwrap (lua_State *L) { + lua_State *co = lua_tothread(L, lua_upvalueindex(1)); + int r = resume_helper(L, co, lua_gettop(L), LUA_MULTRET); + if (r == LUA_OK || r == LUA_YIELD) + return lua_gettop(L); + else + return lua_error(L); +} + +static int dfhack_cowrap (lua_State *L) { + luaL_checktype(L, 1, LUA_TFUNCTION); + lua_settop(L, 1); + Lua::NewCoroutine(L); + lua_pushcclosure(L, dfhack_coauxwrap, 1); + return 1; +} + +lua_State *DFHack::Lua::NewCoroutine(lua_State *L) { + lua_State *NL = lua_newthread(L); + lua_swap(L); + lua_xmove(L, NL, 1); /* move function from L to NL */ + return NL; +} + +int DFHack::Lua::SafeResume(color_ostream &out, lua_State *from, lua_State *thread, int nargs, int nres, bool perr) +{ + AssertCoreSuspend(from); + + color_ostream *cur_out = Lua::GetOutput(from); + set_dfhack_output(from, &out); + + int rv = resume_helper(from, thread, nargs, nres); + + if (rv != LUA_OK && rv != LUA_YIELD && perr) + { + report_error(from, &out); + lua_pop(from, 1); + } + + set_dfhack_output(from, cur_out); + + return rv; +} + +int DFHack::Lua::SafeResume(color_ostream &out, lua_State *from, int nargs, int nres, bool perr) +{ + int base = lua_gettop(from) - nargs; + lua_State *thread = lua_tothread(from, base); + + int rv = SafeResume(out, from, thread, nargs, nres, perr); + + lua_remove(from, base); + return rv; +} + +/* + * Module loading + */ + static int DFHACK_LOADED_TOKEN = 0; bool DFHack::Lua::PushModule(color_ostream &out, lua_State *state, const char *module) @@ -586,29 +751,28 @@ bool DFHack::Lua::SafeCallString(color_ostream &out, lua_State *state, const std return Lua::SafeCall(out, state, nargs, nres, perr); } +/* + * Coroutine interactive query loop + */ + static int resume_query_loop(color_ostream &out, - lua_State *thread, lua_State *state, bool start, + lua_State *thread, lua_State *state, int nargs, std::string &prompt, std::string &histfile) { - color_ostream *cur_out = Lua::GetOutput(state); - set_dfhack_output(state, &out); - - int rv = lua_resume(thread, state, lua_gettop(thread)-(start?1:0)); - - set_dfhack_output(state, cur_out); + int rv = Lua::SafeResume(out, state, thread, nargs, 2); if (rv == LUA_YIELD || rv == LUA_OK) { - lua_settop(thread, 2); - prompt = ifnull(lua_tostring(thread, 1), ""); - histfile = ifnull(lua_tostring(thread, 2), ""); + prompt = ifnull(lua_tostring(state, -2), ""); + histfile = ifnull(lua_tostring(state, -1), ""); + lua_pop(state, 2); } return rv; } bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state, - bool (*init)(color_ostream&, lua_State*, lua_State*, void*), + bool (*init)(color_ostream&, lua_State*, void*), void *arg) { if (!out.is_console()) @@ -630,18 +794,19 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state, CoreSuspender suspend; int base = lua_gettop(state); - thread = lua_newthread(state); - if (!init(out, state, thread, arg)) + if (!init(out, state, arg)) { lua_settop(state, base); return false; } - lua_settop(state, base+1); + lua_pushvalue(state, base+1); + lua_remove(state, base+1); + thread = Lua::NewCoroutine(state); lua_rawsetp(state, LUA_REGISTRYINDEX, thread); - rv = resume_query_loop(out, thread, state, true, prompt, histfile); + rv = resume_query_loop(out, thread, state, lua_gettop(state)-base, prompt, histfile); } while (rv == LUA_YIELD) @@ -668,10 +833,8 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state, { CoreSuspender suspend; - lua_settop(thread, 0); - lua_pushlstring(thread, curline.data(), curline.size()); - - rv = resume_query_loop(out, thread, state, false, prompt, histfile); + lua_pushlstring(state, curline.data(), curline.size()); + rv = resume_query_loop(out, thread, state, 1, prompt, histfile); } } @@ -681,20 +844,6 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state, { CoreSuspender suspend; - if (rv != LUA_OK) - { - lua_xmove(thread, state, 1); - - if (convert_to_exception(state)) - { - luaL_traceback(state, thread, NULL, 1); - lua_setfield(state, -2, "stacktrace"); - } - - report_error(state, &out); - lua_pop(state, 1); - } - lua_pushnil(state); lua_rawsetp(state, LUA_REGISTRYINDEX, thread); } @@ -709,15 +858,14 @@ namespace { }; } -static bool init_interpreter(color_ostream &out, lua_State *state, lua_State *thread, void *info) +static bool init_interpreter(color_ostream &out, lua_State *state, void *info) { auto args = (InterpreterArgs*)info; lua_getglobal(state, "dfhack"); lua_getfield(state, -1, "interpreter"); + lua_remove(state, -2); lua_pushstring(state, args->prompt); lua_pushstring(state, args->hfile); - lua_xmove(state, thread, 3); - lua_pop(state, 1); return true; } @@ -888,6 +1036,7 @@ static const luaL_Reg dfhack_funcs[] = { { "is_interactive", lua_dfhack_is_interactive }, { "lineedit", lua_dfhack_lineedit }, { "safecall", lua_dfhack_safecall }, + { "saferesume", dfhack_saferesume }, { "onerror", dfhack_onerror }, { "call_with_finalizer", dfhack_call_with_finalizer }, { "with_suspend", lua_dfhack_with_suspend }, @@ -895,6 +1044,12 @@ static const luaL_Reg dfhack_funcs[] = { { NULL, NULL } }; +static const luaL_Reg dfhack_coro_funcs[] = { + { "resume", dfhack_coresume }, + { "wrap", dfhack_cowrap }, + { NULL, NULL } +}; + /************ * Events * ************/ @@ -1077,6 +1232,11 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_LOADED_TOKEN); lua_pop(state, 1); + // replace some coroutine functions + lua_getglobal(state, "coroutine"); + luaL_setfuncs(state, dfhack_coro_funcs, 0); + lua_pop(state, 1); + // load dfhack.lua Require(out, state, "dfhack"); diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 7e38f5bb3..5e1638251 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -145,6 +145,24 @@ namespace DFHack {namespace Lua { */ DFHACK_EXPORT bool SafeCall(color_ostream &out, lua_State *state, int nargs, int nres, bool perr = true); + /** + * Pops a function from the top of the stack, and pushes a new coroutine. + */ + DFHACK_EXPORT lua_State *NewCoroutine(lua_State *state); + + /** + * Resume the coroutine using nargs values from state from. Results or the error are moved back. + * If an error is signalled, and perr is true, it is printed and popped from the stack. + * Returns the lua_resume return value. + */ + DFHACK_EXPORT int SafeResume(color_ostream &out, lua_State *from, lua_State *thread, int nargs, int nres, bool perr = true); + + /** + * Works just like SafeCall, only expects a coroutine on the stack + * instead of a function. Returns the lua_resume return value. + */ + DFHACK_EXPORT int SafeResume(color_ostream &out, lua_State *from, int nargs, int nres, bool perr = true); + /** * Parse code from string with debug_tag and env_idx, then call it using SafeCall. * In case of error, it is either left on the stack, or printed like SafeCall does. @@ -166,10 +184,12 @@ namespace DFHack {namespace Lua { const char *prompt = NULL, const char *hfile = NULL); /** - * Run an interactive prompt loop. All access to lua is done inside CoreSuspender. + * Run an interactive prompt loop. All access to the lua state + * is done inside CoreSuspender, while waiting for input happens + * without the suspend lock. */ DFHACK_EXPORT bool RunCoreQueryLoop(color_ostream &out, lua_State *state, - bool (*init)(color_ostream&, lua_State*, lua_State*, void*), + bool (*init)(color_ostream&, lua_State*, void*), void *arg); /** From 9c253512818adc0b36f5a32b8be499dcf65f34a2 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 16 Apr 2012 18:01:21 +0400 Subject: [PATCH 3/5] Add a template to make using lua_pcallk a bit more convenient. --- library/LuaTools.cpp | 54 ++++++++++++++++---------------------- library/include/LuaTools.h | 26 ++++++++++++++++++ 2 files changed, 48 insertions(+), 32 deletions(-) diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 564384ef3..993efa747 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -467,29 +467,24 @@ static bool do_finish_pcall(lua_State *L, bool success, int base = 1, int space } } -static int finish_dfhack_safecall (lua_State *L, bool success) -{ - success = do_finish_pcall(L, success); - - if (!success) - report_error(L); +namespace { + int safecall_cont(lua_State *L, int status, int) + { + bool success = do_finish_pcall(L, Lua::IsSuccess(status)); - return lua_gettop(L); -} + if (!success) + report_error(L); -static int safecall_cont (lua_State *L) -{ - int status = lua_getctx(L, NULL); - return finish_dfhack_safecall(L, (status == LUA_YIELD)); + return lua_gettop(L); + } } -static int lua_dfhack_safecall (lua_State *L) +static int dfhack_safecall (lua_State *L) { luaL_checkany(L, 1); lua_pushcfunction(L, dfhack_onerror); lua_insert(L, 1); - int status = lua_pcallk(L, lua_gettop(L) - 2, LUA_MULTRET, 1, 0, safecall_cont); - return finish_dfhack_safecall(L, (status == LUA_OK)); + return Lua::TailPCallK(L, lua_gettop(L) - 2, LUA_MULTRET, 1, 0); } bool DFHack::Lua::SafeCall(color_ostream &out, lua_State *L, int nargs, int nres, bool perr) @@ -538,7 +533,7 @@ static int resume_helper(lua_State *L, lua_State *co, int narg, int nres) } lua_xmove(L, co, narg); int status = lua_resume(co, L, narg); - if (status == LUA_OK || status == LUA_YIELD) + if (Lua::IsSuccess(status)) { int nact = lua_gettop(co); if (nres == LUA_MULTRET) @@ -569,7 +564,7 @@ static int dfhack_coresume (lua_State *L) { lua_State *co = lua_tothread(L, 1); luaL_argcheck(L, !!co, 1, "coroutine expected"); int r = resume_helper(L, co, lua_gettop(L) - 1, LUA_MULTRET); - bool ok = (r == LUA_OK || r == LUA_YIELD); + bool ok = Lua::IsSuccess(r); lua_pushboolean(L, ok); lua_insert(L, 2); return lua_gettop(L) - 1; @@ -579,7 +574,7 @@ static int dfhack_saferesume (lua_State *L) { lua_State *co = lua_tothread(L, 1); luaL_argcheck(L, !!co, 1, "coroutine expected"); int r = resume_helper(L, co, lua_gettop(L) - 1, LUA_MULTRET); - bool ok = (r == LUA_OK || r == LUA_YIELD); + bool ok = Lua::IsSuccess(r); lua_pushboolean(L, ok); lua_insert(L, 2); if (!ok) @@ -590,7 +585,7 @@ static int dfhack_saferesume (lua_State *L) { static int dfhack_coauxwrap (lua_State *L) { lua_State *co = lua_tothread(L, lua_upvalueindex(1)); int r = resume_helper(L, co, lua_gettop(L), LUA_MULTRET); - if (r == LUA_OK || r == LUA_YIELD) + if (Lua::IsSuccess(r)) return lua_gettop(L); else return lua_error(L); @@ -620,7 +615,7 @@ int DFHack::Lua::SafeResume(color_ostream &out, lua_State *from, lua_State *thre int rv = resume_helper(from, thread, nargs, nres); - if (rv != LUA_OK && rv != LUA_YIELD && perr) + if (!Lua::IsSuccess(rv) && perr) { report_error(from, &out); lua_pop(from, 1); @@ -761,7 +756,7 @@ static int resume_query_loop(color_ostream &out, { int rv = Lua::SafeResume(out, state, thread, nargs, 2); - if (rv == LUA_YIELD || rv == LUA_OK) + if (Lua::IsSuccess(rv)) { prompt = ifnull(lua_tostring(state, -2), ""); histfile = ifnull(lua_tostring(state, -1), ""); @@ -906,8 +901,10 @@ static bool do_invoke_cleanup(lua_State *L, int nargs, int errorfun, bool succes return success; } -static int finish_dfhack_cleanup (lua_State *L, bool success) +int dfhack_cleanup_cont(lua_State *L, int status, int) { + bool success = Lua::IsSuccess(status); + int nargs = lua_tointeger(L, 1); bool always = lua_toboolean(L, 2); int rvbase = 4+nargs; @@ -948,12 +945,6 @@ static int finish_dfhack_cleanup (lua_State *L, bool success) return numret; } -static int dfhack_cleanup_cont (lua_State *L) -{ - int status = lua_getctx(L, NULL); - return finish_dfhack_cleanup(L, (status == LUA_YIELD)); -} - static int dfhack_call_with_finalizer (lua_State *L) { int nargs = luaL_checkint(L, 1); @@ -988,8 +979,7 @@ static int dfhack_call_with_finalizer (lua_State *L) // Actually invoke // stack: [nargs] [always] [errorfun] [cleanup fun] [cleanup args...] |rvbase+1:| [fun] [args...] - int status = lua_pcallk(L, lua_gettop(L)-rvbase-1, LUA_MULTRET, 3, 0, dfhack_cleanup_cont); - return finish_dfhack_cleanup(L, (status == LUA_OK)); + return Lua::TailPCallK(L, lua_gettop(L)-rvbase-1, LUA_MULTRET, 3, 0); } static int lua_dfhack_with_suspend(lua_State *L) @@ -1034,8 +1024,8 @@ static const luaL_Reg dfhack_funcs[] = { { "printerr", lua_dfhack_printerr }, { "color", lua_dfhack_color }, { "is_interactive", lua_dfhack_is_interactive }, - { "lineedit", lua_dfhack_lineedit }, - { "safecall", lua_dfhack_safecall }, + { "lineedit", dfhack_lineedit }, + { "safecall", dfhack_safecall }, { "saferesume", dfhack_saferesume }, { "onerror", dfhack_onerror }, { "call_with_finalizer", dfhack_call_with_finalizer }, diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 5e1638251..54aa6dcc7 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -139,6 +139,32 @@ namespace DFHack {namespace Lua { return AssignDFObject(out, state, df::identity_traits::get(), target, val_index, perr); } + /** + * Check if the status is a success, i.e. LUA_OK or LUA_YIELD. + */ + inline bool IsSuccess(int status) { + return (status == LUA_OK || status == LUA_YIELD); + } + + // Internal helper + template + int TailPCallK_Thunk(lua_State *state) { + int tmp; + int rv = lua_getctx(state, &tmp); + return cb(state, rv, tmp); + } + + /** + * A utility for using the restartable pcall feature more conveniently; + * specifically, the callback is called with the same kind of arguments + * in both yield and non-yield case. + */ + template + int TailPCallK(lua_State *state, int narg, int nret, int errfun, int ctx) { + int rv = lua_pcallk(state, narg, nret, errfun, ctx, &TailPCallK_Thunk); + return cb(state, rv, ctx); + } + /** * Invoke lua function via pcall. Returns true if success. * If an error is signalled, and perr is true, it is printed and popped from the stack. From 1e64a6a2f623a6f6a3ce1b0b10ccfe40acbcdde4 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 16 Apr 2012 18:05:42 +0400 Subject: [PATCH 4/5] Make dfhack.lineedit automatically interact with RunCoreQueryLoop. It still falls back to the original waiting mode if yield fails. --- library/LuaTools.cpp | 62 +++++++++++++++++++++++++++++++++++++----- library/lua/dfhack.lua | 11 +------- 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 993efa747..be4fa864e 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -240,15 +240,14 @@ static int lua_dfhack_is_interactive(lua_State *S) return 1; } -static int lua_dfhack_lineedit(lua_State *S) +static int dfhack_lineedit_sync(lua_State *S, Console *pstream) { - const char *prompt = luaL_optstring(S, 1, ">> "); - const char *hfile = luaL_optstring(S, 2, NULL); - - Console *pstream = get_console(S); if (!pstream) return 2; + const char *prompt = luaL_optstring(S, 1, ">> "); + const char *hfile = luaL_optstring(S, 2, NULL); + DFHack::CommandHistory hist; if (hfile) hist.load(hfile); @@ -271,6 +270,47 @@ static int lua_dfhack_lineedit(lua_State *S) } } +static int DFHACK_QUERY_COROTABLE_TOKEN = 0; + +static int yield_helper(lua_State *S) +{ + return lua_yield(S, lua_gettop(S)); +} + +namespace { + int dfhack_lineedit_cont(lua_State *L, int status, int) + { + if (Lua::IsSuccess(status)) + return lua_gettop(L) - 2; + else + return dfhack_lineedit_sync(L, get_console(L)); + } +} + +static int dfhack_lineedit(lua_State *S) +{ + lua_settop(S, 2); + + Console *pstream = get_console(S); + if (!pstream) + return 2; + + lua_rawgetp(S, LUA_REGISTRYINDEX, &DFHACK_QUERY_COROTABLE_TOKEN); + lua_rawgetp(S, -1, S); + bool in_coroutine = !lua_isnil(S, -1); + lua_settop(S, 2); + + if (in_coroutine) + { + lua_pushcfunction(S, yield_helper); + lua_pushvalue(S, 1); + lua_pushvalue(S, 2); + return Lua::TailPCallK(S, 2, LUA_MULTRET, 0, 0); + } + + return dfhack_lineedit_sync(S, pstream); +} + /* * Exception handling */ @@ -796,10 +836,12 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state, return false; } + lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_QUERY_COROTABLE_TOKEN); lua_pushvalue(state, base+1); lua_remove(state, base+1); thread = Lua::NewCoroutine(state); - lua_rawsetp(state, LUA_REGISTRYINDEX, thread); + lua_rawsetp(state, -2, thread); + lua_pop(state, 1); rv = resume_query_loop(out, thread, state, lua_gettop(state)-base, prompt, histfile); } @@ -839,8 +881,10 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state, { CoreSuspender suspend; + lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_QUERY_COROTABLE_TOKEN); lua_pushnil(state); - lua_rawsetp(state, LUA_REGISTRYINDEX, thread); + lua_rawsetp(state, -2, thread); + lua_pop(state, 1); } return (rv == LUA_OK); @@ -1180,6 +1224,10 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) luaL_openlibs(state); AttachDFGlobals(state); + // Table of query coroutines + lua_newtable(state); + lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_QUERY_COROTABLE_TOKEN); + // Replace the print function of the standard library lua_pushcfunction(state, lua_dfhack_println); lua_setglobal(state, "print"); diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 595363da8..b5985f0c1 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -130,15 +130,6 @@ end -- Interactive -function dfhack.query(prompt,hfile) - local _,main = coroutine.running() - if main then - return dfhack.lineedit(prompt,hfile) - else - return coroutine.yield(prompt,hfile) - end -end - local print_banner = true function dfhack.interpreter(prompt,hfile,env) @@ -163,7 +154,7 @@ function dfhack.interpreter(prompt,hfile,env) setmetatable(prompt_env, { __index = env or _G }) while true do - local cmdline = dfhack.query(prompt_str, hfile) + local cmdline = dfhack.lineedit(prompt_str, hfile) if cmdline == nil or cmdline == 'quit' then break From ee7100216e7ac43ad51f1cdd5e51d89daa545b91 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 16 Apr 2012 18:32:12 +0400 Subject: [PATCH 5/5] Fix lua interpreter bug: the C call counter is already unwound by yield. Decrementing it causes underflow and subsequent spurious stack overflow. --- depends/lua/CMakeLists.txt | 2 +- depends/lua/src/ldo.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/depends/lua/CMakeLists.txt b/depends/lua/CMakeLists.txt index 3a56aa639..b77bce6f7 100644 --- a/depends/lua/CMakeLists.txt +++ b/depends/lua/CMakeLists.txt @@ -1,7 +1,7 @@ PROJECT ( lua CXX ) CMAKE_MINIMUM_REQUIRED(VERSION 2.8) -SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-DLUA_USE_APICHECK") +SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -DLUA_USE_APICHECK") IF(WIN32) ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE ) diff --git a/depends/lua/src/ldo.c b/depends/lua/src/ldo.c index 26f9a6747..e0f13c44e 100644 --- a/depends/lua/src/ldo.c +++ b/depends/lua/src/ldo.c @@ -403,7 +403,7 @@ static void finishCcall (lua_State *L) { lua_assert(ci->u.c.k != NULL); /* must have a continuation */ lua_assert(L->nny == 0); /* finish 'luaD_call' */ - L->nCcalls--; + //L->nCcalls--; /* finish 'lua_callk' */ adjustresults(L, ci->nresults); /* call continuation function */ @@ -513,7 +513,7 @@ static void resume (lua_State *L, void *ud) { api_checknelems(L, n); firstArg = L->top - n; /* yield results come from continuation */ } - L->nCcalls--; /* finish 'luaD_call' */ + //L->nCcalls--; /* finish 'luaD_call' */ luaD_poscall(L, firstArg); /* finish 'luaD_precall' */ } unroll(L, NULL);