add lt gt lte gte restrictions, update unit tests, update documentation

This commit is contained in:
ado 2021-01-03 15:38:07 +01:00
parent cc7e6f7806
commit fdae9b6413
3 changed files with 155 additions and 33 deletions

View File

@ -76,23 +76,6 @@ $ make test
# Usage # Usage
## Error handling
Detailed error messages can be accessed via the **error_msg** method, and to
enable them the error mode has to be changed to **error_mode::error_string** using
the **set_error_mode** method:
```cpp
void parser::set_error_mode(ss::error_mode);
const std::string& parser::error_msg();
bool parser::valid();
bool parser::eof();
```
Error messages can always be disabled by setting the error mode to
**error_mode::error_bool**. An error can be detected using the **valid** method which
would return **false** if the file could not be opened, or if the conversion
could not be made (invalid types, invalid number of columns, ...).
The **eof** method can be used to detect if the end of the file was reached.
## Conversions ## Conversions
The above example will be used to show some of the features of the library. The above example will be used to show some of the features of the library.
As seen above, the **get_next** method returns a tuple of objects specified As seen above, the **get_next** method returns a tuple of objects specified
@ -212,7 +195,7 @@ auto [name, age, grade] =
``` ```
If the restrictions are not met, the conversion will fail. If the restrictions are not met, the conversion will fail.
Other predefined restrictions are **ss::ax** (all except), **ss::nx** (none except) Other predefined restrictions are **ss::ax** (all except), **ss::nx** (none except)
and **ss::oor** (out of range): and **ss::oor** (out of range), ss::lt (less than), ...(see *restrictions.hpp*):
```cpp ```cpp
// all ints exept 10 and 20 // all ints exept 10 and 20
ss::ax<int, 10, 20> ss::ax<int, 10, 20>
@ -246,7 +229,7 @@ auto [name, age] = p.get_next<std::string, even<int>, void>();
``` ```
## Custom conversions ## Custom conversions
Custom types can be used when converting values. An override of the **ss::extract** Custom types can be used when converting values. A specialization of the **ss::extract**
function needs to be made and you are good to go. Custom conversion for an enum function needs to be made and you are good to go. Custom conversion for an enum
would look like this: would look like this:
```cpp ```cpp
@ -267,11 +250,28 @@ inline bool ss::extract(const char* begin, const char* end, shape& dst) {
return false; return false;
} }
``` ```
The shape enum will be in an example below. The **inline** is there just to prevent The shape enum will be used in an example below. The **inline** is there just to prevent
multiple definition errors. The function returns **true** if the conversion was multiple definition errors. The function returns **true** if the conversion was
a success, and **false** otherwise. The function uses **const char*** begin and end a success, and **false** otherwise. The function uses **const char*** begin and end
for performance reasons. for performance reasons.
## Error handling
Detailed error messages can be accessed via the **error_msg** method, and to
enable them the error mode has to be changed to **error_mode::error_string** using
the **set_error_mode** method:
```cpp
void parser::set_error_mode(ss::error_mode);
const std::string& parser::error_msg();
bool parser::valid();
bool parser::eof();
```
Error messages can always be disabled by setting the error mode to
**error_mode::error_bool**. An error can be detected using the **valid** method which
would return **false** if the file could not be opened, or if the conversion
could not be made (invalid types, invalid number of columns, ...).
The **eof** method can be used to detect if the end of the file was reached.
## Substitute conversions ## Substitute conversions
The parser can also be used to effectively parse files whose rows are not The parser can also be used to effectively parse files whose rows are not
@ -298,11 +298,12 @@ if (!p.valid()) {
std::vector<std::pair<shape, double>> shapes; std::vector<std::pair<shape, double>> shapes;
while (!p.eof()) { while (!p.eof()) {
using ss::nx; // non negative double
using udbl = ss::gte<double, 0>;
auto [circle_or_square, rectangle, triangle] = auto [circle_or_square, rectangle, triangle] =
p.try_next<nx<shape, shape::circle, shape::square>, double>() p.try_next<ss::nx<shape, shape::circle, shape::square>, udbl>()
.or_else<nx<shape, shape::rectangle>, double, double>() .or_else<ss::nx<shape, shape::rectangle>, udbl, udbl>()
.or_else<nx<shape, shape::triangle>, double, double, double>() .or_else<ss::nx<shape, shape::triangle>, udbl, udbl, udbl>()
.values(); .values();
if (circle_or_square) { if (circle_or_square) {
@ -319,10 +320,46 @@ while (!p.eof()) {
if (triangle) { if (triangle) {
auto& [s, a, b, c] = triangle.value(); auto& [s, a, b, c] = triangle.value();
double sh = (a + b + c) / 2; double sh = (a + b + c) / 2;
shapes.emplace_back(s, sqrt(sh * (sh - a) * (sh - b) * (sh - c))); if (sh >= a && sh >= b && sh >= c) {
double area = sqrt(sh * (sh - a) * (sh - b) * (sh - c));
shapes.emplace_back(s, area);
}
} }
} }
/* do something with the stored shapes */ /* do something with the stored shapes */
/* ... */ /* ... */
``` ```
It is quite hard to make an error this way since most things will be checked
at compile time.
The **try_next** method works in a similar way as **get_next** but returns a **composit**
which holds a **tuple** with an **optional** to the **tuple** returned by **get_next**.
This **composite** has a **or_else** method (looks a bit like tl::expected) which
is able to try additional conversions if the previous failed.
It also returns a **composite**, but in its tuple is the **optional** to the **tuple**
of the previous conversions and an **optional** to the **tuple** to the new conversion.
To fetch the **tuple** from the **composite** the **values** method is used.
The value of the above used conversion would look something like this
(with the restrictions applied to the values of shape - ss::nx)
```cpp
std::tuple<
std::optional<std::tuple<shape, double>>,
std::optional<std::tuple<shape, double, double>>,
std::optional<std::tuple<shape, double, double, double>>
>
```
Similar to the way that **get_next** has a **get_object** alternative, **try_next** has a **try_object**
alternative, and **or_else** has a **or_object** alternative. Also all rules applied
to **get_next** also work with **try_next** , **or_else**, and all the other **composite** conversions.
Each of those **composite** conversions can accept a lambda (or anything callable) as
an argument and invoke it in case of a valid conversion. That lambda
itself need not have any arguments, but if they do, they must either
accept the whole **tuple**/object as one argument or the elements of the tuple
separately. If the lambda returns something that can be interpreted as **false**,
The conversion will fail, and the next conversion will try to apply.
Rewriting the whole while loop using lambdas would look like this:

View File

@ -52,6 +52,41 @@ public:
} }
}; };
////////////////
// greater than or equal to
// greater than
// less than
// less than or equal to
////////////////
template <typename T, auto N>
struct gt {
bool ss_valid(const T& value) const {
return value > N;
}
};
template <typename T, auto N>
struct gte {
bool ss_valid(const T& value) const {
return value >= N;
}
};
template <typename T, auto N>
struct lt {
bool ss_valid(const T& value) const {
return value < N;
}
};
template <typename T, auto N>
struct lte {
bool ss_valid(const T& value) const {
return value <= N;
}
};
//////////////// ////////////////
// in range // in range
//////////////// ////////////////
@ -61,10 +96,6 @@ struct ir {
bool ss_valid(const T& value) const { bool ss_valid(const T& value) const {
return value >= Min && value <= Max; return value >= Min && value <= Max;
} }
const char* error() const {
return "out of range";
}
}; };
//////////////// ////////////////
@ -76,10 +107,6 @@ struct oor {
bool ss_valid(const T& value) const { bool ss_valid(const T& value) const {
return value < Min || value > Max; return value < Min || value > Max;
} }
const char* error() const {
return "in restricted range";
}
}; };
//////////////// ////////////////

View File

@ -323,6 +323,64 @@ TEST_CASE("testing ss:ne restriction (not empty)") {
} }
} }
TEST_CASE("testing ss:lt ss::lte ss::gt ss::gte restriction (in range)") {
ss::converter c;
c.convert<ss::lt<int, 3>>("3");
REQUIRE(!c.valid());
c.convert<ss::lt<int, 2>>("3");
REQUIRE(!c.valid());
c.convert<ss::gt<int, 3>>("3");
REQUIRE(!c.valid());
c.convert<ss::gt<int, 4>>("3");
REQUIRE(!c.valid());
c.convert<ss::lte<int, 2>>("3");
REQUIRE(!c.valid());
c.convert<ss::gte<int, 4>>("3");
REQUIRE(!c.valid());
{
auto tup = c.convert<ss::lt<int, 4>>("3");
REQUIRE(c.valid());
CHECK(tup == 3);
}
{
auto tup = c.convert<ss::gt<int, 2>>("3");
REQUIRE(c.valid());
CHECK(tup == 3);
}
{
auto tup = c.convert<ss::lte<int, 4>>("3");
REQUIRE(c.valid());
CHECK(tup == 3);
}
{
auto tup = c.convert<ss::lte<int, 3>>("3");
REQUIRE(c.valid());
CHECK(tup == 3);
}
{
auto tup = c.convert<ss::gte<int, 2>>("3");
REQUIRE(c.valid());
CHECK(tup == 3);
}
{
auto tup = c.convert<ss::gte<int, 3>>("3");
REQUIRE(c.valid());
CHECK(tup == 3);
}
}
TEST_CASE("testing error mode") { TEST_CASE("testing error mode") {
ss::converter c; ss::converter c;