From d6662dbb5327df025831e5d093c3016a092af77b Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Tue, 9 Dec 2025 20:52:41 +0800 Subject: [PATCH] feat: basically finish clap parser - basically finish clap parser except ctor. - add skeleton for clap resolver. --- src/yycc/carton/clap/option.cpp | 4 + src/yycc/carton/clap/option.hpp | 1 + src/yycc/carton/clap/parser.cpp | 215 +++++++++++++++++++++++++++++- src/yycc/carton/clap/parser.hpp | 62 ++++++++- src/yycc/carton/clap/resolver.cpp | 87 ++++-------- src/yycc/carton/clap/resolver.hpp | 48 ++++--- src/yycc/carton/clap/variable.cpp | 7 +- src/yycc/carton/clap/variable.hpp | 4 +- src/yycc/env.cpp | 2 +- src/yycc/env.hpp | 4 +- 10 files changed, 350 insertions(+), 84 deletions(-) diff --git a/src/yycc/carton/clap/option.cpp b/src/yycc/carton/clap/option.cpp index 2638ecd..a50f7a3 100644 --- a/src/yycc/carton/clap/option.cpp +++ b/src/yycc/carton/clap/option.cpp @@ -34,6 +34,10 @@ namespace yycc::carton::clap::option { Option::~Option() {} + bool Option::has_value() const { + return this->value_hint.has_value(); + } + std::optional Option::get_short_name() const { return this->short_name; } diff --git a/src/yycc/carton/clap/option.hpp b/src/yycc/carton/clap/option.hpp index d786d7e..792491c 100644 --- a/src/yycc/carton/clap/option.hpp +++ b/src/yycc/carton/clap/option.hpp @@ -20,6 +20,7 @@ namespace yycc::carton::clap::option { YYCC_DEFAULT_COPY_MOVE(Option) public: + bool has_value() const; std::optional get_short_name() const; std::optional get_long_name() const; std::optional get_value_hint() const; diff --git a/src/yycc/carton/clap/parser.cpp b/src/yycc/carton/clap/parser.cpp index 4102d21..5ecaf41 100644 --- a/src/yycc/carton/clap/parser.cpp +++ b/src/yycc/carton/clap/parser.cpp @@ -1,3 +1,216 @@ #include "parser.hpp" +#include "types.hpp" +#include "../../env.hpp" +#include +#include -namespace yycc::carton::clap::parser {} +#define TYPES ::yycc::carton::clap::types +#define APPLICATION ::yycc::carton::clap::application +#define ENV ::yycc::env + +namespace yycc::carton::clap::parser { + +#pragma region Misc + + /// @brief The kind of argument. + enum class ArgumentKind { LongName, ShortName, Value }; + /// @brief Representing a classified argument. + struct ClassifiedArgument { + ClassifiedArgument(const std::u8string_view& arg) { + if (arg.starts_with(TYPES::DOUBLE_DASH)) { + kind = ArgumentKind::LongName; + content = arg.substr(TYPES::DOUBLE_DASH.length()); + } else if (arg.starts_with(TYPES::DASH)) { + kind = ArgumentKind::ShortName; + content = arg.substr(TYPES::DASH.length()); + } else { + kind = ArgumentKind::Value; + content = arg; + } + } + YYCC_DEFAULT_COPY_MOVE(ClassifiedArgument) + + /// @brief The kind of argument. + ArgumentKind kind; + /** + * @brief The data of argument. + * @details For long and short name, it is the body of option, the words removing any leading dash. + * For value, it just the value self. + */ + std::u8string_view content; + }; + + /// @brief The states of parser internal state machine. + enum class ParserState { + Normal, ///< Normal state. Expect an option. + WaitingValue, ///< Waiting an associated value. + }; + /// @brief The state machine context prepared for parser. + struct ParserContext { + ParserContext(const APPLICATION::Application& app) : state(ParserState::Normal), opt_waiting(std::nullopt), app(app), values() {} + YYCC_DEFAULT_COPY_MOVE(ParserContext) + + ParserState state; ///< Current state. + std::optional opt_waiting; ///< The token to the option waiting for associated value. + const APPLICATION::Application& app; ///< Associated application. + std::map> values; ///< The container storing captured options. + }; + +#pragma endregion + +#pragma region Core + + /// @brief Core capture function. + static TYPES::ClapResult>> capture(const APPLICATION::Application& app) { + // Create context. + ParserContext ctx(app); + + // Fetch commandline arguments + // And skip the first argument because it is the path to executable. + // Then start to execute until all arguments are consumed. + for (const auto& arg : ENV::get_args() | std::views::drop(1)) { + // Fetch argument kind + ClassifiedArgument classified_arg(arg); + + // Execute handler according to state. + TYPES::ClapResult rv; + switch (ctx.state) { + case ParserState::Normal: + rv = normal_state(ctx, classified_arg); + break; + case ParserState::WaitingValue: + rv = waiting_value_state(ctx, classified_arg); + break; + } + if (!rv.has_value()) { + return std::unexpected(rv.error()); + } + } + + // If the final state is waiting value, + // it means that the last option lose its asociated value. + // So we need report error. + if (ctx.state == ParserState::WaitingValue && ctx.opt_waiting.has_value()) { + return std::unexpected(TYPES::ClapError::LostValue); + } + + // Return capture result. + return ctx.values; + } + + /// @brief The handler for state machine Normal state. + static TYPES::ClapResult normal_state(ParserContext& ctx, const ClassifiedArgument& arg) { + // Do thing according all registered options. + const auto& options = ctx.app.get_options(); + + // In normal state, we should fetch a name. + // Check whether this name is existing, report error if not. + std::optional opt_token; + switch (arg.kind) { + // We can not meet associated value in normal state. + case ArgumentKind::Value: + return std::unexpected(TYPES::ClapError::UnexpectedValue); + // Fetch token according to long name or short name. + case ArgumentKind::LongName: + opt_token = options.find_long_name(arg.content); + break; + case ArgumentKind::ShortName: + opt_token = options.find_short_name(arg.content); + break; + } + if (!opt_token.has_value()) { + return std::unexpected(TYPES::ClapError::InvalidName); + } + TYPES::Token token = opt_token.value(); + + // Check whether this token has been captured. + // If it is, report error. + if (ctx.values.contains(token)) { + return std::unexpected(TYPES::ClapError::DuplicatedAssign); + } + + // Finally, check whether this token has associated value. + // If it has, set state machine, otherwise push it into capture list directly. + if (options.get_option(token).has_value()) { + ctx.opt_waiting = token; + ctx.state = ParserState::WaitingValue; + } else { + ctx.values.emplace(token, std::nullopt); + } + + return {}; + } + + /// @brief The handler for state machine WaitingValue state. + static TYPES::ClapResult waiting_value_state(ParserContext& ctx, const ClassifiedArgument& arg) { + // Do thing according all registered options. + const auto& options = ctx.app.get_options(); + TYPES::Token token = ctx.opt_waiting.value(); + switch (arg.kind) { + case ArgumentKind::LongName: + case ArgumentKind::ShortName: + // Got option name when waiting associated value. + // It means that previous option lost associated value. + return std::unexpected(TYPES::ClapError::LostValue); + case ArgumentKind::Value: + // We got associated value. + // Push it with option waiting value into capture list. + ctx.values.emplace(token, arg.content); + // Clear token waiting value. + ctx.opt_waiting = std::nullopt; + // Reset state at last. + ctx.state = ParserState::Normal; + } + + return {}; + } + +#pragma endregion + +#pragma region Parser Class + + TYPES::ClapResult Parser::from_user(const APPLICATION::Application& app, const std::vector& args) { + return TYPES::ClapResult(); + } + + TYPES::ClapResult Parser::from_system(const APPLICATION::Application& app) { + return TYPES::ClapResult(); + } + + Parser::Parser(decltype(Parser::values)&& value) : values(std::move(values)) {} + + Parser::~Parser() {} + + bool Parser::has_option(TYPES::Token token) const { + return this->values.contains(token); + } + + TYPES::ClapResult Parser::get_flag_option(TYPES::Token token) const { + auto finder = this->values.find(token); + if (finder == this->values.end()) { + // Not found. + return false; + } else { + // Found. + auto val = finder->second; + if (val.has_value()) throw std::logic_error("get flag option as value option."); + else return true; + } + } + + TYPES::ClapResult Parser::get_raw_value_option(TYPES::Token token) const { + auto finder = this->values.find(token); + if (finder == this->values.end()) { + // Not found. + return std::unexpected(TYPES::ClapError::NotCaptured); + } else { + // Found. + auto val = finder->second; + if (val.has_value()) return std::u8string_view(val.value()); + else throw std::logic_error("get value option as flag option."); + } + } + +#pragma endregion + +} // namespace yycc::carton::clap::parser diff --git a/src/yycc/carton/clap/parser.hpp b/src/yycc/carton/clap/parser.hpp index 9446226..d702882 100644 --- a/src/yycc/carton/clap/parser.hpp +++ b/src/yycc/carton/clap/parser.hpp @@ -1,5 +1,65 @@ #pragma once +#include "../../macro/class_copy_move.hpp" +#include "application.hpp" +#include "validator.hpp" +#include +#include +#include +#include +#include +#include + +#define NS_YYCC_CLAP_TYPES ::yycc::carton::clap::types +#define NS_YYCC_CLAP_APPLICATION ::yycc::carton::clap::application +#define NS_YYCC_CLAP_VALIDATOR ::yycc::carton::clap::validator namespace yycc::carton::clap::parser { -} + class Parser { + private: + /** + * @brief All captured commandline argument. + * @details Key is the token to already registered option. + * Value is the associated value for key token. + * If it is no-value option, the value will be \c std::nullopt. + */ + std::map> values; + + public: + static NS_YYCC_CLAP_TYPES::ClapResult from_user(const NS_YYCC_CLAP_APPLICATION::Application& app, + const std::vector& args); + static NS_YYCC_CLAP_TYPES::ClapResult from_system(const NS_YYCC_CLAP_APPLICATION::Application& app); + + private: + Parser(decltype(Parser::values)&& value); + + public: + ~Parser(); + YYCC_DEFAULT_COPY_MOVE(Parser) + + private: + NS_YYCC_CLAP_TYPES::ClapResult get_raw_value_option(NS_YYCC_CLAP_TYPES::Token token) const; + + public: + bool has_option(NS_YYCC_CLAP_TYPES::Token token) const; + NS_YYCC_CLAP_TYPES::ClapResult get_flag_option(NS_YYCC_CLAP_TYPES::Token token) const; + template + NS_YYCC_CLAP_TYPES::ClapResult> get_value_option( + NS_YYCC_CLAP_TYPES::Token token) const { + auto raw_value = this->get_raw_value_option(token); + if (raw_value.has_value()) { + T validator{}; + auto value = validator.validate(raw_value.value()); + if (value.has_value()) return value.value(); + else return std::unexpected(NS_YYCC_CLAP_TYPES::ClapError::BadCast); + } else { + return std::unexpected(raw_value.error()) + } + } + }; + +} // namespace yycc::carton::clap::parser + +#undef NS_YYCC_CLAP_VALIDATOR +#undef NS_YYCC_CLAP_APPLICATION +#undef NS_YYCC_CLAP_TYPES diff --git a/src/yycc/carton/clap/resolver.cpp b/src/yycc/carton/clap/resolver.cpp index 6bfd614..32ec900 100644 --- a/src/yycc/carton/clap/resolver.cpp +++ b/src/yycc/carton/clap/resolver.cpp @@ -1,81 +1,50 @@ #include "resolver.hpp" #include "types.hpp" -#include #define TYPES ::yycc::carton::clap::types namespace yycc::carton::clap::resolver { -#pragma region Misc - - /// @brief The states of parser internal state machine. - enum class ParserState { - Normal, ///< Normal state. Expect an option. - WaitingValue, ///< Waiting an associated value. - }; - /// @brief The state machine context prepared for parser. - struct ParserContext { - ParserContext() : state(ParserState::Normal), opt_waiting(std::nullopt) {} - YYCC_DEFAULT_COPY_MOVE(ParserContext) - - ParserState state; ///< Current state. - std::optional opt_waiting; ///< The token to the option waiting for associated value. - }; - - /// @brief The kind of argument. - enum class ArgumentKind { LongName, ShortName, Value }; - /// @brief Representing a classified argument. - struct ClassifiedArgument { - ClassifiedArgument(const std::u8string_view& arg) { - if (arg.starts_with(TYPES::DOUBLE_DASH)) { - kind = ArgumentKind::LongName; - content = arg.substr(TYPES::DOUBLE_DASH.length()); - } else if (arg.starts_with(TYPES::DASH)) { - kind = ArgumentKind::ShortName; - content = arg.substr(TYPES::DASH.length()); - } else { - kind = ArgumentKind::Value; - content = arg; - } - } - YYCC_DEFAULT_COPY_MOVE(ClassifiedArgument) - - /// @brief The kind of argument. - ArgumentKind kind; - /** - * @brief The data of argument. - * @details For long and short name, it is the body of option, the words removing any leading dash. - * For value, it just the value self. - */ - std::u8string_view content; - }; - -#pragma endregion - #pragma region Core #pragma endregion -#pragma region Parser Class +#pragma region Resolver Class - Parser::Parser(const NS_YYCC_CLAP_APPLICATION::Application& app) {} + Resolver::Resolver(decltype(Resolver::values)&& values) : values(std::move(values)) {} - Parser::~Parser() {} + Resolver::~Resolver() {} - bool Parser::has_option(TYPES::Token token) const { - // TODO: - return false; + bool Resolver::has_variable(TYPES::Token token) const { + return this->values.contains(token); } - TYPES::ClapResult Parser::get_flag_option(TYPES::Token token) const { - // TODO: - return TYPES::ClapResult(); + TYPES::ClapResult Resolver::get_flag_variable(TYPES::Token token) const { + auto finder = this->values.find(token); + if (finder == this->values.end()) { + // Not found. + return false; + } else { + // Found. + auto val = finder->second; + if (val.has_value()) throw std::logic_error("get flag variable as value variable."); + else return true; + } } - TYPES::ClapResult Parser::get_raw_value_option(TYPES::Token token) const { - // TODO: - return TYPES::ClapResult(); + TYPES::ClapResult Resolver::get_raw_value_variable(TYPES::Token token) const { + auto finder = this->values.find(token); + if (finder == this->values.end()) { + // Not found. + return std::unexpected(TYPES::ClapError::NotCaptured); + } else { + // Found. + auto val = finder->second; + if (val.has_value()) return std::u8string_view(val.value()); + else throw std::logic_error("get value variable as flag variable."); + } } + #pragma endregion } // namespace yycc::carton::clap::resolver diff --git a/src/yycc/carton/clap/resolver.hpp b/src/yycc/carton/clap/resolver.hpp index 50488b8..86ec807 100644 --- a/src/yycc/carton/clap/resolver.hpp +++ b/src/yycc/carton/clap/resolver.hpp @@ -2,9 +2,12 @@ #include "../../macro/class_copy_move.hpp" #include "application.hpp" #include "validator.hpp" +#include +#include #include #include #include +#include #define NS_YYCC_CLAP_TYPES ::yycc::carton::clap::types #define NS_YYCC_CLAP_APPLICATION ::yycc::carton::clap::application @@ -12,22 +15,38 @@ namespace yycc::carton::clap::resolver { - class Parser { + class Resolver { + private: + /** + * @brief All captured environment variable. + * @details Key is the token to already registered variable. + * Value is the associated value for key token. + * If it is no-value variable, the value will be \c std::nullopt. + */ + std::map> values; + public: - Parser(const NS_YYCC_CLAP_APPLICATION::Application& app); - ~Parser(); - YYCC_DEFAULT_COPY_MOVE(Parser) + static NS_YYCC_CLAP_TYPES::ClapResult from_user( + const NS_YYCC_CLAP_APPLICATION::Application& app, const std::vector>& vars); + static NS_YYCC_CLAP_TYPES::ClapResult from_system(const NS_YYCC_CLAP_APPLICATION::Application& app); private: - NS_YYCC_CLAP_TYPES::ClapResult get_raw_value_option(NS_YYCC_CLAP_TYPES::Token token) const; + Resolver(decltype(Resolver::values)&& values); public: - bool has_option(NS_YYCC_CLAP_TYPES::Token token) const; - NS_YYCC_CLAP_TYPES::ClapResult get_flag_option(NS_YYCC_CLAP_TYPES::Token token) const; + ~Resolver(); + YYCC_DEFAULT_COPY_MOVE(Resolver) + + private: + NS_YYCC_CLAP_TYPES::ClapResult get_raw_value_variable(NS_YYCC_CLAP_TYPES::Token token) const; + + public: + bool has_variable(NS_YYCC_CLAP_TYPES::Token token) const; + NS_YYCC_CLAP_TYPES::ClapResult get_flag_variable(NS_YYCC_CLAP_TYPES::Token token) const; template - NS_YYCC_CLAP_TYPES::ClapResult> get_value_option( + NS_YYCC_CLAP_TYPES::ClapResult> get_value_variable( NS_YYCC_CLAP_TYPES::Token token) const { - auto raw_value = this->get_raw_value_option(token); + auto raw_value = this->get_raw_value_variable(token); if (raw_value.has_value()) { T validator{}; auto value = validator.validate(raw_value.value()); @@ -37,17 +56,10 @@ namespace yycc::carton::clap::resolver { return std::unexpected(raw_value.error()) } } - - private: - /** - * @brief All captured commandline argument. - * @details Key is the token to already registered option. - * Value is the associated value for key token. - * If it is no-value option, the value will be \c std::nullopt. - */ - std::map> values; }; } // namespace yycc::carton::clap::resolver +#undef NS_YYCC_CLAP_VALIDATOR +#undef NS_YYCC_CLAP_APPLICATION #undef NS_YYCC_CLAP_TYPES diff --git a/src/yycc/carton/clap/variable.cpp b/src/yycc/carton/clap/variable.cpp index 8459294..892e77d 100644 --- a/src/yycc/carton/clap/variable.cpp +++ b/src/yycc/carton/clap/variable.cpp @@ -9,7 +9,8 @@ namespace yycc::carton::clap::variable { #pragma region Variable - Variable::Variable(const std::u8string_view &name, const std::u8string_view &description) : name(name), description(description) { + Variable::Variable(const std::u8string_view &name, const std::u8string_view &description, bool care_value) : + name(name), description(description), care_value(care_value) { if (name.empty()) { throw std::logic_error("the name of variable should not be empty"); } @@ -17,6 +18,10 @@ namespace yycc::carton::clap::variable { Variable::~Variable() {} + bool Variable::is_care_value() const { + return this->care_value; + } + std::u8string_view Variable::get_name() const { return this->name; } diff --git a/src/yycc/carton/clap/variable.hpp b/src/yycc/carton/clap/variable.hpp index e3a0eb4..0bb0282 100644 --- a/src/yycc/carton/clap/variable.hpp +++ b/src/yycc/carton/clap/variable.hpp @@ -12,15 +12,17 @@ namespace yycc::carton::clap::variable { class Variable { public: - Variable(const std::u8string_view& name, const std::u8string_view& description); + Variable(const std::u8string_view& name, const std::u8string_view& description, bool care_value); ~Variable(); YYCC_DEFAULT_COPY_MOVE(Variable) public: + bool is_care_value() const; std::u8string_view get_name() const; std::u8string_view get_description() const; private: + bool care_value; std::u8string name; std::u8string description; }; diff --git a/src/yycc/env.cpp b/src/yycc/env.cpp index 23d7ca3..54af0a0 100644 --- a/src/yycc/env.cpp +++ b/src/yycc/env.cpp @@ -146,7 +146,7 @@ namespace yycc::env { #endif } - VarResult> get_vars() { + std::vector get_vars() { // TODO: finish this function according to Rust implementation. // Considering whether replace return value with an iterator. throw std::logic_error("not implemented"); diff --git a/src/yycc/env.hpp b/src/yycc/env.hpp index 617c461..5ff4f22 100644 --- a/src/yycc/env.hpp +++ b/src/yycc/env.hpp @@ -72,7 +72,7 @@ namespace yycc::env { * for all the environment variables of the current process. * @return The list holding all variables. */ - VarResult> get_vars(); + std::vector get_vars(); #pragma endregion @@ -131,7 +131,7 @@ namespace yycc::env { * @brief Returns the arguments that this program was started with (normally passed via the command line). * @return The list holding all argument one by one. */ - ArgResult> get_args(); + std::vector get_args(); #pragma endregion