diff options
| -rw-r--r-- | BuildTools/SCons/SConscript.boot | 5 | ||||
| -rw-r--r-- | BuildTools/SCons/SConstruct | 16 | ||||
| -rw-r--r-- | Sluift/.gitignore | 1 | ||||
| -rw-r--r-- | Sluift/Completer.cpp | 12 | ||||
| -rw-r--r-- | Sluift/Completer.h | 19 | ||||
| -rw-r--r-- | Sluift/Console.cpp | 279 | ||||
| -rw-r--r-- | Sluift/Console.h | 41 | ||||
| -rw-r--r-- | Sluift/EditlineTerminal.cpp | 98 | ||||
| -rw-r--r-- | Sluift/EditlineTerminal.h | 25 | ||||
| -rw-r--r-- | Sluift/Lua/LuaUtils.h | 6 | ||||
| -rw-r--r-- | Sluift/SConscript | 24 | ||||
| -rw-r--r-- | Sluift/SConscript.variant | 29 | ||||
| -rw-r--r-- | Sluift/StandardTerminal.cpp | 38 | ||||
| -rw-r--r-- | Sluift/StandardTerminal.h | 22 | ||||
| -rw-r--r-- | Sluift/Terminal.cpp | 15 | ||||
| -rw-r--r-- | Sluift/Terminal.h | 35 | ||||
| -rw-r--r-- | Sluift/UnitTest/TokenizeTest.cpp | 64 | ||||
| -rw-r--r-- | Sluift/linit.c | 37 | ||||
| -rw-r--r-- | Sluift/main.cpp | 168 | ||||
| -rw-r--r-- | Sluift/sluift.h | 4 | ||||
| -rw-r--r-- | Sluift/tokenize.cpp | 86 | ||||
| -rw-r--r-- | Sluift/tokenize.h | 16 | 
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&); +	} +} | 
 Swift
 Swift