refactor(parser): replace variant-based actions with polymorphic interface

Simplify action handling by introducing a polymorphic base class for actions
Remove std::variant usage and improve type safety with proper cloning
Clean up unused includes and test code in main.cpp
This commit is contained in:
2025-10-04 00:29:00 +04:00
parent 179f5e5d1b
commit 6c3adc3dda
2 changed files with 75 additions and 44 deletions

View File

@@ -12,64 +12,106 @@
#include <sstream> #include <sstream>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <type_traits>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <variant>
namespace argument_parser { namespace argument_parser {
template <typename T> class action_base {
class parametered_action {
public: public:
// Type alias to expose the parameter type, crucial for the visitor. virtual ~action_base() = default;
virtual bool expects_parameter() const = 0;
virtual void invoke() const = 0;
virtual void invoke_with_parameter(const std::string& param) const = 0;
virtual std::unique_ptr<action_base> clone() const = 0;
};
template <typename T>
class parametered_action : public action_base {
public:
explicit parametered_action(std::function<void(const T&)> const& handler) : handler(handler) {}
using parameter_type = T; using parameter_type = T;
parametered_action(std::function<void(const T&)> const& function) : handler(function) {}
void invoke(const T& arg) const { void invoke(const T& arg) const {
handler(arg); handler(arg);
} }
bool expects_parameter() const override { return true; }
void invoke() const override {
throw std::runtime_error("Parametered action requires a parameter");
}
void invoke_with_parameter(const std::string& param) const override {
T parsed_value = parsing_traits::parser_trait<T>::parse(param);
invoke(parsed_value);
}
std::unique_ptr<action_base> clone() const override {
return std::make_unique<parametered_action<T>>(handler);
}
private: private:
std::function<void(const T&)> handler; std::function<void(const T&)> handler;
}; };
class non_parametered_action { class non_parametered_action : public action_base {
public: public:
non_parametered_action(std::function<void()> const& function) : handler(function) {} explicit non_parametered_action(std::function<void()> const& handler) : handler(handler) {}
void invoke() const { void invoke() const override {
handler(); handler();
} }
bool expects_parameter() const override { return false; }
void invoke_with_parameter(const std::string& param) const override {
invoke();
}
std::unique_ptr<action_base> clone() const override {
return std::make_unique<non_parametered_action>(handler);
}
private: private:
std::function<void()> handler; std::function<void()> handler;
}; };
using action_variant = std::variant<
non_parametered_action,
parametered_action<std::string>,
parametered_action<int>,
parametered_action<float>,
parametered_action<bool>
>;
class base_parser; class base_parser;
class argument { class argument {
public: public:
argument() : id(0), name(), required(false), invoked(false), action(non_parametered_action([](){})) {} argument() : id(0), name(), required(false), invoked(false), action(std::make_unique<non_parametered_action>([](){})) {}
template <typename ActionType> template <typename ActionType>
argument(int id, std::string const& name, ActionType const& action) argument(int id, std::string const& name, ActionType const& action)
: id(id), name(name), action(action), required(false), invoked(false) {} : id(id), name(name), action(action.clone()), required(false), invoked(false) {}
argument(const argument& other)
: id(other.id), name(other.name), action(other.action->clone()),
required(other.required), invoked(other.invoked), help_text(other.help_text) {}
argument& operator=(const argument& other) {
if (this != &other) {
id = other.id;
name = other.name;
action = other.action->clone();
required = other.required;
invoked = other.invoked;
help_text = other.help_text;
}
return *this;
}
argument(argument&& other) noexcept = default;
argument& operator=(argument&& other) noexcept = default;
bool is_required() const { return required; } bool is_required() const { return required; }
std::string get_name() const { return name; } std::string get_name() const { return name; }
bool is_invoked() const { return invoked; } bool is_invoked() const { return invoked; }
bool expects_parameter() const { bool expects_parameter() const {
return !std::holds_alternative<non_parametered_action>(action); return action->expects_parameter();
} }
private: private:
@@ -81,7 +123,7 @@ namespace argument_parser {
int id; int id;
std::string name; std::string name;
action_variant action; std::unique_ptr<action_base> action;
bool required; bool required;
bool invoked; bool invoked;
std::string help_text; std::string help_text;
@@ -138,24 +180,15 @@ namespace argument_parser {
try { try {
argument& corresponding_argument = get_argument(extracted); argument& corresponding_argument = get_argument(extracted);
std::visit([&](auto&& action) { if (corresponding_argument.expects_parameter()) {
using ActionType = std::decay_t<decltype(action)>; if (convention_type->requires_next_token() && (it + 1) == parsed_arguments.end()) {
throw std::runtime_error("expected value for argument " + extracted.second);
if constexpr (std::is_same_v<ActionType, non_parametered_action>) {
action.invoke();
} else {
if (convention_type->requires_next_token() && (it + 1) == parsed_arguments.end()) {
throw std::runtime_error("expected value for argument " + extracted.second);
}
auto value_raw = convention_type->requires_next_token() ? *(++it) : convention_type->extract_value(*it);
using ParamType = typename ActionType::parameter_type;
auto value = parsing_traits::parser_trait<ParamType>::parse(value_raw);
action.invoke(value);
} }
}, corresponding_argument.action); auto value_raw = convention_type->requires_next_token() ? *(++it) : convention_type->extract_value(*it);
corresponding_argument.action->invoke_with_parameter(value_raw);
} else {
corresponding_argument.action->invoke();
}
corresponding_argument.set_invoked(true); corresponding_argument.set_invoked(true);
arg_correctly_handled = true; arg_correctly_handled = true;

View File

@@ -1,12 +1,9 @@
#include "argument_parser.hpp"
#include "fake_parser.hpp"
#include <argparse> #include <argparse>
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <memory> #include <memory>
#include <utility> #include <utility>
#include <vector>
using namespace argument_parser::conventions; using namespace argument_parser::conventions;
@@ -108,8 +105,9 @@ auto make_grep_action(argument_parser::base_parser& parser) {
} }
int main() { int main() {
std::vector<std::string> fake_args = { "-g", "add", "-f", "src/main.cpp", "-ep", "1,2" }; // std::vector<std::string> fake_args = { "-g", "add", "-f", "src/main.cpp", "-ep", "1,2" };
auto parser = argument_parser::fake_parser{"test", std::move(fake_args)}; // auto parser = argument_parser::fake_parser{"test", std::move(fake_args)};
auto parser = argument_parser::parser{};
auto [file, grep] = make_grep_action(parser); auto [file, grep] = make_grep_action(parser);
parser.add_argument("e", "echo", "echoes given variable", echo, false); parser.add_argument("e", "echo", "echoes given variable", echo, false);
parser.add_argument("ep", "echo-point", "echoes given point", echo_point, false); parser.add_argument("ep", "echo-point", "echoes given point", echo_point, false);