summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRemko Tronçon <git@el-tramo.be>2013-12-28 16:13:08 (GMT)
committerRemko Tronçon <git@el-tramo.be>2014-01-03 11:09:06 (GMT)
commit0b19dc7292b7672c9fbb711a411c392bc5b2bb34 (patch)
treed5af147193d52a3e63d348dd794ca92cdd934974
parent5a89265623214164fa7ce36721de05183d53058d (diff)
downloadswift-0b19dc7292b7672c9fbb711a411c392bc5b2bb34.zip
swift-0b19dc7292b7672c9fbb711a411c392bc5b2bb34.tar.bz2
Sluift: Custom console/interpreter
- Prints results of each command (if it can be interpreted as an expression) - Stores results of last command in _1, _2, ... - Supports tab completion - Compatible with Lua 5.2 Other changes: - Add support for specifying custom editline library - Don't load sluift into global namespace. Tab completion should be convenient enough. Change-Id: I2a26346469d67c281d09d47cacaa0b267f5ea9f9
-rw-r--r--BuildTools/SCons/SConscript.boot5
-rw-r--r--BuildTools/SCons/SConstruct16
-rw-r--r--Sluift/.gitignore1
-rw-r--r--Sluift/Completer.cpp12
-rw-r--r--Sluift/Completer.h19
-rw-r--r--Sluift/Console.cpp279
-rw-r--r--Sluift/Console.h41
-rw-r--r--Sluift/EditlineTerminal.cpp98
-rw-r--r--Sluift/EditlineTerminal.h25
-rw-r--r--Sluift/Lua/LuaUtils.h6
-rw-r--r--Sluift/SConscript24
-rw-r--r--Sluift/SConscript.variant29
-rw-r--r--Sluift/StandardTerminal.cpp38
-rw-r--r--Sluift/StandardTerminal.h22
-rw-r--r--Sluift/Terminal.cpp15
-rw-r--r--Sluift/Terminal.h35
-rw-r--r--Sluift/UnitTest/TokenizeTest.cpp64
-rw-r--r--Sluift/linit.c37
-rw-r--r--Sluift/main.cpp168
-rw-r--r--Sluift/sluift.h4
-rw-r--r--Sluift/tokenize.cpp86
-rw-r--r--Sluift/tokenize.h16
22 files changed, 968 insertions, 72 deletions
diff --git a/BuildTools/SCons/SConscript.boot b/BuildTools/SCons/SConscript.boot
index 6a115d6..767c326 100644
--- a/BuildTools/SCons/SConscript.boot
+++ b/BuildTools/SCons/SConscript.boot
@@ -64,6 +64,11 @@ vars.Add(PathVariable("lua_includedir", "Lua headers location", None, PathVariab
vars.Add(PathVariable("lua_libdir", "Lua library location", None, PathVariable.PathAccept))
vars.Add("lua_libname", "Lua library name", "liblua" if os.name == "nt" else "lua")
vars.Add("lua_force_bundled", "Force use of the bundled Lua", None)
+
+vars.Add(PathVariable("editline_includedir", "Readline headers location", None, PathVariable.PathAccept))
+vars.Add(PathVariable("editline_libdir", "Readline library location", None, PathVariable.PathAccept))
+vars.Add("editline_libname", "Readline library name", "libedit" if os.name == "nt" else "edit")
+
vars.Add(PathVariable("avahi_includedir", "Avahi headers location", None, PathVariable.PathAccept))
vars.Add(PathVariable("avahi_libdir", "Avahi library location", None, PathVariable.PathAccept))
vars.Add(PathVariable("qt", "Qt location", "", PathVariable.PathAccept))
diff --git a/BuildTools/SCons/SConstruct b/BuildTools/SCons/SConstruct
index 8c5258e..acbd531 100644
--- a/BuildTools/SCons/SConstruct
+++ b/BuildTools/SCons/SConstruct
@@ -436,10 +436,18 @@ else :
conf.Finish()
# Readline
-conf = Configure(conf_env)
-if conf.CheckCHeader(["stdio.h", "readline/readline.h"]) and conf.CheckLib("readline") :
- env["HAVE_READLINE"] = True
- env["READLINE_FLAGS"] = { "LIBS": ["readline"] }
+editline_conf_env = conf_env.Clone()
+editline_flags = {}
+if env.get("editline_libdir", None) :
+ editline_flags["LIBPATH"] = [env["editline_libdir"]]
+if env.get("editline_includedir", None) :
+ editline_flags["CPPPATH"] = [env["editline_includedir"]]
+editline_conf_env.MergeFlags(editline_flags)
+conf = Configure(editline_conf_env)
+if conf.CheckLibWithHeader(env["editline_libname"], ["stdio.h", "editline/readline.h"], "c") :
+ env["HAVE_EDITLINE"] = 1
+ env["EDITLINE_FLAGS"] = { "LIBS": [env["editline_libname"]] }
+ env["EDITLINE_FLAGS"].update(editline_flags)
conf.Finish()
# Avahi
diff --git a/Sluift/.gitignore b/Sluift/.gitignore
index 940fc86..e2d8bbf 100644
--- a/Sluift/.gitignore
+++ b/Sluift/.gitignore
@@ -1,4 +1,5 @@
lua.c
+Version.h
sluift_dll.cpp
sluift
dll.c
diff --git a/Sluift/Completer.cpp b/Sluift/Completer.cpp
new file mode 100644
index 0000000..4bd1eaf
--- /dev/null
+++ b/Sluift/Completer.cpp
@@ -0,0 +1,12 @@
+/*
+ * Copyright (c) 2013 Remko Tronçon
+ * Licensed under the GNU General Public License.
+ * See the COPYING file for more information.
+ */
+
+#include <Sluift/Completer.h>
+
+using namespace Swift;
+
+Completer::~Completer() {
+}
diff --git a/Sluift/Completer.h b/Sluift/Completer.h
new file mode 100644
index 0000000..9651eb1
--- /dev/null
+++ b/Sluift/Completer.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2013 Remko Tronçon
+ * Licensed under the GNU General Public License.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+namespace Swift {
+ class Completer {
+ public:
+ virtual ~Completer();
+
+ virtual std::vector<std::string> getCompletions(const std::string& buffer, int start, int end) = 0;
+ };
+}
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;
+}
+
diff --git a/Sluift/Console.h b/Sluift/Console.h
new file mode 100644
index 0000000..2d10b38
--- /dev/null
+++ b/Sluift/Console.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2013 Remko Tronçon
+ * Licensed under the GNU General Public License.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <boost/optional/optional_fwd.hpp>
+#include <Sluift/Completer.h>
+#include <Swiften/Base/Override.h>
+
+struct lua_State;
+
+namespace Swift {
+ class Terminal;
+
+ class Console : public Completer {
+ public:
+ Console(lua_State* L, Terminal* terminal);
+ virtual ~Console();
+
+ void run();
+
+ static int call(lua_State* L, int numberOfArguments, bool keepResult);
+
+ private:
+ std::string getPrompt(bool firstLine) const;
+ std::string getErrorMessage() const;
+ bool readCommand();
+ int tryLoadCommand(const std::string& command);
+
+ virtual std::vector<std::string> getCompletions(const std::string&, int start, int end) SWIFTEN_OVERRIDE;
+
+ private:
+ lua_State* L;
+ Terminal* terminal;
+ int previousNumberOfReturnArguments;
+ };
+}
diff --git a/Sluift/EditlineTerminal.cpp b/Sluift/EditlineTerminal.cpp
new file mode 100644
index 0000000..ec2c7a2
--- /dev/null
+++ b/Sluift/EditlineTerminal.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2013 Remko Tronçon
+ * Licensed under the GNU General Public License.
+ * See the COPYING file for more information.
+ */
+
+#include <Sluift/EditlineTerminal.h>
+
+#include <boost/optional.hpp>
+#include <iostream>
+#include <editline/readline.h>
+#include <boost/numeric/conversion/cast.hpp>
+#include <cassert>
+#include <vector>
+#include <cstring>
+
+#include <Swiften/Base/Platform.h>
+#include <Sluift/Completer.h>
+
+using namespace Swift;
+
+static EditlineTerminal* globalInstance = NULL;
+
+static int completionStart = -1;
+static int completionEnd = -1;
+
+#if defined(SWIFTEN_PLATFORM_WINDOWS)
+static char* getEmptyCompletions(const char*, int) {
+#else
+static int getEmptyCompletions(const char*, int) {
+#endif
+ return 0;
+}
+
+static char* getCompletions(const char*, int state) {
+ rl_completion_append_character = 0;
+#if RL_READLINE_VERSION >= 0x0600
+ rl_completion_suppress_append = 1;
+#endif
+
+ static std::vector<std::string> completions;
+ if (state == 0) {
+ assert(globalInstance);
+ completions.clear();
+ if (globalInstance->getCompleter()) {
+ completions = globalInstance->getCompleter()->getCompletions(rl_line_buffer, completionStart, completionEnd);
+ }
+ }
+ if (boost::numeric_cast<size_t>(state) >= completions.size()) {
+ return 0;
+ }
+ return strdup(completions[boost::numeric_cast<size_t>(state)].c_str());
+}
+
+static char** getAttemptedCompletions(const char* text, int start, int end) {
+ completionStart = start;
+ completionEnd = end;
+ return rl_completion_matches(text, getCompletions);
+}
+
+EditlineTerminal& EditlineTerminal::getInstance() {
+ static EditlineTerminal instance;
+ globalInstance = &instance;
+ return instance;
+}
+
+EditlineTerminal::EditlineTerminal() {
+ rl_attempted_completion_function = getAttemptedCompletions;
+ rl_completion_entry_function = getEmptyCompletions; // Fallback. Do nothing.
+#if defined(SWIFTEN_PLATFORM_WINDOWS)
+ // rl_basic_word_break is a cons char[] in MinGWEditLine.
+ // This one seems to work, although it doesn't on OS X for some reason.
+ rl_completer_word_break_characters = strdup(" \t\n.:+-*/><=;|&()[]{}");
+#else
+ rl_basic_word_break_characters = strdup(" \t\n.:+-*/><=;|&()[]{}");
+#endif
+}
+
+EditlineTerminal::~EditlineTerminal() {
+}
+
+void EditlineTerminal::printError(const std::string& message) {
+ std::cout << message << std::endl;
+}
+
+boost::optional<std::string> EditlineTerminal::readLine(const std::string& prompt) {
+ const char* line = readline(prompt.c_str());
+ return line ? std::string(line) : boost::optional<std::string>();
+}
+
+void EditlineTerminal::addToHistory(const std::string& line) {
+#if defined(SWIFTEN_PLATFORM_WINDOWS)
+ // MinGWEditLine copies the string, so this is safe
+ add_history(const_cast<char*>(line.c_str()));
+#else
+ add_history(line.c_str());
+#endif
+}
diff --git a/Sluift/EditlineTerminal.h b/Sluift/EditlineTerminal.h
new file mode 100644
index 0000000..2f671b7
--- /dev/null
+++ b/Sluift/EditlineTerminal.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2013 Remko Tronçon
+ * Licensed under the GNU General Public License.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Base/Override.h>
+#include <Sluift/Terminal.h>
+
+namespace Swift {
+ class EditlineTerminal : public Terminal {
+ public:
+ static EditlineTerminal& getInstance();
+
+ private:
+ EditlineTerminal();
+ virtual ~EditlineTerminal();
+
+ virtual boost::optional<std::string> readLine(const std::string& prompt) SWIFTEN_OVERRIDE;
+ virtual void printError(const std::string& message) SWIFTEN_OVERRIDE;
+ virtual void addToHistory(const std::string& command);
+ };
+}
diff --git a/Sluift/Lua/LuaUtils.h b/Sluift/Lua/LuaUtils.h
index bad307c..f677307 100644
--- a/Sluift/Lua/LuaUtils.h
+++ b/Sluift/Lua/LuaUtils.h
@@ -8,8 +8,12 @@
#include <lua.hpp>
#include <boost/optional.hpp>
+#include <string>
+#include <vector>
-struct lua_State;
+#if LUA_VERSION_NUM < 502
+#define lua_pushglobaltable(L) lua_pushvalue(L, LUA_GLOBALSINDEX)
+#endif
namespace Swift {
namespace Lua {
diff --git a/Sluift/SConscript b/Sluift/SConscript
index c8f1108..116c5f1 100644
--- a/Sluift/SConscript
+++ b/Sluift/SConscript
@@ -47,22 +47,10 @@ elif env["SCONS_STAGE"] == "build" :
if sluift_env["PLATFORM"] == "win32" :
sluift_env.Append(CPPDEFINES = ["SLUIFT_BUILD_DLL"])
- # Generate a customized lua.c
- sluift_env["SLUIFT_VERSION"] = Version.getBuildVersion(env.Dir("#").abspath, "sluift")
- def patchLua(env, target, source) :
- f = open(source[0].abspath, "r")
- contents = f.read()
- f.close()
- if env["PLATFORM"] == "win32" :
- key = "Z"
- else :
- key = "D"
- contents = contents.replace("LUA_RELEASE", "\"== Sluift XMPP Console (%(version)s) == \\nPress Ctrl-%(key)s to exit\"" % {"version": source[1].get_contents(), "key" : key})
- contents = contents.replace("LUA_COPYRIGHT", "")
- f = open(target[0].abspath, "w")
- f.write(contents)
- f.close()
- sluift_env.Command("lua.c", ["#/3rdParty/Lua/src/lua.c", sluift_env.Value(sluift_env["SLUIFT_VERSION"])], env.Action(patchLua, cmdstr = "$GENCOMSTR"))
+ # Generate Version.h
+ version_header = "#pragma once\n\n"
+ version_header += "#define SLUIFT_VERSION_STRING \"" + Version.getBuildVersion(env.Dir("#").abspath, "sluift") + "\"\n"
+ sluift_env.WriteVal("Version.h", sluift_env.Value(version_header))
# Generate core.cpp
def generate_embedded_lua(env, target, source) :
@@ -74,10 +62,6 @@ elif env["SCONS_STAGE"] == "build" :
f.close()
sluift_env.Command("core.c", ["core.lua"], env.Action(generate_embedded_lua, cmdstr="$GENCOMSTR"))
- if sluift_env.get("HAVE_READLINE", False) :
- sluift_env.Append(CPPDEFINES = ["LUA_USE_READLINE"])
- sluift_env.MergeFlags(sluift_env["READLINE_FLAGS"])
-
sluift_env.WriteVal("dll.c", sluift_env.Value(""))
sluift_sources = [env.File(x) for x in sluift_sources]
diff --git a/Sluift/SConscript.variant b/Sluift/SConscript.variant
index 92ee493..d0e2b18 100644
--- a/Sluift/SConscript.variant
+++ b/Sluift/SConscript.variant
@@ -6,12 +6,29 @@ Import('sluift_variant')
Import('sluift_sources')
if sluift_variant == 'exe' :
- env["SLUIFT"] = sluift_env.Program("sluift", sluift_sources + [
- "#/Sluift/lua.c",
- "#/Sluift/linit.c",
- ])
- if sluift_env.get("SLUIFT_INSTALLDIR", "") :
- sluift_env.Install(os.path.join(sluift_env["SLUIFT_INSTALLDIR"], "bin"), env["SLUIFT"])
+ common_objects = sluift_env.StaticObject(sluift_sources)
+
+ sluift_exe_env = sluift_env.Clone()
+ tokenize = sluift_exe_env.StaticObject("#/Sluift/tokenize.cpp")
+ exe_sources = tokenize + [
+ "#/Sluift/Console.cpp",
+ "#/Sluift/Terminal.cpp",
+ "#/Sluift/StandardTerminal.cpp",
+ "#/Sluift/Completer.cpp",
+ "#/Sluift/main.cpp",
+ ]
+
+ if sluift_exe_env.get("HAVE_EDITLINE", False) :
+ sluift_exe_env.Append(CPPDEFINES = ["HAVE_EDITLINE"])
+ sluift_exe_env.MergeFlags(sluift_exe_env["EDITLINE_FLAGS"])
+ exe_sources += ["#/Sluift/EditlineTerminal.cpp"]
+
+ env["SLUIFT"] = sluift_exe_env.Program("sluift", common_objects + exe_sources)
+ if sluift_exe_env.get("SLUIFT_INSTALLDIR", "") :
+ sluift_exe_env.Install(os.path.join(sluift_exe_env["SLUIFT_INSTALLDIR"], "bin"), env["SLUIFT"])
+
+ # Unit tests
+ env.Append(UNITTEST_OBJECTS = tokenize + ["#/Sluift/UnitTest/TokenizeTest.cpp"])
else :
sluift_env["SLUIFT_DLL_SUFFIX"] = "${SHLIBSUFFIX}"
if sluift_env["PLATFORM"] == "darwin" :
diff --git a/Sluift/StandardTerminal.cpp b/Sluift/StandardTerminal.cpp
new file mode 100644
index 0000000..f055684
--- /dev/null
+++ b/Sluift/StandardTerminal.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2013 Remko Tronçon
+ * Licensed under the GNU General Public License.
+ * See the COPYING file for more information.
+ */
+
+#include <Sluift/StandardTerminal.h>
+
+#include <boost/optional.hpp>
+#include <iostream>
+#include <stdexcept>
+
+using namespace Swift;
+
+StandardTerminal::StandardTerminal() {
+}
+
+StandardTerminal::~StandardTerminal() {
+}
+
+void StandardTerminal::printError(const std::string& message) {
+ std::cout << message << std::endl;
+}
+
+boost::optional<std::string> StandardTerminal::readLine(const std::string& prompt) {
+ std::cout << prompt << std::flush;
+ std::string input;
+ if (!std::getline(std::cin, input)) {
+ if (std::cin.eof()) {
+ return boost::optional<std::string>();
+ }
+ throw std::runtime_error("Input error");
+ }
+ return input;
+}
+
+void StandardTerminal::addToHistory(const std::string&) {
+}
diff --git a/Sluift/StandardTerminal.h b/Sluift/StandardTerminal.h
new file mode 100644
index 0000000..420df6b
--- /dev/null
+++ b/Sluift/StandardTerminal.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2013 Remko Tronçon
+ * Licensed under the GNU General Public License.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <Swiften/Base/Override.h>
+#include <Sluift/Terminal.h>
+
+namespace Swift {
+ class StandardTerminal : public Terminal {
+ public:
+ StandardTerminal();
+ virtual ~StandardTerminal();
+
+ virtual boost::optional<std::string> readLine(const std::string& prompt) SWIFTEN_OVERRIDE;
+ virtual void printError(const std::string& message) SWIFTEN_OVERRIDE;
+ virtual void addToHistory(const std::string& command);
+ };
+}
diff --git a/Sluift/Terminal.cpp b/Sluift/Terminal.cpp
new file mode 100644
index 0000000..9233233
--- /dev/null
+++ b/Sluift/Terminal.cpp
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) 2013 Remko Tronçon
+ * Licensed under the GNU General Public License.
+ * See the COPYING file for more information.
+ */
+
+#include <Sluift/Terminal.h>
+
+using namespace Swift;
+
+Terminal::Terminal() : completer(NULL) {
+}
+
+Terminal::~Terminal() {
+}
diff --git a/Sluift/Terminal.h b/Sluift/Terminal.h
new file mode 100644
index 0000000..2d5e41b
--- /dev/null
+++ b/Sluift/Terminal.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2013 Remko Tronçon
+ * Licensed under the GNU General Public License.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <boost/optional/optional_fwd.hpp>
+
+namespace Swift {
+ class Completer;
+
+ class Terminal {
+ public:
+ Terminal();
+ virtual ~Terminal();
+
+ Completer* getCompleter() const {
+ return completer;
+ }
+
+ void setCompleter(Completer* completer) {
+ this->completer = completer;
+ }
+
+ virtual boost::optional<std::string> readLine(const std::string& prompt) = 0;
+ virtual void addToHistory(const std::string& command) = 0;
+ virtual void printError(const std::string& message) = 0;
+
+ private:
+ Completer* completer;
+ };
+}
diff --git a/Sluift/UnitTest/TokenizeTest.cpp b/Sluift/UnitTest/TokenizeTest.cpp
new file mode 100644
index 0000000..1bf20ed
--- /dev/null
+++ b/Sluift/UnitTest/TokenizeTest.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2013 Remko Tronçon
+ * Licensed under the GNU General Public License.
+ * See the COPYING file for more information.
+ */
+
+#include <cppunit/extensions/HelperMacros.h>
+#include <cppunit/extensions/TestFactoryRegistry.h>
+
+#include <Sluift/tokenize.h>
+
+using namespace Swift;
+
+class TokenizeTest : public CppUnit::TestFixture {
+ CPPUNIT_TEST_SUITE(TokenizeTest);
+ CPPUNIT_TEST(testTokenize);
+ CPPUNIT_TEST(testTokenize);
+ CPPUNIT_TEST(testTokenize_String);
+ CPPUNIT_TEST(testTokenize_IncompleteString);
+ CPPUNIT_TEST(testTokenize_Identifier);
+ CPPUNIT_TEST_SUITE_END();
+
+ public:
+ void testTokenize() {
+ std::vector<std::string> tokens = Lua::tokenize("foo.bar + 1.23 - bam");
+
+ CPPUNIT_ASSERT_EQUAL(7, static_cast<int>(tokens.size()));
+ CPPUNIT_ASSERT_EQUAL(std::string("foo"), tokens[0]);
+ CPPUNIT_ASSERT_EQUAL(std::string("."), tokens[1]);
+ CPPUNIT_ASSERT_EQUAL(std::string("bar"), tokens[2]);
+ CPPUNIT_ASSERT_EQUAL(std::string("+"), tokens[3]);
+ CPPUNIT_ASSERT_EQUAL(std::string("1.23"), tokens[4]);
+ CPPUNIT_ASSERT_EQUAL(std::string("-"), tokens[5]);
+ CPPUNIT_ASSERT_EQUAL(std::string("bam"), tokens[6]);
+ }
+
+ void testTokenize_String() {
+ std::vector<std::string> tokens = Lua::tokenize(" foo .. \"1234\\\"bla blo\"");
+
+ CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(tokens.size()));
+ CPPUNIT_ASSERT_EQUAL(std::string("foo"), tokens[0]);
+ CPPUNIT_ASSERT_EQUAL(std::string(".."), tokens[1]);
+ CPPUNIT_ASSERT_EQUAL(std::string("\"1234\\\"bla blo\""), tokens[2]);
+ }
+
+ void testTokenize_IncompleteString() {
+ std::vector<std::string> tokens = Lua::tokenize("\"1234");
+
+ CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(tokens.size()));
+ CPPUNIT_ASSERT_EQUAL(std::string("\"1234"), tokens[0]);
+ }
+
+ void testTokenize_Identifier() {
+ std::vector<std::string> tokens = Lua::tokenize("foo.bar_baz");
+
+ CPPUNIT_ASSERT_EQUAL(3, static_cast<int>(tokens.size()));
+ CPPUNIT_ASSERT_EQUAL(std::string("foo"), tokens[0]);
+ CPPUNIT_ASSERT_EQUAL(std::string("."), tokens[1]);
+ CPPUNIT_ASSERT_EQUAL(std::string("bar_baz"), tokens[2]);
+ }
+
+};
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TokenizeTest);
diff --git a/Sluift/linit.c b/Sluift/linit.c
deleted file mode 100644
index ad27b37..0000000
--- a/Sluift/linit.c
+++ /dev/null
@@ -1,37 +0,0 @@
-#include <lua.h>
-#include <lualib.h>
-#include <lauxlib.h>
-#include "sluift.h"
-
-static const luaL_Reg lualibs[] = {
- {"", luaopen_base},
- {LUA_LOADLIBNAME, luaopen_package},
- {LUA_TABLIBNAME, luaopen_table},
- {LUA_IOLIBNAME, luaopen_io},
- {LUA_OSLIBNAME, luaopen_os},
- {LUA_STRLIBNAME, luaopen_string},
- {LUA_MATHLIBNAME, luaopen_math},
- {LUA_DBLIBNAME, luaopen_debug},
- {"sluift", luaopen_sluift},
- {NULL, NULL}
-};
-
-
-LUALIB_API void luaL_openlibs (lua_State *L) {
- const luaL_Reg *lib = lualibs;
- for (; lib->func; lib++) {
- lua_pushcfunction(L, lib->func);
- lua_pushstring(L, lib->name);
- lua_call(L, 1, 0);
- }
-
- /* Import sluift into global namespace */
- lua_pushglobaltable(L);
- lua_getfield(L, -1, "sluift");
- for (lua_pushnil(L); lua_next(L, -2); ) {
- lua_pushvalue(L, -2);
- lua_pushvalue(L, -2);
- lua_settable(L, -6);
- lua_pop(L, 1);
- }
-}
diff --git a/Sluift/main.cpp b/Sluift/main.cpp
new file mode 100644
index 0000000..fcff2aa
--- /dev/null
+++ b/Sluift/main.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2013 Remko Tronçon
+ * Licensed under the GNU General Public License.
+ * See the COPYING file for more information.
+ */
+
+#include <string>
+#include <vector>
+#include <lua.hpp>
+#include <Swiften/Base/foreach.h>
+#include <Swiften/Base/Platform.h>
+#include <boost/program_options/options_description.hpp>
+#include <boost/program_options/variables_map.hpp>
+#include <boost/program_options.hpp>
+#include <boost/version.hpp>
+#include <boost/numeric/conversion/cast.hpp>
+#include <Sluift/Console.h>
+#include <Sluift/StandardTerminal.h>
+#include <Sluift/sluift.h>
+#include <Sluift/Lua/LuaUtils.h>
+#ifdef HAVE_EDITLINE
+#include <Sluift/EditlineTerminal.h>
+#endif
+#include <Sluift/Version.h>
+
+using namespace Swift;
+
+#ifdef SWIFTEN_PLATFORM_WINDOWS
+#define EXIT_KEY "Z"
+#else
+#define EXIT_KEY "D"
+#endif
+
+static const std::string SLUIFT_WELCOME_STRING(
+ "== Sluift XMPP Console (" SLUIFT_VERSION_STRING ")\nPress Ctrl-" EXIT_KEY " to exit");
+
+static const luaL_Reg defaultLibraries[] = {
+ {"", luaopen_base},
+ {LUA_LOADLIBNAME, luaopen_package},
+ {LUA_TABLIBNAME, luaopen_table},
+ {LUA_IOLIBNAME, luaopen_io},
+ {LUA_OSLIBNAME, luaopen_os},
+ {LUA_STRLIBNAME, luaopen_string},
+ {LUA_MATHLIBNAME, luaopen_math},
+ {LUA_DBLIBNAME, luaopen_debug},
+ {"sluift", luaopen_sluift},
+ {NULL, NULL}
+};
+
+static void checkResult(lua_State* L, int result) {
+ if (result && !lua_isnil(L, -1)) {
+ const char* errorMessage = lua_tostring(L, -1);
+ throw std::runtime_error(errorMessage ? errorMessage : "Unknown error");
+ }
+}
+
+static void initialize(lua_State* L) {
+ lua_gc(L, LUA_GCSTOP, 0);
+ for (const luaL_Reg* lib = defaultLibraries; lib->func; lib++) {
+ lua_pushcfunction(L, lib->func);
+ lua_pushstring(L, lib->name);
+ lua_call(L, 1, 0);
+ }
+ lua_gc(L, LUA_GCRESTART, 0);
+}
+
+static void runScript(lua_State* L, const std::string& script, const std::vector<std::string>& scriptArguments) {
+ // Create arguments table
+ lua_createtable(L, boost::numeric_cast<int>(scriptArguments.size()), 0);
+ for (size_t i = 0; i < scriptArguments.size(); ++i) {
+ lua_pushstring(L, scriptArguments[i].c_str());
+ lua_rawseti(L, -2, boost::numeric_cast<int>(i+1));
+ }
+ lua_setglobal(L, "arg");
+
+ // Load file
+ checkResult(L, luaL_loadfile(L, script.c_str()));
+ foreach (const std::string& scriptArgument, scriptArguments) {
+ lua_pushstring(L, scriptArgument.c_str());
+ }
+ checkResult(L, Console::call(L, boost::numeric_cast<int>(scriptArguments.size()), false));
+}
+
+// void runConsole() {
+ // contents = contents.replace("LUA_RELEASE", "\"== Sluift XMPP Console (%(version)s) == \\nPress Ctrl-%(key)s to exit\"" % {"version": source[1].get_contents(), "key" : key})
+// }
+
+int main(int argc, char* argv[]) {
+ // Parse program options
+ boost::program_options::options_description visibleOptions("Options");
+ visibleOptions.add_options()
+ ("help,h", "Display this help message")
+ ("version,v", "Display version information")
+ ("interactive,i", "Enter interactive mode after executing script")
+ ;
+ boost::program_options::options_description hiddenOptions("Hidden Options");
+ hiddenOptions.add_options()
+ ("script", boost::program_options::value< std::string >(), "Script to be executed")
+ ("script-arguments", boost::program_options::value< std::vector<std::string> >(), "Script arguments")
+ ;
+ boost::program_options::options_description options("All Options");
+ options.add(visibleOptions).add(hiddenOptions);
+
+ boost::program_options::positional_options_description positional_options;
+ positional_options.add("script", 1).add("script-arguments", -1);
+
+ boost::program_options::variables_map arguments;
+ try {
+ boost::program_options::store(
+ boost::program_options::command_line_parser(argc, argv)
+ .options(options)
+ .positional(positional_options).run(), arguments);
+ }
+ catch (const boost::program_options::unknown_option& option) {
+#if BOOST_VERSION >= 104200
+ std::cout << "Ignoring unknown option " << option.get_option_name() << " but continuing." << std::endl;
+#else
+ std::cout << "Error: " << option.what() << " (continuing)" << std::endl;
+#endif
+ }
+ catch (const std::exception& e) {
+ std::cout << "Error: " << e.what() << std::endl;
+ return -1;
+ }
+ boost::program_options::notify(arguments);
+
+ // Help & version
+ if (arguments.count("help")) {
+ std::cout << visibleOptions << "\n";
+ return 0;
+ }
+ else if (arguments.count("version")) {
+ std::cout << SLUIFT_VERSION_STRING;
+ return 0;
+ }
+
+ lua_State* L = luaL_newstate();
+ initialize(L);
+ try {
+ // Run script
+ if (arguments.count("script")) {
+ std::vector<std::string> scriptArguments;
+ if (arguments.count("script-arguments")) {
+ scriptArguments = arguments["script-arguments"].as< std::vector<std::string> >();
+ }
+ runScript(L, arguments["script"].as<std::string>(), scriptArguments);
+ }
+
+ // Run console
+ if (arguments.count("interactive") || arguments.count("script") == 0) {
+ std::cout << SLUIFT_WELCOME_STRING << std::endl;
+#ifdef HAVE_EDITLINE
+ EditlineTerminal& terminal = EditlineTerminal::getInstance();
+#else
+ StandardTerminal terminal;
+#endif
+ Console console(L, &terminal);
+ console.run();
+ }
+ }
+ catch (const std::exception& e) {
+ std::cerr << e.what() << std::endl;
+ lua_close(L);
+ return -1;
+ }
+ lua_close(L);
+ return 0;
+}
diff --git a/Sluift/sluift.h b/Sluift/sluift.h
index 2613370..b82e1c4 100644
--- a/Sluift/sluift.h
+++ b/Sluift/sluift.h
@@ -20,10 +20,6 @@
#include <lua.h>
#endif
-#if LUA_VERSION_NUM < 502
-#define lua_pushglobaltable(L) lua_pushvalue(L, LUA_GLOBALSINDEX)
-#endif
-
#if defined(__cplusplus)
extern "C"
#endif
diff --git a/Sluift/tokenize.cpp b/Sluift/tokenize.cpp
new file mode 100644
index 0000000..b089cdb
--- /dev/null
+++ b/Sluift/tokenize.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2013 Remko Tronçon
+ * Licensed under the GNU General Public License.
+ * See the COPYING file for more information.
+ */
+
+#include <Sluift/tokenize.h>
+
+#include <boost/tokenizer.hpp>
+#include <cctype>
+
+using namespace Swift;
+
+namespace {
+ struct LuaTokenizeFunctor {
+ void reset() {
+ }
+
+ template<typename InputIterator, typename Token>
+ bool operator()(InputIterator& next, InputIterator& end, Token& result) {
+ while (next != end && std::isspace(*next)) {
+ ++next;
+ }
+ if (next == end) {
+ return false;
+ }
+
+ std::vector<char> token;
+ char c = *next++;
+ token.push_back(c);
+
+ // String literal
+ if (c == '\'' || c == '"') {
+ char quote = c;
+ bool inEscape = false;
+ for (; next != end; ++next) {
+ c = *next;
+ token.push_back(c);
+ if (inEscape) {
+ inEscape = false;
+ }
+ else if (c == '\\') {
+ inEscape = true;
+ }
+ else if (c == quote) {
+ break;
+ }
+ }
+ if (next != end) {
+ ++next;
+ }
+ }
+ // Identifier
+ else if (std::isalpha(c) || c == '_') {
+ while (next != end && (std::isalpha(*next) || *next == '_' || std::isdigit(*next))) {
+ token.push_back(*next);
+ ++next;
+ }
+ }
+ // Digit
+ else if (std::isdigit(c)) {
+ while (next != end && !std::isspace(*next)) {
+ token.push_back(*next);
+ ++next;
+ }
+ }
+ // Dots
+ else if (c == '.') {
+ while (next != end && *next == '.') {
+ token.push_back(*next);
+ ++next;
+ }
+ }
+
+ result = Token(&token[0], token.size());
+ return true;
+ }
+ };
+}
+
+
+std::vector<std::string> Lua::tokenize(const std::string& input) {
+ boost::tokenizer<LuaTokenizeFunctor> tokenizer(input);
+ return std::vector<std::string>(tokenizer.begin(), tokenizer.end());
+}
+
diff --git a/Sluift/tokenize.h b/Sluift/tokenize.h
new file mode 100644
index 0000000..6f09345
--- /dev/null
+++ b/Sluift/tokenize.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2013 Remko Tronçon
+ * Licensed under the GNU General Public License.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <vector>
+#include <string>
+
+namespace Swift {
+ namespace Lua {
+ std::vector<std::string> tokenize(const std::string&);
+ }
+}