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; }