fix: fix CmdHelper but still not finished

This commit is contained in:
2024-08-25 22:27:17 +08:00
parent 0db8007fcb
commit bd96f26cfd
2 changed files with 309 additions and 243 deletions

View File

@ -7,7 +7,6 @@
#include <map>
#include <set>
#include <functional>
#include <algorithm>
#include <initializer_list>
#include <type_traits>
#include <stdexcept>
@ -129,7 +128,7 @@ namespace Unvirt::CmdHelper {
throw std::runtime_error("try to add an existing key.");
}
template<class _Ty, std::enable_if_t<std::is_base_of_v<AMItems::AbstractItem, _Ty>, int> = 0>
const _Ty& Get() const {
const _Ty& Get(const std::u8string_view& key) const {
// check argument
if (key.empty())
throw std::invalid_argument("argument key should not be empty");
@ -185,10 +184,55 @@ namespace Unvirt::CmdHelper {
std::vector<ResultItem> m_Results;
};
class ConflictSet {
public:
ConflictSet();
~ConflictSet();
YYCC_DEF_CLS_COPY_MOVE(ConflictSet);
public:
/**
* @brief Add literal item into conflict set.
* @param[in] value Literal item.
* @remarks
* \li Literal item is the string input in command line.
* \li The word in Literal, and the vocabulary in Choice should be put by this function.
* \li Added item will add \c literal: prefix to make it in literal scope,
* so that it will not be compared with argument name items.
* Because we allow 2 literal item and argument name item have same name.
*/
void AddLiteral(const std::u8string_view& value);
/**
* @brief Add argument name item into conflict
* @param[in] value Argument name item.
* @remarks
* \li Argument name item is the key name put in ArgumentsMap.
* \li The argument name in Choice and Argument should be put by this function.
* \li Added item will add \c argument: prefix to make it in argument name scope,
* so that it will not be compared with literal items.
* Because we allow 2 literal item and argument name item have same name.
*/
void AddArgument(const std::u8string_view& value);
/**
* @brief Check whether this set is conflict with another.
* @param[in] rhs The set to be compared.
* @return True if they have conflict.
* @remarks
* This function simply compute the intersection of two set.
* If the result is not empty, it means that there is a conflict.
*/
bool IsConflictWith(const ConflictSet& rhs) const;
protected:
std::set<std::u8string> m_ConflictSet;
};
// Forward declaration of CommandParser for Nodes::RootNode
class CommandParser;
namespace Nodes {
class AbstractNode {
friend class CommandRoot;
public:
using FctExecution_t = void(*)(const ArgumentsMap&);
@ -220,9 +264,17 @@ namespace Unvirt::CmdHelper {
bool Consume(CmdSplitter::Result_t& al, ArgumentsMap& am);
protected:
/**
* @brief Check whether current node is root node.
* @return True if it is, otherwise false.
* @remarks
* This function usually return false.
* It only return true when using special node called root node with CommandParser.
*/
virtual bool IsRootNode() = 0;
/**
* @brief Check whether current node is argument.
* @return True if it is.
* @return True if it is, otherwise false.
* @remakrs
* \li Sub-class must implement this function.
* \li This function is used internally because when consuming nodes,
@ -248,7 +300,7 @@ namespace Unvirt::CmdHelper {
* \li Choice: Put its vocabulary and associated argument name in set.
* \li Argument: Put its argument name in set.
*/
virtual const std::set<std::u8string>& GetConflictSet() = 0;
virtual const ConflictSet& GetConflictSet() = 0;
/**
* @brief Get the string presented in syntax part in help messages.
* @return The string presented in syntax part in help messages.
@ -292,24 +344,18 @@ namespace Unvirt::CmdHelper {
* @param[in] node The node instance to be added.
* @return Return self for chain calling.
*/
template<class _Ty, std::enable_if_t<std::is_base_of_v<AbstractNode, _Ty>, int> = 0>
template<class _Ty, std::enable_if_t<std::is_base_of_v<AbstractNode, _Ty> && !std::is_same_v<AbstractNode, _Ty>, int> = 0>
AbstractNode& Then(_Ty&& node) {
// create node first
auto new_node = std::make_shared<_Ty>(std::forward<_Types>(node));
auto new_node = std::make_shared<_Ty>(std::forward<_Ty>(node));
// check root node.
if (new_node->IsRootNode())
throw std::invalid_argument("root node should not be inserted as child node.");
// check conflict
std::vector<std::u8string> intersection;
const auto& new_node_set = new_node->GetConflictSet();
for (auto& node : mNodes) {
for (auto& node : m_Nodes) {
const auto& node_set = node->GetConflictSet();
// compute intersection
intersection.clear();
std::set_intersection(
new_node_set.begin(), new_node_set.end(),
node_set.begin(), node_set.begin(),
std::back_inserter(intersection)
);
// check whether it is empty intersection
if (!intersection.empty())
if (new_node_set.IsConflictWith(node_set))
throw std::invalid_argument("try to add a conflict node. please check your code.");
}
// add into list
@ -333,6 +379,22 @@ namespace Unvirt::CmdHelper {
};
class RootNode : public AbstractNode {
friend class CommandParser;
public:
RootNode();
virtual ~RootNode();
YYCC_DEF_CLS_COPY_MOVE(RootNode);
protected:
virtual bool IsRootNode() override;
virtual bool IsArgument() override;
virtual const ConflictSet& GetConflictSet() override;
virtual std::u8string GetHelpSymbol() override;
virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override;
virtual void EndConsume(ArgumentsMap& am) override;
};
class Literal : public AbstractNode {
public:
Literal(const std::u8string_view& words);
@ -340,34 +402,36 @@ namespace Unvirt::CmdHelper {
YYCC_DEF_CLS_COPY_MOVE(Literal);
protected:
virtual bool IsRootNode() override;
virtual bool IsArgument() override;
virtual const std::set<std::u8string>& GetConflictSet() override;
virtual const ConflictSet& GetConflictSet() override;
virtual std::u8string GetHelpSymbol() override;
virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override;
virtual void EndConsume(ArgumentsMap& am) override;
std::u8string m_Literal;
std::set<std::u8string> m_ConflictSet;
ConflictSet m_ConflictSet;
};
class Choice : public AbstractNode {
public:
using ArgValue_t = size_t;
using ArgValue_t = AMItems::ArithmeticItem<size_t>;
public:
Choice(const std::u8string_view& argname, const std::initializer_list<std::u8string>& vocabulary);
virtual ~Choice();
YYCC_DEF_CLS_COPY_MOVE(Choice);
protected:
virtual bool IsRootNode() override;
virtual bool IsArgument() override;
virtual const std::set<std::u8string>& GetConflictSet() override;
virtual const ConflictSet& GetConflictSet() override;
virtual std::u8string GetHelpSymbol() override;
virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override;
virtual void EndConsume(ArgumentsMap& am) override;
std::u8string m_ChoiceName;
std::vector<std::u8string> m_Vocabulary;
std::set<std::u8string> m_ConflictSet;
ConflictSet m_ConflictSet;
};
class AbstractArgument : public AbstractNode {
@ -375,65 +439,77 @@ namespace Unvirt::CmdHelper {
AbstractArgument(const std::u8string_view& argname);
virtual ~AbstractArgument();
YYCC_DEF_CLS_COPY_MOVE(AbstractArgument);
// AbstractArgument do not implement BeginConsume().
// Because it involve the detail of data parsing.
// However, other parts are shared by all argument type.
protected:
virtual bool IsRootNode() override;
virtual bool IsArgument() override;
virtual const std::set<std::u8string>& GetConflictSet() override;
virtual const ConflictSet& GetConflictSet() override;
virtual std::u8string GetHelpSymbol() override;
// virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override;
//virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override;
virtual void EndConsume(ArgumentsMap& am) override;
std::u8string m_ArgName;
std::set<std::u8string> m_ConflictSet;
std::u8string m_ArgumentName;
ConflictSet m_ConflictSet;
};
/**
* @brief Return true mean this value can accept.
*/
using IntLimit = std::function<bool(int32_t)>;
class IntArgument : public AbstractArgument {
template<typename _Ty, std::enable_if_t<std::is_arithmetic_v<_Ty>, int> = 0>
class ArithmeticArgument : public AbstractArgument {
public:
using vType = int32_t;
IntArgument(const char* argname, IntLimit limit = nullptr) :
AbstractArgument(argname), m_IntLimit(limit) {}
virtual ~IntArgument() {}
YYCC_DEL_CLS_COPY_MOVE(IntArgument);
using ArgValue_t = AMItems::ArithmeticItem<_Ty>;
using Constraint_t = YYCC::Constraints::Constraint<_Ty>;
public:
ArithmeticArgument(const std::u8string_view& argname, Constraint_t constraint = Constraint_t {}) :
AbstractArgument(argname), m_Constraint(constraint) {}
virtual ~ArithmeticArgument() {}
YYCC_DEF_CLS_COPY_MOVE(ArithmeticArgument);
protected:
virtual bool BeginParse(const std::string&) override;
virtual void EndParse() override;
IntLimit m_IntLimit;
virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override {
// try parse
_Ty result;
if (!YYCC::ParserHelper::TryParse<_Ty>(cur_cmd, result))
return false;
// check constraint
if (m_Constraint.IsValid() && !m_Constraint.m_CheckFct(result))
return false;
// okey
am.Add<ArgValue_t>(m_ArgumentName, result);
return true;
}
protected:
Constraint_t m_Constraint;
};
class StringArgument : public AbstractArgument {
public:
using vType = std::string;
StringArgument(const char* argname) :
AbstractArgument(argname) {}
using ArgValue_t = AMItems::StringItem;
using Constraint_t = YYCC::Constraints::Constraint<std::u8string>;
public:
StringArgument(const std::u8string_view& argname, Constraint_t constraint = Constraint_t {}) :
AbstractArgument(argname), m_Constraint(constraint) {}
virtual ~StringArgument() {}
YYCC_DEL_CLS_COPY_MOVE(StringArgument);
YYCC_DEF_CLS_COPY_MOVE(StringArgument);
protected:
virtual bool BeginParse(const std::string&) override;
virtual void EndParse() override;
virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override;
Constraint_t m_Constraint;
};
class EncodingArgument : public AbstractArgument {
class EncodingListArgument : public AbstractArgument {
public:
using vType = std::vector<std::string>;
EncodingArgument(const char* argname) :
using ArgValue_t = AMItems::StringArrayItem;
public:
EncodingListArgument(const std::u8string_view& argname) :
AbstractArgument(argname) {}
virtual ~EncodingArgument() {}
YYCC_DEL_CLS_COPY_MOVE(EncodingArgument);
virtual ~EncodingListArgument() {}
YYCC_DEF_CLS_COPY_MOVE(EncodingListArgument);
protected:
virtual bool BeginParse(const std::string&) override;
virtual void EndParse() override;
virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override;
};
}
@ -444,24 +520,12 @@ namespace Unvirt::CmdHelper {
~CommandParser();
public:
bool Parse(std::deque<std::string>& cmds);
bool Parse(const CmdSplitter::Result_t& cmds);
HelpDocument Help();
Nodes::RootNode& GetRoot();
private:
class RootNode : Nodes::AbstractNode {
public:
RootNode();
virtual ~RootNode();
YYCC_DEF_CLS_COPY_MOVE(RootNode);
protected:
virtual bool IsArgument() override;
virtual const std::set<std::u8string>& GetConflictSet() override;
virtual std::u8string GetHelpSymbol() override;
virtual bool BeginConsume(const std::u8string& cur_cmd, ArgumentsMap& am) override;
virtual void EndConsume(ArgumentsMap& am) override;
};
RootNode m_RootNode;
Nodes::RootNode m_RootNode;
};
//class CommandRoot : public AbstractNode {