From 3b2f0fa4cf8eaf14299f1a8d66a06f747083055c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20St=C3=A4ck?= Date: Sun, 23 Jun 2019 20:43:57 +0200 Subject: [PATCH] Add argument parsing for eeprom stuff --- simulation/include/EEPROM.h | 2 +- simulation/src/argparse.hpp | 468 ++++++++++++++++++++++++++++++++++++ simulation/src/nuevisim.cpp | 29 ++- 3 files changed, 495 insertions(+), 4 deletions(-) create mode 100644 simulation/src/argparse.hpp diff --git a/simulation/include/EEPROM.h b/simulation/include/EEPROM.h index 39fa35e..5631e64 100644 --- a/simulation/include/EEPROM.h +++ b/simulation/include/EEPROM.h @@ -37,7 +37,7 @@ struct EEPROMClass // } private: - char someFakeEEPROM_memory[4096]; + char someFakeEEPROM_memory[2048]; }; diff --git a/simulation/src/argparse.hpp b/simulation/src/argparse.hpp new file mode 100644 index 0000000..f63b7e8 --- /dev/null +++ b/simulation/src/argparse.hpp @@ -0,0 +1,468 @@ +/* + +This library comes from https://github.com/hbristow/argparse + +Copyright (c) 2017, Hilton Bristow +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* 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. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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. + +*/ + +#ifndef ARGPARSE_HPP_ +#define ARGPARSE_HPP_ + +#if __cplusplus >= 201103L +#include +typedef std::unordered_map IndexMap; +#else +#include +typedef std::map IndexMap; +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +/*! @class ArgumentParser + * @brief A simple command-line argument parser based on the design of + * python's parser of the same name. + * + * ArgumentParser is a simple C++ class that can parse arguments from + * the command-line or any array of strings. The syntax is familiar to + * anyone who has used python's ArgumentParser: + * \code + * // create a parser and add the options + * ArgumentParser parser; + * parser.addArgument("-n", "--name"); + * parser.addArgument("--inputs", '+'); + * + * // parse the command-line arguments + * parser.parse(argc, argv); + * + * // get the inputs and iterate over them + * string name = parser.retrieve("name"); + * vector inputs = parser.retrieve>("inputs"); + * \endcode + * + */ +class ArgumentParser { +private: + class Any; + struct Argument; + class PlaceHolder; + class Holder; + typedef std::string String; + typedef std::vector AnyVector; + typedef std::vector StringVector; + typedef std::vector ArgumentVector; + + // -------------------------------------------------------------------------- + // Type-erasure internal storage + // -------------------------------------------------------------------------- + class Any { + public: + // constructor + Any() : content(0) {} + // destructor + ~Any() { delete content; } + // INWARD CONVERSIONS + Any(const Any& other) : content(other.content ? other.content->clone() : 0) {} + template + Any(const ValueType& other) + : content(new Holder(other)) {} + Any& swap(Any& other) { + std::swap(content, other.content); + return *this; + } + Any& operator=(const Any& rhs) { + Any tmp(rhs); + return swap(tmp); + } + template + Any& operator=(const ValueType& rhs) { + Any tmp(rhs); + return swap(tmp); + } + // OUTWARD CONVERSIONS + template + ValueType* toPtr() const { + return content->type_info() == typeid(ValueType) + ? &static_cast*>(content)->held_ + : 0; + } + template + ValueType& castTo() { + if (!toPtr()) throw std::bad_cast(); + return *toPtr(); + } + template + const ValueType& castTo() const { + if (!toPtr()) throw std::bad_cast(); + return *toPtr(); + } + + private: + // Inner placeholder interface + class PlaceHolder { + public: + virtual ~PlaceHolder() {} + virtual const std::type_info& type_info() const = 0; + virtual PlaceHolder* clone() const = 0; + }; + // Inner template concrete instantiation of PlaceHolder + template + class Holder : public PlaceHolder { + public: + ValueType held_; + Holder(const ValueType& value) : held_(value) {} + virtual const std::type_info& type_info() const { return typeid(ValueType); } + virtual PlaceHolder* clone() const { return new Holder(held_); } + }; + PlaceHolder* content; + }; + + // -------------------------------------------------------------------------- + // Argument + // -------------------------------------------------------------------------- + static String delimit(const String& name) { + return String(std::min(name.size(), (size_t)2), '-').append(name); + } + static String strip(const String& name) { + size_t begin = 0; + begin += name.size() > 0 ? name[0] == '-' : 0; + begin += name.size() > 3 ? name[1] == '-' : 0; + return name.substr(begin); + } + static String upper(const String& in) { + String out(in); + std::transform(out.begin(), out.end(), out.begin(), ::toupper); + return out; + } + static String escape(const String& in) { + String out(in); + if (in.find(' ') != std::string::npos) out = String("\"").append(out).append("\""); + return out; + } + + struct Argument { + Argument() : short_name(""), name(""), optional(true), fixed_nargs(0), fixed(true) {} + Argument(const String& _short_name, const String& _name, bool _optional, char nargs) + : short_name(_short_name), name(_name), optional(_optional) { + if (nargs == '+' || nargs == '*') { + variable_nargs = nargs; + fixed = false; + } else { + fixed_nargs = nargs; + fixed = true; + } + } + String short_name; + String name; + bool optional; + union { + size_t fixed_nargs; + char variable_nargs; + }; + bool fixed; + String canonicalName() const { return (name.empty()) ? short_name : name; } + String toString(bool named = true) const { + std::ostringstream s; + String uname = name.empty() ? upper(strip(short_name)) : upper(strip(name)); + if (named && optional) s << "["; + if (named) s << canonicalName(); + if (fixed) { + size_t N = std::min((size_t)3, fixed_nargs); + for (size_t n = 0; n < N; ++n) s << " " << uname; + if (N < fixed_nargs) s << " ..."; + } + if (!fixed) { + s << " "; + if (variable_nargs == '*') s << "["; + s << uname << " "; + if (variable_nargs == '+') s << "["; + s << uname << "...]"; + } + if (named && optional) s << "]"; + return s.str(); + } + }; + + void insertArgument(const Argument& arg) { + size_t N = arguments_.size(); + arguments_.push_back(arg); + if (arg.fixed && arg.fixed_nargs <= 1) { + variables_.push_back(String()); + } else { + variables_.push_back(StringVector()); + } + if (!arg.short_name.empty()) index_[arg.short_name] = N; + if (!arg.name.empty()) index_[arg.name] = N; + if (!arg.optional) required_++; + } + + // -------------------------------------------------------------------------- + // Error handling + // -------------------------------------------------------------------------- + void argumentError(const std::string& msg, bool show_usage = false) { + if (use_exceptions_) throw std::invalid_argument(msg); + std::cerr << "ArgumentParser error: " << msg << std::endl; + if (show_usage) std::cerr << usage() << std::endl; + exit(-5); + } + + // -------------------------------------------------------------------------- + // Member variables + // -------------------------------------------------------------------------- + IndexMap index_; + bool ignore_first_; + bool use_exceptions_; + size_t required_; + String app_name_; + String final_name_; + ArgumentVector arguments_; + AnyVector variables_; + +public: + ArgumentParser() : ignore_first_(true), use_exceptions_(false), required_(0) {} + // -------------------------------------------------------------------------- + // addArgument + // -------------------------------------------------------------------------- + void appName(const String& name) { app_name_ = name; } + void addArgument(const String& name, char nargs = 0, bool optional = true) { + if (name.size() > 2) { + Argument arg("", verify(name), optional, nargs); + insertArgument(arg); + } else { + Argument arg(verify(name), "", optional, nargs); + insertArgument(arg); + } + } + void addArgument(const String& short_name, const String& name, char nargs = 0, + bool optional = true) { + Argument arg(verify(short_name), verify(name), optional, nargs); + insertArgument(arg); + } + void addFinalArgument(const String& name, char nargs = 1, bool optional = false) { + final_name_ = delimit(name); + Argument arg("", final_name_, optional, nargs); + insertArgument(arg); + } + void ignoreFirstArgument(bool ignore_first) { ignore_first_ = ignore_first; } + String verify(const String& name) { + if (name.empty()) argumentError("argument names must be non-empty"); + if ((name.size() == 2 && name[0] != '-') || name.size() == 3) + argumentError(String("invalid argument '") + .append(name) + .append("'. Short names must begin with '-'")); + if (name.size() > 3 && (name[0] != '-' || name[1] != '-')) + argumentError(String("invalid argument '") + .append(name) + .append("'. Multi-character names must begin with '--'")); + return name; + } + + // -------------------------------------------------------------------------- + // Parse + // -------------------------------------------------------------------------- + void parse(size_t argc, const char** argv) { parse(StringVector(argv, argv + argc)); } + + void parse(const StringVector& argv) { + // check if the app is named + if (app_name_.empty() && ignore_first_ && !argv.empty()) app_name_ = argv[0]; + + // set up the working set + Argument active; + Argument final = final_name_.empty() ? Argument() : arguments_[index_[final_name_]]; + size_t consumed = 0; + size_t nrequired = final.optional ? required_ : required_ - 1; + size_t nfinal = final.optional ? 0 : (final.fixed ? final.fixed_nargs + : (final.variable_nargs == '+' ? 1 : 0)); + + // iterate over each element of the array + for (StringVector::const_iterator in = argv.begin() + ignore_first_; + in < argv.end() - nfinal; ++in) { + String active_name = active.canonicalName(); + String el = *in; + // check if the element is a key + if (index_.count(el) == 0) { + // input + // is the current active argument expecting more inputs? + if (active.fixed && active.fixed_nargs <= consumed) + argumentError(String("attempt to pass too many inputs to ").append(active_name), + true); + if (active.fixed && active.fixed_nargs == 1) { + variables_[index_[active_name]].castTo() = el; + } else { + variables_[index_[active_name]].castTo().push_back(el); + } + consumed++; + } else { + // new key! + // has the active argument consumed enough elements? + if ((active.fixed && active.fixed_nargs != consumed) || + (!active.fixed && active.variable_nargs == '+' && consumed < 1)) + argumentError(String("encountered argument ") + .append(el) + .append(" when expecting more inputs to ") + .append(active_name), + true); + active = arguments_[index_[el]]; + // check if we've satisfied the required arguments + if (active.optional && nrequired > 0) + argumentError(String("encountered optional argument ") + .append(el) + .append(" when expecting more required arguments"), + true); + // are there enough arguments for the new argument to consume? + if ((active.fixed && active.fixed_nargs > (argv.end() - in - nfinal - 1)) || + (!active.fixed && active.variable_nargs == '+' && + !(argv.end() - in - nfinal - 1))) + argumentError(String("too few inputs passed to argument ").append(el), true); + if (!active.optional) nrequired--; + consumed = 0; + } + } + + for (StringVector::const_iterator in = + std::max(argv.begin() + ignore_first_, argv.end() - nfinal); + in != argv.end(); ++in) { + String el = *in; + // check if we accidentally find an argument specifier + if (index_.count(el)) + argumentError(String("encountered argument specifier ") + .append(el) + .append(" while parsing final required inputs"), + true); + if (final.fixed && final.fixed_nargs == 1) { + variables_[index_[final_name_]].castTo() = el; + } else { + variables_[index_[final_name_]].castTo().push_back(el); + } + nfinal--; + } + + // check that all of the required arguments have been encountered + if (nrequired > 0 || nfinal > 0) + argumentError(String("too few required arguments passed to ").append(app_name_), true); + } + + // -------------------------------------------------------------------------- + // Retrieve + // -------------------------------------------------------------------------- + template + T& retrieve(const String& name) { + if (index_.count(delimit(name)) == 0) throw std::out_of_range("Key not found"); + size_t N = index_[delimit(name)]; + return variables_[N].castTo(); + } + + // -------------------------------------------------------------------------- + // Properties + // -------------------------------------------------------------------------- + String usage() { + // premable app name + std::ostringstream help; + help << "Usage: " << escape(app_name_); + size_t indent = help.str().size(); + size_t linelength = 0; + + // get the required arguments + for (ArgumentVector::const_iterator it = arguments_.begin(); it != arguments_.end(); ++it) { + Argument arg = *it; + if (arg.optional) continue; + if (arg.name.compare(final_name_) == 0) continue; + help << " "; + String argstr = arg.toString(); + if (argstr.size() + linelength > 80) { + help << "\n" << String(indent, ' '); + linelength = 0; + } else { + linelength += argstr.size(); + } + help << argstr; + } + + // get the optional arguments + for (ArgumentVector::const_iterator it = arguments_.begin(); it != arguments_.end(); ++it) { + Argument arg = *it; + if (!arg.optional) continue; + if (arg.name.compare(final_name_) == 0) continue; + help << " "; + String argstr = arg.toString(); + if (argstr.size() + linelength > 80) { + help << "\n" << String(indent, ' '); + linelength = 0; + } else { + linelength += argstr.size(); + } + help << argstr; + } + + // get the final argument + if (!final_name_.empty()) { + Argument arg = arguments_[index_[final_name_]]; + String argstr = arg.toString(false); + if (argstr.size() + linelength > 80) { + help << "\n" << String(indent, ' '); + linelength = 0; + } else { + linelength += argstr.size(); + } + help << argstr; + } + + return help.str(); + } + void useExceptions(bool state) { use_exceptions_ = state; } + bool empty() const { return index_.empty(); } + void clear() { + ignore_first_ = true; + required_ = 0; + index_.clear(); + arguments_.clear(); + variables_.clear(); + } + bool exists(const String& name) const { return index_.count(delimit(name)) > 0; } + size_t count(const String& name) { + // check if the name is an argument + if (index_.count(delimit(name)) == 0) return 0; + size_t N = index_[delimit(name)]; + Argument arg = arguments_[N]; + Any var = variables_[N]; + // check if the argument is a vector + if (arg.fixed) { + return !var.castTo().empty(); + } else { + return var.castTo().size(); + } + } +}; +#endif diff --git a/simulation/src/nuevisim.cpp b/simulation/src/nuevisim.cpp index f00cf69..d17d216 100644 --- a/simulation/src/nuevisim.cpp +++ b/simulation/src/nuevisim.cpp @@ -1,4 +1,5 @@ #include +#include #include @@ -14,6 +15,8 @@ #include +#include "argparse.hpp" + // Forward declarations static void SimQuit(void); static int SimInit(void); @@ -503,7 +506,7 @@ static void SimLoop(std::function continue_predicate, std::function("eeprom-file"); + eepromWrite = parser.exists("eeprom-write"); + factoryReset = parser.exists("factory-reset"); + + printf("%d: %s\n",eepromWrite, eepromFile.c_str()); + return 0; + + return SimRun(eepromFile, eepromWrite, factoryReset); }