1
0

feat: basically finish clap parser

- basically finish clap parser except ctor.
- add skeleton for clap resolver.
This commit is contained in:
2025-12-09 20:52:41 +08:00
parent eb9e576d33
commit d6662dbb53
10 changed files with 350 additions and 84 deletions

View File

@ -34,6 +34,10 @@ namespace yycc::carton::clap::option {
Option::~Option() {}
bool Option::has_value() const {
return this->value_hint.has_value();
}
std::optional<std::u8string_view> Option::get_short_name() const {
return this->short_name;
}

View File

@ -20,6 +20,7 @@ namespace yycc::carton::clap::option {
YYCC_DEFAULT_COPY_MOVE(Option)
public:
bool has_value() const;
std::optional<std::u8string_view> get_short_name() const;
std::optional<std::u8string_view> get_long_name() const;
std::optional<std::u8string_view> get_value_hint() const;

View File

@ -1,3 +1,216 @@
#include "parser.hpp"
#include "types.hpp"
#include "../../env.hpp"
#include <stdexcept>
#include <ranges>
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<TYPES::Token> opt_waiting; ///< The token to the option waiting for associated value.
const APPLICATION::Application& app; ///< Associated application.
std::map<TYPES::Token, std::optional<std::u8string>> values; ///< The container storing captured options.
};
#pragma endregion
#pragma region Core
/// @brief Core capture function.
static TYPES::ClapResult<std::map<TYPES::Token, std::optional<std::u8string>>> 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<void> 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<void> 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<TYPES::Token> 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<void> 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> Parser::from_user(const APPLICATION::Application& app, const std::vector<std::u8string_view>& args) {
return TYPES::ClapResult<Parser>();
}
TYPES::ClapResult<Parser> Parser::from_system(const APPLICATION::Application& app) {
return TYPES::ClapResult<Parser>();
}
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<bool> 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<std::u8string_view> 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

View File

@ -1,5 +1,65 @@
#pragma once
#include "../../macro/class_copy_move.hpp"
#include "application.hpp"
#include "validator.hpp"
#include <vector>
#include <utility>
#include <map>
#include <optional>
#include <string>
#include <string_view>
#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<NS_YYCC_CLAP_TYPES::Token, std::optional<std::u8string>> values;
public:
static NS_YYCC_CLAP_TYPES::ClapResult<Parser> from_user(const NS_YYCC_CLAP_APPLICATION::Application& app,
const std::vector<std::u8string_view>& args);
static NS_YYCC_CLAP_TYPES::ClapResult<Parser> 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<std::u8string_view> 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<bool> get_flag_option(NS_YYCC_CLAP_TYPES::Token token) const;
template<NS_YYCC_CLAP_VALIDATOR::Validator T>
NS_YYCC_CLAP_TYPES::ClapResult<NS_YYCC_CLAP_VALIDATOR::ValidatorReturnType<T>> 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

View File

@ -1,81 +1,50 @@
#include "resolver.hpp"
#include "types.hpp"
#include <string_view>
#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<TYPES::Token> 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<bool> Parser::get_flag_option(TYPES::Token token) const {
// TODO:
return TYPES::ClapResult<bool>();
TYPES::ClapResult<bool> 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<std::u8string_view> Parser::get_raw_value_option(TYPES::Token token) const {
// TODO:
return TYPES::ClapResult<std::u8string_view>();
TYPES::ClapResult<std::u8string_view> 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

View File

@ -2,9 +2,12 @@
#include "../../macro/class_copy_move.hpp"
#include "application.hpp"
#include "validator.hpp"
#include <vector>
#include <utility>
#include <map>
#include <optional>
#include <string>
#include <string_view>
#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<NS_YYCC_CLAP_TYPES::Token, std::optional<std::u8string>> values;
public:
Parser(const NS_YYCC_CLAP_APPLICATION::Application& app);
~Parser();
YYCC_DEFAULT_COPY_MOVE(Parser)
static NS_YYCC_CLAP_TYPES::ClapResult<Resolver> from_user(
const NS_YYCC_CLAP_APPLICATION::Application& app, const std::vector<std::pair<std::u8string_view, std::u8string_view>>& vars);
static NS_YYCC_CLAP_TYPES::ClapResult<Resolver> from_system(const NS_YYCC_CLAP_APPLICATION::Application& app);
private:
NS_YYCC_CLAP_TYPES::ClapResult<std::u8string_view> 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<bool> get_flag_option(NS_YYCC_CLAP_TYPES::Token token) const;
~Resolver();
YYCC_DEFAULT_COPY_MOVE(Resolver)
private:
NS_YYCC_CLAP_TYPES::ClapResult<std::u8string_view> 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<bool> get_flag_variable(NS_YYCC_CLAP_TYPES::Token token) const;
template<NS_YYCC_CLAP_VALIDATOR::Validator T>
NS_YYCC_CLAP_TYPES::ClapResult<NS_YYCC_CLAP_VALIDATOR::ValidatorReturnType<T>> get_value_option(
NS_YYCC_CLAP_TYPES::ClapResult<NS_YYCC_CLAP_VALIDATOR::ValidatorReturnType<T>> 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<NS_YYCC_CLAP_TYPES::Token, std::optional<std::u8string>> values;
};
} // namespace yycc::carton::clap::resolver
#undef NS_YYCC_CLAP_VALIDATOR
#undef NS_YYCC_CLAP_APPLICATION
#undef NS_YYCC_CLAP_TYPES

View File

@ -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;
}

View File

@ -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;
};

View File

@ -146,7 +146,7 @@ namespace yycc::env {
#endif
}
VarResult<std::vector<VarPair>> get_vars() {
std::vector<VarPair> get_vars() {
// TODO: finish this function according to Rust implementation.
// Considering whether replace return value with an iterator.
throw std::logic_error("not implemented");

View File

@ -72,7 +72,7 @@ namespace yycc::env {
* for all the environment variables of the current process.
* @return The list holding all variables.
*/
VarResult<std::vector<VarPair>> get_vars();
std::vector<VarPair> 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<std::vector<std::u8string>> get_args();
std::vector<std::u8string> get_args();
#pragma endregion