From a1756a864cc7c7a6f498c5cfe96e0a9d0ae57828 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 15 Apr 2012 21:50:22 +0400 Subject: [PATCH] Implement a way to do prompts from core context. The trick obviously is doing it without forcing DF to wait suspended. Fortunately, lua has built-in coroutine support, so the interactive prompt can simply yield and rely on the external loop to do the job. To use this however the REPL had to be replaced with lua code. --- library/LuaTools.cpp | 199 ++++++++++++++++++++---------------- library/include/LuaTools.h | 10 +- library/lua/dfhack.lua | 75 ++++++++++++++ plugins/Dfusion/dfusion.cpp | 3 +- 4 files changed, 197 insertions(+), 90 deletions(-) diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 304d79bcc..bf40e4041 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -426,7 +426,7 @@ static bool do_finish_pcall(lua_State *L, bool success, int base = 1, int space { lua_pushboolean(L, success); lua_replace(L, base); /* put first result in first slot */ - return true; + return success; } } @@ -586,131 +586,157 @@ bool DFHack::Lua::SafeCallString(color_ostream &out, lua_State *state, const std return Lua::SafeCall(out, state, nargs, nres, perr); } -bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state, - const char *prompt, int env, const char *hfile) +static int resume_query_loop(color_ostream &out, + lua_State *thread, lua_State *state, bool start, + std::string &prompt, std::string &histfile) { - AssertCoreSuspend(state); + 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); + + if (rv == LUA_YIELD || rv == LUA_OK) + { + lua_settop(thread, 2); + prompt = ifnull(lua_tostring(thread, 1), ""); + histfile = ifnull(lua_tostring(thread, 2), ""); + } + + return rv; +} +bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state, + bool (*init)(color_ostream&, lua_State*, lua_State*, void*), + void *arg) +{ if (!out.is_console()) return false; if (!lua_checkstack(state, 20)) return false; - if (!hfile) - hfile = "lua.history"; - if (!prompt) - prompt = "lua"; + Console &con = static_cast(out); + + lua_State *thread; + int rv; + std::string prompt; + std::string histfile; DFHack::CommandHistory hist; - hist.load(hfile); + std::string histname; - out.print("Type quit to exit interactive lua interpreter.\n"); + { + CoreSuspender suspend; - static bool print_banner = true; - if (print_banner) { - out.print("Shortcuts:\n" - " '= foo' => '_1,_2,... = foo'\n" - " '! foo' => 'print(foo)'\n" - "Both save the first result as '_'.\n"); - print_banner = false; - } + int base = lua_gettop(state); + thread = lua_newthread(state); - Console &con = static_cast(out); + if (!init(out, state, thread, arg)) + { + lua_settop(state, base); + return false; + } - // Make a proxy global environment. - lua_newtable(state); - int base = lua_gettop(state); + lua_settop(state, base+1); + lua_rawsetp(state, LUA_REGISTRYINDEX, thread); - lua_newtable(state); - if (env) - lua_pushvalue(state, env); - else - lua_rawgeti(state, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); - lua_setfield(state, -2, "__index"); - lua_setmetatable(state, -2); + rv = resume_query_loop(out, thread, state, true, prompt, histfile); + } - // Main interactive loop - int vcnt = 1; - string curline; - string prompt_str = "[" + string(prompt) + "]# "; + while (rv == LUA_YIELD) + { + if (histfile != histname) + { + if (!histname.empty()) + hist.save(histname.c_str()); - for (;;) { - lua_settop(state, base); + hist.clear(); + histname = histfile; - con.lineedit(prompt_str,curline,hist); + if (!histname.empty()) + hist.load(histname.c_str()); + } - if (curline.empty()) - continue; - if (curline == "quit") - break; + if (prompt.empty()) + prompt = ">> "; + std::string curline; + con.lineedit(prompt,curline,hist); hist.add(curline); - char pfix = curline[0]; - - if (pfix == '=' || pfix == '!') { - curline = "return " + curline.substr(1); + CoreSuspender suspend; - if (!Lua::SafeCallString(out, state, curline, 0, LUA_MULTRET, true, "=(interactive)", base)) - continue; + lua_settop(thread, 0); + lua_pushlstring(thread, curline.data(), curline.size()); - int numret = lua_gettop(state) - base; + rv = resume_query_loop(out, thread, state, false, prompt, histfile); + } + } - if (numret >= 1) - { - lua_pushvalue(state, base+1); - lua_setfield(state, base, "_"); - - if (pfix == '!') - { - lua_pushcfunction(state, lua_dfhack_println); - lua_insert(state, base+1); - SafeCall(out, state, numret, 0); - continue; - } - } + if (!histname.empty()) + hist.save(histname.c_str()); - for (int i = 1; i <= numret; i++) - { - std::string name = stl_sprintf("_%d", vcnt++); - lua_pushvalue(state, base + i); - lua_setfield(state, base, name.c_str()); + { + CoreSuspender suspend; - out.print("%s = ", name.c_str()); + if (rv != LUA_OK) + { + lua_xmove(thread, state, 1); - lua_pushcfunction(state, lua_dfhack_println); - lua_pushvalue(state, base + i); - SafeCall(out, state, 1, 0); + if (convert_to_exception(state)) + { + luaL_traceback(state, thread, NULL, 1); + lua_setfield(state, -2, "stacktrace"); } + + report_error(state, &out); + lua_pop(state, 1); } - else - { - if (!Lua::SafeCallString(out, state, curline, 0, LUA_MULTRET, true, "=(interactive)", base)) - continue; - } + + lua_pushnil(state); + lua_rawsetp(state, LUA_REGISTRYINDEX, thread); } - lua_settop(state, base-1); + return (rv == LUA_OK); +} + +namespace { + struct InterpreterArgs { + const char *prompt; + const char *hfile; + }; +} - hist.save(hfile); +static bool init_interpreter(color_ostream &out, lua_State *state, lua_State *thread, void *info) +{ + auto args = (InterpreterArgs*)info; + lua_getglobal(state, "dfhack"); + lua_getfield(state, -1, "interpreter"); + lua_pushstring(state, args->prompt); + lua_pushstring(state, args->hfile); + lua_xmove(state, thread, 3); + lua_pop(state, 1); return true; } -static int lua_dfhack_interpreter(lua_State *state) +bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state, + const char *prompt, const char *hfile) { - Console *pstream = get_console(state); - if (!pstream) - return 2; + if (!out.is_console()) + return false; - int argc = lua_gettop(state); + if (!hfile) + hfile = "lua.history"; + if (!prompt) + prompt = "lua"; - const char *prompt = (argc >= 1 ? lua_tostring(state, 1) : NULL); - int env = (argc >= 2 && !lua_isnil(state,2) ? 2 : 0); - const char *hfile = (argc >= 3 ? lua_tostring(state, 3) : NULL); + InterpreterArgs args; + args.prompt = prompt; + args.hfile = hfile; - lua_pushboolean(state, Lua::InterpreterLoop(*pstream, state, prompt, env, hfile)); - return 1; + return RunCoreQueryLoop(out, state, init_interpreter, &args); } static bool do_invoke_cleanup(lua_State *L, int nargs, int errorfun, bool success) @@ -861,7 +887,6 @@ static const luaL_Reg dfhack_funcs[] = { { "color", lua_dfhack_color }, { "is_interactive", lua_dfhack_is_interactive }, { "lineedit", lua_dfhack_lineedit }, - { "interpreter", lua_dfhack_interpreter }, { "safecall", lua_dfhack_safecall }, { "onerror", dfhack_onerror }, { "call_with_finalizer", dfhack_call_with_finalizer }, diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 70a2e5fca..549bdaaf0 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -160,9 +160,17 @@ namespace DFHack {namespace Lua { /** * Run an interactive interpreter loop if possible, or return false. + * Uses RunCoreQueryLoop internally. */ DFHACK_EXPORT bool InterpreterLoop(color_ostream &out, lua_State *state, - const char *prompt = NULL, int env = 0, const char *hfile = NULL); + const char *prompt = NULL, const char *hfile = NULL); + + /** + * Run an interactive prompt loop. All access to lua is done inside CoreSuspender. + */ + DFHACK_EXPORT bool RunCoreQueryLoop(color_ostream &out, lua_State *state, + bool (*init)(color_ostream&, lua_State*, lua_State*, void*), + void *arg); /** * Push utility functions diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 63de69864..595363da8 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -128,5 +128,80 @@ function dfhack.maps.getTileSize() return map.x_count, map.y_count, map.z_count 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) + if not dfhack.is_interactive() then + return nil, 'not interactive' + end + + print("Type quit to exit interactive lua interpreter.") + + if print_banner then + print("Shortcuts:\n".. + " '= foo' => '_1,_2,... = foo'\n".. + " '! foo' => 'print(foo)'\n".. + "Both save the first result as '_'.") + print_banner = false + end + + local prompt_str = "["..(prompt or 'lua').."]# " + local prompt_env = {} + local vcnt = 1 + + setmetatable(prompt_env, { __index = env or _G }) + + while true do + local cmdline = dfhack.query(prompt_str, hfile) + + if cmdline == nil or cmdline == 'quit' then + break + elseif cmdline ~= '' then + local pfix = string.sub(cmdline,1,1) + + if pfix == '!' or pfix == '=' then + cmdline = 'return '..string.sub(cmdline,2) + end + + local code,err = load(cmdline, '=(interactive)', 't', prompt_env) + + if code == nil then + dfhack.printerr(err) + else + local data = table.pack(safecall(code)) + + if data[1] and data.n > 1 then + prompt_env._ = data[2] + + if pfix == '!' then + safecall(print, table.unpack(data,2,data.n)) + else + for i=2,data.n do + local varname = '_'..vcnt + prompt_env[varname] = data[i] + dfhack.print(varname..' = ') + safecall(print, data[i]) + vcnt = vcnt + 1 + end + end + end + end + end + end + + return true +end + -- Feed the table back to the require() mechanism. return dfhack diff --git a/plugins/Dfusion/dfusion.cpp b/plugins/Dfusion/dfusion.cpp index cfd2e27cd..d8710c2db 100644 --- a/plugins/Dfusion/dfusion.cpp +++ b/plugins/Dfusion/dfusion.cpp @@ -123,8 +123,7 @@ command_result lua_run (color_ostream &out, std::vector ¶meter { if (!parameters.empty() && parameters[0] == "--core-context") { - CoreSuspender suspend; - Lua::InterpreterLoop(out, Lua::Core::State); + Lua::InterpreterLoop(out, Lua::Core::State, "core lua"); return CR_OK; }