From 7dc19efbaa2aa49c9c733f2f54b51221f63e2538 Mon Sep 17 00:00:00 2001 From: Brett Laptop Date: Mon, 17 Feb 2025 21:43:09 -0500 Subject: [PATCH] more metaprogramming fun --- CMakeLists.txt | 2 +- include/blt/meta/type_traits.h | 31 ++ ...e_v2 (conflicted copy 2025-02-13 174730).h | 484 ++++++++++++++++++ include/blt/parse/argparse_v2.h | 36 +- src/blt/parse/argparse_v2.cpp | 74 ++- 5 files changed, 587 insertions(+), 40 deletions(-) create mode 100644 include/blt/meta/type_traits.h create mode 100644 include/blt/parse/argparse_v2 (conflicted copy 2025-02-13 174730).h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ae9eac..81e6256 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) include(cmake/color.cmake) -set(BLT_VERSION 4.0.11) +set(BLT_VERSION 4.0.12) set(BLT_TARGET BLT) diff --git a/include/blt/meta/type_traits.h b/include/blt/meta/type_traits.h new file mode 100644 index 0000000..d811aa5 --- /dev/null +++ b/include/blt/meta/type_traits.h @@ -0,0 +1,31 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_META_TYPE_TRAITS_H +#define BLT_META_TYPE_TRAITS_H + +#include + +namespace blt::meta { + + template + using remove_cvref_t = std::remove_volatile_t>>; + +} + +#endif // BLT_META_TYPE_TRAITS_H diff --git a/include/blt/parse/argparse_v2 (conflicted copy 2025-02-13 174730).h b/include/blt/parse/argparse_v2 (conflicted copy 2025-02-13 174730).h new file mode 100644 index 0000000..e72fc0f --- /dev/null +++ b/include/blt/parse/argparse_v2 (conflicted copy 2025-02-13 174730).h @@ -0,0 +1,484 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef BLT_PARSE_ARGPARSE_V2_H +#define BLT_PARSE_ARGPARSE_V2_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace blt::argparse +{ + class argument_string_t; + class argument_consumer_t; + class argument_parser_t; + class argument_subparser_t; + class argument_builder_t; + class argument_storage_t; + + enum class action_t + { + STORE, + STORE_CONST, + STORE_TRUE, + STORE_FALSE, + APPEND, + APPEND_CONST, + COUNT, + HELP, + VERSION, + EXTEND, + SUBCOMMAND + }; + + enum class nargs_t + { + IF_POSSIBLE, + ALL, + ALL_AT_LEAST_ONE + }; + + using nargs_v = std::variant; + + namespace detail + { + class bad_flag final : public std::runtime_error + { + public: + explicit bad_flag(const std::string& message): std::runtime_error(message) + { + } + + explicit bad_flag(const char* message): std::runtime_error(message) + { + } + }; + + class missing_argument_error final : public std::runtime_error + { + public: + explicit missing_argument_error(const std::string& message): std::runtime_error(message) + { + } + } + + class subparse_error final : public std::exception + { + public: + explicit subparse_error(const std::string_view found_string, std::vector> allowed_strings): + m_found_string(found_string), + m_allowed_strings(std::move(allowed_strings)) + { + } + + [[nodiscard]] const std::vector>& get_allowed_strings() const + { + return m_allowed_strings; + } + + [[nodiscard]] std::string_view get_found_string() const + { + return m_found_string; + } + + [[nodiscard]] std::string error_string() const + { + std::string message = "Subparser Error: "; + message += m_found_string; + message += " is not a valid command. Allowed commands are: {"; + for (const auto [i, allowed_string] : enumerate(m_allowed_strings)) + { + if (allowed_string.size() > 1) + message += '['; + for (const auto [j, alias] : enumerate(allowed_string)) + { + message += alias; + if (j < alias.size() - 2) + message += ", "; + else if (j < alias.size()) + message += ", or "; + } + if (allowed_string.size() > 1) + message += ']'; + if (i != m_allowed_strings.size() - 1) + message += ' '; + } + message += "}"; + return message; + } + + [[nodiscard]] const char* what() const override + { + return "Please use error_string() method instead of what(). This exception should *always* be caught!"; + } + + private: + std::string_view m_found_string; + std::vector> m_allowed_strings; + }; + + template + struct arg_data_helper_t + { + using arg_primitive_data_t = std::variant; + using arg_list_data_t = std::variant...>; + }; + + using data_helper_t = arg_data_helper_t; + + using arg_primitive_data_t = data_helper_t::arg_primitive_data_t; + using arg_list_data_t = data_helper_t::arg_list_data_t; + using arg_data_t = std::variant; + + template + struct arg_type_t + { + static T convert(const std::string_view value) + { + static_assert(std::is_arithmetic_v || std::is_same_v || std::is_same_v, + "Type must be arithmetic, string_view or string!"); + const std::string temp{value}; + + if constexpr (std::is_same_v) + { + return std::stof(temp); + } + else if constexpr (std::is_same_v) + { + return std::stod(temp); + } + else if constexpr (std::is_unsigned_v) + { + return static_cast(std::stoull(temp)); + } + else if constexpr (std::is_signed_v) + { + return static_cast(std::stoll(temp)); + } + else if constexpr (std::is_same_v) + { + return value; + } + else if constexpr (std::is_same_v) + { + return std::string(value); + } + BLT_UNREACHABLE; + } + }; + + void test(); + } + + class argument_string_t + { + public: + explicit argument_string_t(const char* input, const hashset_t& allowed_flag_prefix): m_argument(input), + allowed_flag_prefix(&allowed_flag_prefix) + { + if (input == nullptr) + throw detail::bad_flag("Argument cannot be null!"); + process_argument(); + } + + [[nodiscard]] std::string_view get_flag() const + { + return m_flag_section; + } + + [[nodiscard]] std::string_view get_name() const + { + return m_name_section; + } + + [[nodiscard]] std::string_view value() const + { + return get_name(); + } + + [[nodiscard]] bool is_flag() const + { + return !m_flag_section.empty(); + } + + [[nodiscard]] std::string_view get_argument() const + { + return m_argument; + } + + private: + void process_argument() + { + size_t start = 0; + for (; start < m_argument.size() && allowed_flag_prefix->contains(m_argument[start]); start++) + { + } + + m_flag_section = {m_argument.data(), start}; + m_name_section = {m_argument.data() + start, m_argument.size() - start}; + } + + std::string_view m_argument; + std::string_view m_flag_section; + std::string_view m_name_section; + const hashset_t* allowed_flag_prefix; + }; + + class argument_consumer_t + { + public: + explicit argument_consumer_t(const span& args): m_args(args) + { + } + + [[nodiscard]] argument_string_t peek(const i32 offset = 0) const + { + return m_args[m_forward_index + offset]; + } + + argument_string_t consume() + { + return m_args[m_forward_index++]; + } + + [[nodiscard]] i32 position() const + { + return m_forward_index; + } + + [[nodiscard]] i32 remaining() const + { + return static_cast(m_args.size()) - m_forward_index; + } + + [[nodiscard]] bool has_next(const i32 offset = 0) const + { + return (offset + m_forward_index) < m_args.size(); + } + + private: + span m_args; + i32 m_forward_index = 0; + }; + + class argument_storage_t + { + friend argument_parser_t; + friend argument_subparser_t; + friend argument_builder_t; + + public: + template + const T& get(const std::string_view key) + { + return std::get(m_data[key]); + } + + std::string_view get(const std::string_view key) + { + return std::get(m_data[key]); + } + + bool contains(const std::string_view key) + { + return m_data.find(key) != m_data.end(); + } + + private: + hashmap_t m_data; + }; + + class argument_builder_t + { + friend argument_parser_t; + + public: + argument_builder_t() + { + dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value) + { + storage.m_data[dest] = value; + }; + } + + template + argument_builder_t& as_type() + { + dest_func = [](const std::string_view dest, argument_storage_t& storage, std::string_view value) + { + storage.m_data[dest] = detail::arg_type_t::convert(value); + }; + return *this; + } + + private: + action_t action = action_t::STORE; + bool required = false; // do we require this argument to be provided as an argument? + nargs_v nargs = 1; // number of arguments to consume + std::optional metavar; // variable name to be used in the help string + std::optional help; // help string to be used in the help string + std::optional> choices; // optional allowed choices for this argument + std::optional default_value; + std::optional const_value; + // dest, storage, value input + std::function dest_func; + }; + + class argument_parser_t + { + friend argument_subparser_t; + + public: + explicit argument_parser_t(const std::optional name = {}, const std::optional usage = {}, + const std::optional description = {}, const std::optional epilogue = {}): + m_name(name), m_usage(usage), m_description(description), m_epilogue(epilogue) + { + } + + template + argument_builder_t& add_flag(const std::string_view arg, Aliases... aliases) + { + static_assert( + std::conjunction_v, std::is_constructible< + std::string_view, Aliases>>...>, + "Arguments must be of type string_view, convertible to string_view or be string_view constructable"); + m_argument_builders.emplace_back(); + m_flag_arguments[arg] = &m_argument_builders.back(); + ((m_flag_arguments[std::string_view{aliases}] = &m_argument_builders.back()), ...); + return m_argument_builders.back(); + } + + argument_builder_t& add_positional(const std::string_view arg) + { + m_argument_builders.emplace_back(); + m_positional_arguments[arg] = &m_argument_builders.back(); + return m_argument_builders.back(); + } + + argument_subparser_t& add_subparser(std::string_view dest); + + void parse(argument_consumer_t& consumer); // NOLINT + + void print_help(); + + argument_parser_t& set_name(const std::string_view name) + { + m_name = name; + return *this; + } + + argument_parser_t& set_usage(const std::string_view usage) + { + m_usage = usage; + return *this; + } + + [[nodiscard]] const std::optional& get_usage() const + { + return m_usage; + } + + argument_parser_t& set_description(const std::string_view description) + { + m_description = description; + return *this; + } + + [[nodiscard]] const std::optional& get_description() const + { + return m_description; + } + + argument_parser_t& set_epilogue(const std::string_view epilogue) + { + m_epilogue = epilogue; + return *this; + } + + [[nodiscard]] const std::optional& get_epilogue() const + { + return m_epilogue; + } + + private: + std::optional m_name; + std::optional m_usage; + std::optional m_description; + std::optional m_epilogue; + std::vector> m_subparsers; + std::vector m_argument_builders; + hashmap_t m_flag_arguments; + hashmap_t m_positional_arguments; + }; + + class argument_subparser_t + { + public: + explicit argument_subparser_t(const argument_parser_t& parent): m_parent(&parent) + { + } + + template + argument_parser_t& add_parser(const std::string_view name, Aliases... aliases) + { + static_assert( + std::conjunction_v, std::is_constructible< + std::string_view, Aliases>>...>, + "Arguments must be of type string_view, convertible to string_view or be string_view constructable"); + m_parsers.emplace(name); + ((m_aliases[std::string_view{aliases}] = &m_parsers[name]), ...); + return m_parsers[name]; + } + + + /** + * Parses the next argument using the provided argument consumer. + * + * This function uses an argument consumer to extract and process the next argument. + * If the argument is a flag or if it cannot be matched against the available parsers, + * an exception is thrown. + * + * @param consumer Reference to an argument_consumer_t object, which handles argument parsing. + * The consumer provides the next argument to be parsed. + * + * @throws detail::subparse_error If the argument is a flag or does not match any known parser. + */ + argument_string_t parse(argument_consumer_t& consumer); // NOLINT + + private: + [[nodiscard]] std::vector> get_allowed_strings() const; + + const argument_parser_t* m_parent; + hashmap_t m_parsers; + hashmap_t m_aliases; + }; +} + +#endif //BLT_PARSE_ARGPARSE_V2_H diff --git a/include/blt/parse/argparse_v2.h b/include/blt/parse/argparse_v2.h index c5f53cf..bb35c98 100644 --- a/include/blt/parse/argparse_v2.h +++ b/include/blt/parse/argparse_v2.h @@ -134,39 +134,17 @@ namespace blt::argparse using arg_t = meta::arg_helper; using arg_vec_t = meta::arg_helper...>; - template - static auto make_lists_only_visitor_per_object_in_vec(const PerObjectAction& action) + template + static auto make_visitor(const DefaultPrimitiveAction& primitive_action, const DefaultListAction& list_action) { return lambda_visitor{ - invalid_option_lambda, - ([&action](std::vector arg_vec) + ([&primitive_action](Args& arg) { - for (const auto& arg : arg_vec) - action(arg_vec, arg); - })... - }; - } - - template - static auto make_lists_only_visitor_on_vec(const DefaultAction& action) - { - return lambda_visitor{ - invalid_option_lambda..., - ([&action](std::vector arg_vec) + return primitive_action(arg); + })..., + ([&list_action](std::vector& arg_vec) { - action(arg_vec); - })... - }; - } - - template - static auto make_reject_lists_visitor_t(const DefaultAction& action) - { - return lambda_visitor{ - invalid_option_lambda...>, - ([&action](Args arg) - { - action(arg); + return list_action(arg_vec); })... }; } diff --git a/src/blt/parse/argparse_v2.cpp b/src/blt/parse/argparse_v2.cpp index 5ea7735..bcaf666 100644 --- a/src/blt/parse/argparse_v2.cpp +++ b/src/blt/parse/argparse_v2.cpp @@ -18,6 +18,7 @@ #include #include #include +#include namespace blt::argparse { @@ -318,10 +319,12 @@ namespace blt::argparse switch (flag->m_action) { case action_t::STORE: + flag->m_dest_func(dest, parsed_args, args.front()); break; case action_t::APPEND: case action_t::EXTEND: { + break; } case action_t::APPEND_CONST: @@ -334,17 +337,44 @@ namespace blt::argparse if (parsed_args.contains(dest)) { auto& data = parsed_args.m_data[dest]; - if (data.index() != flag->m_const_value->index()) - { - std::cerr << "Constant value for flag '" << arg << "' type doesn't values already present!" << std::endl; - std::exit(1); - } - auto visitor = detail::arg_meta_type_helper_t::make_lists_only_visitor_on_vec([&flag](auto& vec) - { - vec.push_back(std::get>::value_type>(*flag->m_const_value)); - }); + auto visitor = detail::arg_meta_type_helper_t::make_visitor( + [arg](auto& primitive) + { + std::cerr << "Invalid type - '" << arg << "' expected list type, found '" + << blt::type_string() << "' with value " << primitive << std::endl; + std::exit(1); + }, + [&flag, arg](auto& vec) + { + using type = typename meta::remove_cvref_t::value_type; + if (!std::holds_alternative(*flag->m_const_value)) + { + std::cerr << "Constant value for flag '" << arg << + "' type doesn't match values already present! Expected to be of type '" << + blt::type_string() << "'!" << std::endl; + std::exit(1); + } + vec.push_back(std::get(*flag->m_const_value)); + }); std::visit(visitor, data); } + else + { + auto visitor = detail::arg_meta_type_helper_t::make_visitor( + [&flag, &parsed_args, &dest](auto& primitive) + { + std::vector> vec; + vec.push_back(primitive); + parsed_args.m_data.insert({dest, std::move(vec)}); + }, + [](auto&) + { + std::cerr << "Append const should not be a list type!" << std::endl; + std::exit(1); + }); + std::visit(visitor, *flag->m_const_value); + } + break; case action_t::STORE_CONST: std::cerr << "Store const flag called with an argument. This condition doesn't make sense." << std::endl; print_usage(); @@ -358,7 +388,31 @@ namespace blt::argparse print_usage(); std::exit(1); case action_t::COUNT: - parsed_args.m_data.insert({dest, args.size()}); + if (parsed_args.m_data.contains(dest)) + { + auto visitor = detail::arg_meta_type_helper_t::make_visitor( + [&args](auto& primitive) -> detail::arg_data_t + { + using type = meta::remove_cvref_t; + if constexpr (std::is_convertible_v) + { + return primitive + static_cast(args.size()); + } else + { + std::cerr << "Error: count called but stored type is " << blt::type_string() << std::endl; + std::exit(1); + } + }, + [](auto&) -> detail::arg_data_t + { + std::cerr << "List present on count. This condition doesn't make any sense! (How did we get here, please report this!)"; + std::exit(1); + } + ); + parsed_args.m_data[dest] = std::visit(visitor, parsed_args.m_data[dest]); + } + else + parsed_args.m_data.insert({dest, args.size()}); break; case action_t::HELP: print_help();