Implement probe_lua and add it to lpxclient/lpxserver

This commit is contained in:
2021-11-26 13:56:24 -05:00
parent c02109699e
commit 1e93533246
10 changed files with 134 additions and 35 deletions

View File

@@ -671,12 +671,7 @@ LuaDefine(unittests_animqueue, "c") {
// Change the queue size limit.
aq.set_limit(13);
LuaAssert(L, diff_works(aq, aqds));
// compare again, should be no differences.
LuaAssert(L, aqds.version_identical(aq));
LuaAssert(L, aqds.size_and_steps_equal(aq));
LuaAssert(L, diff_works(aq, aqds));
// Discard all but the last action.
aq.set_limit(1);
LuaAssert(L, diff_works(aq, aqds));

View File

@@ -94,10 +94,16 @@ public:
inv.serialize(sb);
}
void do_lua_command(const StringVec &words) {
void do_luainvoke_command(const StringVec &words) {
send_invocation(Invocation(Invocation::KIND_LUA, actor_id_, actor_id_, words[1]));
}
void do_luaprobe_command(const StringVec &words) {
world_to_asynchronous();
stdostream() << world_->probe_lua(actor_id_, words[1]);
world_to_synchronous();
}
void do_syntax_command(const StringVec &words) {
stdostream() << "Syntax Error: " << words[1] << std::endl;
}
@@ -131,7 +137,8 @@ public:
void do_command(const util::StringVec &words) {
if (words.empty()) return;
else if (words[0] == "lua") do_lua_command(words);
else if (words[0] == "luainvoke") do_luainvoke_command(words);
else if (words[0] == "luaprobe") do_luaprobe_command(words);
else if (words[0] == "syntax") do_syntax_command(words);
else if (words[0] == "view") do_view_command(words);
else if (words[0] == "menu") do_menu_command(words);

View File

@@ -45,10 +45,16 @@ public:
get_stdio_channel()->set_prompt(console_.get_prompt());
}
void do_lua_command(const util::StringVec &words) {
void do_luainvoke_command(const util::StringVec &words) {
master_->invoke(Invocation(Invocation::KIND_LUA, admin_id_, admin_id_, words[1]));
}
void do_luaprobe_command(const util::StringVec &words) {
master_->snapshot();
stdostream() << master_->probe_lua(admin_id_, words[1]);;
master_->rollback();
}
void do_syntax_command(const util::StringVec &words) {
stdostream() << "Syntax Error: " << words[1] << std::endl;
}
@@ -59,7 +65,8 @@ public:
void do_command(const util::StringVec &words) {
if (words.empty()) return;
else if (words[0] == "lua") do_lua_command(words);
else if (words[0] == "luainvoke") do_luainvoke_command(words);
else if (words[0] == "luaprobe") do_luaprobe_command(words);
else if (words[0] == "syntax") do_syntax_command(words);
else if (words[0] == "quit") do_quit_command(words);
else {

View File

@@ -78,14 +78,6 @@ void LuaConsole::simplify(const StringVec &words) {
if (words.size() != 1) {
synerr("/quit takes no arguments");
}
} else if (words[0] == "snap") {
if (words.size() != 1) {
synerr("/snap takes no arguments");
}
} else if (words[0] == "roll") {
if (words.size() != 1) {
synerr("/roll takes no arguments");
}
} else if (words[0] == "tick") {
if ((words.size() == 2)&&(util::validinteger(words[1]))) {
// OK
@@ -120,11 +112,20 @@ void LuaConsole::add(std::string line) {
return;
}
// Translate lua expression with leading '=' to 'return'
// Translate lua expression, deal with initial prefix.
std::string partial;
if (raw_input_[0] == '=') {
std::string lua_mode;
if (util::has_prefix(raw_input_, "?=")) {
lua_mode = "luaprobe";
partial = std::string("return ") + raw_input_.substr(2);
} else if (util::has_prefix(raw_input_, "?")) {
lua_mode = "luaprobe";
partial = raw_input_.substr(1);
} else if (util::has_prefix(raw_input_, "=")) {
lua_mode = "luainvoke";
partial = std::string("return ") + raw_input_.substr(1);
} else {
lua_mode = "luainvoke";
partial = raw_input_;
}
@@ -144,7 +145,7 @@ void LuaConsole::add(std::string line) {
clear_raw_input();
}
} else {
words_.push_back("lua");
words_.push_back(lua_mode);
words_.push_back(util::rtrim(partial));
clear_raw_input();
}

View File

@@ -18,15 +18,21 @@
// /quit - exit the program
// /view - display the nearby tangibles
// /menu [tanid] - display the menu for tangible
// /snap - snapshot current state
// /roll - rollback to previous state
// /tick [timevalue] - advance the simulation clock
// /choose [number] - choose menu item number
// /1234 - choose menu item 1234
//
// If you type anything else, the LuaConsole will generate a
// syntax error. Note that not all of the commands above are
// supported in all interpreters.
//
// Lua commands can be one of the following:
//
// <expression> - invoke "<expression>""
// = <expression> - invoke "return <expression>"
// ? <expression> - probe "<expression>"
// ?= <expression> - probe "return <expression>"
//
// Once a command has been typed (or a syntax error has been
// typed), the LuaConsole will return the command. The command
// can be fetched using command(), int_arg(), and str_arg()

View File

@@ -28,10 +28,16 @@ private:
Gui gui_;
int64_t actor_id_;
void do_lua_command(const StringVec &words) {
void do_luainvoke_command(const StringVec &words) {
world_->invoke(Invocation(Invocation::KIND_LUA, actor_id_, actor_id_, words[1]));
}
void do_luaprobe_command(const StringVec &words) {
world_->snapshot();
stdostream() << world_->probe_lua(actor_id_, words[1]);
world_->rollback();
}
void do_syntax_command(const StringVec &words) {
stdostream() << "Syntax Error: " << words[1] << std::endl;
}
@@ -57,14 +63,6 @@ private:
world_->invoke(inv);
}
void do_snapshot_command(const StringVec &cmd) {
world_->snapshot();
}
void do_rollback_command(const StringVec &cmd) {
world_->rollback();
}
void do_tick_command(const StringVec &cmd) {
world_->run_scheduled_threads(util::strtoint(cmd[1], -1));
}
@@ -75,13 +73,12 @@ private:
void do_command(const StringVec &words) {
if (words.empty()) return;
else if (words[0] == "lua") do_lua_command(words);
else if (words[0] == "luainvoke") do_luainvoke_command(words);
else if (words[0] == "luaprobe") do_luaprobe_command(words);
else if (words[0] == "syntax") do_syntax_command(words);
else if (words[0] == "view") do_view_command(words);
else if (words[0] == "menu") do_menu_command(words);
else if (words[0] == "quit") do_quit_command(words);
else if (words[0] == "snap") do_snapshot_command(words);
else if (words[0] == "roll") do_rollback_command(words);
else if (words[0] == "tick") do_tick_command(words);
else if (words[0] == "choose") do_choose_command(words);
else {

View File

@@ -185,6 +185,18 @@ std::string toupper(std::string input) {
return input;
}
bool has_prefix(const std::string &s, const std::string &prefix) {
return 0 == s.compare(0, prefix.size(), prefix);
}
bool has_suffix(const std::string &s, const std::string &suffix) {
if (s.length() >= suffix.length()) {
return (0 == s.compare (s.length() - suffix.length(), suffix.length(), suffix));
} else {
return false;
}
}
bool validinteger(const std::string &value) {
char *endptr;
if (value.size() == 0) return false;

View File

@@ -77,6 +77,10 @@ int common_prefix_length(const std::string &a, const std::string &b);
std::string tolower(std::string input);
std::string toupper(std::string input);
// Return true if the string has the specified prefix or suffix.
bool has_prefix(const std::string &s, const std::string &prefix);
bool has_suffix(const std::string &s, const std::string &suffix);
// Return true if the string can be parsed as an integer.
bool validinteger(const std::string &value);

View File

@@ -257,6 +257,65 @@ int64_t World::create_login_actor() {
return tan->id();
}
std::string World::probe_lua(int64_t actor_id, const std::string &lua) {
assert(stack_is_clear());
lua_State *L = state();
Tangible *actor = tangible_get(actor_id);
if (actor == nullptr) {
return "";
}
LuaVar closure;
LuaStack LS(L, closure);
// create the compiled closure.
int status = luaL_loadbuffer(L, lua.c_str(), lua.size(), "=probe");
lua_replace(L, closure.index());
if (status != LUA_OK) {
// The closure is actually an error message. Do nothing.
// This should normally not happen: LuaConsole should filter
// out syntax errors.
LS.result();
return "";
}
// Call the closure.
int top = lua_gettop(L);
lua_pushvalue(L, closure.index());
open_lthread_state(actor_id, actor_id, false, true);
status = traceback_pcall(L, 0, LUA_MULTRET);
// If there's an error message, print it.
// Otherwise, pretty-print the results.
std::ostream *ostream = lthread_print_stream();
if (status == LUA_OK) {
for (int i = top + 1; i <= lua_gettop(L); i++) {
LuaSpecial root(i);
pprint(LS, root, true, ostream);
(*ostream) << std::endl;
}
} else {
const char *msg = lua_tostring(L, -1);
if (msg == NULL) {
msg = "(error object is not a string)";
}
(*ostream) << msg << std::endl;
}
// Collect the lthread_prints (and also make sure they
// don't go into the printbuffer).
std::string result = lthread_prints_->str();
lthread_prints_.reset();
close_lthread_state();
// And we're done.
LS.result();
assert(stack_is_clear());
return result;
}
void World::update_gui(int64_t actor_id, int64_t place_id, Gui *gui) {
assert(stack_is_clear());
gui->clear(place_id);
@@ -391,6 +450,8 @@ void World::invoke_lua(int64_t actor_id, int64_t place_id, const std::string &ac
lua_replace(L, closure.index());
if (status != LUA_OK) {
// The closure is actually an error message. Do nothing.
// This should normally not happen: LuaConsole should filter
// out syntax errors.
LS.result();
return;
}

View File

@@ -166,6 +166,15 @@ public:
//
Redirects fetch_redirects();
// Probe an arbitrary lua expression.
//
// Any print-statements in the lua code are sent into
// a stringstream. The return value of probe_lua is the string
// from the stringstream. If the lua expression returns a
// value, that is also printed to the stringstream.
//
std::string probe_lua(int64_t actor_id, const std::string &lua);
// Probe the 'interface' function of the specified sprite.
//
void update_gui(int64_t actor_id, int64_t place_id, Gui *gui);