diff options
Diffstat (limited to 'Sluift/Console.cpp')
-rw-r--r-- | Sluift/Console.cpp | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/Sluift/Console.cpp b/Sluift/Console.cpp new file mode 100644 index 0000000..7b5b437 --- /dev/null +++ b/Sluift/Console.cpp @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2013 Remko Tronçon + * Licensed under the GNU General Public License. + * See the COPYING file for more information. + */ + +#include <Sluift/Console.h> +#include <lua.hpp> +#include <stdexcept> +#include <iostream> +#include <boost/optional.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/algorithm/string/predicate.hpp> +#include <boost/numeric/conversion/cast.hpp> +#include <Sluift/Terminal.h> +#include <Sluift/tokenize.h> +#include <Sluift/Lua/LuaUtils.h> +#include <cctype> + +using namespace Swift; + +/** + * This function is called by pcall() when an error happens. + * Adds the backtrace to the error message. + */ +static int traceback(lua_State* L) { + if (!lua_isstring(L, 1)) { + return 1; + } + lua_getglobal(L, "debug"); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + return 1; + } + lua_getfield(L, -1, "traceback"); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 2); + return 1; + } + lua_pushvalue(L, 1); + lua_pushinteger(L, 2); + lua_call(L, 2, 1); + return 1; +} + + +Console::Console(lua_State* L, Terminal* terminal) : L(L), terminal(terminal), previousNumberOfReturnArguments(0) { + terminal->setCompleter(this); +} + +Console::~Console() { +} + +void Console::run() { + while (true) { + lua_settop(L, 0); + try { + if (!readCommand()) { + return; + } + int result = call(L, 0, true); + if (result != 0) { + throw std::runtime_error(getErrorMessage()); + } + + // Clear the previous results + for (int i = 0; i < previousNumberOfReturnArguments; ++i) { + lua_pushnil(L); + lua_setglobal(L, ("_" + boost::lexical_cast<std::string>(i+1)).c_str()); + } + + // Store the results + for (int i = 0; i < lua_gettop(L); ++i) { + lua_pushvalue(L, i+1); + lua_setglobal(L, ("_" + boost::lexical_cast<std::string>(i+1)).c_str()); + } + previousNumberOfReturnArguments = lua_gettop(L); + + // Print results + if (lua_gettop(L) > 0) { + lua_getglobal(L, "print"); + lua_insert(L, 1); + if (lua_pcall(L, lua_gettop(L)-1, 0, 0) != 0) { + throw std::runtime_error("Error calling 'print': " + getErrorMessage()); + } + } + } + catch (const std::exception& e) { + terminal->printError(e.what()); + } + } + +} + +int Console::tryLoadCommand(const std::string& originalCommand) { + std::string command = originalCommand; + + // Replace '=' by 'return' (for compatibility with Lua console) + if (boost::algorithm::starts_with(command, "=")) { + command = "return " + command.substr(1); + } + + std::string commandAsExpression = "return " + command; + + // Try to load the command as an expression + if (luaL_loadbuffer(L, commandAsExpression.c_str(), commandAsExpression.size(), "=stdin") == 0) { + return 0; + } + lua_pop(L, 1); + + // Try to load the command as a regular command + return luaL_loadbuffer(L, command.c_str(), command.size(), "=stdin"); +} + +bool Console::readCommand() { + boost::optional<std::string> line = terminal->readLine(getPrompt(true)); + if (!line) { + return false; + } + std::string command = *line; + while (true) { + int result = tryLoadCommand(command); + + // Check if we need to read more + if (result == LUA_ERRSYNTAX) { + std::string errorMessage(lua_tostring(L, -1)); + if (boost::algorithm::ends_with(errorMessage, "'<eof>'")) { + lua_pop(L, 1); + + // Read another line + boost::optional<std::string> line = terminal->readLine(getPrompt(false)); + if (!line) { + return false; + } + command = command + "\n" + *line; + continue; + } + } + if (!command.empty()) { + terminal->addToHistory(command); + } + if (result != 0) { + throw std::runtime_error(getErrorMessage()); + } + return true; + } +} + +std::string Console::getErrorMessage() const { + if (lua_isnil(L, -1)) { + return "<null error>"; + } + const char* errorMessage = lua_tostring(L, -1); + return errorMessage ? errorMessage : "<error is not a string>"; +} + +int Console::call(lua_State* L, int numberOfArguments, bool keepResult) { + // Put traceback function on stack below call + int tracebackIndex = lua_gettop(L) - numberOfArguments; + lua_pushcfunction(L, traceback); + lua_insert(L, tracebackIndex); + + int result = lua_pcall(L, numberOfArguments, keepResult ? LUA_MULTRET : 0, tracebackIndex); + + // Remove traceback + lua_remove(L, tracebackIndex); + + return result; +} + +std::string Console::getPrompt(bool firstLine) const { + lua_getglobal(L,firstLine ? "_PROMPT" : "_PROMPT2"); + const char* rawPrompt = lua_tostring(L, -1); + std::string prompt; + if (rawPrompt) { + prompt = std::string(rawPrompt); + } + else { + prompt = firstLine ? "> " : ">> "; + } + lua_pop(L, 1); + return prompt; +} + +static void addMatchingTableKeys(lua_State* L, const std::string& match, std::vector<std::string>& result) { + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + const char* rawKey = lua_tostring(L, -2); + if (rawKey) { + std::string key(rawKey); + if (boost::starts_with(key, match) && !(match == "" && boost::starts_with(key, "_"))) { + result.push_back(key); + } + } + } +} + +static void addMatchingTableValues(lua_State* L, const std::string& match, std::vector<std::string>& result) { + for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { + const char* rawValue = lua_tostring(L, -1); + if (rawValue) { + std::string key(rawValue); + if (boost::starts_with(key, match) && !(match == "" && boost::starts_with(key, "_"))) { + result.push_back(key); + } + } + } +} + +std::vector<std::string> Console::getCompletions(const std::string& input, int start, int end) { + std::string prefix = input.substr(boost::numeric_cast<size_t>(start), boost::numeric_cast<size_t>(end - start)); + + std::vector<std::string> tokens; + if (end) { + tokens = Lua::tokenize(input.substr(0, boost::numeric_cast<size_t>(end))); + } + + // Don't autocomplete strings + if (!tokens.empty() && ((*tokens.rbegin())[0] == '\'' || (*tokens.rbegin())[0] == '"')) { + return std::vector<std::string>(); + } + + std::vector<std::string> context; + for (std::vector<std::string>::reverse_iterator i = tokens.rbegin(); i != tokens.rend(); ++i) { + if (std::isalpha((*i)[0]) || (*i)[0] == '_') { + if (i != tokens.rbegin()) { + context.push_back(*i); + } + } + else if (*i != "." && *i != ":") { + break; + } + } + + // Drill into context + int top = lua_gettop(L); + lua_pushglobaltable(L); + for (std::vector<std::string>::reverse_iterator i = context.rbegin(); i != context.rend(); ++i) { + if (lua_istable(L, -1) || lua_isuserdata(L, -1)) { + lua_getfield(L, -1, i->c_str()); + if (!lua_isnil(L, 1)) { + continue; + } + } + lua_settop(L, top); + return std::vector<std::string>(); + } + + // Collect all keys from the table + std::vector<std::string> result; + if (lua_istable(L, -1)) { + addMatchingTableKeys(L, prefix, result); + } + + // Collect all keys from the metatable + if (lua_getmetatable(L, -1)) { + lua_getfield(L, -1, "__index"); + if (lua_istable(L, -1)) { + addMatchingTableKeys(L, prefix, result); + } + lua_pop(L, 1); + + lua_getfield(L, -1, "_completions"); + if (lua_isfunction(L, -1)) { + lua_pushvalue(L, -3); + if (lua_pcall(L, 1, 1, 0) != 0) { + throw std::runtime_error("Error calling '_completions': " + getErrorMessage()); + } + } + if (lua_istable(L, -1)) { + addMatchingTableValues(L, prefix, result); + } + lua_pop(L, 2); + } + + lua_settop(L, top); + + return result; +} + |