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.
develop
Alexander Gavrilov 2012-04-15 21:50:22 +04:00
parent 14709e5d45
commit a1756a864c
4 changed files with 197 additions and 90 deletions

@ -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";
DFHack::CommandHistory hist;
hist.load(hfile);
Console &con = static_cast<Console&>(out);
out.print("Type quit to exit interactive lua interpreter.\n");
lua_State *thread;
int rv;
std::string prompt;
std::string histfile;
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;
}
DFHack::CommandHistory hist;
std::string histname;
Console &con = static_cast<Console&>(out);
{
CoreSuspender suspend;
// Make a proxy global environment.
lua_newtable(state);
int base = lua_gettop(state);
thread = lua_newthread(state);
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);
// Main interactive loop
int vcnt = 1;
string curline;
string prompt_str = "[" + string(prompt) + "]# ";
for (;;) {
if (!init(out, state, thread, arg))
{
lua_settop(state, base);
return false;
}
con.lineedit(prompt_str,curline,hist);
lua_settop(state, base+1);
lua_rawsetp(state, LUA_REGISTRYINDEX, thread);
if (curline.empty())
continue;
if (curline == "quit")
break;
rv = resume_query_loop(out, thread, state, true, prompt, histfile);
}
hist.add(curline);
while (rv == LUA_YIELD)
{
if (histfile != histname)
{
if (!histname.empty())
hist.save(histname.c_str());
char pfix = curline[0];
hist.clear();
histname = histfile;
if (pfix == '=' || pfix == '!')
{
curline = "return " + curline.substr(1);
if (!histname.empty())
hist.load(histname.c_str());
}
if (!Lua::SafeCallString(out, state, curline, 0, LUA_MULTRET, true, "=(interactive)", base))
continue;
if (prompt.empty())
prompt = ">> ";
int numret = lua_gettop(state) - base;
std::string curline;
con.lineedit(prompt,curline,hist);
hist.add(curline);
if (numret >= 1)
{
lua_pushvalue(state, base+1);
lua_setfield(state, base, "_");
CoreSuspender suspend;
if (pfix == '!')
{
lua_pushcfunction(state, lua_dfhack_println);
lua_insert(state, base+1);
SafeCall(out, state, numret, 0);
continue;
lua_settop(thread, 0);
lua_pushlstring(thread, curline.data(), curline.size());
rv = resume_query_loop(out, thread, state, false, prompt, histfile);
}
}
for (int i = 1; i <= numret; i++)
if (!histname.empty())
hist.save(histname.c_str());
{
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);
}
}
else
if (convert_to_exception(state))
{
if (!Lua::SafeCallString(out, state, curline, 0, LUA_MULTRET, true, "=(interactive)", base))
continue;
luaL_traceback(state, thread, NULL, 1);
lua_setfield(state, -2, "stacktrace");
}
report_error(state, &out);
lua_pop(state, 1);
}
lua_settop(state, base-1);
lua_pushnil(state);
lua_rawsetp(state, LUA_REGISTRYINDEX, thread);
}
hist.save(hfile);
return (rv == LUA_OK);
}
namespace {
struct InterpreterArgs {
const char *prompt;
const char *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 },

@ -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

@ -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

@ -123,8 +123,7 @@ command_result lua_run (color_ostream &out, std::vector <std::string> &parameter
{
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;
}