2014-07-22
- 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