diff options
| Commit | 3e5cf5941db07bcd3527a73b62077d5277d57f2d (patch) | |
|---|---|---|
| Author | 2018-11-28 09:42:47 +0200 | |
| Committer | 2018-11-28 09:42:47 +0200 | |
| Files | 1d3d4a9cecfd9d314905abc3af3dd54ac99a94aa | |
| IPL-3e5cf5941db07bcd3527a73b62077d5277d57f2d.tar IPL-3e5cf5941db07bcd3527a73b62077d5277d57f2d.tar.gz IPL-3e5cf5941db07bcd3527a73b62077d5277d57f2d.zip | ||
| Parent | 5288b0429fcdac891be3bced8e21787bd452d44a (diff) | |
Adding 3rd homework - Brainfuck Interpreter (#44)
* Add 1st hw
* --amend
* moving readme for 1st hw to designated folder
* renamed TokenType enum to TokenClass
* fixed character constant lexing. added sampe output file
* add new line at end-of-file. added gitignore
* a bit of refactoring
* Add BF interpreter
6 files changed, 4300 insertions, 0 deletions
| -rw-r--r-- | homework/81568/BF_Interpreter/CLI11.hpp | 4043 | ||||
| -rw-r--r-- | homework/81568/BF_Interpreter/Makefile | 2 | ||||
| -rw-r--r-- | homework/81568/BF_Interpreter/README.md | 40 | ||||
| -rw-r--r-- | homework/81568/BF_Interpreter/bf_interpreter.cpp | 114 | ||||
| -rw-r--r-- | homework/81568/BF_Interpreter/bf_interpreter.h | 33 | ||||
| -rw-r--r-- | homework/81568/BF_Interpreter/main.cpp | 68 |
diff --git a/homework/81568/BF_Interpreter/CLI11.hpp b/homework/81568/BF_Interpreter/CLI11.hpp new file mode 100644 index 0000000..bf3f813 --- /dev/null +++ b/homework/81568/BF_Interpreter/CLI11.hpp @@ -0,0 +1,4043 @@ +#pragma once + +// CLI11: Version 1.6.1 +// Originally designed by Henry Schreiner +// https://github.com/CLIUtils/CLI11 +// +// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts +// from: v1.6.1 +// +// From LICENSE: +// +// CLI11 1.6 Copyright (c) 2017-2018 University of Cincinnati, developed by Henry +// Schreiner under NSF AWARD 1414736. All rights reserved. +// +// Redistribution and use in source and binary forms of CLI11, 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. 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. + + +// Standard combined includes: + +#include <algorithm> +#include <deque> +#include <exception> +#include <fstream> +#include <functional> +#include <iomanip> +#include <iostream> +#include <istream> +#include <iterator> +#include <locale> +#include <map> +#include <memory> +#include <numeric> +#include <set> +#include <sstream> +#include <stdexcept> +#include <string> +#include <sys/stat.h> +#include <sys/types.h> +#include <tuple> +#include <type_traits> +#include <utility> +#include <vector> + + +// Verbatim copy from CLI/Version.hpp: + + +#define CLI11_VERSION_MAJOR 1 +#define CLI11_VERSION_MINOR 6 +#define CLI11_VERSION_PATCH 1 +#define CLI11_VERSION "1.6.1" + + + + +// Verbatim copy from CLI/Macros.hpp: + + +// The following version macro is very similar to the one in PyBind11 +#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) +#if __cplusplus >= 201402L +#define CLI11_CPP14 +#if __cplusplus >= 201703L +#define CLI11_CPP17 +#if __cplusplus > 201703L +#define CLI11_CPP20 +#endif +#endif +#endif +#elif defined(_MSC_VER) && __cplusplus == 199711L +// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented) +// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer +#if _MSVC_LANG >= 201402L +#define CLI11_CPP14 +#if _MSVC_LANG > 201402L && _MSC_VER >= 1910 +#define CLI11_CPP17 +#if __MSVC_LANG > 201703L && _MSC_VER >= 1910 +#define CLI11_CPP20 +#endif +#endif +#endif +#endif + +#if defined(CLI11_CPP14) +#define CLI11_DEPRECATED(reason) [[deprecated(reason)]] +#elif defined(_MSC_VER) +#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) +#else +#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) +#endif + + + + +// Verbatim copy from CLI/Optional.hpp: + +#ifdef __has_include + +#if defined(CLI11_CPP17) && __has_include(<optional>) && \ + !defined(CLI11_STD_OPTIONAL) +#define CLI11_STD_OPTIONAL 1 +#endif + +#if defined(CLI11_CPP14) && __has_include(<experimental/optional>) && \ + !defined(CLI11_EXPERIMENTAL_OPTIONAL) +#define CLI11_EXPERIMENTAL_OPTIONAL 1 +#endif + +#if __has_include(<boost/optional.hpp>) && !defined(CLI11_BOOST_OPTIONAL) +#include <boost/version.hpp> +#if BOOST_VERSION >= 105800 +#define CLI11_BOOST_OPTIONAL 1 +#endif +#endif + +#endif + +#if CLI11_STD_OPTIONAL +#include <optional> +#endif +#if CLI11_EXPERIMENTAL_OPTIONAL +#include <experimental/optional> +#endif +#if CLI11_BOOST_OPTIONAL +#include <boost/optional.hpp> +#endif + + +// From CLI/Version.hpp: + + + +// From CLI/Macros.hpp: + + + +// From CLI/Optional.hpp: + +namespace CLI { + +#if CLI11_STD_OPTIONAL +template <typename T> std::istream &operator>>(std::istream &in, std::optional<T> &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +#if CLI11_EXPERIMENTAL_OPTIONAL +template <typename T> std::istream &operator>>(std::istream &in, std::experimental::optional<T> &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +#if CLI11_BOOST_OPTIONAL +template <typename T> std::istream &operator>>(std::istream &in, boost::optional<T> &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +// Export the best optional to the CLI namespace +#if CLI11_STD_OPTIONAL +using std::optional; +#elif CLI11_EXPERIMENTAL_OPTIONAL +using std::experimental::optional; +#elif CLI11_BOOST_OPTIONAL +using boost::optional; +#endif + +// This is true if any optional is found +#if CLI11_STD_OPTIONAL || CLI11_EXPERIMENTAL_OPTIONAL || CLI11_BOOST_OPTIONAL +#define CLI11_OPTIONAL 1 +#endif + +} // namespace CLI + +// From CLI/StringTools.hpp: + +namespace CLI { +namespace detail { + +// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c +/// Split a string by a delim +inline std::vector<std::string> split(const std::string &s, char delim) { + std::vector<std::string> elems; + // Check to see if empty string, give consistent result + if(s.empty()) + elems.emplace_back(""); + else { + std::stringstream ss; + ss.str(s); + std::string item; + while(std::getline(ss, item, delim)) { + elems.push_back(item); + } + } + return elems; +} + +/// Simple function to join a string +template <typename T> std::string join(const T &v, std::string delim = ",") { + std::ostringstream s; + size_t start = 0; + for(const auto &i : v) { + if(start++ > 0) + s << delim; + s << i; + } + return s.str(); +} + +/// Join a string in reverse order +template <typename T> std::string rjoin(const T &v, std::string delim = ",") { + std::ostringstream s; + for(size_t start = 0; start < v.size(); start++) { + if(start > 0) + s << delim; + s << v[v.size() - start - 1]; + } + return s.str(); +} + +// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string + +/// Trim whitespace from left of string +inline std::string <rim(std::string &str) { + auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace<char>(ch, std::locale()); }); + str.erase(str.begin(), it); + return str; +} + +/// Trim anything from left of string +inline std::string <rim(std::string &str, const std::string &filter) { + auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(str.begin(), it); + return str; +} + +/// Trim whitespace from right of string +inline std::string &rtrim(std::string &str) { + auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace<char>(ch, std::locale()); }); + str.erase(it.base(), str.end()); + return str; +} + +/// Trim anything from right of string +inline std::string &rtrim(std::string &str, const std::string &filter) { + auto it = + std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(it.base(), str.end()); + return str; +} + +/// Trim whitespace from string +inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); } + +/// Trim anything from string +inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); } + +/// Make a copy of the string and then trim it +inline std::string trim_copy(const std::string &str) { + std::string s = str; + return trim(s); +} + +/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) +inline std::string trim_copy(const std::string &str, const std::string &filter) { + std::string s = str; + return trim(s, filter); +} +/// Print a two part "help" string +inline std::ostream &format_help(std::ostream &out, std::string name, std::string description, size_t wid) { + name = " " + name; + out << std::setw(static_cast<int>(wid)) << std::left << name; + if(!description.empty()) { + if(name.length() >= wid) + out << "\n" << std::setw(static_cast<int>(wid)) << ""; + out << description; + } + out << "\n"; + return out; +} + +/// Verify the first character of an option +template <typename T> bool valid_first_char(T c) { return std::isalpha(c, std::locale()) || c == '_'; } + +/// Verify following characters of an option +template <typename T> bool valid_later_char(T c) { + return std::isalnum(c, std::locale()) || c == '_' || c == '.' || c == '-'; +} + +/// Verify an option name +inline bool valid_name_string(const std::string &str) { + if(str.empty() || !valid_first_char(str[0])) + return false; + for(auto c : str.substr(1)) + if(!valid_later_char(c)) + return false; + return true; +} + +/// Return a lower case version of a string +inline std::string to_lower(std::string str) { + std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) { + return std::tolower(x, std::locale()); + }); + return str; +} + +/// Split a string '"one two" "three"' into 'one two', 'three' +inline std::vector<std::string> split_up(std::string str) { + + std::vector<char> delims = {'\'', '\"'}; + auto find_ws = [](char ch) { return std::isspace<char>(ch, std::locale()); }; + trim(str); + + std::vector<std::string> output; + + while(!str.empty()) { + if(str[0] == '\'') { + auto end = str.find('\'', 1); + if(end != std::string::npos) { + output.push_back(str.substr(1, end - 1)); + str = str.substr(end + 1); + } else { + output.push_back(str.substr(1)); + str = ""; + } + } else if(str[0] == '\"') { + auto end = str.find('\"', 1); + if(end != std::string::npos) { + output.push_back(str.substr(1, end - 1)); + str = str.substr(end + 1); + } else { + output.push_back(str.substr(1)); + str = ""; + } + + } else { + auto it = std::find_if(std::begin(str), std::end(str), find_ws); + if(it != std::end(str)) { + std::string value = std::string(str.begin(), it); + output.push_back(value); + str = std::string(it, str.end()); + } else { + output.push_back(str); + str = ""; + } + } + trim(str); + } + + return output; +} + +/// Add a leader to the beginning of all new lines (nothing is added +/// at the start of the first line). `"; "` would be for ini files +/// +/// Can't use Regex, or this would be a subs. +inline std::string fix_newlines(std::string leader, std::string input) { + std::string::size_type n = 0; + while(n != std::string::npos && n < input.size()) { + n = input.find('\n', n); + if(n != std::string::npos) { + input = input.substr(0, n + 1) + leader + input.substr(n + 1); + n += leader.size(); + } + } + return input; +} + +} // namespace detail +} // namespace CLI + +// From CLI/Error.hpp: + +namespace CLI { + +// Use one of these on all error classes. +// These are temporary and are undef'd at the end of this file. +#define CLI11_ERROR_DEF(parent, name) \ + protected: \ + name(std::string name, std::string msg, int exit_code) : parent(std::move(name), std::move(msg), exit_code) {} \ + name(std::string name, std::string msg, ExitCodes exit_code) \ + : parent(std::move(name), std::move(msg), exit_code) {} \ + \ + public: \ + name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ + name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {} + +// This is added after the one above if a class is used directly and builds its own message +#define CLI11_ERROR_SIMPLE(name) \ + explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {} + +/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, +/// int values from e.get_error_code(). +enum class ExitCodes { + Success = 0, + IncorrectConstruction = 100, + BadNameString, + OptionAlreadyAdded, + FileError, + ConversionError, + ValidationError, + RequiredError, + RequiresError, + ExcludesError, + ExtrasError, + ConfigError, + InvalidError, + HorribleError, + OptionNotFound, + ArgumentMismatch, + BaseClass = 127 +}; + +// Error definitions + +/// @defgroup error_group Errors +/// @brief Errors thrown by CLI11 +/// +/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. +/// @{ + +/// All errors derive from this one +class Error : public std::runtime_error { + int exit_code; + std::string name{"Error"}; + + public: + int get_exit_code() const { return exit_code; } + + std::string get_name() const { return name; } + + Error(std::string name, std::string msg, int exit_code = static_cast<int>(ExitCodes::BaseClass)) + : runtime_error(msg), exit_code(exit_code), name(std::move(name)) {} + + Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast<int>(exit_code)) {} +}; + +// Note: Using Error::Error constructors does not work on GCC 4.7 + +/// Construction errors (not in parsing) +class ConstructionError : public Error { + CLI11_ERROR_DEF(Error, ConstructionError) +}; + +/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) +class IncorrectConstruction : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) + CLI11_ERROR_SIMPLE(IncorrectConstruction) + static IncorrectConstruction PositionalFlag(std::string name) { + return IncorrectConstruction(name + ": Flags cannot be positional"); + } + static IncorrectConstruction Set0Opt(std::string name) { + return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); + } + static IncorrectConstruction SetFlag(std::string name) { + return IncorrectConstruction(name + ": Cannot set an expected number for flags"); + } + static IncorrectConstruction ChangeNotVector(std::string name) { + return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); + } + static IncorrectConstruction AfterMultiOpt(std::string name) { + return IncorrectConstruction( + name + ": You can't change expected arguments after you've changed the multi option policy!"); + } + static IncorrectConstruction MissingOption(std::string name) { + return IncorrectConstruction("Option " + name + " is not defined"); + } + static IncorrectConstruction MultiOptionPolicy(std::string name) { + return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options"); + } +}; + +/// Thrown on construction of a bad name +class BadNameString : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, BadNameString) + CLI11_ERROR_SIMPLE(BadNameString) + static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } + static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } + static BadNameString DashesOnly(std::string name) { + return BadNameString("Must have a name, not just dashes: " + name); + } + static BadNameString MultiPositionalNames(std::string name) { + return BadNameString("Only one positional name allowed, remove: " + name); + } +}; + +/// Thrown when an option already exists +class OptionAlreadyAdded : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) + explicit OptionAlreadyAdded(std::string name) + : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {} + static OptionAlreadyAdded Requires(std::string name, std::string other) { + return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded); + } + static OptionAlreadyAdded Excludes(std::string name, std::string other) { + return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded); + } +}; + +// Parsing errors + +/// Anything that can error in Parse +class ParseError : public Error { + CLI11_ERROR_DEF(Error, ParseError) +}; + +// Not really "errors" + +/// This is a successful completion on parsing, supposed to exit +class Success : public ParseError { + CLI11_ERROR_DEF(ParseError, Success) + Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {} +}; + +/// -h or --help on command line +class CallForHelp : public ParseError { + CLI11_ERROR_DEF(ParseError, CallForHelp) + CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Usually somethign like --help-all on command line +class CallForAllHelp : public ParseError { + CLI11_ERROR_DEF(ParseError, CallForAllHelp) + CallForAllHelp() + : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code. +class RuntimeError : public ParseError { + CLI11_ERROR_DEF(ParseError, RuntimeError) + explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} +}; + +/// Thrown when parsing an INI file and it is missing +class FileError : public ParseError { + CLI11_ERROR_DEF(ParseError, FileError) + CLI11_ERROR_SIMPLE(FileError) + static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); } +}; + +/// Thrown when conversion call back fails, such as when an int fails to coerce to a string +class ConversionError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConversionError) + CLI11_ERROR_SIMPLE(ConversionError) + ConversionError(std::string member, std::string name) + : ConversionError("The value " + member + " is not an allowed value for " + name) {} + ConversionError(std::string name, std::vector<std::string> results) + : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {} + static ConversionError TooManyInputsFlag(std::string name) { + return ConversionError(name + ": too many inputs for a flag"); + } + static ConversionError TrueFalse(std::string name) { + return ConversionError(name + ": Should be true/false or a number"); + } +}; + +/// Thrown when validation of results fails +class ValidationError : public ParseError { + CLI11_ERROR_DEF(ParseError, ValidationError) + CLI11_ERROR_SIMPLE(ValidationError) + explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {} +}; + +/// Thrown when a required option is missing +class RequiredError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiredError) + explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} + static RequiredError Subcommand(size_t min_subcom) { + if(min_subcom == 1) + return RequiredError("A subcommand"); + else + return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands", + ExitCodes::RequiredError); + } +}; + +/// Thrown when the wrong number of arguments has been received +class ArgumentMismatch : public ParseError { + CLI11_ERROR_DEF(ParseError, ArgumentMismatch) + CLI11_ERROR_SIMPLE(ArgumentMismatch) + ArgumentMismatch(std::string name, int expected, size_t recieved) + : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + + ", got " + std::to_string(recieved)) + : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + + ", got " + std::to_string(recieved)), + ExitCodes::ArgumentMismatch) {} + + static ArgumentMismatch AtLeast(std::string name, int num) { + return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required"); + } + static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); + } +}; + +/// Thrown when a requires option is missing +class RequiresError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiresError) + RequiresError(std::string curname, std::string subname) + : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {} +}; + +/// Thrown when an excludes option is present +class ExcludesError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExcludesError) + ExcludesError(std::string curname, std::string subname) + : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {} +}; + +/// Thrown when too many positionals or options are found +class ExtrasError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExtrasError) + explicit ExtrasError(std::vector<std::string> args) + : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} +}; + +/// Thrown when extra values are found in an INI file +class ConfigError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConfigError) + CLI11_ERROR_SIMPLE(ConfigError) + static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); } + static ConfigError NotConfigurable(std::string item) { + return ConfigError(item + ": This option is not allowed in a configuration file"); + } +}; + +/// Thrown when validation fails before parsing +class InvalidError : public ParseError { + CLI11_ERROR_DEF(ParseError, InvalidError) + explicit InvalidError(std::string name) + : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) { + } +}; + +/// This is just a safety check to verify selection and parsing match - you should not ever see it +/// Strings are directly added to this error, but again, it should never be seen. +class HorribleError : public ParseError { + CLI11_ERROR_DEF(ParseError, HorribleError) + CLI11_ERROR_SIMPLE(HorribleError) +}; + +// After parsing + +/// Thrown when counting a non-existent option +class OptionNotFound : public Error { + CLI11_ERROR_DEF(Error, OptionNotFound) + explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} +}; + +#undef CLI11_ERROR_DEF +#undef CLI11_ERROR_SIMPLE + +/// @} + +} // namespace CLI + +// From CLI/TypeTools.hpp: + +namespace CLI { + +// Type tools + +/// A copy of enable_if_t from C++14, compatible with C++11. +/// +/// We could check to see if C++14 is being used, but it does not hurt to redefine this +/// (even Google does this: https://github.com/google/skia/blob/master/include/private/SkTLogic.h) +/// It is not in the std namespace anyway, so no harm done. + +template <bool B, class T = void> using enable_if_t = typename std::enable_if<B, T>::type; + +/// Check to see if something is a vector (fail check by default) +template <typename T> struct is_vector { static const bool value = false; }; + +/// Check to see if something is a vector (true if actually a vector) +template <class T, class A> struct is_vector<std::vector<T, A>> { static bool const value = true; }; + +/// Check to see if something is bool (fail check by default) +template <typename T> struct is_bool { static const bool value = false; }; + +/// Check to see if something is bool (true if actually a bool) +template <> struct is_bool<bool> { static bool const value = true; }; + +namespace detail { +// Based generally on https://rmf.io/cxx11/almost-static-if +/// Simple empty scoped class +enum class enabler {}; + +/// An instance to use in EnableIf +constexpr enabler dummy = {}; + +// Type name print + +/// Was going to be based on +/// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template +/// But this is cleaner and works better in this case + +template <typename T, + enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "INT"; +} + +template <typename T, + enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "UINT"; +} + +template <typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "FLOAT"; +} + +/// This one should not be used, since vector types print the internal type +template <typename T, enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "VECTOR"; +} + +template <typename T, + enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !is_vector<T>::value, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "TEXT"; +} + +// Lexical cast + +/// Signed integers / enums +template <typename T, + enable_if_t<(std::is_integral<T>::value && std::is_signed<T>::value), detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + try { + size_t n = 0; + long long output_ll = std::stoll(input, &n, 0); + output = static_cast<T>(output_ll); + return n == input.size() && static_cast<long long>(output) == output_ll; + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// Unsigned integers +template <typename T, + enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + if(!input.empty() && input.front() == '-') + return false; // std::stoull happily converts negative values to junk without any errors. + + try { + size_t n = 0; + unsigned long long output_ll = std::stoull(input, &n, 0); + output = static_cast<T>(output_ll); + return n == input.size() && static_cast<unsigned long long>(output) == output_ll; + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// Floats +template <typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + try { + size_t n = 0; + output = static_cast<T>(std::stold(input, &n)); + return n == input.size(); + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// String and similar +template <typename T, + enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && + std::is_assignable<T &, std::string>::value, + detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + output = input; + return true; +} + +/// Non-string parsable +template <typename T, + enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && + !std::is_assignable<T &, std::string>::value, + detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + std::istringstream is; + + is.str(input); + is >> output; + return !is.fail() && !is.rdbuf()->in_avail(); +} + +} // namespace detail +} // namespace CLI + +// From CLI/Split.hpp: + +namespace CLI { +namespace detail { + +// Returns false if not a short option. Otherwise, sets opt name and rest and returns true +inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) { + if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { + name = current.substr(1, 1); + rest = current.substr(2); + return true; + } else + return false; +} + +// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true +inline bool split_long(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) { + auto loc = current.find("="); + if(loc != std::string::npos) { + name = current.substr(2, loc - 2); + value = current.substr(loc + 1); + } else { + name = current.substr(2); + value = ""; + } + return true; + } else + return false; +} + +// Splits a string into multiple long and short names +inline std::vector<std::string> split_names(std::string current) { + std::vector<std::string> output; + size_t val; + while((val = current.find(",")) != std::string::npos) { + output.push_back(trim_copy(current.substr(0, val))); + current = current.substr(val + 1); + } + output.push_back(trim_copy(current)); + return output; +} + +/// Get a vector of short names, one of long names, and a single name +inline std::tuple<std::vector<std::string>, std::vector<std::string>, std::string> +get_names(const std::vector<std::string> &input) { + + std::vector<std::string> short_names; + std::vector<std::string> long_names; + std::string pos_name; + + for(std::string name : input) { + if(name.length() == 0) + continue; + else if(name.length() > 1 && name[0] == '-' && name[1] != '-') { + if(name.length() == 2 && valid_first_char(name[1])) + short_names.emplace_back(1, name[1]); + else + throw BadNameString::OneCharName(name); + } else if(name.length() > 2 && name.substr(0, 2) == "--") { + name = name.substr(2); + if(valid_name_string(name)) + long_names.push_back(name); + else + throw BadNameString::BadLongName(name); + } else if(name == "-" || name == "--") { + throw BadNameString::DashesOnly(name); + } else { + if(pos_name.length() > 0) + throw BadNameString::MultiPositionalNames(name); + pos_name = name; + } + } + + return std::tuple<std::vector<std::string>, std::vector<std::string>, std::string>( + short_names, long_names, pos_name); +} + +} // namespace detail +} // namespace CLI + +// From CLI/ConfigFwd.hpp: + +namespace CLI { + +class App; + +namespace detail { + +/// Comma separated join, adds quotes if needed +inline std::string ini_join(std::vector<std::string> args) { + std::ostringstream s; + size_t start = 0; + for(const auto &arg : args) { + if(start++ > 0) + s << " "; + + auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace<char>(ch, std::locale()); }); + if(it == arg.end()) + s << arg; + else if(arg.find(R"(")") == std::string::npos) + s << R"(")" << arg << R"(")"; + else + s << R"(')" << arg << R"(')"; + } + + return s.str(); +} + +} // namespace detail + +/// Holds values to load into Options +struct ConfigItem { + /// This is the list of parents + std::vector<std::string> parents; + + /// This is the name + std::string name; + + /// Listing of inputs + std::vector<std::string> inputs; + + /// The list of parents and name joined by "." + std::string fullname() const { + std::vector<std::string> tmp = parents; + tmp.emplace_back(name); + return detail::join(tmp, "."); + } +}; + +/// This class provides a converter for configuration files. +class Config { + protected: + std::vector<ConfigItem> items; + + public: + /// Convert an app into a configuration + virtual std::string to_config(const App *, bool, bool, std::string) const = 0; + + /// Convert a configuration into an app + virtual std::vector<ConfigItem> from_config(std::istream &) const = 0; + + /// Convert a flag to a bool + virtual std::vector<std::string> to_flag(const ConfigItem &item) const { + if(item.inputs.size() == 1) { + std::string val = item.inputs.at(0); + val = detail::to_lower(val); + + if(val == "true" || val == "on" || val == "yes") { + return std::vector<std::string>(1); + } else if(val == "false" || val == "off" || val == "no") { + return std::vector<std::string>(); + } else { + try { + size_t ui = std::stoul(val); + return std::vector<std::string>(ui); + } catch(const std::invalid_argument &) { + throw ConversionError::TrueFalse(item.fullname()); + } + } + } else { + throw ConversionError::TooManyInputsFlag(item.fullname()); + } + } + + /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure + std::vector<ConfigItem> from_file(const std::string &name) { + std::ifstream input{name}; + if(!input.good()) + throw FileError::Missing(name); + + return from_config(input); + } + + /// virtual destructor + virtual ~Config() = default; +}; + +/// This converter works with INI files +class ConfigINI : public Config { + public: + std::string to_config(const App *, bool default_also, bool write_description, std::string prefix) const override; + + std::vector<ConfigItem> from_config(std::istream &input) const override { + std::string line; + std::string section = "default"; + + std::vector<ConfigItem> output; + + while(getline(input, line)) { + std::vector<std::string> items; + + detail::trim(line); + size_t len = line.length(); + if(len > 1 && line[0] == '[' && line[len - 1] == ']') { + section = line.substr(1, len - 2); + } else if(len > 0 && line[0] != ';') { + output.emplace_back(); + ConfigItem &out = output.back(); + + // Find = in string, split and recombine + auto pos = line.find('='); + if(pos != std::string::npos) { + out.name = detail::trim_copy(line.substr(0, pos)); + std::string item = detail::trim_copy(line.substr(pos + 1)); + items = detail::split_up(item); + } else { + out.name = detail::trim_copy(line); + items = {"ON"}; + } + + if(detail::to_lower(section) != "default") { + out.parents = {section}; + } + + if(out.name.find('.') != std::string::npos) { + std::vector<std::string> plist = detail::split(out.name, '.'); + out.name = plist.back(); + plist.pop_back(); + out.parents.insert(out.parents.end(), plist.begin(), plist.end()); + } + + out.inputs.insert(std::end(out.inputs), std::begin(items), std::end(items)); + } + } + return output; + } +}; + +} // namespace CLI + +// From CLI/Validators.hpp: + +namespace CLI { + +/// @defgroup validator_group Validators + +/// @brief Some validators that are provided +/// +/// These are simple `std::string(const std::string&)` validators that are useful. They return +/// a string if the validation fails. A custom struct is provided, as well, with the same user +/// semantics, but with the ability to provide a new type name. +/// @{ + +/// +struct Validator { + /// This is the type name, if empty the type name will not be changed + std::string tname; + + /// This it the base function that is to be called. + /// Returns a string error message if validation fails. + std::function<std::string(const std::string &)> func; + + /// This is the required operator for a validator - provided to help + /// users (CLI11 uses the member `func` directly) + std::string operator()(const std::string &str) const { return func(str); }; + + /// Combining validators is a new validator + Validator operator&(const Validator &other) const { + Validator newval; + newval.tname = (tname == other.tname ? tname : ""); + + // Give references (will make a copy in lambda function) + const std::function<std::string(const std::string &filename)> &f1 = func; + const std::function<std::string(const std::string &filename)> &f2 = other.func; + + newval.func = [f1, f2](const std::string &filename) { + std::string s1 = f1(filename); + std::string s2 = f2(filename); + if(!s1.empty() && !s2.empty()) + return s1 + " & " + s2; + else + return s1 + s2; + }; + return newval; + } + + /// Combining validators is a new validator + Validator operator|(const Validator &other) const { + Validator newval; + newval.tname = (tname == other.tname ? tname : ""); + + // Give references (will make a copy in lambda function) + const std::function<std::string(const std::string &filename)> &f1 = func; + const std::function<std::string(const std::string &filename)> &f2 = other.func; + + newval.func = [f1, f2](const std::string &filename) { + std::string s1 = f1(filename); + std::string s2 = f2(filename); + if(s1.empty() || s2.empty()) + return std::string(); + else + return s1 + " & " + s2; + }; + return newval; + } +}; + +// The implementation of the built in validators is using the Validator class; +// the user is only expected to use the const (static) versions (since there's no setup). +// Therefore, this is in detail. +namespace detail { + +/// Check for an existing file (returns error message if check fails) +struct ExistingFileValidator : public Validator { + ExistingFileValidator() { + tname = "FILE"; + func = [](const std::string &filename) { + struct stat buffer; + bool exist = stat(filename.c_str(), &buffer) == 0; + bool is_dir = (buffer.st_mode & S_IFDIR) != 0; + if(!exist) { + return "File does not exist: " + filename; + } else if(is_dir) { + return "File is actually a directory: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an existing directory (returns error message if check fails) +struct ExistingDirectoryValidator : public Validator { + ExistingDirectoryValidator() { + tname = "DIR"; + func = [](const std::string &filename) { + struct stat buffer; + bool exist = stat(filename.c_str(), &buffer) == 0; + bool is_dir = (buffer.st_mode & S_IFDIR) != 0; + if(!exist) { + return "Directory does not exist: " + filename; + } else if(!is_dir) { + return "Directory is actually a file: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an existing path +struct ExistingPathValidator : public Validator { + ExistingPathValidator() { + tname = "PATH"; + func = [](const std::string &filename) { + struct stat buffer; + bool const exist = stat(filename.c_str(), &buffer) == 0; + if(!exist) { + return "Path does not exist: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an non-existing path +struct NonexistentPathValidator : public Validator { + NonexistentPathValidator() { + tname = "PATH"; + func = [](const std::string &filename) { + struct stat buffer; + bool exist = stat(filename.c_str(), &buffer) == 0; + if(exist) { + return "Path already exists: " + filename; + } + return std::string(); + }; + } +}; +} // namespace detail + +// Static is not needed here, because global const implies static. + +/// Check for existing file (returns error message if check fails) +const detail::ExistingFileValidator ExistingFile; + +/// Check for an existing directory (returns error message if check fails) +const detail::ExistingDirectoryValidator ExistingDirectory; + +/// Check for an existing path +const detail::ExistingPathValidator ExistingPath; + +/// Check for an non-existing path +const detail::NonexistentPathValidator NonexistentPath; + +/// Produce a range (factory). Min and max are inclusive. +struct Range : public Validator { + /// This produces a range with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template <typename T> Range(T min, T max) { + std::stringstream out; + out << detail::type_name<T>() << " in [" << min << " - " << max << "]"; + + tname = out.str(); + func = [min, max](std::string input) { + T val; + detail::lexical_cast(input, val); + if(val < min || val > max) + return "Value " + input + " not in range " + std::to_string(min) + " to " + std::to_string(max); + + return std::string(); + }; + } + + /// Range of one value is 0 to value + template <typename T> explicit Range(T max) : Range(static_cast<T>(0), max) {} +}; + +/// @} + +} // namespace CLI + +// From CLI/FormatterFwd.hpp: + +namespace CLI { + +class Option; +class App; + +/// This enum signifies the type of help requested +/// +/// This is passed in by App; all user classes must accept this as +/// the second argument. + +enum class AppFormatMode { + Normal, //< The normal, detailed help + All, //< A fully expanded help + Sub, //< Used when printed as part of expanded subcommand +}; + +/// This is the minimum requirements to run a formatter. +/// +/// A user can subclass this is if they do not care at all +/// about the structure in CLI::Formatter. +class FormatterBase { + protected: + /// @name Options + ///@{ + + /// The width of the first column + size_t column_width_{30}; + + /// @brief The required help printout labels (user changeable) + /// Values are Needs, Excludes, etc. + std::map<std::string, std::string> labels_; + + ///@} + /// @name Basic + ///@{ + + public: + FormatterBase() = default; + FormatterBase(const FormatterBase &) = default; + FormatterBase(FormatterBase &&) = default; + virtual ~FormatterBase() = default; + + /// This is the key method that puts together help + virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0; + + ///@} + /// @name Setters + ///@{ + + /// Set the "REQUIRED" label + void label(std::string key, std::string val) { labels_[key] = val; } + + /// Set the column width + void column_width(size_t val) { column_width_ = val; } + + ///@} + /// @name Getters + ///@{ + + /// Get the current value of a name (REQUIRED, etc.) + std::string get_label(std::string key) const { + if(labels_.find(key) == labels_.end()) + return key; + else + return labels_.at(key); + } + + /// Get the current column width + size_t get_column_width() const { return column_width_; } + + ///@} +}; + +/// This is a specialty override for lambda functions +class FormatterLambda final : public FormatterBase { + using funct_t = std::function<std::string(const App *, std::string, AppFormatMode)>; + + /// The lambda to hold and run + funct_t lambda_; + + public: + /// Create a FormatterLambda with a lambda function + explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {} + + /// This will simply call the lambda function + std::string make_help(const App *app, std::string name, AppFormatMode mode) const override { + return lambda_(app, name, mode); + } +}; + +/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few +/// overridable methods, to be highly customizable with minimal effort. +class Formatter : public FormatterBase { + public: + Formatter() = default; + Formatter(const Formatter &) = default; + Formatter(Formatter &&) = default; + + /// @name Overridables + ///@{ + + /// This prints out a group of options with title + /// + virtual std::string make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const; + + /// This prints out just the positionals "group" + virtual std::string make_positionals(const App *app) const; + + /// This prints out all the groups of options + std::string make_groups(const App *app, AppFormatMode mode) const; + + /// This prints out all the subcommands + virtual std::string make_subcommands(const App *app, AppFormatMode mode) const; + + /// This prints out a subcommand + virtual std::string make_subcommand(const App *sub) const; + + /// This prints out a subcommand in help-all + virtual std::string make_expanded(const App *sub) const; + + /// This prints out all the groups of options + virtual std::string make_footer(const App *app) const; + + /// This displays the description line + virtual std::string make_description(const App *app) const; + + /// This displays the usage line + virtual std::string make_usage(const App *app, std::string name) const; + + /// This puts everything together + std::string make_help(const App *, std::string, AppFormatMode) const override; + + ///@} + /// @name Options + ///@{ + + /// This prints out an option help line, either positional or optional form + virtual std::string make_option(const Option *opt, bool is_positional) const { + std::stringstream out; + detail::format_help( + out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_); + return out.str(); + } + + /// @brief This is the name part of an option, Default: left column + virtual std::string make_option_name(const Option *, bool) const; + + /// @brief This is the options part of the name, Default: combined into left column + virtual std::string make_option_opts(const Option *) const; + + /// @brief This is the description. Default: Right column, on new line if left column too large + virtual std::string make_option_desc(const Option *) const; + + /// @brief This is used to print the name on the USAGE line + virtual std::string make_option_usage(const Option *opt) const; + + ///@} +}; + +} // namespace CLI + +// From CLI/Option.hpp: + +namespace CLI { + +using results_t = std::vector<std::string>; +using callback_t = std::function<bool(results_t)>; + +class Option; +class App; + +using Option_p = std::unique_ptr<Option>; + +enum class MultiOptionPolicy { Throw, TakeLast, TakeFirst, Join }; + +/// This is the CRTP base class for Option and OptionDefaults. It was designed this way +/// to share parts of the class; an OptionDefaults can copy to an Option. +template <typename CRTP> class OptionBase { + friend App; + + protected: + /// The group membership + std::string group_ = std::string("Options"); + + /// True if this is a required option + bool required_{false}; + + /// Ignore the case when matching (option, not value) + bool ignore_case_{false}; + + /// Allow this option to be given in a configuration file + bool configurable_{true}; + + /// Policy for multiple arguments when `expected_ == 1` (can be set on bool flags, too) + MultiOptionPolicy multi_option_policy_{MultiOptionPolicy::Throw}; + + /// Copy the contents to another similar class (one based on OptionBase) + template <typename T> void copy_to(T *other) const { + other->group(group_); + other->required(required_); + other->ignore_case(ignore_case_); + other->configurable(configurable_); + other->multi_option_policy(multi_option_policy_); + } + + public: + // setters + + /// Changes the group membership + CRTP *group(std::string name) { + group_ = name; + return static_cast<CRTP *>(this); + ; + } + + /// Set the option as required + CRTP *required(bool value = true) { + required_ = value; + return static_cast<CRTP *>(this); + } + + /// Support Plumbum term + CRTP *mandatory(bool value = true) { return required(value); } + + // Getters + + /// Get the group of this option + const std::string &get_group() const { return group_; } + + /// True if this is a required option + bool get_required() const { return required_; } + + /// The status of ignore case + bool get_ignore_case() const { return ignore_case_; } + + /// The status of configurable + bool get_configurable() const { return configurable_; } + + /// The status of the multi option policy + MultiOptionPolicy get_multi_option_policy() const { return multi_option_policy_; } + + // Shortcuts for multi option policy + + /// Set the multi option policy to take last + CRTP *take_last() { + auto self = static_cast<CRTP *>(this); + self->multi_option_policy(MultiOptionPolicy::TakeLast); + return self; + } + + /// Set the multi option policy to take last + CRTP *take_first() { + auto self = static_cast<CRTP *>(this); + self->multi_option_policy(MultiOptionPolicy::TakeFirst); + return self; + } + + /// Set the multi option policy to take last + CRTP *join() { + auto self = static_cast<CRTP *>(this); + self->multi_option_policy(MultiOptionPolicy::Join); + return self; + } + + /// Allow in a configuration file + CRTP *configurable(bool value = true) { + configurable_ = value; + return static_cast<CRTP *>(this); + } +}; + +/// This is a version of OptionBase that only supports setting values, +/// for defaults. It is stored as the default option in an App. +class OptionDefaults : public OptionBase<OptionDefaults> { + public: + OptionDefaults() = default; + + // Methods here need a different implementation if they are Option vs. OptionDefault + + /// Take the last argument if given multiple times + OptionDefaults *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) { + multi_option_policy_ = value; + return this; + } + + /// Ignore the case of the option name + OptionDefaults *ignore_case(bool value = true) { + ignore_case_ = value; + return this; + } +}; + +class Option : public OptionBase<Option> { + friend App; + + protected: + /// @name Names + ///@{ + + /// A list of the short names (`-a`) without the leading dashes + std::vector<std::string> snames_; + + /// A list of the long names (`--a`) without the leading dashes + std::vector<std::string> lnames_; + + /// A positional name + std::string pname_; + + /// If given, check the environment for this option + std::string envname_; + + ///@} + /// @name Help + ///@{ + + /// The description for help strings + std::string description_; + + /// A human readable default value, usually only set if default is true in creation + std::string defaultval_; + + /// A human readable type value, set when App creates this + /// + /// This is a lambda function so "types" can be dynamic, such as when a set prints its contents. + std::function<std::string()> type_name_{[]() { return std::string(); }}; + + /// True if this option has a default + bool default_{false}; + + ///@} + /// @name Configuration + ///@{ + + /// The number of arguments that make up one option. -1=unlimited (vector-like), 0=flag, 1=normal option, + /// 2=complex/pair, etc. Set only when the option is created; this is intrinsic to the type. Eventually, -2 may mean + /// vector of pairs. + int type_size_{1}; + + /// The number of expected values, type_size_ must be < 0. Ignored for flag. N < 0 means at least -N values. + int expected_{1}; + + /// A list of validators to run on each value parsed + std::vector<std::function<std::string(std::string &)>> validators_; + + /// A list of options that are required with this option + std::set<Option *> requires_; + + /// A list of options that are excluded with this option + std::set<Option *> excludes_; + + ///@} + /// @name Other + ///@{ + + /// Remember the parent app + App *parent_; + + /// Options store a callback to do all the work + callback_t callback_; + + /// Options can short-circuit for help options or similar (called before parsing is validated) + bool short_circuit_{false}; + + ///@} + /// @name Parsing results + ///@{ + + /// Results of parsing + results_t results_; + + /// Whether the callback has run (needed for INI parsing) + bool callback_run_{false}; + + ///@} + + /// Making an option by hand is not defined, it must be made by the App class + Option( + std::string name, std::string description, std::function<bool(results_t)> callback, bool defaulted, App *parent) + : description_(std::move(description)), default_(defaulted), parent_(parent), + callback_(callback ? std::move(callback) : [](results_t) { return true; }) { + std::tie(snames_, lnames_, pname_) = detail::get_names(detail::split_names(name)); + } + + public: + /// @name Basic + ///@{ + + /// Count the total number of times an option was passed + size_t count() const { return results_.size(); } + + /// True if the option was not passed + size_t empty() const { return results_.empty(); } + + /// This class is true if option is passed. + operator bool() const { return !empty(); } + + /// Clear the parsed results (mostly for testing) + void clear() { results_.clear(); } + + ///@} + /// @name Setting options + ///@{ + + /// Set the number of expected arguments (Flags don't use this) + Option *expected(int value) { + // Break if this is a flag + if(type_size_ == 0) + throw IncorrectConstruction::SetFlag(get_name(true, true)); + + // Setting 0 is not allowed + else if(value == 0) + throw IncorrectConstruction::Set0Opt(get_name()); + + // No change is okay, quit now + else if(expected_ == value) + return this; + + // Type must be a vector + else if(type_size_ >= 0) + throw IncorrectConstruction::ChangeNotVector(get_name()); + + // TODO: Can support multioption for non-1 values (except for join) + else if(value != 1 && multi_option_policy_ != MultiOptionPolicy::Throw) + throw IncorrectConstruction::AfterMultiOpt(get_name()); + + expected_ = value; + return this; + } + + /// Adds a validator with a built in type name + Option *check(const Validator &validator) { + validators_.emplace_back(validator.func); + if(!validator.tname.empty()) + type_name(validator.tname); + return this; + } + + /// Adds a validator. Takes a const string& and returns an error message (empty if conversion/check is okay). + Option *check(std::function<std::string(const std::string &)> validator) { + validators_.emplace_back(validator); + return this; + } + + /// Adds a validator-like function that can change result + Option *transform(std::function<std::string(std::string)> func) { + validators_.emplace_back([func](std::string &inout) { + try { + inout = func(inout); + } catch(const ValidationError &e) { + return std::string(e.what()); + } + return std::string(); + }); + return this; + } + + /// Adds a user supplied function to run on each item passed in (communicate though lambda capture) + Option *each(std::function<void(std::string)> func) { + validators_.emplace_back([func](std::string &inout) { + func(inout); + return std::string(); + }); + return this; + } + + /// Sets required options + Option *needs(Option *opt) { + auto tup = requires_.insert(opt); + if(!tup.second) + throw OptionAlreadyAdded::Requires(get_name(), opt->get_name()); + return this; + } + + /// Can find a string if needed + template <typename T = App> Option *needs(std::string opt_name) { + for(const Option_p &opt : dynamic_cast<T *>(parent_)->options_) + if(opt.get() != this && opt->check_name(opt_name)) + return needs(opt.get()); + throw IncorrectConstruction::MissingOption(opt_name); + } + + /// Any number supported, any mix of string and Opt + template <typename A, typename B, typename... ARG> Option *needs(A opt, B opt1, ARG... args) { + needs(opt); + return needs(opt1, args...); + } + + /// Sets excluded options + Option *excludes(Option *opt) { + excludes_.insert(opt); + + // Help text should be symmetric - excluding a should exclude b + opt->excludes_.insert(this); + + // Ignoring the insert return value, excluding twice is now allowed. + // (Mostly to allow both directions to be excluded by user, even though the library does it for you.) + + return this; + } + + /// Can find a string if needed + template <typename T = App> Option *excludes(std::string opt_name) { + for(const Option_p &opt : dynamic_cast<T *>(parent_)->options_) + if(opt.get() != this && opt->check_name(opt_name)) + return excludes(opt.get()); + throw IncorrectConstruction::MissingOption(opt_name); + } + + /// Any number supported, any mix of string and Opt + template <typename A, typename B, typename... ARG> Option *excludes(A opt, B opt1, ARG... args) { + excludes(opt); + return excludes(opt1, args...); + } + + /// Sets environment variable to read if no option given + Option *envname(std::string name) { + envname_ = name; + return this; + } + + /// Ignore case + /// + /// The template hides the fact that we don't have the definition of App yet. + /// You are never expected to add an argument to the template here. + template <typename T = App> Option *ignore_case(bool value = true) { + ignore_case_ = value; + auto *parent = dynamic_cast<T *>(parent_); + + for(const Option_p &opt : parent->options_) + if(opt.get() != this && *opt == *this) + throw OptionAlreadyAdded(opt->get_name(true, true)); + + return this; + } + + /// Take the last argument if given multiple times (or another policy) + Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) { + + if(get_items_expected() < 0) + throw IncorrectConstruction::MultiOptionPolicy(get_name()); + multi_option_policy_ = value; + return this; + } + + /// Options with a short circuit set will run this function before parsing is finished. + /// + /// This is set on help functions, for example, to escape the normal validation. + Option *short_circuit(bool value = true) { + short_circuit_ = value; + return this; + } + + ///@} + /// @name Accessors + ///@{ + + /// The number of arguments the option expects + int get_type_size() const { return type_size_; } + + /// The environment variable associated to this value + std::string get_envname() const { return envname_; } + + /// The set of options needed + std::set<Option *> get_needs() const { return requires_; } + + /// The set of options excluded + std::set<Option *> get_excludes() const { return excludes_; } + + /// The default value (for help printing) + std::string get_defaultval() const { return defaultval_; } + + /// See if this is supposed to short circuit (skip validation, INI, etc) (Used for help flags) + bool get_short_circuit() const { return short_circuit_; } + + /// Get the callback function + callback_t get_callback() const { return callback_; } + + /// Get the long names + const std::vector<std::string> get_lnames() const { return lnames_; } + + /// Get the short names + const std::vector<std::string> get_snames() const { return snames_; } + + /// The number of times the option expects to be included + int get_expected() const { return expected_; } + + /// \brief The total number of expected values (including the type) + /// This is positive if exactly this number is expected, and negitive for at least N values + /// + /// v = fabs(size_type*expected) + /// !MultiOptionPolicy::Throw + /// | Expected < 0 | Expected == 0 | Expected > 0 + /// Size < 0 | -v | 0 | -v + /// Size == 0 | 0 | 0 | 0 + /// Size > 0 | -v | 0 | -v // Expected must be 1 + /// + /// MultiOptionPolicy::Throw + /// | Expected < 0 | Expected == 0 | Expected > 0 + /// Size < 0 | -v | 0 | v + /// Size == 0 | 0 | 0 | 0 + /// Size > 0 | v | 0 | v // Expected must be 1 + /// + int get_items_expected() const { + return std::abs(type_size_ * expected_) * + ((multi_option_policy_ != MultiOptionPolicy::Throw || (expected_ < 0 && type_size_ < 0) ? -1 : 1)); + } + + /// True if this has a default value + int get_default() const { return default_; } + + /// True if the argument can be given directly + bool get_positional() const { return pname_.length() > 0; } + + /// True if option has at least one non-positional name + bool nonpositional() const { return (snames_.size() + lnames_.size()) > 0; } + + /// True if option has description + bool has_description() const { return description_.length() > 0; } + + /// Get the description + const std::string &get_description() const { return description_; } + + ///@} + /// @name Help tools + ///@{ + + /// \brief Gets a comma seperated list of names. + /// Will include / prefer the positional name if positional is true. + /// If all_options is false, pick just the most descriptive name to show. + /// Use `get_name(true)` to get the positional name (replaces `get_pname`) + std::string get_name(bool positional = false, //<[input] Show the positional name + bool all_options = false //<[input] Show every option + ) const { + + if(all_options) { + + std::vector<std::string> name_list; + + /// The all list wil never include a positional unless asked or that's the only name. + if((positional && pname_.length()) || (snames_.empty() && lnames_.empty())) + name_list.push_back(pname_); + + for(const std::string &sname : snames_) + name_list.push_back("-" + sname); + + for(const std::string &lname : lnames_) + name_list.push_back("--" + lname); + + return detail::join(name_list); + + } else { + + // This returns the positional name no matter what + if(positional) + return pname_; + + // Prefer long name + else if(!lnames_.empty()) + return std::string("--") + lnames_[0]; + + // Or short name if no long name + else if(!snames_.empty()) + return std::string("-") + snames_[0]; + + // If positional is the only name, it's okay to use that + else + return pname_; + } + } + + ///@} + /// @name Parser tools + ///@{ + + /// Process the callback + void run_callback() { + + // Run the validators (can change the string) + if(!validators_.empty()) { + for(std::string &result : results_) + for(const std::function<std::string(std::string &)> &vali : validators_) { + std::string err_msg = vali(result); + if(!err_msg.empty()) + throw ValidationError(get_name(), err_msg); + } + } + + bool local_result; + + // Num items expected or length of vector, always at least 1 + // Only valid for a trimming policy + int trim_size = + std::min<int>(std::max<int>(std::abs(get_items_expected()), 1), static_cast<int>(results_.size())); + + // Operation depends on the policy setting + if(multi_option_policy_ == MultiOptionPolicy::TakeLast) { + // Allow multi-option sizes (including 0) + results_t partial_result{results_.end() - trim_size, results_.end()}; + local_result = !callback_(partial_result); + + } else if(multi_option_policy_ == MultiOptionPolicy::TakeFirst) { + results_t partial_result{results_.begin(), results_.begin() + trim_size}; + local_result = !callback_(partial_result); + + } else if(multi_option_policy_ == MultiOptionPolicy::Join) { + results_t partial_result = {detail::join(results_, "\n")}; + local_result = !callback_(partial_result); + + } else { + // Exact number required + if(get_items_expected() > 0) { + if(results_.size() != static_cast<size_t>(get_items_expected())) + throw ArgumentMismatch(get_name(), get_items_expected(), results_.size()); + // Variable length list + } else if(get_items_expected() < 0) { + // Require that this be a multiple of expected size and at least as many as expected + if(results_.size() < static_cast<size_t>(-get_items_expected()) || + results_.size() % static_cast<size_t>(std::abs(get_type_size())) != 0) + throw ArgumentMismatch(get_name(), get_items_expected(), results_.size()); + } + local_result = !callback_(results_); + } + + if(local_result) + throw ConversionError(get_name(), results_); + } + + /// If options share any of the same names, they are equal (not counting positional) + bool operator==(const Option &other) const { + for(const std::string &sname : snames_) + if(other.check_sname(sname)) + return true; + for(const std::string &lname : lnames_) + if(other.check_lname(lname)) + return true; + // We need to do the inverse, just in case we are ignore_case + for(const std::string &sname : other.snames_) + if(check_sname(sname)) + return true; + for(const std::string &lname : other.lnames_) + if(check_lname(lname)) + return true; + return false; + } + + /// Check a name. Requires "-" or "--" for short / long, supports positional name + bool check_name(std::string name) const { + + if(name.length() > 2 && name.substr(0, 2) == "--") + return check_lname(name.substr(2)); + else if(name.length() > 1 && name.substr(0, 1) == "-") + return check_sname(name.substr(1)); + else { + std::string local_pname = pname_; + if(ignore_case_) { + local_pname = detail::to_lower(local_pname); + name = detail::to_lower(name); + } + return name == local_pname; + } + } + + /// Requires "-" to be removed from string + bool check_sname(std::string name) const { + if(ignore_case_) { + name = detail::to_lower(name); + return std::find_if(std::begin(snames_), std::end(snames_), [&name](std::string local_sname) { + return detail::to_lower(local_sname) == name; + }) != std::end(snames_); + } else + return std::find(std::begin(snames_), std::end(snames_), name) != std::end(snames_); + } + + /// Requires "--" to be removed from string + bool check_lname(std::string name) const { + if(ignore_case_) { + name = detail::to_lower(name); + return std::find_if(std::begin(lnames_), std::end(lnames_), [&name](std::string local_sname) { + return detail::to_lower(local_sname) == name; + }) != std::end(lnames_); + } else + return std::find(std::begin(lnames_), std::end(lnames_), name) != std::end(lnames_); + } + + /// Puts a result at the end + Option *add_result(std::string s) { + results_.push_back(s); + callback_run_ = false; + return this; + } + + /// Set the results vector all at once + Option *set_results(std::vector<std::string> results) { + results_ = results; + callback_run_ = false; + return this; + } + + /// Get a copy of the results + std::vector<std::string> results() const { return results_; } + + /// See if the callback has been run already + bool get_callback_run() const { return callback_run_; } + + ///@} + /// @name Custom options + ///@{ + + /// Set the type function to run when displayed on this option + Option *type_name_fn(std::function<std::string()> typefun) { + type_name_ = typefun; + return this; + } + + /// Set a custom option typestring + Option *type_name(std::string typeval) { + type_name_fn([typeval]() { return typeval; }); + return this; + } + + /// Provided for backward compatibility \deprecated + CLI11_DEPRECATED("Please use type_name instead") + Option *set_type_name(std::string typeval) { return type_name(typeval); } + + /// Set a custom option size + Option *type_size(int type_size) { + type_size_ = type_size; + if(type_size_ == 0) + required_ = false; + if(type_size < 0) + expected_ = -1; + return this; + } + + /// Set the default value string representation + Option *default_str(std::string val) { + defaultval_ = val; + return this; + } + + /// Set the default value string representation and evaluate + Option *default_val(std::string val) { + default_str(val); + auto old_results = results_; + results_ = {val}; + run_callback(); + results_ = std::move(old_results); + return this; + } + + /// Get the typename for this option + std::string get_type_name() const { return type_name_(); } +}; + +} // namespace CLI + +// From CLI/App.hpp: + +namespace CLI { + +#ifndef CLI11_PARSE +#define CLI11_PARSE(app, argc, argv) \ + try { \ + (app).parse((argc), (argv)); \ + } catch(const CLI::ParseError &e) { \ + return (app).exit(e); \ + } +#endif + +namespace detail { +enum class Classifer { NONE, POSITIONAL_MARK, SHORT, LONG, SUBCOMMAND }; +struct AppFriend; +} // namespace detail + +namespace FailureMessage { +std::string simple(const App *app, const Error &e); +std::string help(const App *app, const Error &e); +} // namespace FailureMessage + +class App; + +using App_p = std::unique_ptr<App>; + +/// Creates a command line program, with very few defaults. +/** To use, create a new `Program()` instance with `argc`, `argv`, and a help description. The templated + * add_option methods make it easy to prepare options. Remember to call `.start` before starting your + * program, so that the options can be evaluated and the help option doesn't accidentally run your program. */ +class App { + friend Option; + friend detail::AppFriend; + + protected: + // This library follows the Google style guide for member names ending in underscores + + /// @name Basics + ///@{ + + /// Subcommand name or program name (from parser if name is empty) + std::string name_; + + /// Description of the current program/subcommand + std::string description_; + + /// If true, allow extra arguments (ie, don't throw an error). INHERITABLE + bool allow_extras_{false}; + + /// If true, allow extra arguments in the ini file (ie, don't throw an error). INHERITABLE + bool allow_config_extras_{false}; + + /// If true, return immediately on an unrecognised option (implies allow_extras) INHERITABLE + bool prefix_command_{false}; + + /// This is a function that runs when complete. Great for subcommands. Can throw. + std::function<void()> callback_; + + ///@} + /// @name Options + ///@{ + + /// The default values for options, customizable and changeable INHERITABLE + OptionDefaults option_defaults_; + + /// The list of options, stored locally + std::vector<Option_p> options_; + + ///@} + /// @name Help + ///@{ + + /// Footer to put after all options in the help output INHERITABLE + std::string footer_; + + /// A pointer to the help flag if there is one INHERITABLE + Option *help_ptr_{nullptr}; + + /// A pointer to the help all flag if there is one INHERITABLE + Option *help_all_ptr_{nullptr}; + + /// This is the formatter for help printing. Default provided. INHERITABLE (same pointer) + std::shared_ptr<FormatterBase> formatter_{new Formatter()}; + + /// The error message printing function INHERITABLE + std::function<std::string(const App *, const Error &e)> failure_message_ = FailureMessage::simple; + + ///@} + /// @name Parsing + ///@{ + + using missing_t = std::vector<std::pair<detail::Classifer, std::string>>; + + /// Pair of classifier, string for missing options. (extra detail is removed on returning from parse) + /// + /// This is faster and cleaner than storing just a list of strings and reparsing. This may contain the -- separator. + missing_t missing_; + + /// This is a list of pointers to options with the original parse order + std::vector<Option *> parse_order_; + + /// This is a list of the subcommands collected, in order + std::vector<App *> parsed_subcommands_; + + ///@} + /// @name Subcommands + ///@{ + + /// Storage for subcommand list + std::vector<App_p> subcommands_; + + /// If true, the program name is not case sensitive INHERITABLE + bool ignore_case_{false}; + + /// Allow subcommand fallthrough, so that parent commands can collect commands after subcommand. INHERITABLE + bool fallthrough_{false}; + + /// A pointer to the parent if this is a subcommand + App *parent_{nullptr}; + + /// True if this command/subcommand was parsed + bool parsed_{false}; + + /// Minimum required subcommands (not inheritable!) + size_t require_subcommand_min_ = 0; + + /// Max number of subcommands allowed (parsing stops after this number). 0 is unlimited INHERITABLE + size_t require_subcommand_max_ = 0; + + /// The group membership INHERITABLE + std::string group_{"Subcommands"}; + + ///@} + /// @name Config + ///@{ + + /// The name of the connected config file + std::string config_name_; + + /// True if ini is required (throws if not present), if false simply keep going. + bool config_required_{false}; + + /// Pointer to the config option + Option *config_ptr_{nullptr}; + + /// This is the formatter for help printing. Default provided. INHERITABLE (same pointer) + std::shared_ptr<Config> config_formatter_{new ConfigINI()}; + + ///@} + + /// Special private constructor for subcommand + App(std::string description_, std::string name, App *parent) + : name_(std::move(name)), description_(std::move(description_)), parent_(parent) { + // Inherit if not from a nullptr + if(parent_ != nullptr) { + if(parent_->help_ptr_ != nullptr) + set_help_flag(parent_->help_ptr_->get_name(false, true), parent_->help_ptr_->get_description()); + if(parent_->help_all_ptr_ != nullptr) + set_help_all_flag(parent_->help_all_ptr_->get_name(false, true), + parent_->help_all_ptr_->get_description()); + + /// OptionDefaults + option_defaults_ = parent_->option_defaults_; + + // INHERITABLE + failure_message_ = parent_->failure_message_; + allow_extras_ = parent_->allow_extras_; + allow_config_extras_ = parent_->allow_config_extras_; + prefix_command_ = parent_->prefix_command_; + ignore_case_ = parent_->ignore_case_; + fallthrough_ = parent_->fallthrough_; + group_ = parent_->group_; + footer_ = parent_->footer_; + formatter_ = parent_->formatter_; + config_formatter_ = parent_->config_formatter_; + require_subcommand_max_ = parent_->require_subcommand_max_; + } + } + + public: + /// @name Basic + ///@{ + + /// Create a new program. Pass in the same arguments as main(), along with a help string. + explicit App(std::string description_ = "", std::string name = "") : App(description_, name, nullptr) { + set_help_flag("-h,--help", "Print this help message and exit"); + } + + /// virtual destructor + virtual ~App() = default; + + /// Set a callback for the end of parsing. + /// + /// Due to a bug in c++11, + /// it is not possible to overload on std::function (fixed in c++14 + /// and backported to c++11 on newer compilers). Use capture by reference + /// to get a pointer to App if needed. + App *callback(std::function<void()> callback) { + callback_ = callback; + return this; + } + + /// Set a name for the app (empty will use parser to set the name) + App *name(std::string name = "") { + name_ = name; + return this; + } + + /// Remove the error when extras are left over on the command line. + App *allow_extras(bool allow = true) { + allow_extras_ = allow; + return this; + } + + /// Remove the error when extras are left over on the command line. + /// Will also call App::allow_extras(). + App *allow_config_extras(bool allow = true) { + allow_extras(allow); + allow_config_extras_ = allow; + return this; + } + + /// Do not parse anything after the first unrecognised option and return + App *prefix_command(bool allow = true) { + prefix_command_ = allow; + return this; + } + + /// Ignore case. Subcommand inherit value. + App *ignore_case(bool value = true) { + ignore_case_ = value; + if(parent_ != nullptr) { + for(const auto &subc : parent_->subcommands_) { + if(subc.get() != this && (this->check_name(subc->name_) || subc->check_name(this->name_))) + throw OptionAlreadyAdded(subc->name_); + } + } + return this; + } + + /// Set the help formatter + App *formatter(std::shared_ptr<FormatterBase> fmt) { + formatter_ = fmt; + return this; + } + + /// Set the help formatter + App *formatter_fn(std::function<std::string(const App *, std::string, AppFormatMode)> fmt) { + formatter_ = std::make_shared<FormatterLambda>(fmt); + return this; + } + + /// Set the config formatter + App *config_formatter(std::shared_ptr<Config> fmt) { + config_formatter_ = fmt; + return this; + } + + /// Check to see if this subcommand was parsed, true only if received on command line. + bool parsed() const { return parsed_; } + + /// Get the OptionDefault object, to set option defaults + OptionDefaults *option_defaults() { return &option_defaults_; } + + ///@} + /// @name Adding options + ///@{ + + /// Add an option, will automatically understand the type for common types. + /// + /// To use, create a variable with the expected type, and pass it in after the name. + /// After start is called, you can use count to see if the value was passed, and + /// the value will be initialized properly. Numbers, vectors, and strings are supported. + /// + /// ->required(), ->default, and the validators are options, + /// The positional options take an optional number of arguments. + /// + /// For example, + /// + /// std::string filename; + /// program.add_option("filename", filename, "description of filename"); + /// + Option *add_option(std::string name, callback_t callback, std::string description = "", bool defaulted = false) { + Option myopt{name, description, callback, defaulted, this}; + + if(std::find_if(std::begin(options_), std::end(options_), [&myopt](const Option_p &v) { + return *v == myopt; + }) == std::end(options_)) { + options_.emplace_back(); + Option_p &option = options_.back(); + option.reset(new Option(name, description, callback, defaulted, this)); + option_defaults_.copy_to(option.get()); + return option.get(); + } else + throw OptionAlreadyAdded(myopt.get_name()); + } + + /// Add option for non-vectors (duplicate copy needed without defaulted to avoid `iostream << value`) + template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy> + Option *add_option(std::string name, + T &variable, ///< The variable to set + std::string description = "") { + + CLI::callback_t fun = [&variable](CLI::results_t res) { return detail::lexical_cast(res[0], variable); }; + + Option *opt = add_option(name, fun, description, false); + opt->type_name(detail::type_name<T>()); + return opt; + } + + /// Add option for non-vectors with a default print + template <typename T, enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy> + Option *add_option(std::string name, + T &variable, ///< The variable to set + std::string description, + bool defaulted) { + + CLI::callback_t fun = [&variable](CLI::results_t res) { return detail::lexical_cast(res[0], variable); }; + + Option *opt = add_option(name, fun, description, defaulted); + opt->type_name(detail::type_name<T>()); + if(defaulted) { + std::stringstream out; + out << variable; + opt->default_str(out.str()); + } + return opt; + } + + /// Add option for vectors (no default) + template <typename T> + Option *add_option(std::string name, + std::vector<T> &variable, ///< The variable vector to set + std::string description = "") { + + CLI::callback_t fun = [&variable](CLI::results_t res) { + bool retval = true; + variable.clear(); + for(const auto &a : res) { + variable.emplace_back(); + retval &= detail::lexical_cast(a, variable.back()); + } + return (!variable.empty()) && retval; + }; + + Option *opt = add_option(name, fun, description, false); + opt->type_name(detail::type_name<T>())->type_size(-1); + return opt; + } + + /// Add option for vectors + template <typename T> + Option *add_option(std::string name, + std::vector<T> &variable, ///< The variable vector to set + std::string description, + bool defaulted) { + + CLI::callback_t fun = [&variable](CLI::results_t res) { + bool retval = true; + variable.clear(); + for(const auto &a : res) { + variable.emplace_back(); + retval &= detail::lexical_cast(a, variable.back()); + } + return (!variable.empty()) && retval; + }; + + Option *opt = add_option(name, fun, description, defaulted); + opt->type_name(detail::type_name<T>())->type_size(-1); + if(defaulted) + opt->default_str("[" + detail::join(variable) + "]"); + return opt; + } + + /// Set a help flag, replace the existing one if present + Option *set_help_flag(std::string name = "", std::string description = "") { + if(help_ptr_ != nullptr) { + remove_option(help_ptr_); + help_ptr_ = nullptr; + } + + // Empty name will simply remove the help flag + if(!name.empty()) { + help_ptr_ = add_flag_function(name, [](size_t) -> void { throw CallForHelp(); }, description); + help_ptr_->short_circuit(true); + help_ptr_->configurable(false); + } + + return help_ptr_; + } + + /// Set a help all flag, replaced the existing one if present + Option *set_help_all_flag(std::string name = "", std::string description = "") { + if(help_all_ptr_ != nullptr) { + remove_option(help_all_ptr_); + help_all_ptr_ = nullptr; + } + + // Empty name will simply remove the help all flag + if(!name.empty()) { + help_all_ptr_ = add_flag_function(name, [](size_t) -> void { throw CallForAllHelp(); }, description); + help_all_ptr_->short_circuit(true); + help_all_ptr_->configurable(false); + } + + return help_all_ptr_; + } + + /// Add option for flag + Option *add_flag(std::string name, std::string description = "") { + CLI::callback_t fun = [](CLI::results_t) { return true; }; + + Option *opt = add_option(name, fun, description, false); + if(opt->get_positional()) + throw IncorrectConstruction::PositionalFlag(name); + opt->type_size(0); + return opt; + } + + /// Add option for flag integer + template <typename T, + enable_if_t<std::is_integral<T>::value && !is_bool<T>::value, detail::enabler> = detail::dummy> + Option *add_flag(std::string name, + T &count, ///< A variable holding the count + std::string description = "") { + + count = 0; + CLI::callback_t fun = [&count](CLI::results_t res) { + count = static_cast<T>(res.size()); + return true; + }; + + Option *opt = add_option(name, fun, description, false); + if(opt->get_positional()) + throw IncorrectConstruction::PositionalFlag(name); + opt->type_size(0); + return opt; + } + + /// Bool version - defaults to allowing multiple passings, but can be forced to one if + /// `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used. + template <typename T, enable_if_t<is_bool<T>::value, detail::enabler> = detail::dummy> + Option *add_flag(std::string name, + T &count, ///< A variable holding true if passed + std::string description = "") { + + count = false; + CLI::callback_t fun = [&count](CLI::results_t res) { + count = true; + return res.size() == 1; + }; + + Option *opt = add_option(name, fun, description, false); + if(opt->get_positional()) + throw IncorrectConstruction::PositionalFlag(name); + opt->type_size(0); + opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast); + return opt; + } + + /// Add option for callback + Option *add_flag_function(std::string name, + std::function<void(size_t)> function, ///< A function to call, void(size_t) + std::string description = "") { + + CLI::callback_t fun = [function](CLI::results_t res) { + auto count = static_cast<size_t>(res.size()); + function(count); + return true; + }; + + Option *opt = add_option(name, fun, description, false); + if(opt->get_positional()) + throw IncorrectConstruction::PositionalFlag(name); + opt->type_size(0); + return opt; + } + +#ifdef CLI11_CPP14 + /// Add option for callback (C++14 or better only) + Option *add_flag(std::string name, + std::function<void(size_t)> function, ///< A function to call, void(size_t) + std::string description = "") { + return add_flag_function(name, function, description); + } +#endif + + /// Add set of options (No default, temp refernce, such as an inline set) + template <typename T> + Option *add_set(std::string name, + T &member, ///< The selected member of the set + const std::set<T> &&options, ///< The set of possibilities + std::string description = "") { + + std::string simple_name = CLI::detail::split(name, ',').at(0); + CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) { + bool retval = detail::lexical_cast(res[0], member); + if(!retval) + throw ConversionError(res[0], simple_name); + return std::find(std::begin(options), std::end(options), member) != std::end(options); + }; + + Option *opt = add_option(name, fun, description, false); + std::string typeval = detail::type_name<T>(); + typeval += " in {" + detail::join(options) + "}"; + opt->type_name(typeval); + return opt; + } + + /// Add set of options (No default, non-temp refernce, such as an existing set) + template <typename T> + Option *add_set(std::string name, + T &member, ///< The selected member of the set + const std::set<T> &options, ///< The set of possibilities + std::string description = "") { + + std::string simple_name = CLI::detail::split(name, ',').at(0); + CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) { + bool retval = detail::lexical_cast(res[0], member); + if(!retval) + throw ConversionError(res[0], simple_name); + return std::find(std::begin(options), std::end(options), member) != std::end(options); + }; + + Option *opt = add_option(name, fun, description, false); + opt->type_name_fn( + [&options]() { return std::string(detail::type_name<T>()) + " in {" + detail::join(options) + "}"; }); + + return opt; + } + + /// Add set of options (with default, R value, such as an inline set) + template <typename T> + Option *add_set(std::string name, + T &member, ///< The selected member of the set + const std::set<T> &&options, ///< The set of posibilities + std::string description, + bool defaulted) { + + std::string simple_name = CLI::detail::split(name, ',').at(0); + CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) { + bool retval = detail::lexical_cast(res[0], member); + if(!retval) + throw ConversionError(res[0], simple_name); + return std::find(std::begin(options), std::end(options), member) != std::end(options); + }; + + Option *opt = add_option(name, fun, description, defaulted); + std::string typeval = detail::type_name<T>(); + typeval += " in {" + detail::join(options) + "}"; + opt->type_name(typeval); + if(defaulted) { + std::stringstream out; + out << member; + opt->default_str(out.str()); + } + return opt; + } + + /// Add set of options (with default, L value refernce, such as an existing set) + template <typename T> + Option *add_set(std::string name, + T &member, ///< The selected member of the set + const std::set<T> &options, ///< The set of posibilities + std::string description, + bool defaulted) { + + std::string simple_name = CLI::detail::split(name, ',').at(0); + CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) { + bool retval = detail::lexical_cast(res[0], member); + if(!retval) + throw ConversionError(res[0], simple_name); + return std::find(std::begin(options), std::end(options), member) != std::end(options); + }; + + Option *opt = add_option(name, fun, description, defaulted); + opt->type_name_fn( + [&options]() { return std::string(detail::type_name<T>()) + " in {" + detail::join(options) + "}"; }); + if(defaulted) { + std::stringstream out; + out << member; + opt->default_str(out.str()); + } + return opt; + } + + /// Add set of options, string only, ignore case (no default, R value) + Option *add_set_ignore_case(std::string name, + std::string &member, ///< The selected member of the set + const std::set<std::string> &&options, ///< The set of possibilities + std::string description = "") { + + std::string simple_name = CLI::detail::split(name, ',').at(0); + CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) { + member = detail::to_lower(res[0]); + auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) { + return detail::to_lower(val) == member; + }); + if(iter == std::end(options)) + throw ConversionError(member, simple_name); + else { + member = *iter; + return true; + } + }; + + Option *opt = add_option(name, fun, description, false); + std::string typeval = detail::type_name<std::string>(); + typeval += " in {" + detail::join(options) + "}"; + opt->type_name(typeval); + + return opt; + } + + /// Add set of options, string only, ignore case (no default, L value) + Option *add_set_ignore_case(std::string name, + std::string &member, ///< The selected member of the set + const std::set<std::string> &options, ///< The set of possibilities + std::string description = "") { + + std::string simple_name = CLI::detail::split(name, ',').at(0); + CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) { + member = detail::to_lower(res[0]); + auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) { + return detail::to_lower(val) == member; + }); + if(iter == std::end(options)) + throw ConversionError(member, simple_name); + else { + member = *iter; + return true; + } + }; + + Option *opt = add_option(name, fun, description, false); + opt->type_name_fn([&options]() { + return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; + }); + + return opt; + } + + /// Add set of options, string only, ignore case (default, R value) + Option *add_set_ignore_case(std::string name, + std::string &member, ///< The selected member of the set + const std::set<std::string> &&options, ///< The set of posibilities + std::string description, + bool defaulted) { + + std::string simple_name = CLI::detail::split(name, ',').at(0); + CLI::callback_t fun = [&member, options, simple_name](CLI::results_t res) { + member = detail::to_lower(res[0]); + auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) { + return detail::to_lower(val) == member; + }); + if(iter == std::end(options)) + throw ConversionError(member, simple_name); + else { + member = *iter; + return true; + } + }; + + Option *opt = add_option(name, fun, description, defaulted); + std::string typeval = detail::type_name<std::string>(); + typeval += " in {" + detail::join(options) + "}"; + opt->type_name(typeval); + if(defaulted) { + opt->default_str(member); + } + return opt; + } + + /// Add set of options, string only, ignore case (default, L value) + Option *add_set_ignore_case(std::string name, + std::string &member, ///< The selected member of the set + const std::set<std::string> &options, ///< The set of posibilities + std::string description, + bool defaulted) { + + std::string simple_name = CLI::detail::split(name, ',').at(0); + CLI::callback_t fun = [&member, &options, simple_name](CLI::results_t res) { + member = detail::to_lower(res[0]); + auto iter = std::find_if(std::begin(options), std::end(options), [&member](std::string val) { + return detail::to_lower(val) == member; + }); + if(iter == std::end(options)) + throw ConversionError(member, simple_name); + else { + member = *iter; + return true; + } + }; + + Option *opt = add_option(name, fun, description, defaulted); + opt->type_name_fn([&options]() { + return std::string(detail::type_name<std::string>()) + " in {" + detail::join(options) + "}"; + }); + if(defaulted) { + opt->default_str(member); + } + return opt; + } + + /// Add a complex number + template <typename T> + Option *add_complex(std::string name, + T &variable, + std::string description = "", + bool defaulted = false, + std::string label = "COMPLEX") { + + std::string simple_name = CLI::detail::split(name, ',').at(0); + CLI::callback_t fun = [&variable, simple_name, label](results_t res) { + if(res[1].back() == 'i') + res[1].pop_back(); + double x, y; + bool worked = detail::lexical_cast(res[0], x) && detail::lexical_cast(res[1], y); + if(worked) + variable = T(x, y); + return worked; + }; + + CLI::Option *opt = add_option(name, fun, description, defaulted); + opt->type_name(label)->type_size(2); + if(defaulted) { + std::stringstream out; + out << variable; + opt->default_str(out.str()); + } + return opt; + } + + /// Set a configuration ini file option, or clear it if no name passed + Option *set_config(std::string name = "", + std::string default_filename = "", + std::string help = "Read an ini file", + bool required = false) { + + // Remove existing config if present + if(config_ptr_ != nullptr) + remove_option(config_ptr_); + + // Only add config if option passed + if(!name.empty()) { + config_name_ = default_filename; + config_required_ = required; + config_ptr_ = add_option(name, config_name_, help, !default_filename.empty()); + config_ptr_->configurable(false); + } + + return config_ptr_; + } + + /// Removes an option from the App. Takes an option pointer. Returns true if found and removed. + bool remove_option(Option *opt) { + auto iterator = + std::find_if(std::begin(options_), std::end(options_), [opt](const Option_p &v) { return v.get() == opt; }); + if(iterator != std::end(options_)) { + options_.erase(iterator); + return true; + } + return false; + } + + ///@} + /// @name Subcommmands + ///@{ + + /// Add a subcommand. Inherits INHERITABLE and OptionDefaults, and help flag + App *add_subcommand(std::string name, std::string description = "") { + subcommands_.emplace_back(new App(description, name, this)); + for(const auto &subc : subcommands_) + if(subc.get() != subcommands_.back().get()) + if(subc->check_name(subcommands_.back()->name_) || subcommands_.back()->check_name(subc->name_)) + throw OptionAlreadyAdded(subc->name_); + return subcommands_.back().get(); + } + + /// Check to see if a subcommand is part of this command (doesn't have to be in command line) + App *get_subcommand(App *subcom) const { + for(const App_p &subcomptr : subcommands_) + if(subcomptr.get() == subcom) + return subcom; + throw OptionNotFound(subcom->get_name()); + } + + /// Check to see if a subcommand is part of this command (text version) + App *get_subcommand(std::string subcom) const { + for(const App_p &subcomptr : subcommands_) + if(subcomptr->check_name(subcom)) + return subcomptr.get(); + throw OptionNotFound(subcom); + } + + /// Changes the group membership + App *group(std::string name) { + group_ = name; + return this; + } + + /// The argumentless form of require subcommand requires 1 or more subcommands + App *require_subcommand() { + require_subcommand_min_ = 1; + require_subcommand_max_ = 0; + return this; + } + + /// Require a subcommand to be given (does not affect help call) + /// The number required can be given. Negative values indicate maximum + /// number allowed (0 for any number). Max number inheritable. + App *require_subcommand(int value) { + if(value < 0) { + require_subcommand_min_ = 0; + require_subcommand_max_ = static_cast<size_t>(-value); + } else { + require_subcommand_min_ = static_cast<size_t>(value); + require_subcommand_max_ = static_cast<size_t>(value); + } + return this; + } + + /// Explicitly control the number of subcommands required. Setting 0 + /// for the max means unlimited number allowed. Max number inheritable. + App *require_subcommand(size_t min, size_t max) { + require_subcommand_min_ = min; + require_subcommand_max_ = max; + return this; + } + + /// Stop subcommand fallthrough, so that parent commands cannot collect commands after subcommand. + /// Default from parent, usually set on parent. + App *fallthrough(bool value = true) { + fallthrough_ = value; + return this; + } + + /// Check to see if this subcommand was parsed, true only if received on command line. + /// This allows the subcommand to be directly checked. + operator bool() const { return parsed_; } + + ///@} + /// @name Extras for subclassing + ///@{ + + /// This allows subclasses to inject code before callbacks but after parse. + /// + /// This does not run if any errors or help is thrown. + virtual void pre_callback() {} + + ///@} + /// @name Parsing + ///@{ + // + /// Reset the parsed data + void clear() { + + parsed_ = false; + missing_.clear(); + parsed_subcommands_.clear(); + + for(const Option_p &opt : options_) { + opt->clear(); + } + for(const App_p &app : subcommands_) { + app->clear(); + } + } + + /// Parses the command line - throws errors + /// This must be called after the options are in but before the rest of the program. + void parse(int argc, const char *const *argv) { + // If the name is not set, read from command line + if(name_.empty()) + name_ = argv[0]; + + std::vector<std::string> args; + for(int i = argc - 1; i > 0; i--) + args.emplace_back(argv[i]); + parse(args); + } + + /// The real work is done here. Expects a reversed vector. + /// Changes the vector to the remaining options. + void parse(std::vector<std::string> &args) { + // Clear if parsed + if(parsed_) + clear(); + + // Redundant (set by _parse on commands/subcommands) + // but placed here to make sure this is cleared when + // running parse after an error is thrown, even by _validate. + parsed_ = true; + + _validate(); + _parse(args); + run_callback(); + } + + /// Provide a function to print a help message. The function gets access to the App pointer and error. + void failure_message(std::function<std::string(const App *, const Error &e)> function) { + failure_message_ = function; + } + + /// Print a nice error message and return the exit code + int exit(const Error &e, std::ostream &out = std::cout, std::ostream &err = std::cerr) const { + + /// Avoid printing anything if this is a CLI::RuntimeError + if(dynamic_cast<const CLI::RuntimeError *>(&e) != nullptr) + return e.get_exit_code(); + + if(dynamic_cast<const CLI::CallForHelp *>(&e) != nullptr) { + out << help(); + return e.get_exit_code(); + } + + if(dynamic_cast<const CLI::CallForAllHelp *>(&e) != nullptr) { + out << help("", AppFormatMode::All); + return e.get_exit_code(); + } + + if(e.get_exit_code() != static_cast<int>(ExitCodes::Success)) { + if(failure_message_) + err << failure_message_(this, e) << std::flush; + } + + return e.get_exit_code(); + } + + ///@} + /// @name Post parsing + ///@{ + + /// Counts the number of times the given option was passed. + size_t count(std::string name) const { + for(const Option_p &opt : options_) { + if(opt->check_name(name)) { + return opt->count(); + } + } + throw OptionNotFound(name); + } + + /// Get a subcommand pointer list to the currently selected subcommands (after parsing by by default, in command + /// line order; use parsed = false to get the original definition list.) + std::vector<App *> get_subcommands() const { return parsed_subcommands_; } + + /// Get a filtered subcommand pointer list from the original definition list. An empty function will provide all + /// subcommands (const) + std::vector<const App *> get_subcommands(const std::function<bool(const App *)> &filter) const { + std::vector<const App *> subcomms(subcommands_.size()); + std::transform(std::begin(subcommands_), std::end(subcommands_), std::begin(subcomms), [](const App_p &v) { + return v.get(); + }); + + if(filter) { + subcomms.erase(std::remove_if(std::begin(subcomms), + std::end(subcomms), + [&filter](const App *app) { return !filter(app); }), + std::end(subcomms)); + } + + return subcomms; + } + + /// Get a filtered subcommand pointer list from the original definition list. An empty function will provide all + /// subcommands + std::vector<App *> get_subcommands(const std::function<bool(App *)> &filter) { + std::vector<App *> subcomms(subcommands_.size()); + std::transform(std::begin(subcommands_), std::end(subcommands_), std::begin(subcomms), [](const App_p &v) { + return v.get(); + }); + + if(filter) { + subcomms.erase( + std::remove_if(std::begin(subcomms), std::end(subcomms), [&filter](App *app) { return !filter(app); }), + std::end(subcomms)); + } + + return subcomms; + } + + /// Check to see if given subcommand was selected + bool got_subcommand(App *subcom) const { + // get subcom needed to verify that this was a real subcommand + return get_subcommand(subcom)->parsed_; + } + + /// Check with name instead of pointer to see if subcommand was selected + bool got_subcommand(std::string name) const { return get_subcommand(name)->parsed_; } + + ///@} + /// @name Help + ///@{ + + /// Set footer. + App *footer(std::string footer) { + footer_ = footer; + return this; + } + + /// Produce a string that could be read in as a config of the current values of the App. Set default_also to include + /// default arguments. Prefix will add a string to the beginning of each option. + std::string config_to_str(bool default_also = false, bool write_description = false) const { + return config_formatter_->to_config(this, default_also, write_description, ""); + } + + /// Makes a help message, using the currently configured formatter + /// Will only do one subcommand at a time + std::string help(std::string prev = "", AppFormatMode mode = AppFormatMode::Normal) const { + if(prev.empty()) + prev = get_name(); + else + prev += " " + get_name(); + + // Delegate to subcommand if needed + auto selected_subcommands = get_subcommands(); + if(!selected_subcommands.empty()) + return selected_subcommands.at(0)->help(prev); + else + return formatter_->make_help(this, prev, mode); + } + + /// Provided for backwards compatibility \deprecated + CLI11_DEPRECATED("Please use footer instead") + App *set_footer(std::string msg) { return footer(msg); } + + /// Provided for backwards compatibility \deprecated + CLI11_DEPRECATED("Please use name instead") + App *set_name(std::string msg) { return name(msg); } + + /// Provided for backwards compatibility \deprecated + CLI11_DEPRECATED("Please use callback instead") + App *set_callback(std::function<void()> fn) { return callback(fn); } + + ///@} + /// @name Getters + ///@{ + + /// Access the formatter + std::shared_ptr<FormatterBase> get_formatter() const { return formatter_; } + + /// Access the config formatter + std::shared_ptr<Config> get_config_formatter() const { return config_formatter_; } + + /// Get the app or subcommand description + std::string get_description() const { return description_; } + + /// Get the list of options (user facing function, so returns raw pointers), has optional filter function + std::vector<const Option *> get_options(const std::function<bool(const Option *)> filter = {}) const { + std::vector<const Option *> options(options_.size()); + std::transform(std::begin(options_), std::end(options_), std::begin(options), [](const Option_p &val) { + return val.get(); + }); + + if(filter) { + options.erase(std::remove_if(std::begin(options), + std::end(options), + [&filter](const Option *opt) { return !filter(opt); }), + std::end(options)); + } + + return options; + } + + /// Get an option by name + const Option *get_option(std::string name) const { + for(const Option_p &opt : options_) { + if(opt->check_name(name)) { + return opt.get(); + } + } + throw OptionNotFound(name); + } + + /// Get an option by name (non-const version) + Option *get_option(std::string name) { + for(Option_p &opt : options_) { + if(opt->check_name(name)) { + return opt.get(); + } + } + throw OptionNotFound(name); + } + + /// Check the status of ignore_case + bool get_ignore_case() const { return ignore_case_; } + + /// Check the status of fallthrough + bool get_fallthrough() const { return fallthrough_; } + + /// Get the group of this subcommand + const std::string &get_group() const { return group_; } + + /// Get footer. + std::string get_footer() const { return footer_; } + + /// Get the required min subcommand value + size_t get_require_subcommand_min() const { return require_subcommand_min_; } + + /// Get the required max subcommand value + size_t get_require_subcommand_max() const { return require_subcommand_max_; } + + /// Get the prefix command status + bool get_prefix_command() const { return prefix_command_; } + + /// Get the status of allow extras + bool get_allow_extras() const { return allow_extras_; } + + /// Get the status of allow extras + bool get_allow_config_extras() const { return allow_config_extras_; } + + /// Get a pointer to the help flag. + Option *get_help_ptr() { return help_ptr_; } + + /// Get a pointer to the help flag. (const) + const Option *get_help_ptr() const { return help_ptr_; } + + /// Get a pointer to the help all flag. (const) + const Option *get_help_all_ptr() const { return help_all_ptr_; } + + /// Get a pointer to the config option. + Option *get_config_ptr() { return config_ptr_; } + + /// Get a pointer to the config option. (const) + const Option *get_config_ptr() const { return config_ptr_; } + + /// Get the parent of this subcommand (or nullptr if master app) + App *get_parent() { return parent_; } + + /// Get the parent of this subcommand (or nullptr if master app) (const version) + const App *get_parent() const { return parent_; } + + /// Get the name of the current app + std::string get_name() const { return name_; } + + /// Check the name, case insensitive if set + bool check_name(std::string name_to_check) const { + std::string local_name = name_; + if(ignore_case_) { + local_name = detail::to_lower(name_); + name_to_check = detail::to_lower(name_to_check); + } + + return local_name == name_to_check; + } + + /// Get the groups available directly from this option (in order) + std::vector<std::string> get_groups() const { + std::vector<std::string> groups; + + for(const Option_p &opt : options_) { + // Add group if it is not already in there + if(std::find(groups.begin(), groups.end(), opt->get_group()) == groups.end()) { + groups.push_back(opt->get_group()); + } + } + + return groups; + } + + /// This gets a vector of pointers with the original parse order + const std::vector<Option *> &parse_order() const { return parse_order_; } + + /// This returns the missing options from the current subcommand + std::vector<std::string> remaining(bool recurse = false) const { + std::vector<std::string> miss_list; + for(const std::pair<detail::Classifer, std::string> &miss : missing_) { + miss_list.push_back(std::get<1>(miss)); + } + + // Recurse into subcommands + if(recurse) { + for(const App *sub : parsed_subcommands_) { + std::vector<std::string> output = sub->remaining(recurse); + std::copy(std::begin(output), std::end(output), std::back_inserter(miss_list)); + } + } + return miss_list; + } + + /// This returns the number of remaining options, minus the -- seperator + size_t remaining_size(bool recurse = false) const { + auto count = static_cast<size_t>(std::count_if( + std::begin(missing_), std::end(missing_), [](const std::pair<detail::Classifer, std::string> &val) { + return val.first != detail::Classifer::POSITIONAL_MARK; + })); + if(recurse) { + for(const App_p &sub : subcommands_) { + count += sub->remaining_size(recurse); + } + } + return count; + } + + ///@} + + protected: + /// Check the options to make sure there are no conflicts. + /// + /// Currently checks to see if multiple positionals exist with -1 args + void _validate() const { + auto count = std::count_if(std::begin(options_), std::end(options_), [](const Option_p &opt) { + return opt->get_items_expected() < 0 && opt->get_positional(); + }); + if(count > 1) + throw InvalidError(name_); + for(const App_p &app : subcommands_) + app->_validate(); + } + + /// Internal function to run (App) callback, top down + void run_callback() { + pre_callback(); + if(callback_) + callback_(); + for(App *subc : get_subcommands()) { + subc->run_callback(); + } + } + + /// Check to see if a subcommand is valid. Give up immediately if subcommand max has been reached. + bool _valid_subcommand(const std::string ¤t) const { + // Don't match if max has been reached - but still check parents + if(require_subcommand_max_ != 0 && parsed_subcommands_.size() >= require_subcommand_max_) { + return parent_ != nullptr && parent_->_valid_subcommand(current); + } + + for(const App_p &com : subcommands_) + if(com->check_name(current) && !*com) + return true; + + // Check parent if exists, else return false + return parent_ != nullptr && parent_->_valid_subcommand(current); + } + + /// Selects a Classifier enum based on the type of the current argument + detail::Classifer _recognize(const std::string ¤t) const { + std::string dummy1, dummy2; + + if(current == "--") + return detail::Classifer::POSITIONAL_MARK; + if(_valid_subcommand(current)) + return detail::Classifer::SUBCOMMAND; + if(detail::split_long(current, dummy1, dummy2)) + return detail::Classifer::LONG; + if(detail::split_short(current, dummy1, dummy2)) + return detail::Classifer::SHORT; + return detail::Classifer::NONE; + } + + /// Internal parse function + void _parse(std::vector<std::string> &args) { + parsed_ = true; + bool positional_only = false; + + while(!args.empty()) { + _parse_single(args, positional_only); + } + + for(const Option_p &opt : options_) + if(opt->get_short_circuit() && opt->count() > 0) + opt->run_callback(); + + // Process an INI file + if(config_ptr_ != nullptr) { + if(*config_ptr_) { + config_ptr_->run_callback(); + config_required_ = true; + } + if(!config_name_.empty()) { + try { + std::vector<ConfigItem> values = config_formatter_->from_file(config_name_); + _parse_config(values); + } catch(const FileError &) { + if(config_required_) + throw; + } + } + } + + // Get envname options if not yet passed + for(const Option_p &opt : options_) { + if(opt->count() == 0 && !opt->envname_.empty()) { + char *buffer = nullptr; + std::string ename_string; + +#ifdef _MSC_VER + // Windows version + size_t sz = 0; + if(_dupenv_s(&buffer, &sz, opt->envname_.c_str()) == 0 && buffer != nullptr) { + ename_string = std::string(buffer); + free(buffer); + } +#else + // This also works on Windows, but gives a warning + buffer = std::getenv(opt->envname_.c_str()); + if(buffer != nullptr) + ename_string = std::string(buffer); +#endif + + if(!ename_string.empty()) { + opt->add_result(ename_string); + } + } + } + + // Process callbacks + for(const Option_p &opt : options_) { + if(opt->count() > 0 && !opt->get_callback_run()) { + opt->run_callback(); + } + } + + // Verify required options + for(const Option_p &opt : options_) { + // Required or partially filled + if(opt->get_required() || opt->count() != 0) { + // Make sure enough -N arguments parsed (+N is already handled in parsing function) + if(opt->get_items_expected() < 0 && opt->count() < static_cast<size_t>(-opt->get_items_expected())) + throw ArgumentMismatch::AtLeast(opt->get_name(), -opt->get_items_expected()); + + // Required but empty + if(opt->get_required() && opt->count() == 0) + throw RequiredError(opt->get_name()); + } + // Requires + for(const Option *opt_req : opt->requires_) + if(opt->count() > 0 && opt_req->count() == 0) + throw RequiresError(opt->get_name(), opt_req->get_name()); + // Excludes + for(const Option *opt_ex : opt->excludes_) + if(opt->count() > 0 && opt_ex->count() != 0) + throw ExcludesError(opt->get_name(), opt_ex->get_name()); + } + + auto selected_subcommands = get_subcommands(); + if(require_subcommand_min_ > selected_subcommands.size()) + throw RequiredError::Subcommand(require_subcommand_min_); + + // Convert missing (pairs) to extras (string only) + if(!(allow_extras_ || prefix_command_)) { + size_t num_left_over = remaining_size(); + if(num_left_over > 0) { + args = remaining(false); + throw ExtrasError(args); + } + } + + if(parent_ == nullptr) { + args = remaining(false); + } + } + + /// Parse one config param, return false if not found in any subcommand, remove if it is + /// + /// If this has more than one dot.separated.name, go into the subcommand matching it + /// Returns true if it managed to find the option, if false you'll need to remove the arg manually. + void _parse_config(std::vector<ConfigItem> &args) { + for(ConfigItem item : args) { + if(!_parse_single_config(item) && !allow_config_extras_) + throw ConfigError::Extras(item.fullname()); + } + } + + /// Fill in a single config option + bool _parse_single_config(const ConfigItem &item, size_t level = 0) { + if(level < item.parents.size()) { + App *subcom; + try { + std::cout << item.parents.at(level) << std::endl; + subcom = get_subcommand(item.parents.at(level)); + } catch(const OptionNotFound &) { + return false; + } + return subcom->_parse_single_config(item, level + 1); + } + + Option *op; + try { + op = get_option("--" + item.name); + } catch(const OptionNotFound &) { + // If the option was not present + if(get_allow_config_extras()) + // Should we worry about classifying the extras properly? + missing_.emplace_back(detail::Classifer::NONE, item.fullname()); + return false; + } + + if(!op->get_configurable()) + throw ConfigError::NotConfigurable(item.fullname()); + + if(op->empty()) { + // Flag parsing + if(op->get_type_size() == 0) { + op->set_results(config_formatter_->to_flag(item)); + } else { + op->set_results(item.inputs); + op->run_callback(); + } + } + + return true; + } + + /// Parse "one" argument (some may eat more than one), delegate to parent if fails, add to missing if missing from + /// master + void _parse_single(std::vector<std::string> &args, bool &positional_only) { + + detail::Classifer classifer = positional_only ? detail::Classifer::NONE : _recognize(args.back()); + switch(classifer) { + case detail::Classifer::POSITIONAL_MARK: + missing_.emplace_back(classifer, args.back()); + args.pop_back(); + positional_only = true; + break; + case detail::Classifer::SUBCOMMAND: + _parse_subcommand(args); + break; + case detail::Classifer::LONG: + // If already parsed a subcommand, don't accept options_ + _parse_arg(args, true); + break; + case detail::Classifer::SHORT: + // If already parsed a subcommand, don't accept options_ + _parse_arg(args, false); + break; + case detail::Classifer::NONE: + // Probably a positional or something for a parent (sub)command + _parse_positional(args); + } + } + + /// Count the required remaining positional arguments + size_t _count_remaining_positionals(bool required = false) const { + size_t retval = 0; + for(const Option_p &opt : options_) + if(opt->get_positional() && (!required || opt->get_required()) && opt->get_items_expected() > 0 && + static_cast<int>(opt->count()) < opt->get_items_expected()) + retval = static_cast<size_t>(opt->get_items_expected()) - opt->count(); + + return retval; + } + + /// Parse a positional, go up the tree to check + void _parse_positional(std::vector<std::string> &args) { + + std::string positional = args.back(); + for(const Option_p &opt : options_) { + // Eat options, one by one, until done + if(opt->get_positional() && + (static_cast<int>(opt->count()) < opt->get_items_expected() || opt->get_items_expected() < 0)) { + + opt->add_result(positional); + parse_order_.push_back(opt.get()); + args.pop_back(); + return; + } + } + + if(parent_ != nullptr && fallthrough_) + return parent_->_parse_positional(args); + else { + args.pop_back(); + missing_.emplace_back(detail::Classifer::NONE, positional); + + if(prefix_command_) { + while(!args.empty()) { + missing_.emplace_back(detail::Classifer::NONE, args.back()); + args.pop_back(); + } + } + } + } + + /// Parse a subcommand, modify args and continue + /// + /// Unlike the others, this one will always allow fallthrough + void _parse_subcommand(std::vector<std::string> &args) { + if(_count_remaining_positionals(/* required */ true) > 0) + return _parse_positional(args); + for(const App_p &com : subcommands_) { + if(com->check_name(args.back())) { + args.pop_back(); + if(std::find(std::begin(parsed_subcommands_), std::end(parsed_subcommands_), com.get()) == + std::end(parsed_subcommands_)) + parsed_subcommands_.push_back(com.get()); + com->_parse(args); + return; + } + } + if(parent_ != nullptr) + return parent_->_parse_subcommand(args); + else + throw HorribleError("Subcommand " + args.back() + " missing"); + } + + /// Parse a short (false) or long (true) argument, must be at the top of the list + void _parse_arg(std::vector<std::string> &args, bool second_dash) { + + detail::Classifer current_type = second_dash ? detail::Classifer::LONG : detail::Classifer::SHORT; + + std::string current = args.back(); + + std::string name; + std::string value; + std::string rest; + + if(second_dash) { + if(!detail::split_long(current, name, value)) + throw HorribleError("Long parsed but missing (you should not see this):" + args.back()); + } else { + if(!detail::split_short(current, name, rest)) + throw HorribleError("Short parsed but missing! You should not see this"); + } + + auto op_ptr = std::find_if(std::begin(options_), std::end(options_), [name, second_dash](const Option_p &opt) { + return second_dash ? opt->check_lname(name) : opt->check_sname(name); + }); + + // Option not found + if(op_ptr == std::end(options_)) { + // If a subcommand, try the master command + if(parent_ != nullptr && fallthrough_) + return parent_->_parse_arg(args, second_dash); + // Otherwise, add to missing + else { + args.pop_back(); + missing_.emplace_back(current_type, current); + return; + } + } + + args.pop_back(); + + // Get a reference to the pointer to make syntax bearable + Option_p &op = *op_ptr; + + int num = op->get_items_expected(); + + // Make sure we always eat the minimum for unlimited vectors + int collected = 0; + + // --this=value + if(!value.empty()) { + // If exact number expected + if(num > 0) + num--; + op->add_result(value); + parse_order_.push_back(op.get()); + collected += 1; + } else if(num == 0) { + op->add_result(""); + parse_order_.push_back(op.get()); + // -Trest + } else if(!rest.empty()) { + if(num > 0) + num--; + op->add_result(rest); + parse_order_.push_back(op.get()); + rest = ""; + collected += 1; + } + + // Unlimited vector parser + if(num < 0) { + while(!args.empty() && _recognize(args.back()) == detail::Classifer::NONE) { + if(collected >= -num) { + // We could break here for allow extras, but we don't + + // If any positionals remain, don't keep eating + if(_count_remaining_positionals() > 0) + break; + } + op->add_result(args.back()); + parse_order_.push_back(op.get()); + args.pop_back(); + collected++; + } + + // Allow -- to end an unlimited list and "eat" it + if(!args.empty() && _recognize(args.back()) == detail::Classifer::POSITIONAL_MARK) + args.pop_back(); + + } else { + while(num > 0 && !args.empty()) { + num--; + std::string current_ = args.back(); + args.pop_back(); + op->add_result(current_); + parse_order_.push_back(op.get()); + } + + if(num > 0) { + throw ArgumentMismatch::TypedAtLeast(op->get_name(), num, op->get_type_name()); + } + } + + if(!rest.empty()) { + rest = "-" + rest; + args.push_back(rest); + } + } +}; + +namespace FailureMessage { + +/// Printout a clean, simple message on error (the default in CLI11 1.5+) +inline std::string simple(const App *app, const Error &e) { + std::string header = std::string(e.what()) + "\n"; + if(app->get_help_ptr() != nullptr) + header += "Run with " + app->get_help_ptr()->get_name() + " for more information.\n"; + return header; +} + +/// Printout the full help string on error (if this fn is set, the old default for CLI11) +inline std::string help(const App *app, const Error &e) { + std::string header = std::string("ERROR: ") + e.get_name() + ": " + e.what() + "\n"; + header += app->help(); + return header; +} + +} // namespace FailureMessage + +namespace detail { +/// This class is simply to allow tests access to App's protected functions +struct AppFriend { + + /// Wrap _parse_short, perfectly forward arguments and return + template <typename... Args> + static auto parse_arg(App *app, Args &&... args) -> + typename std::result_of<decltype (&App::_parse_arg)(App, Args...)>::type { + return app->_parse_arg(std::forward<Args>(args)...); + } + + /// Wrap _parse_subcommand, perfectly forward arguments and return + template <typename... Args> + static auto parse_subcommand(App *app, Args &&... args) -> + typename std::result_of<decltype (&App::_parse_subcommand)(App, Args...)>::type { + return app->_parse_subcommand(std::forward<Args>(args)...); + } +}; +} // namespace detail + +} // namespace CLI + +// From CLI/Config.hpp: + +namespace CLI { + +inline std::string +ConfigINI::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const { + std::stringstream out; + for(const Option *opt : app->get_options({})) { + + // Only process option with a long-name and configurable + if(!opt->get_lnames().empty() && opt->get_configurable()) { + std::string name = prefix + opt->get_lnames()[0]; + std::string value; + + // Non-flags + if(opt->get_type_size() != 0) { + + // If the option was found on command line + if(opt->count() > 0) + value = detail::ini_join(opt->results()); + + // If the option has a default and is requested by optional argument + else if(default_also && !opt->get_defaultval().empty()) + value = opt->get_defaultval(); + // Flag, one passed + } else if(opt->count() == 1) { + value = "true"; + + // Flag, multiple passed + } else if(opt->count() > 1) { + value = std::to_string(opt->count()); + + // Flag, not present + } else if(opt->count() == 0 && default_also) { + value = "false"; + } + + if(!value.empty()) { + if(write_description && opt->has_description()) { + if(static_cast<int>(out.tellp()) != 0) { + out << std::endl; + } + out << "; " << detail::fix_newlines("; ", opt->get_description()) << std::endl; + } + out << name << "=" << value << std::endl; + } + } + } + + for(const App *subcom : app->get_subcommands({})) + out << to_config(subcom, default_also, write_description, prefix + subcom->get_name() + "."); + + return out.str(); +} + +} // namespace CLI + +// From CLI/Formatter.hpp: + +namespace CLI { + +inline std::string +Formatter::make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const { + std::stringstream out; + + out << "\n" << group << ":\n"; + for(const Option *opt : opts) { + out << make_option(opt, is_positional); + } + + return out.str(); +} + +inline std::string Formatter::make_positionals(const App *app) const { + std::vector<const Option *> opts = + app->get_options([](const Option *opt) { return !opt->get_group().empty() && opt->get_positional(); }); + + if(opts.empty()) + return std::string(); + else + return make_group(get_label("Positionals"), true, opts); +} + +inline std::string Formatter::make_groups(const App *app, AppFormatMode mode) const { + std::stringstream out; + std::vector<std::string> groups = app->get_groups(); + + // Options + for(const std::string &group : groups) { + std::vector<const Option *> opts = app->get_options([app, mode, &group](const Option *opt) { + return opt->get_group() == group // Must be in the right group + && opt->nonpositional() // Must not be a positional + && (mode != AppFormatMode::Sub // If mode is Sub, then + || (app->get_help_ptr() != opt // Ignore help pointer + && app->get_help_all_ptr() != opt)); // Ignore help all pointer + }); + if(!group.empty() && !opts.empty()) { + out << make_group(group, false, opts); + + if(group != groups.back()) + out << "\n"; + } + } + + return out.str(); +} + +inline std::string Formatter::make_description(const App *app) const { + std::string desc = app->get_description(); + + if(!desc.empty()) + return desc + "\n"; + else + return ""; +} + +inline std::string Formatter::make_usage(const App *app, std::string name) const { + std::stringstream out; + + out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name; + + std::vector<std::string> groups = app->get_groups(); + + // Print an Options badge if any options exist + std::vector<const Option *> non_pos_options = + app->get_options([](const Option *opt) { return opt->nonpositional(); }); + if(!non_pos_options.empty()) + out << " [" << get_label("OPTIONS") << "]"; + + // Positionals need to be listed here + std::vector<const Option *> positionals = app->get_options([](const Option *opt) { return opt->get_positional(); }); + + // Print out positionals if any are left + if(!positionals.empty()) { + // Convert to help names + std::vector<std::string> positional_names(positionals.size()); + std::transform(positionals.begin(), positionals.end(), positional_names.begin(), [this](const Option *opt) { + return make_option_usage(opt); + }); + + out << " " << detail::join(positional_names, " "); + } + + // Add a marker if subcommands are expected or optional + if(!app->get_subcommands({}).empty()) { + out << " " << (app->get_require_subcommand_min() == 0 ? "[" : "") + << get_label(app->get_require_subcommand_max() < 2 || app->get_require_subcommand_min() > 1 ? "SUBCOMMAND" + : "SUBCOMMANDS") + << (app->get_require_subcommand_min() == 0 ? "]" : ""); + } + + out << std::endl; + + return out.str(); +} + +inline std::string Formatter::make_footer(const App *app) const { + std::string footer = app->get_footer(); + if(!footer.empty()) + return footer + "\n"; + else + return ""; +} + +inline std::string Formatter::make_help(const App *app, std::string name, AppFormatMode mode) const { + + // This immediatly forwards to the make_expanded method. This is done this way so that subcommands can + // have overridden formatters + if(mode == AppFormatMode::Sub) + return make_expanded(app); + + std::stringstream out; + + out << make_description(app); + out << make_usage(app, name); + out << make_positionals(app); + out << make_groups(app, mode); + out << make_subcommands(app, mode); + out << make_footer(app); + + return out.str(); +} + +inline std::string Formatter::make_subcommands(const App *app, AppFormatMode mode) const { + std::stringstream out; + + std::vector<const App *> subcommands = app->get_subcommands({}); + + // Make a list in definition order of the groups seen + std::vector<std::string> subcmd_groups_seen; + for(const App *com : subcommands) { + std::string group_key = com->get_group(); + if(!group_key.empty() && + std::find_if(subcmd_groups_seen.begin(), subcmd_groups_seen.end(), [&group_key](std::string a) { + return detail::to_lower(a) == detail::to_lower(group_key); + }) == subcmd_groups_seen.end()) + subcmd_groups_seen.push_back(group_key); + } + + // For each group, filter out and print subcommands + for(const std::string &group : subcmd_groups_seen) { + out << "\n" << group << ":\n"; + std::vector<const App *> subcommands_group = app->get_subcommands( + [&group](const App *app) { return detail::to_lower(app->get_group()) == detail::to_lower(group); }); + for(const App *new_com : subcommands_group) { + if(mode != AppFormatMode::All) { + out << make_subcommand(new_com); + } else { + out << new_com->help(new_com->get_name(), AppFormatMode::Sub); + if(new_com != subcommands_group.back()) + out << "\n"; + } + } + } + + return out.str(); +} + +inline std::string Formatter::make_subcommand(const App *sub) const { + std::stringstream out; + detail::format_help(out, sub->get_name(), sub->get_description(), column_width_); + return out.str(); +} + +inline std::string Formatter::make_expanded(const App *sub) const { + std::stringstream out; + if(sub->get_description().empty()) + out << sub->get_name(); + else + out << sub->get_name() << " -> " << sub->get_description(); + out << make_groups(sub, AppFormatMode::Sub); + return out.str(); +} + +inline std::string Formatter::make_option_name(const Option *opt, bool is_positional) const { + if(is_positional) + return opt->get_name(true, false); + else + return opt->get_name(false, true); +} + +inline std::string Formatter::make_option_opts(const Option *opt) const { + std::stringstream out; + + if(opt->get_type_size() != 0) { + if(!opt->get_type_name().empty()) + out << " " << get_label(opt->get_type_name()); + if(!opt->get_defaultval().empty()) + out << "=" << opt->get_defaultval(); + if(opt->get_expected() > 1) + out << " x " << opt->get_expected(); + if(opt->get_expected() == -1) + out << " ..."; + if(opt->get_required()) + out << " " << get_label("REQUIRED"); + } + if(!opt->get_envname().empty()) + out << " (" << get_label("Env") << ":" << opt->get_envname() << ")"; + if(!opt->get_needs().empty()) { + out << " " << get_label("Needs") << ":"; + for(const Option *op : opt->get_needs()) + out << " " << op->get_name(); + } + if(!opt->get_excludes().empty()) { + out << " " << get_label("Excludes") << ":"; + for(const Option *op : opt->get_excludes()) + out << " " << op->get_name(); + } + return out.str(); +} + +inline std::string Formatter::make_option_desc(const Option *opt) const { return opt->get_description(); } + +inline std::string Formatter::make_option_usage(const Option *opt) const { + // Note that these are positionals usages + std::stringstream out; + out << make_option_name(opt, true); + + if(opt->get_expected() > 1) + out << "(" << std::to_string(opt->get_expected()) << "x)"; + else if(opt->get_expected() < 0) + out << "..."; + + return opt->get_required() ? out.str() : "[" + out.str() + "]"; +} + +} // namespace CLI + diff --git a/homework/81568/BF_Interpreter/Makefile b/homework/81568/BF_Interpreter/Makefile new file mode 100644 index 0000000..b430742 --- /dev/null +++ b/homework/81568/BF_Interpreter/Makefile @@ -0,0 +1,2 @@ +bfi: main.cpp bf_interpreter.cpp + g++ -o bfi main.cpp bf_interpreter.cpp diff --git a/homework/81568/BF_Interpreter/README.md b/homework/81568/BF_Interpreter/README.md new file mode 100644 index 0000000..b8cde8b --- /dev/null +++ b/homework/81568/BF_Interpreter/README.md @@ -0,0 +1,40 @@ +# Brainfuck-Interpreter +Simple Brainfuck interpreter written for IPL course homework from FMI, SU, 2018. + +Uses default memory size of 30000 cells but if you define `#MEMSIZE` with your compiler to something else you can get different size. + +When either > or < instructions overflow the cell pointer, the latter just wraps +around. + +# Installation +BFI uses _make_ for make for simplicity. + +To install simply run: +``` +make +``` + +# Usage +``` +Usage: bfi [OPTIONS] + +Options: + -h,--help Print this help message and exit + -f,--file FILE Excludes: --code + Brainfuck source file + -c,--code TEXT Excludes: --file + Brainfuck source code + -o,--output TEXT File to write the Brainfuck output to. Optional. + -i,--input FILE File to take the Brainfuck input from. Optional. +``` + +# Future +BFI is planned to be made child-friendly by introducing new instructions like: + +* ! - make the current cell value equal to 0. +* ^ - go to cell No. (current cell No. + current cell value). +* & - go to cell No. (current cell No. - current cell value). +* \# - if statement that depends on next value (for comparison) and previous (to use like ^) +* $ - same as above, but go back, instead of forward. + +To be completely child-friendly change the name of the language to Brainmeddler. diff --git a/homework/81568/BF_Interpreter/bf_interpreter.cpp b/homework/81568/BF_Interpreter/bf_interpreter.cpp new file mode 100644 index 0000000..28d4c18 --- /dev/null +++ b/homework/81568/BF_Interpreter/bf_interpreter.cpp @@ -0,0 +1,114 @@ +#include "bf_interpreter.h" + +#include <cmath> +#include <cstring> +#include <iostream> +#include <stack> + +#ifndef MEMSIZE +#define MEMSIZE 30000 +#endif + +BFInterpreter::BFInterpreter(const char *program, std::istream &input, std::ostream &output) : + memory_blocks(nullptr), p(nullptr), + program(nullptr), input(input), output(output), + memory_size(MEMSIZE) { + + this->program = new char[strlen(program) + 1]; + strncpy(this->program, program, strlen(program) + 1); + pp = this->program; + + memory_blocks = new uchar[memory_size]; + for (int i = 0; i < memory_size; ++i) { + memory_blocks[i] = 0; + } + p = memory_blocks; +} + +BFInterpreter::~BFInterpreter() { + delete [] memory_blocks; + delete [] program; +} + +void BFInterpreter::addMemory() { + uchar *new_memory = new uchar[memory_size + ((size_t) log2(memory_size))]; + for (int i = 0; i < memory_size; ++i) { + new_memory[i] = memory_blocks[i]; + } + delete [] memory_blocks; + memory_blocks = new_memory; + memory_size += log2(memory_size); +} + +void BFInterpreter::compile() { + while (*pp) { + switch (*pp) { + case '[': + ++pp; + loop(); + break; + case ']': // that will only happen if code is wrong + printf("Syntax error!\n"); + return; + default: + proccessNonLoopCmd(*pp); + } + ++pp; + } + output.put('\n'); +} + +void BFInterpreter::loop() { + std::stack<char*> loops; + loops.push(pp); + + char *body = loops.top(); + while (!loops.empty()) { + while (*body != ']' && *body != '[') { + proccessNonLoopCmd(*body); + ++body; + } + if (*body == ']') { + if (!*p) { /* End of a loop */ + loops.pop(); + ++body; + } else { /* Loop has not ended. Return to the beginning of the current loop */ + body = loops.top(); + } + } else { /* Start of a new loop */ + loops.push(body + 1); + ++body; + } + } + pp = --body; + return; +} + +void BFInterpreter::proccessNonLoopCmd(char c) { + switch (c) { + case '+': + ++*p; + break; + case '-': + --*p; + break; + case '.': + output.put(*p); + break; + case ',': + *p = input.get(); + break; + case '<': + if (p == memory_blocks) + p = memory_blocks + (memory_size - 1); + else + --p; + break; + case '>': + if (p == memory_blocks + (memory_size - 1)) + p = memory_blocks; + else + ++p; + break; + } +} diff --git a/homework/81568/BF_Interpreter/bf_interpreter.h b/homework/81568/BF_Interpreter/bf_interpreter.h new file mode 100644 index 0000000..9e355ff --- /dev/null +++ b/homework/81568/BF_Interpreter/bf_interpreter.h @@ -0,0 +1,33 @@ +#ifndef BRAINFUCK_INTERPRETER_H +#define BRAINFUCK_INTERPRETER_H + +#include <cstddef> +#include <iostream> + + +class BFInterpreter { +public: + using uchar = unsigned char; + + BFInterpreter(const char *program, std::istream &input = std::cin, std::ostream &output = std::cout); + ~BFInterpreter(); + + void compile(); + +private: + void addMemory(); + void loop(); + void proccessNonLoopCmd(char c); + +private: + std::istream &input; + std::ostream &output; + size_t memory_size; + + uchar *memory_blocks; + uchar *p; + char *program; + char *pp; +}; + +#endif /* BRAINFUCK_INTERPRETER_H */ diff --git a/homework/81568/BF_Interpreter/main.cpp b/homework/81568/BF_Interpreter/main.cpp new file mode 100644 index 0000000..ccde7a5 --- /dev/null +++ b/homework/81568/BF_Interpreter/main.cpp @@ -0,0 +1,68 @@ +#include "bf_interpreter.h" +#include "CLI11.hpp" + +#include <fstream> +#include <iostream> +#include <iterator> +#include <sstream> +#include <string> + +#define USAGE_MSG \ + "Usage %s [-f <code file> | -c <code>] (-i <input file>) (-o <output file>)\n" \ + "If no input/output file is provided standart input/output is used instead.\n" + + +// TODO: implement proper CLI parsing +// TODO: https://github.com/CLIUtils/CLI11 +int main(int argc, char **argv) { + CLI::App app {"Simple Brainfuck interpreter written for " + "IPL course homework from FMI, SU, 2018." }; + + std::string srcFile; + CLI::Option *srcFileOpt = app.add_option("-f,--file", srcFile, "Brainfuck source file"); + + srcFileOpt->check(CLI::ExistingFile); + + std::string srcCode; + CLI::Option *srcCodeOpt = app.add_option("-c,--code", srcCode, "Brainfuck source code"); + + srcFileOpt->excludes(srcCodeOpt); + srcCodeOpt->excludes(srcFileOpt); + + std::string output; + CLI::Option *outputOpt = app.add_option("-o,--output", output, "File to write the Brainfuck output to. Optional."); + + std::string input; + CLI::Option *inputOpt = app.add_option("-i,--input", input, "File to take the Brainfuck input from. Optional."); + + inputOpt->check(CLI::ExistingFile); + + CLI11_PARSE(app, argc, argv); + + std::string source; + if (*srcFileOpt) { + std::ifstream file {srcFile}; + if (file.is_open()) { + source.assign((std::istreambuf_iterator<char>(file)), + std::istreambuf_iterator<char>()); + } + } else { + source = srcCode; + } + + std::ifstream inputFile; + if (*inputOpt) + inputFile.open(input); + + std::ofstream outputFile; + if (*outputOpt) + outputFile.open(output); + + std::istream &is = *inputOpt ? inputFile : std::cin; + std::ostream &os = *outputOpt ? outputFile : std::cout; + + BFInterpreter bfi{source.c_str(), is, os}; + bfi.compile(); + + return 0; +} |
