1st version of handling variable length nargs

To handle variable length nargs, I replaced mNumArgs with mNumArgsRange.
I defined SizeRange class for mNumArgsRange, which has simply min and
max std::size_t member.

To concentrate on this big change, I tentatively deleted remaining
feature, which was originally implemented in the way that mNumArgs = -1
internally and maybe_args() -> Optional wrap method.

Library users may make use of 4 types of interface to set
mNumArgsRange.

1. nargs(std::size_t)
2. nargs(std::size_t, std::size_t)
3. nargs(SizeRange)
4. nargs(NArgsPattern)

1. is expected to behave same as original. This mthod sets min=max
SizeRange to mNumArgsRange, which is actually, not a range, but an
"exact" number.

2. sets min and max.

3. uses SizeRange class. This interface may be unnecessary. It is also
an option to delete this method and make SizeRange class internal.

4. is provided to set common patterns. In Python, they are "?", "*" and
"+". NArgsPattern is an enum class for type safety. std::string
interface is also an option to mimic Python argparse. char interface
would be ambiguous with 1.

Changes on consume method is important.
The parser tries to consume args until the count reaches mNumArgsRanges::max or
it meets another optional like string.
If consumed args count is under mNumArgsRanges::min, the parser fails.

Now, when the required number of arguments are not provided, the parser
will fail.
So, we have to take care of get() method as well.
get() failed when argument count is 0 and default value not provided.
But now there are 0..1 or 0..* nargs are OK.
So this behaviour has to be fixed.
When T is container_v, it returns empty container.

I implemented validate method so that it shows kind message.
This commit is contained in:
Yoshihiro Hokazono 2021-09-11 05:22:33 +09:00
parent ab0a28c3bf
commit c6c3be04d3

View File

@ -37,6 +37,7 @@ SOFTWARE.
#include <functional> #include <functional>
#include <iostream> #include <iostream>
#include <iterator> #include <iterator>
#include <limits>
#include <list> #include <list>
#include <map> #include <map>
#include <numeric> #include <numeric>
@ -312,6 +313,45 @@ template <class T> struct parse_number<T, chars_format::fixed> {
} // namespace details } // namespace details
class SizeRange {
std::size_t mMin;
std::size_t mMax;
public:
SizeRange(std::size_t aMin, std::size_t aMax) {
if (aMin > aMax)
throw std::logic_error("Range of number of arguments is invalid");
mMin = aMin;
mMax = aMax;
}
bool contains(std::size_t value) const {
return value >= mMin && value <= mMax;
}
bool is_exact() const {
return mMin == mMax;
}
bool is_right_bounded() const {
return mMax < std::numeric_limits<std::size_t>::max();
}
std::size_t get_min() const {
return mMin;
}
std::size_t get_max() const {
return mMax;
}
};
enum class NArgsPattern {
ZERO_OR_ONE,
ANY,
AT_LEAST_ONE
};
class ArgumentParser; class ArgumentParser;
class Argument { class Argument {
@ -353,7 +393,7 @@ public:
Argument &implicit_value(std::any aImplicitValue) { Argument &implicit_value(std::any aImplicitValue) {
mImplicitValue = std::move(aImplicitValue); mImplicitValue = std::move(aImplicitValue);
mNumArgs = 0; mNumArgsRange = SizeRange{0, 0};
return *this; return *this;
} }
@ -420,15 +460,33 @@ public:
return *this; return *this;
} }
Argument &nargs(int aNumArgs) { Argument &nargs(std::size_t aNumArgs) {
if (aNumArgs < 0) mNumArgsRange = SizeRange{aNumArgs, aNumArgs};
throw std::logic_error("Number of arguments must be non-negative");
mNumArgs = aNumArgs;
return *this; return *this;
} }
Argument &remaining() { Argument &nargs(std::size_t aNumArgsMin, std::size_t aNumArgsMax) {
mNumArgs = -1; mNumArgsRange = SizeRange{aNumArgsMin, aNumArgsMax};
return *this;
}
Argument &nargs(SizeRange aNumArgsRange) {
mNumArgsRange = aNumArgsRange;
return *this;
}
Argument &nargs(NArgsPattern aNargs) {
switch (aNargs) {
case NArgsPattern::ZERO_OR_ONE:
mNumArgsRange = SizeRange{0, 1};
break;
case NArgsPattern::ANY:
mNumArgsRange = SizeRange{0, std::numeric_limits<std::size_t>::max()};
break;
case NArgsPattern::AT_LEAST_ONE:
mNumArgsRange = SizeRange{1, std::numeric_limits<std::size_t>::max()};
break;
}
return *this; return *this;
} }
@ -440,16 +498,28 @@ public:
} }
mIsUsed = true; mIsUsed = true;
mUsedName = usedName; mUsedName = usedName;
if (mNumArgs == 0) {
const auto numArgsMax = mNumArgsRange.get_max();
const auto numArgsMin = mNumArgsRange.get_min();
if (numArgsMax == 0) {
mValues.emplace_back(mImplicitValue); mValues.emplace_back(mImplicitValue);
return start; return start;
} else if (mNumArgs <= std::distance(start, end)) { } else if (static_cast<std::size_t>(std::distance(start, end)) >= numArgsMin) {
if (auto expected = maybe_nargs()) {
end = std::next(start, *expected); auto it = start;
if (std::any_of(start, end, Argument::is_optional)) { for (std::size_t i = 0; it != end; ++it, ++i) {
throw std::runtime_error("optional argument in parameter sequence"); if (Argument::is_optional(*it)) {
break;
}
if (i >= numArgsMax) {
break;
} }
} }
auto dist = static_cast<std::size_t>(std::distance(start, it));
if (dist < numArgsMin) {
throw std::runtime_error("Too few arguments");
}
end = it;
struct action_apply { struct action_apply {
void operator()(valued_action &f) { void operator()(valued_action &f) {
@ -459,8 +529,7 @@ public:
void operator()(void_action &f) { void operator()(void_action &f) {
std::for_each(start, end, f); std::for_each(start, end, f);
if (!self.mDefaultValue.has_value()) { if (!self.mDefaultValue.has_value()) {
if (auto expected = self.maybe_nargs()) self.mValues.resize(std::distance(start, end));
self.mValues.resize(*expected);
} }
} }
@ -481,47 +550,53 @@ public:
* @throws std::runtime_error if argument values are not valid * @throws std::runtime_error if argument values are not valid
*/ */
void validate() const { void validate() const {
if (auto expected = maybe_nargs()) { if (mIsOptional) {
if (mIsOptional) { if (mIsUsed && !mNumArgsRange.contains(mValues.size()) && !mIsRepeatable &&
if (mIsUsed && mValues.size() != *expected && !mIsRepeatable && !mDefaultValue.has_value()) {
!mDefaultValue.has_value()) { std::stringstream stream;
std::stringstream stream; stream << mUsedName << ": expected ";
stream << mUsedName << ": expected " << *expected << " argument(s). " if (mNumArgsRange.is_exact()) {
<< mValues.size() << " provided."; stream << mNumArgsRange.get_min();
throw std::runtime_error(stream.str()); } else if (mNumArgsRange.is_right_bounded()) {
stream << mNumArgsRange.get_min() << " to " << mNumArgsRange.get_max();
} else { } else {
// TODO: check if an implicit value was programmed for this argument stream << mNumArgsRange.get_min() << " or more";
if (!mIsUsed && !mDefaultValue.has_value() && mIsRequired) {
std::stringstream stream;
stream << mNames[0] << ": required.";
throw std::runtime_error(stream.str());
}
if (mIsUsed && mIsRequired && mValues.size() == 0) {
std::stringstream stream;
stream << mUsedName << ": no value provided.";
throw std::runtime_error(stream.str());
}
} }
stream << " argument(s). "
<< mValues.size() << " provided.";
throw std::runtime_error(stream.str());
} else { } else {
if (mValues.size() != expected && !mDefaultValue.has_value()) { // TODO: check if an implicit value was programmed for this argument
if (!mIsUsed && !mDefaultValue.has_value() && mIsRequired) {
std::stringstream stream; std::stringstream stream;
if (!mUsedName.empty()) stream << mNames[0] << ": required.";
stream << mUsedName << ": "; throw std::runtime_error(stream.str());
stream << *expected << " argument(s) expected. " << mValues.size() }
<< " provided."; if (mIsUsed && mIsRequired && mValues.size() == 0) {
std::stringstream stream;
stream << mUsedName << ": no value provided.";
throw std::runtime_error(stream.str()); throw std::runtime_error(stream.str());
} }
} }
} else {
if (!mNumArgsRange.contains(mValues.size()) && !mDefaultValue.has_value()) {
std::stringstream stream;
if (!mUsedName.empty())
stream << mUsedName << ": ";
if (mNumArgsRange.is_exact()) {
stream << mNumArgsRange.get_min();
} else if (mNumArgsRange.is_right_bounded()) {
stream << mNumArgsRange.get_min() << " to " << mNumArgsRange.get_max();
} else {
stream << mNumArgsRange.get_min() << " or more";
}
stream << " argument(s) expected. " << mValues.size()
<< " provided.";
throw std::runtime_error(stream.str());
}
} }
} }
auto maybe_nargs() const -> std::optional<std::size_t> {
if (mNumArgs < 0)
return std::nullopt;
else
return static_cast<std::size_t>(mNumArgs);
}
std::size_t get_arguments_length() const { std::size_t get_arguments_length() const {
return std::accumulate(std::begin(mNames), std::end(mNames), std::size_t(0), return std::accumulate(std::begin(mNames), std::end(mNames), std::size_t(0),
[](const auto &sum, const auto &s) { [](const auto &sum, const auto &s) {
@ -759,6 +834,9 @@ private:
} }
if (mDefaultValue.has_value()) { if (mDefaultValue.has_value()) {
return std::any_cast<T>(mDefaultValue); return std::any_cast<T>(mDefaultValue);
} else {
if constexpr (details::is_container_v<T>)
return any_cast_container<T>(mValues);
} }
throw std::logic_error("No value provided for '" + mNames.back() + "'."); throw std::logic_error("No value provided for '" + mNames.back() + "'.");
} }
@ -803,7 +881,7 @@ private:
std::in_place_type<valued_action>, std::in_place_type<valued_action>,
[](const std::string &aValue) { return aValue; }}; [](const std::string &aValue) { return aValue; }};
std::vector<std::any> mValues; std::vector<std::any> mValues;
int mNumArgs = 1; SizeRange mNumArgsRange {1, 1};
bool mIsOptional : true; bool mIsOptional : true;
bool mIsRequired : true; bool mIsRequired : true;
bool mIsRepeatable : true; bool mIsRepeatable : true;