From 0b19dc7292b7672c9fbb711a411c392bc5b2bb34 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Sat, 28 Dec 2013 17:13:08 +0100
Subject: 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

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&);
+	}
+}
-- 
cgit v0.10.2-6-g49f6