// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // // SPDX-License-Identifier: BSD-3-Clause #include "app_helper.hpp" #include #include #include using cx = std::complex; CLI::Option * add_option(CLI::App &app, std::string name, cx &variable, std::string description = "", bool defaulted = false) { CLI::callback_t fun = [&variable](CLI::results_t res) { double x = 0, y = 0; bool worked = CLI::detail::lexical_cast(res[0], x) && CLI::detail::lexical_cast(res[1], y); if(worked) variable = cx(x, y); return worked; }; CLI::Option *opt = app.add_option(name, fun, description, defaulted); opt->type_name("COMPLEX")->type_size(2); if(defaulted) { std::stringstream out; out << variable; opt->default_str(out.str()); } return opt; } TEST_CASE_METHOD(TApp, "AddingComplexParser", "[complex]") { cx comp{0, 0}; add_option(app, "-c,--complex", comp); args = {"-c", "1.5", "2.5"}; run(); CHECK(comp.real() == Approx(1.5)); CHECK(comp.imag() == Approx(2.5)); } TEST_CASE_METHOD(TApp, "DefaultedComplex", "[complex]") { cx comp{1, 2}; add_option(app, "-c,--complex", comp, "", true); args = {"-c", "4", "3"}; std::string help = app.help(); CHECK_THAT(help, Contains("1")); CHECK_THAT(help, Contains("2")); CHECK(comp.real() == Approx(1)); CHECK(comp.imag() == Approx(2)); run(); CHECK(comp.real() == Approx(4)); CHECK(comp.imag() == Approx(3)); } // an example of custom complex number converter that can be used to add new parsing options #if defined(__has_include) #if __has_include() // an example of custom converter that can be used to add new parsing options #define HAS_REGEX_INCLUDE #endif #endif #ifdef HAS_REGEX_INCLUDE // Gcc 4.8 and older and the corresponding standard libraries have a broken so this would // fail. And if a clang compiler is using libstd++ then this will generate an error as well so this is just a check to // simplify compilation and prevent a much more complicated #if expression #include namespace CLI { namespace detail { // On MSVC and possibly some other new compilers this can be a free standing function without the template // specialization but this is compiler dependent template <> bool lexical_cast>(const std::string &input, std::complex &output) { // regular expression to handle complex numbers of various formats static const std::regex creg( R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)"); std::smatch m; double x{0.0}, y{0.0}; bool worked = false; std::regex_search(input, m, creg); if(m.size() == 9) { worked = CLI::detail::lexical_cast(m[1], x) && CLI::detail::lexical_cast(m[6], y); if(worked) { if(*m[5].first == '-') { y = -y; } } } else { if((input.back() == 'j') || (input.back() == 'i')) { auto strval = input.substr(0, input.size() - 1); CLI::detail::trim(strval); worked = CLI::detail::lexical_cast(strval, y); } else { std::string ival = input; CLI::detail::trim(ival); worked = CLI::detail::lexical_cast(ival, x); } } if(worked) { output = cx{x, y}; } return worked; } } // namespace detail } // namespace CLI TEST_CASE_METHOD(TApp, "AddingComplexParserDetail", "[complex]") { bool skip_tests = false; try { // check if the library actually supports regex, it is possible to link against a non working regex in the // standard library std::smatch m; std::string input = "1.5+2.5j"; static const std::regex creg( R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)"); auto rsearch = std::regex_search(input, m, creg); if(!rsearch) { skip_tests = true; } else { CHECK(9u == m.size()); } } catch(...) { skip_tests = true; } static_assert(CLI::detail::is_complex::value, "complex should register as complex in this situation"); if(!skip_tests) { cx comp{0, 0}; app.add_option("-c,--complex", comp, "add a complex number option"); args = {"-c", "1.5+2.5j"}; run(); CHECK(comp.real() == Approx(1.5)); CHECK(comp.imag() == Approx(2.5)); args = {"-c", "1.5-2.5j"}; run(); CHECK(comp.real() == Approx(1.5)); CHECK(comp.imag() == Approx(-2.5)); } } #endif // defining a new complex class class complex_new { public: complex_new() = default; complex_new(double v1, double v2) : val1_{v1}, val2_{v2} {}; CLI11_NODISCARD double real() const { return val1_; } CLI11_NODISCARD double imag() const { return val2_; } private: double val1_{0.0}; double val2_{0.0}; }; TEST_CASE_METHOD(TApp, "newComplex", "[complex]") { complex_new cval; static_assert(CLI::detail::is_complex::value, "complex new does not register as a complex type"); static_assert(CLI::detail::classify_object::value == CLI::detail::object_category::complex_number, "complex new does not result in complex number categorization"); app.add_option("-c,--complex", cval, "add a complex number option"); args = {"-c", "1.5+2.5j"}; run(); CHECK(cval.real() == Approx(1.5)); CHECK(cval.imag() == Approx(2.5)); args = {"-c", "1.5-2.5j"}; run(); CHECK(cval.real() == Approx(1.5)); CHECK(cval.imag() == Approx(-2.5)); }