From 635a392a8d180f03a99ab2e22c843f7dc9a81c67 Mon Sep 17 00:00:00 2001 From: "tianlei.richard" Date: Wed, 27 Mar 2024 18:31:10 +0800 Subject: [PATCH] incompleted phone shading impl. --- 3rdparty/cxxopts/CMakeLists.txt | 7 + 3rdparty/cxxopts/include/cxxopts.hpp | 2117 ++++++++++++++++++++++++++ CMakeLists.txt | 1 + src/CMakeLists.txt | 2 +- src/common.h | 12 +- src/main.cc | 22 +- src/mesh.cc | 43 +- src/mesh.h | 19 +- src/minus_renderer.cc | 66 +- src/minus_renderer.h | 4 +- src/phone_material.cc | 80 +- src/phone_material.h | 25 +- src/texture.cc | 41 + src/texture.h | 26 + 14 files changed, 2362 insertions(+), 103 deletions(-) create mode 100644 3rdparty/cxxopts/CMakeLists.txt create mode 100644 3rdparty/cxxopts/include/cxxopts.hpp create mode 100644 src/texture.cc create mode 100644 src/texture.h diff --git a/3rdparty/cxxopts/CMakeLists.txt b/3rdparty/cxxopts/CMakeLists.txt new file mode 100644 index 0000000..56c9635 --- /dev/null +++ b/3rdparty/cxxopts/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.14) +project(cxxopts) + +set(CMAKE_CXX_STANDARD 11) + +add_library(${PROJECT_NAME} INTERFACE) +target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include/) diff --git a/3rdparty/cxxopts/include/cxxopts.hpp b/3rdparty/cxxopts/include/cxxopts.hpp new file mode 100644 index 0000000..0ea6221 --- /dev/null +++ b/3rdparty/cxxopts/include/cxxopts.hpp @@ -0,0 +1,2117 @@ +/* + +Copyright (c) 2014-2022 Jarryd Beck + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// vim: ts=2:sw=2:expandtab + +#ifndef CXXOPTS_HPP_INCLUDED +#define CXXOPTS_HPP_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CXXOPTS_NO_EXCEPTIONS +#include +#endif + +#if defined(__GNUC__) && !defined(__clang__) +#if (__GNUC__ * 10 + __GNUC_MINOR__) < 49 +#define CXXOPTS_NO_REGEX true +#endif +#endif +#if defined(_MSC_VER) && !defined(__clang__) +#define CXXOPTS_LINKONCE_CONST __declspec(selectany) extern +#define CXXOPTS_LINKONCE __declspec(selectany) extern +#else +#define CXXOPTS_LINKONCE_CONST +#define CXXOPTS_LINKONCE +#endif + +#ifndef CXXOPTS_NO_REGEX +#include +#endif // CXXOPTS_NO_REGEX + +// Nonstandard before C++17, which is coincidentally what we also need for +// +#ifdef __has_include +#if __has_include() +#include +#ifdef __cpp_lib_optional +#define CXXOPTS_HAS_OPTIONAL +#endif +#endif +#endif + +#define CXXOPTS_FALLTHROUGH +#ifdef __has_cpp_attribute +#if __has_cpp_attribute(fallthrough) +#undef CXXOPTS_FALLTHROUGH +#define CXXOPTS_FALLTHROUGH [[fallthrough]] +#endif +#endif + +#if __cplusplus >= 201603L +#define CXXOPTS_NODISCARD [[nodiscard]] +#else +#define CXXOPTS_NODISCARD +#endif + +#ifndef CXXOPTS_VECTOR_DELIMITER +#define CXXOPTS_VECTOR_DELIMITER ',' +#endif + +#define CXXOPTS__VERSION_MAJOR 3 +#define CXXOPTS__VERSION_MINOR 2 +#define CXXOPTS__VERSION_PATCH 1 + +#if (__GNUC__ < 10 || (__GNUC__ == 10 && __GNUC_MINOR__ < 1)) && __GNUC__ >= 6 +#define CXXOPTS_NULL_DEREF_IGNORE +#endif + +#if defined(__GNUC__) +#define DO_PRAGMA(x) _Pragma(#x) +#define CXXOPTS_DIAGNOSTIC_PUSH DO_PRAGMA(GCC diagnostic push) +#define CXXOPTS_DIAGNOSTIC_POP DO_PRAGMA(GCC diagnostic pop) +#define CXXOPTS_IGNORE_WARNING(x) DO_PRAGMA(GCC diagnostic ignored x) +#else +// define other compilers here if needed +#define CXXOPTS_DIAGNOSTIC_PUSH +#define CXXOPTS_DIAGNOSTIC_POP +#define CXXOPTS_IGNORE_WARNING(x) +#endif + +#ifdef CXXOPTS_NO_RTTI +#define CXXOPTS_RTTI_CAST static_cast +#else +#define CXXOPTS_RTTI_CAST dynamic_cast +#endif + +namespace cxxopts { +static constexpr struct { + uint8_t major, minor, patch; +} version = {CXXOPTS__VERSION_MAJOR, CXXOPTS__VERSION_MINOR, + CXXOPTS__VERSION_PATCH}; +} // namespace cxxopts + +// when we ask cxxopts to use Unicode, help strings are processed using ICU, +// which results in the correct lengths being computed for strings when they +// are formatted for the help output +// it is necessary to make sure that can be found by the +// compiler, and that icu-uc is linked in to the binary. + +#ifdef CXXOPTS_USE_UNICODE +#include + +namespace cxxopts { + +using String = icu::UnicodeString; + +inline String toLocalString(std::string s) { + return icu::UnicodeString::fromUTF8(std::move(s)); +} + +// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we +// want to silence it: warning: base class 'class +// std::enable_shared_from_this' has accessible non-virtual +// destructor +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor") +// This will be ignored under other compilers like LLVM clang. +class UnicodeStringIterator { +public: + using iterator_category = std::forward_iterator_tag; + using value_type = int32_t; + using difference_type = std::ptrdiff_t; + using pointer = value_type *; + using reference = value_type &; + + UnicodeStringIterator(const icu::UnicodeString *string, int32_t pos) + : s(string), i(pos) {} + + value_type operator*() const { return s->char32At(i); } + + bool operator==(const UnicodeStringIterator &rhs) const { + return s == rhs.s && i == rhs.i; + } + + bool operator!=(const UnicodeStringIterator &rhs) const { + return !(*this == rhs); + } + + UnicodeStringIterator &operator++() { + ++i; + return *this; + } + + UnicodeStringIterator operator+(int32_t v) { + return UnicodeStringIterator(s, i + v); + } + +private: + const icu::UnicodeString *s; + int32_t i; +}; +CXXOPTS_DIAGNOSTIC_POP + +inline String &stringAppend(String &s, String a) { + return s.append(std::move(a)); +} + +inline String &stringAppend(String &s, std::size_t n, UChar32 c) { + for (std::size_t i = 0; i != n; ++i) { + s.append(c); + } + + return s; +} + +template +String &stringAppend(String &s, Iterator begin, Iterator end) { + while (begin != end) { + s.append(*begin); + ++begin; + } + + return s; +} + +inline size_t stringLength(const String &s) { + return static_cast(s.length()); +} + +inline std::string toUTF8String(const String &s) { + std::string result; + s.toUTF8String(result); + + return result; +} + +inline bool empty(const String &s) { return s.isEmpty(); } + +} // namespace cxxopts + +namespace std { + +inline cxxopts::UnicodeStringIterator begin(const icu::UnicodeString &s) { + return cxxopts::UnicodeStringIterator(&s, 0); +} + +inline cxxopts::UnicodeStringIterator end(const icu::UnicodeString &s) { + return cxxopts::UnicodeStringIterator(&s, s.length()); +} + +} // namespace std + +// ifdef CXXOPTS_USE_UNICODE +#else + +namespace cxxopts { + +using String = std::string; + +template T toLocalString(T &&t) { return std::forward(t); } + +inline std::size_t stringLength(const String &s) { return s.length(); } + +inline String &stringAppend(String &s, const String &a) { return s.append(a); } + +inline String &stringAppend(String &s, std::size_t n, char c) { + return s.append(n, c); +} + +template +String &stringAppend(String &s, Iterator begin, Iterator end) { + return s.append(begin, end); +} + +template std::string toUTF8String(T &&t) { + return std::forward(t); +} + +inline bool empty(const std::string &s) { return s.empty(); } + +} // namespace cxxopts + +// ifdef CXXOPTS_USE_UNICODE +#endif + +namespace cxxopts { + +namespace { +CXXOPTS_LINKONCE_CONST std::string LQUOTE("\'"); +CXXOPTS_LINKONCE_CONST std::string RQUOTE("\'"); +} // namespace + +// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we +// want to silence it: warning: base class 'class +// std::enable_shared_from_this' has accessible non-virtual +// destructor This will be ignored under other compilers like LLVM clang. +CXXOPTS_DIAGNOSTIC_PUSH +CXXOPTS_IGNORE_WARNING("-Wnon-virtual-dtor") + +// some older versions of GCC warn under this warning +CXXOPTS_IGNORE_WARNING("-Weffc++") +class Value : public std::enable_shared_from_this { +public: + virtual ~Value() = default; + + virtual std::shared_ptr clone() const = 0; + + virtual void add(const std::string &text) const = 0; + + virtual void parse(const std::string &text) const = 0; + + virtual void parse() const = 0; + + virtual bool has_default() const = 0; + + virtual bool is_container() const = 0; + + virtual bool has_implicit() const = 0; + + virtual std::string get_default_value() const = 0; + + virtual std::string get_implicit_value() const = 0; + + virtual std::shared_ptr default_value(const std::string &value) = 0; + + virtual std::shared_ptr implicit_value(const std::string &value) = 0; + + virtual std::shared_ptr no_implicit_value() = 0; + + virtual bool is_boolean() const = 0; +}; + +CXXOPTS_DIAGNOSTIC_POP + +namespace exceptions { + +class exception : public std::exception { +public: + explicit exception(std::string message) : m_message(std::move(message)) {} + + CXXOPTS_NODISCARD + const char *what() const noexcept override { return m_message.c_str(); } + +private: + std::string m_message; +}; + +class specification : public exception { +public: + explicit specification(const std::string &message) : exception(message) {} +}; + +class parsing : public exception { +public: + explicit parsing(const std::string &message) : exception(message) {} +}; + +class option_already_exists : public specification { +public: + explicit option_already_exists(const std::string &option) + : specification("Option " + LQUOTE + option + RQUOTE + + " already exists") {} +}; + +class invalid_option_format : public specification { +public: + explicit invalid_option_format(const std::string &format) + : specification("Invalid option format " + LQUOTE + format + RQUOTE) {} +}; + +class invalid_option_syntax : public parsing { +public: + explicit invalid_option_syntax(const std::string &text) + : parsing("Argument " + LQUOTE + text + RQUOTE + + " starts with a - but has incorrect syntax") {} +}; + +class no_such_option : public parsing { +public: + explicit no_such_option(const std::string &option) + : parsing("Option " + LQUOTE + option + RQUOTE + " does not exist") {} +}; + +class missing_argument : public parsing { +public: + explicit missing_argument(const std::string &option) + : parsing("Option " + LQUOTE + option + RQUOTE + + " is missing an argument") {} +}; + +class option_requires_argument : public parsing { +public: + explicit option_requires_argument(const std::string &option) + : parsing("Option " + LQUOTE + option + RQUOTE + + " requires an argument") {} +}; + +class gratuitous_argument_for_option : public parsing { +public: + gratuitous_argument_for_option(const std::string &option, + const std::string &arg) + : parsing("Option " + LQUOTE + option + RQUOTE + + " does not take an argument, but argument " + LQUOTE + arg + + RQUOTE + " given") {} +}; + +class requested_option_not_present : public parsing { +public: + explicit requested_option_not_present(const std::string &option) + : parsing("Option " + LQUOTE + option + RQUOTE + " not present") {} +}; + +class option_has_no_value : public exception { +public: + explicit option_has_no_value(const std::string &option) + : exception(!option.empty() + ? ("Option " + LQUOTE + option + RQUOTE + " has no value") + : "Option has no value") {} +}; + +class incorrect_argument_type : public parsing { +public: + explicit incorrect_argument_type(const std::string &arg) + : parsing("Argument " + LQUOTE + arg + RQUOTE + " failed to parse") {} +}; + +} // namespace exceptions + +template void throw_or_mimic(const std::string &text) { + static_assert(std::is_base_of::value, + "throw_or_mimic only works on std::exception and " + "deriving classes"); + +#ifndef CXXOPTS_NO_EXCEPTIONS + // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw + throw T{text}; +#else + // Otherwise manually instantiate the exception, print what() to stderr, + // and exit + T exception{text}; + std::cerr << exception.what() << std::endl; + std::exit(EXIT_FAILURE); +#endif +} + +using OptionNames = std::vector; + +namespace values { + +namespace parser_tool { + +struct IntegerDesc { + std::string negative = ""; + std::string base = ""; + std::string value = ""; +}; +struct ArguDesc { + std::string arg_name = ""; + bool grouping = false; + bool set_value = false; + std::string value = ""; +}; + +#ifdef CXXOPTS_NO_REGEX +inline IntegerDesc SplitInteger(const std::string &text) { + if (text.empty()) { + throw_or_mimic(text); + } + IntegerDesc desc; + const char *pdata = text.c_str(); + if (*pdata == '-') { + pdata += 1; + desc.negative = "-"; + } + if (strncmp(pdata, "0x", 2) == 0) { + pdata += 2; + desc.base = "0x"; + } + if (*pdata != '\0') { + desc.value = std::string(pdata); + } else { + throw_or_mimic(text); + } + return desc; +} + +inline bool IsTrueText(const std::string &text) { + const char *pdata = text.c_str(); + if (*pdata == 't' || *pdata == 'T') { + pdata += 1; + if (strncmp(pdata, "rue\0", 4) == 0) { + return true; + } + } else if (strncmp(pdata, "1\0", 2) == 0) { + return true; + } + return false; +} + +inline bool IsFalseText(const std::string &text) { + const char *pdata = text.c_str(); + if (*pdata == 'f' || *pdata == 'F') { + pdata += 1; + if (strncmp(pdata, "alse\0", 5) == 0) { + return true; + } + } else if (strncmp(pdata, "0\0", 2) == 0) { + return true; + } + return false; +} + +inline OptionNames split_option_names(const std::string &text) { + OptionNames split_names; + + std::string::size_type token_start_pos = 0; + auto length = text.length(); + + if (length == 0) { + throw_or_mimic(text); + } + + while (token_start_pos < length) { + const auto &npos = std::string::npos; + auto next_non_space_pos = text.find_first_not_of(' ', token_start_pos); + if (next_non_space_pos == npos) { + throw_or_mimic(text); + } + token_start_pos = next_non_space_pos; + auto next_delimiter_pos = text.find(',', token_start_pos); + if (next_delimiter_pos == token_start_pos) { + throw_or_mimic(text); + } + if (next_delimiter_pos == npos) { + next_delimiter_pos = length; + } + auto token_length = next_delimiter_pos - token_start_pos; + // validate the token itself matches the regex /([:alnum:][-_[:alnum:]]*/ + { + const char *option_name_valid_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "_-.?"; + + if (!std::isalnum(text[token_start_pos], std::locale::classic()) || + text.find_first_not_of(option_name_valid_chars, token_start_pos) < + next_delimiter_pos) { + throw_or_mimic(text); + } + } + split_names.emplace_back(text.substr(token_start_pos, token_length)); + token_start_pos = next_delimiter_pos + 1; + } + return split_names; +} + +inline ArguDesc ParseArgument(const char *arg, bool &matched) { + ArguDesc argu_desc; + const char *pdata = arg; + matched = false; + if (strncmp(pdata, "--", 2) == 0) { + pdata += 2; + if (isalnum(*pdata, std::locale::classic())) { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + while (isalnum(*pdata, std::locale::classic()) || *pdata == '-' || + *pdata == '_') { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + } + if (argu_desc.arg_name.length() > 1) { + if (*pdata == '=') { + argu_desc.set_value = true; + pdata += 1; + if (*pdata != '\0') { + argu_desc.value = std::string(pdata); + } + matched = true; + } else if (*pdata == '\0') { + matched = true; + } + } + } + } else if (strncmp(pdata, "-", 1) == 0) { + pdata += 1; + argu_desc.grouping = true; + while (isalnum(*pdata, std::locale::classic())) { + argu_desc.arg_name.push_back(*pdata); + pdata += 1; + } + matched = !argu_desc.arg_name.empty() && *pdata == '\0'; + } + return argu_desc; +} + +#else // CXXOPTS_NO_REGEX + +namespace { +CXXOPTS_LINKONCE +const char *const integer_pattern = "(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)"; +CXXOPTS_LINKONCE +const char *const truthy_pattern = "(t|T)(rue)?|1"; +CXXOPTS_LINKONCE +const char *const falsy_pattern = "(f|F)(alse)?|0"; +CXXOPTS_LINKONCE +const char *const option_pattern = + "--([[:alnum:]][-_[:alnum:]\\.]+)(=(.*))?|-([[:alnum:]].*)"; +CXXOPTS_LINKONCE +const char *const option_specifier_pattern = + "([[:alnum:]][-_[:alnum:]\\.]*)(,[ ]*[[:alnum:]][-_[:alnum:]]*)*"; +CXXOPTS_LINKONCE +const char *const option_specifier_separator_pattern = ", *"; + +} // namespace + +inline IntegerDesc SplitInteger(const std::string &text) { + static const std::basic_regex integer_matcher(integer_pattern); + + std::smatch match; + std::regex_match(text, match, integer_matcher); + + if (match.length() == 0) { + throw_or_mimic(text); + } + + IntegerDesc desc; + desc.negative = match[1]; + desc.base = match[2]; + desc.value = match[3]; + + if (match.length(4) > 0) { + desc.base = match[5]; + desc.value = "0"; + return desc; + } + + return desc; +} + +inline bool IsTrueText(const std::string &text) { + static const std::basic_regex truthy_matcher(truthy_pattern); + std::smatch result; + std::regex_match(text, result, truthy_matcher); + return !result.empty(); +} + +inline bool IsFalseText(const std::string &text) { + static const std::basic_regex falsy_matcher(falsy_pattern); + std::smatch result; + std::regex_match(text, result, falsy_matcher); + return !result.empty(); +} + +// Gets the option names specified via a single, comma-separated string, +// and returns the separate, space-discarded, non-empty names +// (without considering which or how many are single-character) +inline OptionNames split_option_names(const std::string &text) { + static const std::basic_regex option_specifier_matcher( + option_specifier_pattern); + if (!std::regex_match(text.c_str(), option_specifier_matcher)) { + throw_or_mimic(text); + } + + OptionNames split_names; + + static const std::basic_regex option_specifier_separator_matcher( + option_specifier_separator_pattern); + constexpr int use_non_matches{-1}; + auto token_iterator = std::sregex_token_iterator( + text.begin(), text.end(), option_specifier_separator_matcher, + use_non_matches); + std::copy(token_iterator, std::sregex_token_iterator(), + std::back_inserter(split_names)); + return split_names; +} + +inline ArguDesc ParseArgument(const char *arg, bool &matched) { + static const std::basic_regex option_matcher(option_pattern); + std::match_results result; + std::regex_match(arg, result, option_matcher); + matched = !result.empty(); + + ArguDesc argu_desc; + if (matched) { + argu_desc.arg_name = result[1].str(); + argu_desc.set_value = result[2].length() > 0; + argu_desc.value = result[3].str(); + if (result[4].length() > 0) { + argu_desc.grouping = true; + argu_desc.arg_name = result[4].str(); + } + } + + return argu_desc; +} + +#endif // CXXOPTS_NO_REGEX +#undef CXXOPTS_NO_REGEX +} // namespace parser_tool + +namespace detail { + +template struct SignedCheck; + +template struct SignedCheck { + template + void operator()(bool negative, U u, const std::string &text) { + if (negative) { + if (u > static_cast((std::numeric_limits::min)())) { + throw_or_mimic(text); + } + } else { + if (u > static_cast((std::numeric_limits::max)())) { + throw_or_mimic(text); + } + } + } +}; + +template struct SignedCheck { + template void operator()(bool, U, const std::string &) const {} +}; + +template +void check_signed_range(bool negative, U value, const std::string &text) { + SignedCheck::is_signed>()(negative, value, text); +} + +} // namespace detail + +template +void checked_negate(R &r, T &&t, const std::string &, std::true_type) { + // if we got to here, then `t` is a positive number that fits into + // `R`. So to avoid MSVC C4146, we first cast it to `R`. + // See https://github.com/jarro2783/cxxopts/issues/62 for more details. + r = static_cast(-static_cast(t - 1) - 1); +} + +template +void checked_negate(R &, T &&, const std::string &text, std::false_type) { + throw_or_mimic(text); +} + +template void integer_parser(const std::string &text, T &value) { + parser_tool::IntegerDesc int_desc = parser_tool::SplitInteger(text); + + using US = typename std::make_unsigned::type; + constexpr bool is_signed = std::numeric_limits::is_signed; + + const bool negative = int_desc.negative.length() > 0; + const uint8_t base = int_desc.base.length() > 0 ? 16 : 10; + const std::string &value_match = int_desc.value; + + US result = 0; + + for (char ch : value_match) { + US digit = 0; + + if (ch >= '0' && ch <= '9') { + digit = static_cast(ch - '0'); + } else if (base == 16 && ch >= 'a' && ch <= 'f') { + digit = static_cast(ch - 'a' + 10); + } else if (base == 16 && ch >= 'A' && ch <= 'F') { + digit = static_cast(ch - 'A' + 10); + } else { + throw_or_mimic(text); + } + + US limit = 0; + if (negative) { + limit = static_cast( + std::abs(static_cast(std::numeric_limits::min()))); + } else { + limit = std::numeric_limits::max(); + } + + if (base != 0 && result > limit / base) { + throw_or_mimic(text); + } + if (result * base > limit - digit) { + throw_or_mimic(text); + } + + result = static_cast(result * base + digit); + } + + detail::check_signed_range(negative, result, text); + + if (negative) { + checked_negate(value, result, text, + std::integral_constant()); + } else { + value = static_cast(result); + } +} + +template +void stringstream_parser(const std::string &text, T &value) { + std::stringstream in(text); + in >> value; + if (!in) { + throw_or_mimic(text); + } +} + +template ::value>::type * = nullptr> +void parse_value(const std::string &text, T &value) { + integer_parser(text, value); +} + +inline void parse_value(const std::string &text, bool &value) { + if (parser_tool::IsTrueText(text)) { + value = true; + return; + } + + if (parser_tool::IsFalseText(text)) { + value = false; + return; + } + + throw_or_mimic(text); +} + +inline void parse_value(const std::string &text, std::string &value) { + value = text; +} + +// The fallback parser. It uses the stringstream parser to parse all types +// that have not been overloaded explicitly. It has to be placed in the +// source code before all other more specialized templates. +template ::value>::type + * = nullptr> +void parse_value(const std::string &text, T &value) { + stringstream_parser(text, value); +} + +#ifdef CXXOPTS_HAS_OPTIONAL +template +void parse_value(const std::string &text, std::optional &value) { + T result; + parse_value(text, result); + value = std::move(result); +} +#endif + +inline void parse_value(const std::string &text, char &c) { + if (text.length() != 1) { + throw_or_mimic(text); + } + + c = text[0]; +} + +template +void parse_value(const std::string &text, std::vector &value) { + if (text.empty()) { + T v; + parse_value(text, v); + value.emplace_back(std::move(v)); + return; + } + std::stringstream in(text); + std::string token; + while (!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) { + T v; + parse_value(token, v); + value.emplace_back(std::move(v)); + } +} + +template void add_value(const std::string &text, T &value) { + parse_value(text, value); +} + +template +void add_value(const std::string &text, std::vector &value) { + T v; + add_value(text, v); + value.emplace_back(std::move(v)); +} + +template struct type_is_container { + static constexpr bool value = false; +}; + +template struct type_is_container> { + static constexpr bool value = true; +}; + +template class abstract_value : public Value { + using Self = abstract_value; + +public: + abstract_value() : m_result(std::make_shared()), m_store(m_result.get()) {} + + explicit abstract_value(T *t) : m_store(t) {} + + ~abstract_value() override = default; + + abstract_value &operator=(const abstract_value &) = default; + + abstract_value(const abstract_value &rhs) { + if (rhs.m_result) { + m_result = std::make_shared(); + m_store = m_result.get(); + } else { + m_store = rhs.m_store; + } + + m_default = rhs.m_default; + m_implicit = rhs.m_implicit; + m_default_value = rhs.m_default_value; + m_implicit_value = rhs.m_implicit_value; + } + + void add(const std::string &text) const override { + add_value(text, *m_store); + } + + void parse(const std::string &text) const override { + parse_value(text, *m_store); + } + + bool is_container() const override { return type_is_container::value; } + + void parse() const override { parse_value(m_default_value, *m_store); } + + bool has_default() const override { return m_default; } + + bool has_implicit() const override { return m_implicit; } + + std::shared_ptr default_value(const std::string &value) override { + m_default = true; + m_default_value = value; + return shared_from_this(); + } + + std::shared_ptr implicit_value(const std::string &value) override { + m_implicit = true; + m_implicit_value = value; + return shared_from_this(); + } + + std::shared_ptr no_implicit_value() override { + m_implicit = false; + return shared_from_this(); + } + + std::string get_default_value() const override { return m_default_value; } + + std::string get_implicit_value() const override { return m_implicit_value; } + + bool is_boolean() const override { return std::is_same::value; } + + const T &get() const { + if (m_store == nullptr) { + return *m_result; + } + return *m_store; + } + +protected: + std::shared_ptr m_result{}; + T *m_store{}; + + bool m_default = false; + bool m_implicit = false; + + std::string m_default_value{}; + std::string m_implicit_value{}; +}; + +template class standard_value : public abstract_value { +public: + using abstract_value::abstract_value; + + CXXOPTS_NODISCARD + std::shared_ptr clone() const override { + return std::make_shared>(*this); + } +}; + +template <> class standard_value : public abstract_value { +public: + ~standard_value() override = default; + + standard_value() { set_default_and_implicit(); } + + explicit standard_value(bool *b) : abstract_value(b) { + m_implicit = true; + m_implicit_value = "true"; + } + + std::shared_ptr clone() const override { + return std::make_shared>(*this); + } + +private: + void set_default_and_implicit() { + m_default = true; + m_default_value = "false"; + m_implicit = true; + m_implicit_value = "true"; + } +}; + +} // namespace values + +template std::shared_ptr value() { + return std::make_shared>(); +} + +template std::shared_ptr value(T &t) { + return std::make_shared>(&t); +} + +class OptionAdder; + +CXXOPTS_NODISCARD +inline const std::string &first_or_empty(const OptionNames &long_names) { + static const std::string empty{""}; + return long_names.empty() ? empty : long_names.front(); +} + +class OptionDetails { +public: + OptionDetails(std::string short_, OptionNames long_, String desc, + std::shared_ptr val) + : m_short(std::move(short_)), m_long(std::move(long_)), + m_desc(std::move(desc)), m_value(std::move(val)), m_count(0) { + m_hash = std::hash{}(first_long_name() + m_short); + } + + OptionDetails(const OptionDetails &rhs) + : m_desc(rhs.m_desc), m_value(rhs.m_value->clone()), + m_count(rhs.m_count) {} + + OptionDetails(OptionDetails &&rhs) = default; + + CXXOPTS_NODISCARD + const String &description() const { return m_desc; } + + CXXOPTS_NODISCARD + const Value &value() const { return *m_value; } + + CXXOPTS_NODISCARD + std::shared_ptr make_storage() const { return m_value->clone(); } + + CXXOPTS_NODISCARD + const std::string &short_name() const { return m_short; } + + CXXOPTS_NODISCARD + const std::string &first_long_name() const { return first_or_empty(m_long); } + + CXXOPTS_NODISCARD + const std::string &essential_name() const { + return m_long.empty() ? m_short : m_long.front(); + } + + CXXOPTS_NODISCARD + const OptionNames &long_names() const { return m_long; } + + std::size_t hash() const { return m_hash; } + +private: + std::string m_short{}; + OptionNames m_long{}; + String m_desc{}; + std::shared_ptr m_value{}; + int m_count; + + std::size_t m_hash{}; +}; + +struct HelpOptionDetails { + std::string s; + OptionNames l; + String desc; + bool has_default; + std::string default_value; + bool has_implicit; + std::string implicit_value; + std::string arg_help; + bool is_container; + bool is_boolean; +}; + +struct HelpGroupDetails { + std::string name{}; + std::string description{}; + std::vector options{}; +}; + +class OptionValue { +public: + void add(const std::shared_ptr &details, + const std::string &text) { + ensure_value(details); + ++m_count; + m_value->add(text); + m_long_names = &details->long_names(); + } + + void parse(const std::shared_ptr &details, + const std::string &text) { + ensure_value(details); + ++m_count; + m_value->parse(text); + m_long_names = &details->long_names(); + } + + void parse_default(const std::shared_ptr &details) { + ensure_value(details); + m_default = true; + m_long_names = &details->long_names(); + m_value->parse(); + } + + void parse_no_value(const std::shared_ptr &details) { + m_long_names = &details->long_names(); + } + +#if defined(CXXOPTS_NULL_DEREF_IGNORE) + CXXOPTS_DIAGNOSTIC_PUSH + CXXOPTS_IGNORE_WARNING("-Wnull-dereference") +#endif + + CXXOPTS_NODISCARD + std::size_t count() const noexcept { return m_count; } + +#if defined(CXXOPTS_NULL_DEREF_IGNORE) + CXXOPTS_DIAGNOSTIC_POP +#endif + + // TODO: maybe default options should count towards the number of arguments + CXXOPTS_NODISCARD + bool has_default() const noexcept { return m_default; } + + template const T &as() const { + if (m_value == nullptr) { + throw_or_mimic( + m_long_names == nullptr ? "" : first_or_empty(*m_long_names)); + } + + return CXXOPTS_RTTI_CAST &>(*m_value).get(); + } + +#ifdef CXXOPTS_HAS_OPTIONAL + template std::optional as_optional() const { + if (m_value == nullptr) { + return std::nullopt; + } + return as(); + } +#endif + +private: + void ensure_value(const std::shared_ptr &details) { + if (m_value == nullptr) { + m_value = details->make_storage(); + } + } + + const OptionNames *m_long_names = nullptr; + // Holding this pointer is safe, since OptionValue's only exist in key-value + // pairs, where the key has the string we point to. + std::shared_ptr m_value{}; + std::size_t m_count = 0; + bool m_default = false; +}; + +class KeyValue { +public: + KeyValue(std::string key_, std::string value_) noexcept + : m_key(std::move(key_)), m_value(std::move(value_)) {} + + CXXOPTS_NODISCARD + const std::string &key() const { return m_key; } + + CXXOPTS_NODISCARD + const std::string &value() const { return m_value; } + + template T as() const { + T result; + values::parse_value(m_value, result); + return result; + } + +private: + std::string m_key; + std::string m_value; +}; + +using ParsedHashMap = std::unordered_map; +using NameHashMap = std::unordered_map; + +class ParseResult { +public: + class Iterator { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = KeyValue; + using difference_type = void; + using pointer = const KeyValue *; + using reference = const KeyValue &; + + Iterator() = default; + Iterator(const Iterator &) = default; + + // GCC complains about m_iter not being initialised in the member + // initializer list + CXXOPTS_DIAGNOSTIC_PUSH + CXXOPTS_IGNORE_WARNING("-Weffc++") + Iterator(const ParseResult *pr, bool end = false) : m_pr(pr) { + if (end) { + m_sequential = false; + m_iter = m_pr->m_defaults.end(); + } else { + m_sequential = true; + m_iter = m_pr->m_sequential.begin(); + + if (m_iter == m_pr->m_sequential.end()) { + m_sequential = false; + m_iter = m_pr->m_defaults.begin(); + } + } + } + CXXOPTS_DIAGNOSTIC_POP + + Iterator &operator++() { + ++m_iter; + if (m_sequential && m_iter == m_pr->m_sequential.end()) { + m_sequential = false; + m_iter = m_pr->m_defaults.begin(); + return *this; + } + return *this; + } + + Iterator operator++(int) { + Iterator retval = *this; + ++(*this); + return retval; + } + + bool operator==(const Iterator &other) const { + return (m_sequential == other.m_sequential) && (m_iter == other.m_iter); + } + + bool operator!=(const Iterator &other) const { return !(*this == other); } + + const KeyValue &operator*() { return *m_iter; } + + const KeyValue *operator->() { return m_iter.operator->(); } + + private: + const ParseResult *m_pr; + std::vector::const_iterator m_iter; + bool m_sequential = true; + }; + + ParseResult() = default; + ParseResult(const ParseResult &) = default; + + ParseResult(NameHashMap &&keys, ParsedHashMap &&values, + std::vector sequential, + std::vector default_opts, + std::vector &&unmatched_args) + : m_keys(std::move(keys)), m_values(std::move(values)), + m_sequential(std::move(sequential)), + m_defaults(std::move(default_opts)), + m_unmatched(std::move(unmatched_args)) {} + + ParseResult &operator=(ParseResult &&) = default; + ParseResult &operator=(const ParseResult &) = default; + + Iterator begin() const { return Iterator(this); } + + Iterator end() const { return Iterator(this, true); } + + std::size_t count(const std::string &o) const { + auto iter = m_keys.find(o); + if (iter == m_keys.end()) { + return 0; + } + + auto viter = m_values.find(iter->second); + + if (viter == m_values.end()) { + return 0; + } + + return viter->second.count(); + } + + const OptionValue &operator[](const std::string &option) const { + auto iter = m_keys.find(option); + + if (iter == m_keys.end()) { + throw_or_mimic(option); + } + + auto viter = m_values.find(iter->second); + + if (viter == m_values.end()) { + throw_or_mimic(option); + } + + return viter->second; + } + +#ifdef CXXOPTS_HAS_OPTIONAL + template + std::optional as_optional(const std::string &option) const { + auto iter = m_keys.find(option); + if (iter != m_keys.end()) { + auto viter = m_values.find(iter->second); + if (viter != m_values.end()) { + return viter->second.as_optional(); + } + } + return std::nullopt; + } +#endif + + const std::vector &arguments() const { return m_sequential; } + + const std::vector &unmatched() const { return m_unmatched; } + + const std::vector &defaults() const { return m_defaults; } + + const std::string arguments_string() const { + std::string result; + for (const auto &kv : m_sequential) { + result += kv.key() + " = " + kv.value() + "\n"; + } + for (const auto &kv : m_defaults) { + result += kv.key() + " = " + kv.value() + " " + "(default)" + "\n"; + } + return result; + } + +private: + NameHashMap m_keys{}; + ParsedHashMap m_values{}; + std::vector m_sequential{}; + std::vector m_defaults{}; + std::vector m_unmatched{}; +}; + +struct Option { + Option(std::string opts, std::string desc, + std::shared_ptr value = ::cxxopts::value(), + std::string arg_help = "") + : opts_(std::move(opts)), desc_(std::move(desc)), + value_(std::move(value)), arg_help_(std::move(arg_help)) {} + + std::string opts_; + std::string desc_; + std::shared_ptr value_; + std::string arg_help_; +}; + +using OptionMap = + std::unordered_map>; +using PositionalList = std::vector; +using PositionalListIterator = PositionalList::const_iterator; + +class OptionParser { +public: + OptionParser(const OptionMap &options, const PositionalList &positional, + bool allow_unrecognised) + : m_options(options), m_positional(positional), + m_allow_unrecognised(allow_unrecognised) {} + + ParseResult parse(int argc, const char *const *argv); + + bool consume_positional(const std::string &a, PositionalListIterator &next); + + void checked_parse_arg(int argc, const char *const *argv, int ¤t, + const std::shared_ptr &value, + const std::string &name); + + void add_to_option(const std::shared_ptr &value, + const std::string &arg); + + void parse_option(const std::shared_ptr &value, + const std::string &name, const std::string &arg = ""); + + void parse_default(const std::shared_ptr &details); + + void parse_no_value(const std::shared_ptr &details); + +private: + void finalise_aliases(); + + const OptionMap &m_options; + const PositionalList &m_positional; + + std::vector m_sequential{}; + std::vector m_defaults{}; + bool m_allow_unrecognised; + + ParsedHashMap m_parsed{}; + NameHashMap m_keys{}; +}; + +class Options { +public: + explicit Options(std::string program_name, std::string help_string = "") + : m_program(std::move(program_name)), + m_help_string(toLocalString(std::move(help_string))), + m_custom_help("[OPTION...]"), + m_positional_help("positional parameters"), m_show_positional(false), + m_allow_unrecognised(false), m_width(76), m_tab_expansion(false), + m_options(std::make_shared()) {} + + Options &positional_help(std::string help_text) { + m_positional_help = std::move(help_text); + return *this; + } + + Options &custom_help(std::string help_text) { + m_custom_help = std::move(help_text); + return *this; + } + + Options &show_positional_help() { + m_show_positional = true; + return *this; + } + + Options &allow_unrecognised_options() { + m_allow_unrecognised = true; + return *this; + } + + Options &set_width(std::size_t width) { + m_width = width; + return *this; + } + + Options &set_tab_expansion(bool expansion = true) { + m_tab_expansion = expansion; + return *this; + } + + ParseResult parse(int argc, const char *const *argv); + + OptionAdder add_options(std::string group = ""); + + void add_options(const std::string &group, + std::initializer_list