summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'Sluift/Console.cpp')
-rw-r--r--Sluift/Console.cpp279
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;
+}
+