/* * Copyright (c) 2013-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include #include #include #include #include #include #include #include #include #include #include #include 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(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(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 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, "''")) { lua_pop(L, 1); // Read another line boost::optional 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 ""; } const char* errorMessage = lua_tostring(L, -1); return errorMessage ? errorMessage : ""; } 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& 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& 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 Console::getCompletions(const std::string& input, int start, int end) { std::string prefix = input.substr(boost::numeric_cast(start), boost::numeric_cast(end - start)); std::vector tokens; if (end) { tokens = Lua::tokenize(input.substr(0, boost::numeric_cast(end))); } // Don't autocomplete strings if (!tokens.empty() && ((*tokens.rbegin())[0] == '\'' || (*tokens.rbegin())[0] == '"')) { return std::vector(); } std::vector context; for (std::vector::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::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(); } // Collect all keys from the table std::vector 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; }