Skip to content

Commit fe989d4

Browse files
tsojhsutter
andauthored
Idea: Adding from_string to enum metafunction (#1185)
* Added from_string to enum metafunction * Changed string to string_view * Changed string to string_view in test result now too * Enable from_string for flag_enums too, and use contract violation handling Changed failures from unconditional abort to a type_safety violation (which defaults to abort but allows a customizable handler) because it really is a precondition violation so a contract makes sense * Fix typo in regression test case * For flag_enums, let from_string take a "( list, of_multiple, flags )" * Changed flag_enum to_string from (a,b) to (a|b) And fixed bug to correct error case `break` * Provide {to|from}_{string|code} to_string/from_string round-trip, don't enum_name::-qualify, and represent a list with comma separators to_code/from_code round-trip, do enum_name::-qualify, and represent a list with | separators * Avoid repeated eval of to_string(prefix) Add regress test case for to_code / from_code Update regression test and generated files --------- Signed-off-by: Herb Sutter <[email protected]> Co-authored-by: Herb Sutter <[email protected]>
1 parent de3f54f commit fe989d4

File tree

10 files changed

+425
-83
lines changed

10 files changed

+425
-83
lines changed

include/cpp2regex.h

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,11 @@ public: constexpr auto operator=(expression_flags const& that) -> expression_fla
211211
public: constexpr expression_flags(expression_flags&& that) noexcept;
212212
public: constexpr auto operator=(expression_flags&& that) noexcept -> expression_flags& ;
213213
public: [[nodiscard]] auto operator<=>(expression_flags const& that) const& -> std::strong_ordering = default;
214+
public: [[nodiscard]] auto to_string_impl(cpp2::impl::in<std::string_view> prefix, cpp2::impl::in<std::string_view> separator) const& -> std::string;
214215
public: [[nodiscard]] auto to_string() const& -> std::string;
216+
public: [[nodiscard]] auto to_code() const& -> std::string;
217+
public: [[nodiscard]] static auto from_string(cpp2::impl::in<std::string_view> s) -> expression_flags;
218+
public: [[nodiscard]] static auto from_code(cpp2::impl::in<std::string_view> s) -> expression_flags;
215219

216220
#line 55 "cpp2regex.h2"
217221
};
@@ -1540,21 +1544,51 @@ constexpr expression_flags::expression_flags(expression_flags&& that) noexcept
15401544
constexpr auto expression_flags::operator=(expression_flags&& that) noexcept -> expression_flags& {
15411545
_value = std::move(that)._value;
15421546
return *this;}
1543-
[[nodiscard]] auto expression_flags::to_string() const& -> std::string{
1547+
[[nodiscard]] auto expression_flags::to_string_impl(cpp2::impl::in<std::string_view> prefix, cpp2::impl::in<std::string_view> separator) const& -> std::string{
15441548

1545-
std::string _ret {"("};
1549+
std::string ret {"("};
15461550

1547-
std::string _comma {};
1551+
std::string sep {};
15481552
if ((*this) == none) {return "(none)"; }
1549-
if (((*this) & case_insensitive) == case_insensitive) {_ret += _comma + "case_insensitive";_comma = ", ";}
1550-
if (((*this) & multiple_lines) == multiple_lines) {_ret += _comma + "multiple_lines";_comma = ", ";}
1551-
if (((*this) & single_line) == single_line) {_ret += _comma + "single_line";_comma = ", ";}
1552-
if (((*this) & no_group_captures) == no_group_captures) {_ret += _comma + "no_group_captures";_comma = ", ";}
1553-
if (((*this) & perl_code_syntax) == perl_code_syntax) {_ret += _comma + "perl_code_syntax";_comma = ", ";}
1554-
if (((*this) & perl_code_syntax_in_classes) == perl_code_syntax_in_classes) {_ret += _comma + "perl_code_syntax_in_classes";_comma = ", ";}
1555-
return cpp2::move(_ret) + ")";
1553+
if (((*this) & case_insensitive) == case_insensitive) {ret += sep + cpp2::to_string(prefix) + "case_insensitive";sep = separator;}
1554+
if (((*this) & multiple_lines) == multiple_lines) {ret += sep + cpp2::to_string(prefix) + "multiple_lines";sep = separator;}
1555+
if (((*this) & single_line) == single_line) {ret += sep + cpp2::to_string(prefix) + "single_line";sep = separator;}
1556+
if (((*this) & no_group_captures) == no_group_captures) {ret += sep + cpp2::to_string(prefix) + "no_group_captures";sep = separator;}
1557+
if (((*this) & perl_code_syntax) == perl_code_syntax) {ret += sep + cpp2::to_string(prefix) + "perl_code_syntax";sep = separator;}
1558+
if (((*this) & perl_code_syntax_in_classes) == perl_code_syntax_in_classes) {ret += sep + cpp2::to_string(prefix) + "perl_code_syntax_in_classes";sep = separator;}
1559+
return cpp2::move(ret) + ")";
15561560
}
15571561

1562+
[[nodiscard]] auto expression_flags::to_string() const& -> std::string { return to_string_impl("", ", "); }
1563+
[[nodiscard]] auto expression_flags::to_code() const& -> std::string { return to_string_impl("expression_flags::", " | "); }
1564+
[[nodiscard]] auto expression_flags::from_string(cpp2::impl::in<std::string_view> s) -> expression_flags{
1565+
1566+
auto ret {none};
1567+
do {{
1568+
for ( auto const& x : cpp2::string_util::split_string_list(s) ) {
1569+
if ("case_insensitive" == x) {ret |= case_insensitive;}
1570+
else {if ("multiple_lines" == x) {ret |= multiple_lines;}
1571+
else {if ("single_line" == x) {ret |= single_line;}
1572+
else {if ("no_group_captures" == x) {ret |= no_group_captures;}
1573+
else {if ("perl_code_syntax" == x) {ret |= perl_code_syntax;}
1574+
else {if ("perl_code_syntax_in_classes" == x) {ret |= perl_code_syntax_in_classes;}
1575+
else {if ("none" == x) {ret |= none;}
1576+
else {goto BREAK_outer;}
1577+
#line 1 "cpp2regex.h2"
1578+
}}}}}}
1579+
}
1580+
1581+
return ret;
1582+
} CPP2_CONTINUE_BREAK(outer) }
1583+
while (
1584+
false
1585+
);
1586+
CPP2_UFCS(report_violation)(cpp2::type_safety, CPP2_UFCS(c_str)(("can't convert string '" + cpp2::to_string(s) + "' to flag_enum of type expression_flags")));
1587+
return none;
1588+
}
1589+
1590+
[[nodiscard]] auto expression_flags::from_code(cpp2::impl::in<std::string_view> s) -> expression_flags{
1591+
std::string str {s}; return from_string(cpp2::string_util::replace_all(cpp2::move(str), "expression_flags::", "")); }
15581592
template <typename Iter> match_group<Iter>::match_group(auto const& start_, auto const& end_, auto const& matched_)
15591593
: start{ start_ }
15601594
, end{ end_ }
@@ -1564,6 +1598,7 @@ template <typename Iter> match_return<Iter>::match_return(auto const& matched_,
15641598
: matched{ matched_ }
15651599
, pos{ pos_ }{}
15661600
template <typename Iter> match_return<Iter>::match_return(){}
1601+
15671602
#line 38 "cpp2regex.h2"
15681603
//-----------------------------------------------------------------------
15691604
//

include/cpp2util.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,40 @@ using _uchar = unsigned char; // normally use u8 instead
378378

379379
namespace string_util {
380380

381+
// Break a string_view into a vector of views of simple qidentifier
382+
// substrings separated by other characters
383+
auto split_string_list(std::string_view str)
384+
-> std::vector<std::string_view>
385+
{
386+
std::vector<std::string_view> ret;
387+
388+
auto is_id_char = [](char c) {
389+
return std::isalnum(c) || c == '_';
390+
};
391+
392+
auto pos = 0;
393+
while( pos < std::ssize(str) ) {
394+
// Skip non-alnum
395+
while (pos < std::ssize(str) && !is_id_char(str[pos])) {
396+
++pos;
397+
}
398+
auto start = pos;
399+
400+
// Find the end of the current component
401+
while (pos < std::ssize(str) && is_id_char(str[pos])) {
402+
++pos;
403+
}
404+
405+
// Add nonempty substring to the vector
406+
if (start < pos) {
407+
ret.emplace_back(str.substr(start, pos - start));
408+
}
409+
}
410+
411+
return ret;
412+
}
413+
414+
381415
// From https://stackoverflow.com/questions/216823/how-to-trim-a-stdstring
382416

383417
// Trim from start (in place)

regression-tests/pure2-enum.cpp2

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,17 @@ main: () = {
3535
x: skat_game = skat_game::clubs;
3636
x2 := skat_game::diamonds;
3737
x2 = x;
38+
x3 := skat_game::from_string("hearts");
39+
x4 := skat_game::from_code("skat_game::hearts");
3840

3941
// if x == 9 { } // error, can't compare skat_game and integer
4042
// if x == rgb::red { } // error, can't compare skat_game and rgb color
4143

4244
std::cout << "x.to_string() is (x.to_string())$\n";
4345
std::cout << "x2.to_string() is (x2.to_string())$\n";
46+
std::cout << "x3.to_string() is (x3.to_string())$\n";
47+
std::cout << "x3.to_code() is (x3.to_code())$\n";
48+
std::cout << "x4.to_string() is (x3.to_string())$\n";
4449

4550
std::cout << "with if else: ";
4651
if x == skat_game::diamonds { // ok, can compare two skat_games
@@ -117,4 +122,14 @@ main: () = {
117122
is (cpp2::has_flags(f2)) = "includes all f2's flags ('cached' and 'current')";
118123
is _ = "something else";
119124
} << "\n";
125+
126+
f_from_string := file_attributes::from_string("cached_and_current");
127+
std::cout << "f_from_string is " << f_from_string.to_string() << "\n";
128+
129+
f_from_string = file_attributes::from_string("(current, obsolete)");
130+
std::cout << "f_from_string is " << f_from_string.to_string() << "\n";
131+
std::cout << "f_from_string.to_code() is " << f_from_string.to_code() << "\n";
132+
133+
f_from_string = file_attributes::from_code("(file_attributes::cached | file_attributes::obsolete)");
134+
std::cout << "f_from_string is " << f_from_string.to_string() << "\n";
120135
}

regression-tests/test-results/clang-12-c++20/pure2-enum.cpp.execution

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
x.to_string() is clubs
22
x2.to_string() is clubs
3+
x3.to_string() is hearts
4+
x3.to_code() is skat_game::hearts
5+
x4.to_string() is hearts
36
with if else: clubs
47
with inspect: clubs
58

@@ -27,3 +30,7 @@ f is (f2) is false
2730
f2 is (f ) is false
2831
(f & f2) == f2 is true
2932
inspecting f: includes all f2's flags ('cached' and 'current')
33+
f_from_string is (cached, current, cached_and_current)
34+
f_from_string is (current, obsolete)
35+
f_from_string.to_code() is (file_attributes::current | file_attributes::obsolete)
36+
f_from_string is (cached, obsolete)

regression-tests/test-results/gcc-10-c++20/pure2-enum.cpp.execution

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
x.to_string() is clubs
22
x2.to_string() is clubs
3+
x3.to_string() is hearts
4+
x3.to_code() is skat_game::hearts
5+
x4.to_string() is hearts
36
with if else: clubs
47
with inspect: clubs
58

@@ -27,3 +30,7 @@ f is (f2) is false
2730
f2 is (f ) is false
2831
(f & f2) == f2 is true
2932
inspecting f: includes all f2's flags ('cached' and 'current')
33+
f_from_string is (cached, current, cached_and_current)
34+
f_from_string is (current, obsolete)
35+
f_from_string.to_code() is (file_attributes::current | file_attributes::obsolete)
36+
f_from_string is (cached, obsolete)

regression-tests/test-results/gcc-14-c++2b/pure2-enum.cpp.execution

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
x.to_string() is clubs
22
x2.to_string() is clubs
3+
x3.to_string() is hearts
4+
x3.to_code() is skat_game::hearts
5+
x4.to_string() is hearts
36
with if else: clubs
47
with inspect: clubs
58

@@ -27,3 +30,7 @@ f is (f2) is false
2730
f2 is (f ) is false
2831
(f & f2) == f2 is true
2932
inspecting f: includes all f2's flags ('cached' and 'current')
33+
f_from_string is (cached, current, cached_and_current)
34+
f_from_string is (current, obsolete)
35+
f_from_string.to_code() is (file_attributes::current | file_attributes::obsolete)
36+
f_from_string is (cached, obsolete)

regression-tests/test-results/msvc-2022-c++latest/pure2-enum.cpp.execution

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
x.to_string() is clubs
22
x2.to_string() is clubs
3+
x3.to_string() is hearts
4+
x3.to_code() is skat_game::hearts
5+
x4.to_string() is hearts
36
with if else: clubs
47
with inspect: clubs
58

@@ -27,3 +30,7 @@ f is (f2) is false
2730
f2 is (f ) is false
2831
(f & f2) == f2 is true
2932
inspecting f: includes all f2's flags ('cached' and 'current')
33+
f_from_string is (cached, current, cached_and_current)
34+
f_from_string is (current, obsolete)
35+
f_from_string.to_code() is (file_attributes::current | file_attributes::obsolete)
36+
f_from_string is (cached, obsolete)

0 commit comments

Comments
 (0)