From 179f5e5d1bb0f6c82a59cb0d68c754e65248e0b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abd=C3=BCssamet=20ERSOYLU?= Date: Sat, 4 Oct 2025 00:12:23 +0400 Subject: [PATCH] better type safety for action types. --- CMakeLists.txt | 1 + compile_commands.json | 8 + include/parser/argument_parser.hpp | 611 +++++++---------------- include/parser/parsing_traits/traits.hpp | 52 ++ src/main.cpp | 25 +- 5 files changed, 262 insertions(+), 435 deletions(-) create mode 100644 compile_commands.json create mode 100644 include/parser/parsing_traits/traits.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 68fa7cc..b24122e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,5 +14,6 @@ include_directories(include/parser) include_directories(include/conventions) include_directories(include/conventions/implementations) include_directories(include/parser/platform_headers) +include_directories(include/parser/parsing_traits) add_executable(test src/main.cpp) \ No newline at end of file diff --git a/compile_commands.json b/compile_commands.json new file mode 100644 index 0000000..7ec9780 --- /dev/null +++ b/compile_commands.json @@ -0,0 +1,8 @@ +[ +{ + "directory": "/Users/killua/Projects/argument-parser/build", + "command": "/usr/bin/clang++ -I/Users/killua/Projects/argument-parser/include -I/Users/killua/Projects/argument-parser/include/parser -I/Users/killua/Projects/argument-parser/include/conventions -I/Users/killua/Projects/argument-parser/include/conventions/implementations -I/Users/killua/Projects/argument-parser/include/parser/platform_headers -I/Users/killua/Projects/argument-parser/include/parser/parsing_traits -g -std=gnu++2b -arch arm64 -o CMakeFiles/test.dir/src/main.cpp.o -c /Users/killua/Projects/argument-parser/src/main.cpp", + "file": "/Users/killua/Projects/argument-parser/src/main.cpp", + "output": "/Users/killua/Projects/argument-parser/build/CMakeFiles/test.dir/src/main.cpp.o" +} +] diff --git a/include/parser/argument_parser.hpp b/include/parser/argument_parser.hpp index a0fb194..bb97feb 100644 --- a/include/parser/argument_parser.hpp +++ b/include/parser/argument_parser.hpp @@ -1,392 +1,209 @@ #pragma once +#ifndef ARGUMENT_PARSER_HPP +#define ARGUMENT_PARSER_HPP + +#include #include #include #include #include #include #include -#include #include #include -#include +#include +#include #include #include -#ifndef ARGUMENT_PARSER_HPP -#define ARGUMENT_PARSER_HPP - -#include -#include - +#include namespace argument_parser { - namespace function { - template - concept no_param = std::is_invocable_r_v; - - template - concept with_param = std::is_invocable_r_v; - - template - concept string_parameter = with_param; + template + class parametered_action { + public: + // Type alias to expose the parameter type, crucial for the visitor. + using parameter_type = T; - template - concept integral_parameter = with_param && std::is_integral_v; + parametered_action(std::function const& function) : handler(function) {} - template - using parametered_action = std::function; - - using non_parametered_action = std::function; - } - - namespace type { - enum class parameter_type { - NONE, - INT, - FLOAT, - BOOL, - STRING - }; - - template - struct param_type_map; - - template<> struct param_type_map { static constexpr parameter_type value = parameter_type::INT; }; - template<> struct param_type_map { static constexpr parameter_type value = parameter_type::FLOAT; }; - template<> struct param_type_map { static constexpr parameter_type value = parameter_type::BOOL; }; - template<> struct param_type_map { static constexpr parameter_type value = parameter_type::STRING; }; - - template - concept supported_parameter_type = requires { - { param_type_map::value } -> std::convertible_to; - }; - } - - namespace error { - enum class type { - missing_parameter, - key_redefined - }; - - constexpr type missing_parameter = type::missing_parameter; - constexpr type key_redefined = type::key_redefined; - } - - template class parametered_action; - class non_parametered_action; - - class base_action {}; - - template - class action_impl : public base_action { - public: - void invoke() { - static_cast(this)->invoke_impl(); - } - - template - void invoke(T_ const& var) { - if (!validate_type(parametered_action{})) - throw std::runtime_error("this action does not take any parameter, or given parameters do not match the required parameter type."); - static_cast(this)->invoke_impl(var); - } - - std::type_info const& get_type() const { - return static_cast(this)->get_type_impl(); + void invoke(const T& arg) const { + handler(arg); } - template - bool validate_type(action_impl const& other) { - return get_type() == other.get_type(); - } - }; - - template - class parametered_action : public action_impl> { - public: - parametered_action() {} - parametered_action(function::parametered_action const& function) : handler(function) {} - template - static std::type_info const& type() { - return typeid(parametered_action); - } + private: + std::function handler; + }; - std::type_info const& get_type_impl() const { - return type(); - } - private: - function::parametered_action handler; - template - void invoke_impl(arg_type const& arg) { - handler(arg); - } - - friend class action_impl>; - }; - - class non_parametered_action : public action_impl { - public: + class non_parametered_action { + public: non_parametered_action(std::function const& function) : handler(function) {} - static std::type_info const& type() { - return typeid(non_parametered_action); - } - - std::type_info const& get_type_impl() const { - return type(); - } - private: - std::function handler; - void invoke_impl() { - this->handler(); - } - - friend class action_impl; - }; - - class helpers { - public: - template - static std::shared_ptr> make_parametered_action_ptr(function::parametered_action const& function) { - return std::make_shared>(parametered_action(function)); - } - template - static parametered_action make_parametered_action(function::parametered_action const& function) { - return parametered_action(function); + void invoke() const { + handler(); } - static non_parametered_action make_non_parametered_action(function::non_parametered_action const& function) { - return non_parametered_action(function); - } - - static std::shared_ptr make_non_parametered_action_ptr(function::non_parametered_action const& function) { - return std::make_shared(non_parametered_action(function)); - } + private: + std::function handler; }; + using action_variant = std::variant< + non_parametered_action, + parametered_action, + parametered_action, + parametered_action, + parametered_action + >; + + class base_parser; + class argument { - using parameter_type = type::parameter_type; - public: - argument() : name(), id(0) {} - argument(std::string const& name, int id) : name(name), id(id) {} + public: + argument() : id(0), name(), required(false), invoked(false), action(non_parametered_action([](){})) {} - // parametered action group - template - argument(int id, std::string const& name, parametered_action const& action) : argument(name, id) { - type = extract_type(); - this->action = std::make_shared>(action); - } + template + argument(int id, std::string const& name, ActionType const& action) + : id(id), name(name), action(action), required(false), invoked(false) {} - template - argument(int id, std::string const& name, std::shared_ptr> const& action) : argument(name, id) { - type = extract_type(); - this->action = action; - } - - template - argument(std::string const& name, parametered_action const& action) : argument(name, 0) { - type = extract_type(); - this->action = std::make_shared>(action); - } - - template - argument(std::string const& name, std::shared_ptr> const& action) : argument(name, 0) { - type = extract_type(); - this->action = action; - } - - // non parametered action group - argument(int id, std::string const& name, non_parametered_action const& action) : argument(name, id) { - type = parameter_type::NONE; - this->action = std::make_shared(action); - } - - argument(int id, std::string const& name, std::shared_ptr const& action) : argument(name, id) { - type = parameter_type::NONE; - this->action = action; - } - - argument(std::string const& name, non_parametered_action const& action) : argument(name, 0) { - type = parameter_type::NONE; - this->action = std::make_shared(action); - } - - argument(std::string const& name, std::shared_ptr const& action) : argument(name, 0) { - type = parameter_type::NONE; - this->action = action; - } - - argument(argument const& other) : - id(other.id), - name(other.name), - action(other.action), - required(other.required), - type(other.type), - help_text(other.help_text), - invoked(other.invoked) - {} - argument& operator=(argument const& other) { - if (this == &other) return *this; - this->~argument(); - new(this) argument{other}; - return *this; - } - - void toggle_required() { - required = !required; - } - - - bool is_required() const { - return required; - } - - std::string get_name() const { - return name; - } - - parameter_type expected_type() const { - return type; - } + bool is_required() const { return required; } + std::string get_name() const { return name; } + bool is_invoked() const { return invoked; } bool expects_parameter() const { - return type != parameter_type::NONE; + return !std::holds_alternative(action); } - bool is_invoked() const { - return invoked; - } + private: + void set_required(bool val) { required = val; } + void set_invoked(bool val) { invoked = val; } + void set_help_text(std::string const& text) { help_text = text; } - template - void invoke(T_ const& argument) { - if (type == parameter_type::NONE) throw std::runtime_error("this argument does not expect any parameter."); - invoked = true; - static_cast>*>(action.get())->invoke(argument); - } + friend class base_parser; - void invoke() { - if (type != parameter_type::NONE) throw std::runtime_error("this argument expects parameter."); - invoked = true; - static_cast*>(action.get())->invoke(); - } + int id; + std::string name; + action_variant action; + bool required; + bool invoked; + std::string help_text; + }; - private: - void set_required(bool val) { - required = val; - } - - void set_help_text(std::string const& help_text) { - this->help_text = help_text; - } - argument(type::parameter_type const& parameter_type) : id(0), name(), type(parameter_type) {} - friend class base_parser; - template - constexpr type::parameter_type extract_type() { - if constexpr (std::is_same_v) - return type::parameter_type::INT; - else if constexpr (std::is_same_v || std::is_convertible_v) - return type::parameter_type::FLOAT; - else if constexpr (std::is_same_v) - return type::parameter_type::BOOL; - else if constexpr (std::is_same_v || std::is_convertible_v) - return type::parameter_type::STRING; - } - - - int const id; - std::string const name; - std::shared_ptr action; - bool required = false; - parameter_type type; - bool invoked = false; - std::string help_text; - }; - - class base_parser { - public: - template - void add_argument(std::string const& short_arg, std::string const& long_arg, parametered_action const& action) { - base_add_argument(short_arg, long_arg, long_arg, action, false); - } - - template - void add_argument(std::string const& short_arg, std::string const& long_arg, parametered_action const& action, bool required) { - base_add_argument(short_arg, long_arg, long_arg, action, required); - } - - template - void add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, parametered_action const& action, bool required) { - base_add_argument(short_arg, long_arg, help_text, action, required); - } - - void add_argument(std::string const& short_arg, std::string const& long_arg, non_parametered_action const& action) { - base_add_argument(short_arg, long_arg, long_arg, action, false); - } - - void add_argument(std::string const& short_arg, std::string const& long_arg, non_parametered_action const& action, bool required) { - base_add_argument(short_arg, long_arg, long_arg, action, required); + class base_parser { + public: + template + void add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, parametered_action const& action, bool required) { + base_add_argument(short_arg, long_arg, help_text, action, required); } void add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, non_parametered_action const& action, bool required) { - base_add_argument(short_arg, long_arg, help_text, action, required); + base_add_argument(short_arg, long_arg, help_text, action, required); } + std::string build_help_text(std::initializer_list convention_types) { + std::stringstream ss; + ss << "Usage: " << program_name << " [OPTIONS]...\n"; + + for (auto const& [id, arg] : argument_map) { + auto short_arg = reverse_short_arguments[id]; + auto long_arg = reverse_long_arguments[id]; + ss << "\t"; + ss << "-" << short_arg << ", --" << long_arg; + ss << "\t\t" << arg.help_text << "\n"; + } + return ss.str(); + } + + argument& get_argument(conventions::parsed_argument const& arg) { + if (arg.first == conventions::argument_type::LONG) { + auto long_pos = long_arguments.find(arg.second); + if (long_pos != long_arguments.end()) return argument_map.at(long_pos->second); + } else if (arg.first == conventions::argument_type::SHORT) { + auto short_pos = short_arguments.find(arg.second); + if (short_pos != short_arguments.end()) return argument_map.at(short_pos->second); + } + throw std::runtime_error("Unknown argument: " + arg.second); + } + void handle_arguments(std::initializer_list convention_types) { for (auto it = parsed_arguments.begin(); it != parsed_arguments.end(); ++it) { - std::stringstream ss; - bool arg_correctly_handled = false; - for(auto const& convention_type : convention_types) { - auto extracted = convention_type->get_argument(*it); + std::stringstream error_stream; + bool arg_correctly_handled = false; + + for (auto const& convention_type : convention_types) { + auto extracted = convention_type->get_argument(*it); if (extracted.first == conventions::argument_type::ERROR) { - ss << "Convention \"" << convention_type->name() << "\" failed with: " << extracted.second << "\n"; + error_stream << "Convention \"" << convention_type->name() << "\" failed with: " << extracted.second << "\n"; continue; } - + try { - argument& coresponding_argument = get_argument(extracted); - if (coresponding_argument.expects_parameter()) { - auto type = coresponding_argument.expected_type(); - if (convention_type->requires_next_token() and (it + 1) == parsed_arguments.end()) - throw std::runtime_error("expected value for argument " + extracted.second + " argument"); - auto value_raw = convention_type->requires_next_token() ? *(++it) : convention_type->extract_value(*it); - if (type == type::parameter_type::BOOL) { - auto value = parse_bool(value_raw); - coresponding_argument.invoke(value); - arg_correctly_handled = true; - break; + argument& corresponding_argument = get_argument(extracted); + + std::visit([&](auto&& action) { + using ActionType = std::decay_t; + + if constexpr (std::is_same_v) { + 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::parse(value_raw); + + action.invoke(value); } - else if (type == type::parameter_type::INT) { - auto value = parse_int(value_raw); - coresponding_argument.invoke(value); - arg_correctly_handled = true; - break; - } - else if (type == type::parameter_type::FLOAT) { - auto value = parse_float(value_raw); - coresponding_argument.invoke(value); - arg_correctly_handled = true; - break; - } - else if (type == type::parameter_type::STRING) { - coresponding_argument.invoke(value_raw); - arg_correctly_handled = true; - break; - } - } - else { - coresponding_argument.invoke(); - arg_correctly_handled = true; - break; - } - } catch(std::runtime_error e) { - ss << "Convention \"" << convention_type->name() << "\" failed with: " << e.what() << "\n"; - continue; + }, corresponding_argument.action); + + corresponding_argument.set_invoked(true); + arg_correctly_handled = true; + break; // Convention succeeded, move to the next argument token + + } catch (const std::runtime_error& e) { + error_stream << "Convention \"" << convention_type->name() << "\" failed with: " << e.what() << "\n"; } } + if (!arg_correctly_handled) { - throw std::runtime_error("All trials for argument: \n\t\"" + *it + "\"\n failed with: \n" + ss.str()); + throw std::runtime_error("All trials for argument: \n\t\"" + *it + "\"\n failed with: \n" + error_stream.str()); } } + check_for_required_arguments(convention_types); + } + void display_help(std::initializer_list convention_types) { + std::cout << build_help_text(convention_types); + } + + protected: + base_parser() = default; + + std::string program_name; + std::vector parsed_arguments; + + private: + template + void base_add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, ActionType const& action, bool required) { + if (short_arguments.count(short_arg) || long_arguments.count(long_arg)) { + throw std::runtime_error("The key already exists!"); + } + + int id = id_counter.fetch_add(1); + + argument arg(id, short_arg + "|" + long_arg, action); + arg.set_required(required); + arg.set_help_text(help_text); + + argument_map[id] = arg; + short_arguments[short_arg] = id; + reverse_short_arguments[id] = short_arg; + long_arguments[long_arg] = id; + reverse_long_arguments[id] = long_arg; + } + + void check_for_required_arguments(std::initializer_list convention_types) { std::vector> required_args; for (auto const& [_, arg] : argument_map) { if (arg.is_required() and not arg.is_invoked()) { @@ -407,104 +224,30 @@ namespace argument_parser { } } - void display_help(std::initializer_list convention_types) { - std::cout << build_help_text(convention_types); - } + inline static std::atomic_int id_counter = 0; + + std::unordered_map argument_map; + std::unordered_map short_arguments; + std::unordered_map reverse_short_arguments; + std::unordered_map long_arguments; + std::unordered_map reverse_long_arguments; - std::string build_help_text(std::initializer_list convention_types) { - std::stringstream ss; - - ss << "Usage: " << program_name - << " [OPTIONS]... [ARGS]...\n"; - - - for (auto const& [id, arg] : argument_map) { - auto short_arg = reverse_short_arguments[id]; - auto long_arg = reverse_long_arguments[id]; - ss << "\t"; - for (auto const& convention : convention_types) { - ss << convention->short_prec() << short_arg << ", "; - } - bool first = true; - for (auto const& convention : convention_types) { - ss << (first ? "" : ", ") << convention->long_prec() << long_arg; - first = false; - } - - ss << "\t\t" << arg.help_text << "\n"; - } - - return ss.str(); - } - - private: - bool parse_bool(std::string const& val) { - if (val == "t" || val == "true" || val == "1") return true; - if (val == "f" || val == "false" || val == "0") return false; - throw std::runtime_error("expected boolean parsable, got:" + val); - } - - int parse_int(std::string const& val) { - return std::stoi(val); - } - - int parse_float(std::string const& val) { - return std::stof(val); - } - - base_parser() : argument_map(), short_arguments(), long_arguments(), reverse_long_arguments(), reverse_short_arguments(), parsed_arguments(), program_name() {} - - template - void base_add_argument(std::string const& short_arg, std::string const& long_arg, std::string const& help_text, T_ const& action, bool required) { - if (short_arguments.find(short_arg) != short_arguments.end() || long_arguments.find(long_arg) != long_arguments.end()) { - throw std::runtime_error("The key already exists!"); - } - - int id = __id_ref.fetch_add(1); - - auto arg = argument(id, short_arg + "|" + long_arg, action);; - arg.set_required(required); - arg.set_help_text(help_text); - - argument_map[id] = arg; - short_arguments[short_arg] = id; - reverse_short_arguments[id] = short_arg; - long_arguments[long_arg] = id; - reverse_long_arguments[id] = long_arg; - } - - public: - argument& get_argument(conventions::parsed_argument const& arg) { - if (arg.first == conventions::argument_type::LONG) { - auto long_pos = long_arguments.find(arg.second); - if (long_pos != long_arguments.end()) return argument_map[long_pos->second]; - } else if (arg.first == conventions::argument_type::SHORT) { - auto short_pos = short_arguments.find(arg.second); - if (short_pos != short_arguments.end()) return argument_map[short_pos->second]; - } else if (arg.first == conventions::argument_type::ERROR) { - throw std::runtime_error{arg.second}; - } - throw std::runtime_error("Unknown argument: " + arg.second); - } - - private: - std::string program_name; - - inline static std::atomic_int __id_ref = std::atomic_int(0); - - std::unordered_map argument_map; - std::unordered_map short_arguments; - std::unordered_map reverse_short_arguments; - std::unordered_map long_arguments; - std::unordered_map reverse_long_arguments; - - std::vector parsed_arguments; - - friend class linux_parser; - friend class windows_parser; - friend class macos_parser; - friend class fake_parser; + friend class linux_parser; + friend class windows_parser; + friend class macos_parser; + friend class fake_parser; }; + + namespace helpers { + template + static parametered_action make_parametered_action(std::function const& function) { + return parametered_action(function); + } + + static non_parametered_action make_non_parametered_action(std::function const& function) { + return non_parametered_action(function); + } + } } -#endif // ARGUMENT_PARSER_HPP +#endif // ARGUMENT_PARSER_HPP \ No newline at end of file diff --git a/include/parser/parsing_traits/traits.hpp b/include/parser/parsing_traits/traits.hpp new file mode 100644 index 0000000..5f52a3b --- /dev/null +++ b/include/parser/parsing_traits/traits.hpp @@ -0,0 +1,52 @@ +#pragma once +#ifndef PARSING_TRAITS_HPP +#define PARSING_TRAITS_HPP + +#include + +namespace argument_parser::parsing_traits { + template + struct parser_trait { + using type = T_; + static T_ parse(const std::string& input); + }; + + template <> + struct parser_trait { + static std::string parse(const std::string& input) { + return input; + } + }; + + template<> + struct parser_trait { + static bool parse(const std::string& input) { + if (input == "t" || input == "true" || input == "1") return true; + if (input == "f" || input == "false" || input == "0") return false; + throw std::runtime_error("Invalid boolean value: " + input); + } + }; + + template <> + struct parser_trait { + static int parse(const std::string& input) { + return std::stoi(input); + } + }; + + template <> + struct parser_trait { + static float parse(const std::string& input) { + return std::stof(input); + } + }; + + template <> + struct parser_trait { + static double parse(const std::string& input) { + return std::stod(input); + } + }; +} + +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 72f70ce..02d8af4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,23 @@ using namespace argument_parser::conventions; +struct Point { + int x, y; +}; + +template<> +struct argument_parser::parsing_traits::parser_trait { + static Point parse(const std::string& input) { + auto comma_pos = input.find(','); + if (comma_pos == std::string::npos) { + throw std::runtime_error("Invalid Point format. Expected 'x,y'."); + } + int x = std::stoi(input.substr(0, comma_pos)); + int y = std::stoi(input.substr(comma_pos + 1)); + return {x, y}; + } +}; + const std::initializer_list conventions = { &gnu_argument_convention, &gnu_equal_argument_convention @@ -19,6 +36,11 @@ const auto echo = argument_parser::helpers::make_parametered_action std::cout << text << std::endl; }); + +const auto echo_point = argument_parser::helpers::make_parametered_action([](Point const& point) { + std::cout << "Point(" << point.x << ", " << point.y << ")" << std::endl; +}); + const auto cat = argument_parser::helpers::make_parametered_action([](std::string const& file_name) { std::ifstream file(file_name); if (!file.is_open()) { @@ -86,10 +108,11 @@ auto make_grep_action(argument_parser::base_parser& parser) { } int main() { - std::vector fake_args = { "-g", "add", "-f", "src/main.cpp" }; + std::vector fake_args = { "-g", "add", "-f", "src/main.cpp", "-ep", "1,2" }; auto parser = argument_parser::fake_parser{"test", std::move(fake_args)}; auto [file, grep] = make_grep_action(parser); 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("f", "file", "File to grep, required only if using grep", file, false); parser.add_argument("g", "grep", "Grep pattern, required only if using grep", grep, false); parser.add_argument("c", "cat", "Prints the content of the file", cat, false);