From 81a85149b448f554676921a0c444bb976485f2a5 Mon Sep 17 00:00:00 2001 From: "killua.z" Date: Mon, 4 May 2026 13:51:54 +0400 Subject: [PATCH] feat: new argument builder. base for ref. --- src/headers/parser/parser_v2.hpp | 3 +- src/headers/parser/parser_v3.hpp | 512 +++++++++++++++++++++++++++++++ 2 files changed, 514 insertions(+), 1 deletion(-) create mode 100644 src/headers/parser/parser_v3.hpp diff --git a/src/headers/parser/parser_v2.hpp b/src/headers/parser/parser_v2.hpp index e64a577..abbe172 100644 --- a/src/headers/parser/parser_v2.hpp +++ b/src/headers/parser/parser_v2.hpp @@ -14,7 +14,7 @@ #include namespace argument_parser::v2 { - enum class add_argument_flags { ShortArgument, LongArgument, Positional, Position, HelpText, Action, Required }; + enum class add_argument_flags { ShortArgument, LongArgument, Positional, Position, HelpText, Action, Required, Reference }; namespace flags { constexpr static inline add_argument_flags ShortArgument = add_argument_flags::ShortArgument; @@ -24,6 +24,7 @@ namespace argument_parser::v2 { constexpr static inline add_argument_flags Required = add_argument_flags::Required; constexpr static inline add_argument_flags Positional = add_argument_flags::Positional; constexpr static inline add_argument_flags Position = add_argument_flags::Position; + constexpr static inline add_argument_flags Reference = add_argument_flags::Reference; } // namespace flags class base_parser : private argument_parser::base_parser { diff --git a/src/headers/parser/parser_v3.hpp b/src/headers/parser/parser_v3.hpp new file mode 100644 index 0000000..9cb5b8f --- /dev/null +++ b/src/headers/parser/parser_v3.hpp @@ -0,0 +1,512 @@ +#pragma once + +#include "argument_parser.hpp" +#include +#include +#include + +#ifndef ARGUMENT_PARSER_PARSER_V3_HPP +#define ARGUMENT_PARSER_PARSER_V3_HPP + +namespace argument_parser::builder { + +class non_type {}; +namespace builder_mask { + using v2_flag = argument_parser::v2::add_argument_flags; + using mask_type = std::uint64_t; + enum class value_mode { + unresolved, + store, + flag, + reference, + nonparametered_action, + parametered_action + }; + + enum class extra_capability : unsigned { + Store = static_cast(v2_flag::Reference) + 1, + Flag + }; + + constexpr auto bit(v2_flag flag) -> mask_type { + return mask_type{1} << static_cast(flag); + } + + constexpr auto bit(extra_capability capability) -> mask_type { + return mask_type{1} << static_cast(capability); + } + + constexpr mask_type short_argument = bit(v2_flag::ShortArgument); + constexpr mask_type long_argument = bit(v2_flag::LongArgument); + constexpr mask_type positional = bit(v2_flag::Positional); + constexpr mask_type position = bit(v2_flag::Position); + constexpr mask_type help_text = bit(v2_flag::HelpText); + constexpr mask_type action = bit(v2_flag::Action); + constexpr mask_type required = bit(v2_flag::Required); + constexpr mask_type reference = bit(v2_flag::Reference); + constexpr mask_type store = bit(extra_capability::Store); + constexpr mask_type flag = bit(extra_capability::Flag); + + constexpr mask_type value_mode_group = action | reference | store | flag; + constexpr mask_type initial = short_argument | long_argument | positional | help_text | action | required | + reference | store | flag; + + constexpr auto has(mask_type mask, mask_type capability) -> bool { + return (mask & capability) == capability; + } + + constexpr auto remove(mask_type mask, mask_type capability) -> mask_type { + return mask & ~capability; + } + + constexpr auto replace(mask_type mask, mask_type remove_bits, mask_type add_bits = 0) -> mask_type { + return (mask & ~remove_bits) | add_bits; + } + + constexpr auto has_selected_identifier(mask_type mask) -> bool { + return !has(mask, short_argument) || !has(mask, long_argument) || !has(mask, positional); + } + + constexpr auto is_buildable(mask_type mask) -> bool { + return has_selected_identifier(mask); + } +} // namespace builder_mask + +template +class argument { +public: + using mask_type = builder_mask::mask_type; + using v2_flag = argument_parser::v2::add_argument_flags; + using value_mode = builder_mask::value_mode; + + static auto start() -> argument { + return {}; + } + + template = 0> + auto short_argument(std::string short_name) const + -> argument { + using next_argument = argument; + + next_argument next{*this}; + next.m_short_argument = std::move(short_name); + return next; + } + + template = 0> + auto long_argument(std::string long_name) const + -> argument { + using next_argument = argument; + + next_argument next{*this}; + next.m_long_argument = std::move(long_name); + return next; + } + + template = 0> + auto positional(std::string positional_name) const + -> argument { + using next_argument = + argument; + + next_argument next{*this}; + next.m_positional_name = std::move(positional_name); + return next; + } + + template = 0> + auto position(int index) const -> argument { + using next_argument = argument; + + next_argument next{*this}; + next.m_position = index; + return next; + } + + template = 0> + auto help_text(std::string help) const -> argument { + using next_argument = argument; + + next_argument next{*this}; + next.m_help_text = std::move(help); + return next; + } + + template = 0> + auto required(bool value = true) const -> argument { + using next_argument = argument; + + next_argument next{*this}; + next.m_required = value; + return next; + } + + template = 0> + auto store() const -> argument { + static_assert(!std::is_same_v, "store() is not supported. Use flag() for boolean-style arguments."); + + using next_argument = argument; + next_argument next{*this}; + next.m_value_mode = value_mode::store; + return next; + } + + template = 0> + auto flag() const -> argument { + using next_argument = argument; + + next_argument next{*this}; + next.m_value_mode = value_mode::flag; + return next; + } + + template = 0, + typename T> + auto reference(T& value) const -> argument { + using next_argument = argument; + + next_argument next{*this}; + next.m_reference = static_cast(std::addressof(value)); + next.m_value_mode = value_mode::reference; + return next; + } + + template = 0, + typename Callable> + auto action(Callable&& handler) const + -> std::enable_if_t, + argument> { + using next_argument = + argument; + + next_argument next{*this}; + next.m_action = std::make_shared( + std::function(std::forward(handler))); + next.m_value_mode = value_mode::nonparametered_action; + return next; + } + + template = 0, typename Callable> + auto action(Callable&& handler) const + -> std::enable_if_t, + argument> { + static_assert(!std::is_same_v, "action(...) is not supported. Use action([] { ... }) instead."); + + using next_argument = argument; + + next_argument next{*this}; + next.m_action = std::make_shared>( + std::function(std::forward(handler))); + next.m_value_mode = value_mode::parametered_action; + return next; + } + + template = 0> + auto build(argument_parser::v2::base_parser& parser) const -> void { + assert_has_identifier(); + + switch (m_value_mode) { + case value_mode::flag: + build_flag(parser); + return; + case value_mode::nonparametered_action: + build_nonparametered_action(parser); + return; + case value_mode::store: + if constexpr (!std::is_same_v) { + build_store(parser); + return; + } + break; + case value_mode::reference: + if constexpr (!std::is_same_v) { + build_reference(parser); + return; + } + break; + case value_mode::parametered_action: + if constexpr (!std::is_same_v) { + build_parametered_action(parser); + return; + } + break; + case value_mode::unresolved: + if (is_positional()) { + build_default_positional(parser); + } else { + build_flag(parser); + } + return; + } + + throw std::logic_error("The builder reached build() without a supported terminal value mode."); + } + +private: + argument() = default; + + template + argument(argument const& other) + : m_short_argument(other.m_short_argument), + m_long_argument(other.m_long_argument), + m_positional_name(other.m_positional_name), + m_position(other.m_position), + m_help_text(other.m_help_text), + m_required(other.m_required), + m_action(other.m_action), + m_reference(other.m_reference), + m_value_mode(other.m_value_mode) {} + + template + using typed_map = std::unordered_map>; + + using non_typed_map = std::unordered_map; + + auto is_positional() const -> bool { + return !m_positional_name.empty(); + } + + auto lookup_key() const -> std::string { + if (is_positional()) { + return m_positional_name; + } + if (!m_long_argument.empty()) { + return m_long_argument; + } + if (!m_short_argument.empty()) { + return m_short_argument; + } + + throw std::logic_error("No argument identifier is available for lookup."); + } + + auto assert_has_identifier() const -> void { + if (!is_positional() && m_short_argument.empty() && m_long_argument.empty()) { + throw std::logic_error("build() requires short_argument(), long_argument(), or positional()."); + } + } + + template + auto add_common_pairs(Map& pairs) const -> void { + using namespace argument_parser::v2::flags; + + if (is_positional()) { + pairs[Positional] = m_positional_name; + if (m_position.has_value()) { + pairs[Position] = m_position.value(); + } + } else { + if (!m_short_argument.empty()) { + pairs[ShortArgument] = m_short_argument; + } + if (!m_long_argument.empty()) { + pairs[LongArgument] = m_long_argument; + } + } + + if (!m_help_text.empty()) { + pairs[HelpText] = m_help_text; + } + if (m_required) { + pairs[Required] = true; + } + } + + template + auto make_typed_pairs() const -> typed_map { + typed_map pairs; + add_common_pairs(pairs); + return pairs; + } + + auto make_non_typed_pairs() const -> non_typed_map { + non_typed_map pairs; + add_common_pairs(pairs); + return pairs; + } + + auto build_flag(argument_parser::v2::base_parser& parser) const -> void { + auto pairs = make_non_typed_pairs(); + parser.add_argument(pairs); + } + + auto build_default_positional(argument_parser::v2::base_parser& parser) const -> void { + auto pairs = make_non_typed_pairs(); + parser.add_argument(pairs); + } + + auto build_store(argument_parser::v2::base_parser& parser) const -> void { + auto pairs = make_typed_pairs(); + parser.template add_argument(pairs); + } + + auto build_reference(argument_parser::v2::base_parser& parser) const -> void { + auto pairs = make_typed_pairs(); + auto* target = static_cast(m_reference); + auto key = lookup_key(); + + if (target == nullptr) { + throw std::logic_error("reference() was selected without a target."); + } + + parser.template add_argument(pairs); + parser.on_complete([target, key](argument_parser::base_parser const& completed_parser) { + if (auto value = completed_parser.template get_optional(key)) { + *target = value.value(); + } + }); + } + + auto build_parametered_action(argument_parser::v2::base_parser& parser) const -> void { + auto const* typed_action = dynamic_cast const*>(m_action.get()); + if (typed_action == nullptr) { + throw std::logic_error("Stored action is not compatible with the requested parameter type."); + } + + auto pairs = make_typed_pairs(); + pairs[argument_parser::v2::flags::Action] = *typed_action; + parser.template add_argument(pairs); + } + + auto build_nonparametered_action(argument_parser::v2::base_parser& parser) const -> void { + auto const* nonparametered_action = dynamic_cast(m_action.get()); + if (nonparametered_action == nullptr) { + throw std::logic_error("Stored action is not a non-parametered action."); + } + + if (is_positional()) { + auto wrapped_action = argument_parser::helpers::make_parametered_action( + [action = *nonparametered_action](std::string const&) { action.invoke(); }); + auto pairs = make_typed_pairs(); + pairs[argument_parser::v2::flags::Action] = wrapped_action; + parser.template add_argument(pairs); + return; + } + + auto pairs = make_non_typed_pairs(); + pairs[argument_parser::v2::flags::Action] = *nonparametered_action; + parser.add_argument(pairs); + } + + std::string m_short_argument{}; + std::string m_long_argument{}; + std::string m_positional_name{}; + std::optional m_position{}; + std::string m_help_text{}; + bool m_required = false; + std::shared_ptr m_action{}; + void* m_reference = nullptr; + value_mode m_value_mode = value_mode::unresolved; + + template + friend class argument; +}; + +namespace assertions { + struct noop_handler { + void operator()() const {} + }; + + template + struct parameter_sink { + void operator()(T const&) const {} + }; + + template + struct can_use_help_text : std::false_type {}; + + template + struct can_use_help_text().help_text(std::declval()))>> : std::true_type {}; + + template + struct can_use_position : std::false_type {}; + + template + struct can_use_position().position(0))>> : std::true_type {}; + + template + struct can_use_nonparametered_action : std::false_type {}; + + template + struct can_use_nonparametered_action().action(noop_handler{}))>> : std::true_type {}; + + template + struct can_use_parametered_action : std::false_type {}; + + template + struct can_use_parametered_action().template action(parameter_sink{}))>> + : std::true_type {}; + + template + struct can_use_store : std::false_type {}; + + template + struct can_use_store().template store())>> : std::true_type {}; + + template + struct can_use_flag : std::false_type {}; + + template + struct can_use_flag().flag())>> : std::true_type {}; + + template + struct can_use_reference : std::false_type {}; + + template + struct can_use_reference().reference(std::declval()))>> : std::true_type {}; + + using after_help_text = decltype(argument<>::start().help_text("help")); + static_assert(!can_use_help_text::value, "help_text() should be single-use."); + + using after_positional = decltype(argument<>::start().positional("path")); + static_assert(can_use_position::value, "positional() should unlock position()."); + + using after_position = decltype(argument<>::start().positional("path").position(0)); + static_assert(!can_use_position::value, "position() should be single-use."); + + using after_positional_mode_selection = decltype(argument<>::start().positional("path").store<>()); + static_assert(!can_use_flag::value, "flag() should not be available for positional arguments."); + + using after_nonparametered_action = + decltype(argument<>::start().short_argument("v").help_text("verbose").action(noop_handler{})); + static_assert(!can_use_nonparametered_action::value, + "action() should not remain callable after selecting a non-parametered action."); + static_assert(!can_use_parametered_action::value, + "typed action() should also be disabled after selecting a non-parametered action."); + static_assert(!can_use_store::value, + "store() should be mutually exclusive with action()."); + static_assert(!can_use_flag::value, + "flag() should be mutually exclusive with action()."); + static_assert(!can_use_reference::value, + "reference() should be mutually exclusive with action()."); + + using after_parametered_action = + decltype(argument<>::start().positional("count").position(0).action(parameter_sink{})); + static_assert(!can_use_nonparametered_action::value, + "non-parametered action() should be disabled after selecting a typed action."); + static_assert(!can_use_parametered_action::value, + "typed action() should be single-use regardless of the parameter type."); + +} +} + +#endif