- added proper commandline interface with options. synergizes very nicely with named enums. :-) added a verbosity level which does nothing just yet

Tue, 22 Jul 2014 19:05:58 +0300

author
Teemu Piippo <crimsondusk64@gmail.com>
date
Tue, 22 Jul 2014 19:05:58 +0300
changeset 139
cf11621ae422
parent 138
a426c1039655
child 140
04a6eb68f226

- added proper commandline interface with options. synergizes very nicely with named enums. :-) added a verbosity level which does nothing just yet

CMakeLists.txt file | annotate | diff | comparison | revisions
src/botStuff.h file | annotate | diff | comparison | revisions
src/commandline.cpp file | annotate | diff | comparison | revisions
src/commandline.h file | annotate | diff | comparison | revisions
src/expression.cpp file | annotate | diff | comparison | revisions
src/format.h file | annotate | diff | comparison | revisions
src/list.h file | annotate | diff | comparison | revisions
src/macros.h file | annotate | diff | comparison | revisions
src/main.cpp file | annotate | diff | comparison | revisions
src/main.h file | annotate | diff | comparison | revisions
src/parser.cpp file | annotate | diff | comparison | revisions
src/parser.h file | annotate | diff | comparison | revisions
src/stringTable.cpp file | annotate | diff | comparison | revisions
src/tokens.h file | annotate | diff | comparison | revisions
src/types.h file | annotate | diff | comparison | revisions
--- a/CMakeLists.txt	Tue Jul 22 12:57:46 2014 +0300
+++ b/CMakeLists.txt	Tue Jul 22 19:05:58 2014 +0300
@@ -2,6 +2,7 @@
 
 set (BOTC_HEADERS
 	src/botStuff.h
+	src/commandline.h
 	src/commands.h
 	src/list.h
 	src/dataBuffer.h
@@ -21,6 +22,7 @@
 )
 
 set (BOTC_SOURCES
+	src/commandline.cpp
 	src/commands.cpp
 	src/dataBuffer.cpp
 	src/events.cpp
--- a/src/botStuff.h	Tue Jul 22 12:57:46 2014 +0300
+++ b/src/botStuff.h	Tue Jul 22 19:05:58 2014 +0300
@@ -122,7 +122,8 @@
 	Swap,
 	Dup,
 	ArraySet,
-	NumDataHeaders
+
+	NumValues
 };
 
 #endif	// BOTC_BOTSTUFF_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/commandline.cpp	Tue Jul 22 19:05:58 2014 +0300
@@ -0,0 +1,177 @@
+/*
+	Copyright 2014 Teemu Piippo
+	All rights reserved.
+
+	Redistribution and use in source and binary forms, with or without
+	modification, are permitted provided that the following conditions
+	are met:
+
+	1. Redistributions of source code must retain the above copyright
+	   notice, this list of conditions and the following disclaimer.
+	2. Redistributions in binary form must reproduce the above copyright
+	   notice, this list of conditions and the following disclaimer in the
+	   documentation and/or other materials provided with the distribution.
+	3. The name of the author may not be used to endorse or promote products
+	   derived from this software without specific prior written permission.
+
+	THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+	IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+	OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+	IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+	INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+	NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+	DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+	THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+	(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+	THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "commandline.h"
+
+// _________________________________________________________________________________________________
+//
+CommandLine::~CommandLine()
+{
+	for (CommandLineOption* opt : _options)
+		delete opt;
+}
+
+// _________________________________________________________________________________________________
+//
+void CommandLine::addOption (CommandLineOption* option)
+{
+	_options << option;
+}
+
+// _________________________________________________________________________________________________
+//
+StringList CommandLine::process (int argc, char* argv[])
+{
+	StringList args;
+
+	for (int argn = 1; argn < argc; ++argn)
+	{
+		String arg (argv[argn]);
+
+		if (arg == "--")
+		{
+			// terminating option - write everything to args and be done with it.
+			while (++argn < argc)
+				args << argv[argn];
+
+			break;
+		}
+
+		if (arg[0] != '-')
+		{
+			// non-option argument - pass to result
+			args << arg;
+			continue;
+		}
+
+		if (arg[1] != '-')
+		{
+			// short-form options
+			for (int i = 1; i < arg.length(); ++i)
+			{
+				CommandLineOption** optionptr = _options.find ([&](CommandLineOption* const& a)
+				{
+					return arg[i] == a->shortform();
+				});
+
+				if (not optionptr)
+				{
+					error ("unknown option -%1", arg[i]);
+				}
+
+				CommandLineOption* option = *optionptr;
+
+				if (option->isOfType<bool>())
+				{
+					// Bool options need no parameters
+					option->handleValue ("");
+					continue;
+				}
+
+				// Ensure we got a valid parameter coming up
+				if (argn == argc - 1)
+					error ("option -%1 requires a parameter", option->describe());
+
+				if (i != arg.length() - 1)
+				{
+					error ("option %1 requires a parameter but has option -%2 stacked on",
+							option->describe(), arg[i + 1]);
+				}
+
+				option->handleValue (argv[++argn]);
+			}
+		}
+		else
+		{
+			// long-form options
+			String name, value;
+			{
+				int idx = arg.firstIndexOf ("=", 2);
+
+				if (idx != -1)
+				{
+					name = arg.mid (2, idx);
+					value = arg.mid (idx + 1);
+				}
+				else
+				{
+					name = arg.mid (2);
+				}
+			}
+
+			CommandLineOption** optionptr = _options.find ([&](CommandLineOption* const& a)
+			{
+				return a->longform() == name;
+			});
+
+			if (not optionptr)
+				error ("unknown option --%1", name);
+
+			if ((*optionptr)->isOfType<bool>() and not value.isEmpty())
+				error ("option %1 does not take a value", (*optionptr)->describe());
+
+			(*optionptr)->handleValue (value);
+		}
+	}
+
+	return args;
+}
+
+// _________________________________________________________________________________________________
+//
+void CommandLineOption::handleValue (String const& value)
+{
+	if (isOfType<bool>())
+	{
+		*reinterpret_cast<bool*> (pointer()) = value;
+	}
+	elif (isOfType<int>())
+	{
+		bool ok;
+		*reinterpret_cast<int*> (pointer()) = value.toLong (&ok);
+
+		if (not ok)
+			error ("bad integral value passed to %1", describe());
+	}
+	elif (isOfType<String>())
+	{
+		*reinterpret_cast<String*> (pointer()) = value;
+	}
+	elif (isOfType<double>())
+	{
+		bool ok;
+		*reinterpret_cast<double*> (pointer()) = value.toDouble (&ok);
+
+		if (not ok)
+			error ("bad floating-point value passed to %1", describe());
+	}
+	else
+	{
+		error ("OMGWTFBBQ: %1 has an invalid type index", describe());
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/commandline.h	Tue Jul 22 19:05:58 2014 +0300
@@ -0,0 +1,176 @@
+/*
+	Copyright 2014 Teemu Piippo
+	All rights reserved.
+
+	Redistribution and use in source and binary forms, with or without
+	modification, are permitted provided that the following conditions
+	are met:
+
+	1. Redistributions of source code must retain the above copyright
+	   notice, this list of conditions and the following disclaimer.
+	2. Redistributions in binary form must reproduce the above copyright
+	   notice, this list of conditions and the following disclaimer in the
+	   documentation and/or other materials provided with the distribution.
+	3. The name of the author may not be used to endorse or promote products
+	   derived from this software without specific prior written permission.
+
+	THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+	IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+	OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+	IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+	INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+	NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+	DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+	THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+	(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+	THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#pragma once
+#include "main.h"
+#include <typeinfo>
+#include <type_traits>
+#include <cstring>
+
+// _________________________________________________________________________________________________
+//
+class CommandLineOption
+{
+private:
+	char _shortform;
+	String _longform;
+	String _description;
+	std::type_info const* _type;
+	void* _ptr;
+
+public:
+	template<typename T>
+	CommandLineOption (T& data, char shortform, const char* longform, const char* description) :
+		_shortform (shortform),
+		_longform (longform),
+		_description (description),
+		_type (&typeid (T)),
+		_ptr (&data)
+	{
+		static_assert (std::is_same<T, int>::value
+			or std::is_same<T, bool>::value
+			or std::is_same<T, String>::value
+			or std::is_same<T, double>::value
+			or std::is_enum<T>::value,
+			"value to CommandLineOption must be either int, bool, String or double");
+
+		if (shortform == '\0' and not std::strlen (longform))
+			error ("commandline option left without short-form or long-form name");
+	}
+
+	virtual ~CommandLineOption() {}
+
+	inline String describe() const
+	{
+		return std::strlen (_longform)
+			? (String ("--") + _longform)
+			: String ({'-', _shortform, '\0'});
+	}
+
+	inline String const& longform() const
+	{
+		return _longform;
+	}
+
+	inline String const& description() const
+	{
+		return _description;
+	}
+
+	inline char shortform() const
+	{
+		return _shortform;
+	}
+
+	inline void* pointer() const
+	{
+		return _ptr;
+	}
+
+	template<typename T>
+	inline bool isOfType() const
+	{
+		return &typeid(T) == _type;
+	}
+
+	virtual void handleValue (String const& a);
+};
+
+template<typename T>
+class EnumeratedCommandLineOption : public CommandLineOption
+{
+	StringList _values;
+
+public:
+	EnumeratedCommandLineOption (T& data, char shortform, const char* longform,
+		const char* description) :
+		CommandLineOption (data, shortform, longform, description)
+	{
+		for (int i = 0; i < int (T::NumValues); ++i)
+		{
+			String value = MakeFormatArgument (T (i)).toLowercase();
+			int pos;
+
+			if ((pos = value.firstIndexOf ("::")) != -1)
+				value = value.mid (pos + 2);
+
+			_values << value;
+		}
+	}
+
+	virtual void handleValue (String const& value) override
+	{
+		String const lowvalue (value.toLowercase());
+
+		for (int i = 0; i < _values.size(); ++i)
+		{
+			if (_values[i].toLowercase() == lowvalue)
+			{
+				*reinterpret_cast<T*> (pointer()) = T (i);
+				return;
+			}
+		}
+
+		error ("bad value passed to %1 (%2), valid values are: %3", describe(), value, _values);
+	}
+};
+
+// _________________________________________________________________________________________________
+//
+class CommandLine
+{
+	List<CommandLineOption*> _options;
+	DELETE_COPY (CommandLine)
+	
+public:
+	CommandLine() = default;
+	~CommandLine();
+	template<typename Enum>
+	void addEnumeratedOption (Enum& e, char shortform, const char* longform,
+		const char* description);
+	void addOption (CommandLineOption* option);
+	StringList process (int argc, char* argv[]);
+
+	template<typename... Args>
+	void addOption (Args... args)
+	{
+		addOption (new CommandLineOption (args...));
+	}
+};
+
+// _________________________________________________________________________________________________
+//
+template<typename Enum>
+void CommandLine::addEnumeratedOption (Enum& e, char shortform, const char* longform,
+	const char* description)
+{
+	static_assert(std::is_enum<Enum>::value, "addEnumeratedOption requires a named enumerator");
+	auto enumoption = new EnumeratedCommandLineOption<Enum> (e, shortform, longform,
+		description);
+	addOption (static_cast<CommandLineOption*> (enumoption));
+}
--- a/src/expression.cpp	Tue Jul 22 12:57:46 2014 +0300
+++ b/src/expression.cpp	Tue Jul 22 19:05:58 2014 +0300
@@ -32,7 +32,7 @@
 	{Token::Bar,				80,		2,	DataHeader::OrBitwise		},
 	{Token::DoubleAmperstand,	90,		2,	DataHeader::AndLogical		},
 	{Token::DoubleBar,			100,	2,	DataHeader::OrLogical		},
-	{Token::QuestionMark,		110,	3,	DataHeader::NumDataHeaders	},
+	{Token::QuestionMark,		110,	3,	DataHeader::NumValues	},
 };
 
 // _________________________________________________________________________________________________
@@ -453,7 +453,7 @@
 		}
 		else
 		{
-			ASSERT_NE (info->header, DataHeader::NumDataHeaders);
+			ASSERT_NE (info->header, DataHeader::NumValues);
 
 			// Generic case: write all arguments and apply the operator's
 			// data header.
--- a/src/format.h	Tue Jul 22 12:57:46 2014 +0300
+++ b/src/format.h	Tue Jul 22 19:05:58 2014 +0300
@@ -129,12 +129,13 @@
 void dvalof (void a);
 #endif // IN_IDE_PARSER
 
-
+// _________________________________________________________________________________________________
 //
 // Formats the given string with the given args.
 //
 String formatArgs (const String& fmtstr, const std::vector<String>& args);
 
+// _________________________________________________________________________________________________
 //
 // Expands the given arguments into a vector of strings.
 //
@@ -151,6 +152,7 @@
 	(void) data;
 }
 
+// _________________________________________________________________________________________________
 //
 // Formats the given formatter string and args and returns the string.
 // This is essentially a modernized sprintf.
@@ -184,6 +186,7 @@
 	return formatArgs (fmtstr, args);
 }
 
+// _________________________________________________________________________________________________
 //
 // This is an overload of format() where no arguments are supplied.
 // It returns the formatter string as-is.
@@ -194,6 +197,7 @@
 	return fmtstr;
 }
 
+// _________________________________________________________________________________________________
 //
 // Processes the given formatter string using format() and prints it to the
 // specified file pointer.
@@ -204,6 +208,7 @@
 	fprintf (fp, "%s", format (fmtstr, args...).c_str());
 }
 
+// _________________________________________________________________________________________________
 //
 // Processes the given formatter string using format() and appends it to the
 // specified file by name.
@@ -221,6 +226,7 @@
 	}
 }
 
+// _________________________________________________________________________________________________
 //
 // Processes the given formatter string using format() and prints the result to
 // stdout.
@@ -231,6 +237,7 @@
 	printTo (stdout, fmtstr, args...);
 }
 
+// _________________________________________________________________________________________________
 //
 // Throws an std::runtime_error with the processed formatted string. The program
 // execution terminates after a call to this function as the exception is first
--- a/src/list.h	Tue Jul 22 12:57:46 2014 +0300
+++ b/src/list.h	Tue Jul 22 19:05:58 2014 +0300
@@ -33,6 +33,7 @@
 #include <algorithm>
 #include <deque>
 #include <initializer_list>
+#include <functional>
 
 template<typename T>
 class List
@@ -59,6 +60,8 @@
 	inline Iterator					end();
 	inline ConstIterator			end() const;
 	int								find (const T& needle) const;
+	T*								find (std::function<bool (T const&)> func);
+	T const*						find (std::function<bool (T const&)> func) const;
 	inline const T&					first() const;
 	inline void						insert (int pos, const T& value);
 	inline bool						isEmpty() const;
@@ -281,6 +284,30 @@
 }
 
 template<typename T>
+T* List<T>::find (std::function<bool (T const&)> func)
+{
+	for (T& element : _deque)
+	{
+		if (func (element))
+			return &element;
+	}
+
+	return nullptr;
+}
+
+template<typename T>
+T const* List<T>::find (std::function<bool (T const&)> func) const
+{
+	for (T const& element : _deque)
+	{
+		if (func (element))
+			return &element;
+	}
+
+	return nullptr;
+}
+
+template<typename T>
 void List<T>::removeOne (const T& a)
 {
 	auto it = std::find (_deque.begin(), _deque.end(), a);
--- a/src/macros.h	Tue Jul 22 12:57:46 2014 +0300
+++ b/src/macros.h	Tue Jul 22 19:05:58 2014 +0300
@@ -80,4 +80,9 @@
 #define ASSERT_RANGE(A,MIN,MAX) { ASSERT_LT_EQ(A, MAX); ASSERT_GT_EQ(A, MIN); }
 #define ASSERT(A) ASSERT_EQ (A, true)
 
+#define DELETE_COPY(T) \
+public: \
+	T (T& other) = delete; \
+	T& operator= (T& other) = delete;
+
 #endif // BOTC_MACROS_H
--- a/src/main.cpp	Tue Jul 22 12:57:46 2014 +0300
+++ b/src/main.cpp	Tue Jul 22 19:05:58 2014 +0300
@@ -34,15 +34,24 @@
 #include "parser.h"
 #include "lexer.h"
 #include "hginfo.h"
+#include "commandline.h"
+#include "enumstrings.h"
 
 int main (int argc, char** argv)
 {
 	try
 	{
-		// Intepret command-line parameters:
-		// -l: list commands
-		// I guess there should be a better way to do this.
-		if (argc == 2 and String (argv[1]) == "-l")
+		Verbosity verboselevel = Verbosity::None;
+		bool listcommands = false;
+
+		CommandLine cmdline;
+		cmdline.addOption (listcommands, 'l', "listfunctions",
+			"List available function signatures and exit");
+		cmdline.addEnumeratedOption (verboselevel, 'V', "verbose",
+			"Output more verbose information (0 through 2)");
+		StringList args = cmdline.process (argc, argv);
+
+		if (listcommands)
 		{
 			BotscriptParser parser;
 			parser.setReadOnly (true);
@@ -54,7 +63,7 @@
 			exit (0);
 		}
 
-		if (argc == 2 and String (argv[1]) == "-v")
+		if (not within (args.size(), 1, 2))
 		{
 			// Print header
 			String header;
@@ -64,32 +73,26 @@
 			header += " (debug build)";
 #endif
 
-			print ("%1\n", header);
-			exit (0);
-		}
-
-		if (argc < 2)
-		{
-			fprintf (stderr, APPNAME " %s\n", versionString (false).c_str());
-			fprintf (stderr, "usage: %s <infile> [outfile] # compiles botscript\n", argv[0]);
-			fprintf (stderr, "       %s -l                 # lists commands\n", argv[0]);
-			fprintf (stderr, "       %s -v                 # displays version info\n", argv[0]);
+			printTo (stderr, "%1\n", header);
+			printTo (stderr, "usage: %1 <infile> [outfile] # compiles botscript\n", argv[0]);
+			printTo (stderr, "       %1 -l                 # lists commands\n", argv[0]);
+			printTo (stderr, "       %1 -v                 # displays version info\n", argv[0]);
 			exit (1);
 		}
 
 		String outfile;
 
-		if (argc < 3)
-			outfile = makeObjectFileName (argv[1]);
+		if (args.size() == 1)
+			outfile = makeObjectFileName (args[0]);
 		else
-			outfile = argv[2];
+			outfile = args[1];
 
 		// Prepare reader and writer
 		BotscriptParser* parser = new BotscriptParser;
 
 		// We're set, begin parsing :)
 		print ("Parsing script...\n");
-		parser->parseBotscript (argv[1]);
+		parser->parseBotscript (args[0]);
 		print ("Script parsed successfully.\n");
 
 		// Parse done, print statistics and write to file
--- a/src/main.h	Tue Jul 22 12:57:46 2014 +0300
+++ b/src/main.h	Tue Jul 22 19:05:58 2014 +0300
@@ -63,4 +63,10 @@
 	return a < b ? a : b;
 }
 
+template<typename T>
+inline bool within (T a, T min, T max)
+{
+	return a >= min and a <= max;
+}
+
 #endif // BOTC_MAIN_H
--- a/src/parser.cpp	Tue Jul 22 12:57:46 2014 +0300
+++ b/src/parser.cpp	Tue Jul 22 19:05:58 2014 +0300
@@ -825,9 +825,9 @@
 		: (m_currentMode == ParserMode::MainLoop) ? DataHeader::EndMainLoop
 		: (m_currentMode == ParserMode::Onenter) ? DataHeader::EndOnEnter
 		: (m_currentMode == ParserMode::Onexit) ? DataHeader::EndOnExit
-		: DataHeader::NumDataHeaders;
+		: DataHeader::NumValues;
 
-	if (dataheader == DataHeader::NumDataHeaders)
+	if (dataheader == DataHeader::NumValues)
 		error ("unexpected `}`");
 
 	// Data header must be written before mode is changed because
@@ -838,8 +838,7 @@
 	m_lexer->next (Token::Semicolon);
 }
 
-// 
--------------------------------------------------------------------------------------------------=
+// _________________________________________________________________________________________________
 //
 void BotscriptParser::parseEventdef()
 {
@@ -856,8 +855,7 @@
 	addEvent (e);
 }
 
-// 
--------------------------------------------------------------------------------------------------=
+// _________________________________________________________________________________________________
 //
 void BotscriptParser::parseFuncdef()
 {
--- a/src/parser.h	Tue Jul 22 12:57:46 2014 +0300
+++ b/src/parser.h	Tue Jul 22 19:05:58 2014 +0300
@@ -95,6 +95,8 @@
 	MainLoop,	// inside mainloop
 	Onenter,	// inside onenter
 	Onexit,		// inside onexit
+
+	NumValues
 };
 
 // _________________________________________________________________________________________________
--- a/src/stringTable.cpp	Tue Jul 22 12:57:46 2014 +0300
+++ b/src/stringTable.cpp	Tue Jul 22 19:05:58 2014 +0300
@@ -31,14 +31,14 @@
 
 static StringList g_StringTable;
 
-// ============================================================================
+// _________________________________________________________________________________________________
 //
 const StringList& getStringTable()
 {
 	return g_StringTable;
 }
 
-// ============================================================================
+// _________________________________________________________________________________________________
 //
 // Potentially adds a string to the table and returns the index of it.
 //
@@ -70,7 +70,7 @@
 	return (g_StringTable.size() - 1);
 }
 
-// ============================================================================
+// _________________________________________________________________________________________________
 //
 // Counts the amount of strings in the table.
 //
--- a/src/tokens.h	Tue Jul 22 12:57:46 2014 +0300
+++ b/src/tokens.h	Tue Jul 22 19:05:58 2014 +0300
@@ -122,6 +122,8 @@
 	Number,
 	String,
 	Any,
+
+	NumValues
 };
 
 static Token const FirstNamedToken = Token::Bool;
--- a/src/types.h	Tue Jul 22 12:57:46 2014 +0300
+++ b/src/types.h	Tue Jul 22 19:05:58 2014 +0300
@@ -72,6 +72,18 @@
 	return (a >= 0) ? a : -a;
 }
 
+// _________________________________________________________________________________________________
+//
+named_enum class Verbosity
+{
+	None,
+	Medium,
+	Full,
+
+	NumValues
+};
+
+
 #ifdef IN_IDE_PARSER
 using FILE = void;
 #endif

mercurial