7#ifndef REAL_STD_REGEX_CORE_HPP
8#define REAL_STD_REGEX_CORE_HPP
33 namespace regex_constants {
54 return static_cast<syntax_option_type>(
static_cast<unsigned>(a) |
static_cast<unsigned>(b));
60 return static_cast<syntax_option_type>(
static_cast<unsigned>(a) &
static_cast<unsigned>(b));
84 return static_cast<match_flag_type>(
static_cast<unsigned>(a) |
static_cast<unsigned>(b));
90 return static_cast<match_flag_type>(
static_cast<unsigned>(a) &
static_cast<unsigned>(b));
131 [[nodiscard]]
const char*
what() const noexcept
override
159 template <
typename CharT,
typename Traits>
161 std::is_same_v<CharT, char> && std::is_same_v<Traits, std::regex_traits<char>>;
168 using namespace regex_constants;
169 return (f & (basic | extended | awk | grep | egrep)) != ECMAScript
170 || (f & collate) != ECMAScript
171 || (f & nosubs) != ECMAScript;
184 const auto is_flag_or_dash = [](
char c) {
185 return c ==
'-' || c ==
'i' || c ==
'm' || c ==
's' || c ==
'x' || c ==
'a';
187 for (std::size_t i = 0; i < p.size(); ++i) {
189 if (i + 2 < p.size() && p[i + 1] ==
'0' && p[i + 2] >=
'0' && p[i + 2] <=
'9') {
199 if (p[i] ==
'(' && i + 2 < p.size() && p[i + 1] ==
'?' && is_flag_or_dash(p[i + 2])) {
212 for (std::size_t i = 0; i < fmt.size(); ++i) {
213 if (fmt[i] ==
'$' && i + 1 < fmt.size()) {
214 if (fmt[i + 1] ==
'$') {
217 else if (fmt[i + 1] ==
'0') {
229 if (name ==
"alpha") {
return "A-Za-z"; }
230 if (name ==
"digit") {
return "0-9"; }
231 if (name ==
"alnum") {
return "0-9A-Za-z"; }
232 if (name ==
"upper") {
return "A-Z"; }
233 if (name ==
"lower") {
return "a-z"; }
234 if (name ==
"xdigit") {
return "0-9A-Fa-f"; }
235 if (name ==
"space") {
return "\\t\\n\\x0b\\f\\r "; }
236 if (name ==
"blank") {
return "\\t "; }
237 if (name ==
"cntrl") {
return "\\x00-\\x1f\\x7f"; }
238 if (name ==
"print") {
return "\\x20-\\x7e"; }
239 if (name ==
"graph") {
return "\\x21-\\x7e"; }
240 if (name ==
"punct") {
return "!-/:-@\\[-`{-~"; }
253 const std::size_t n {p.size()};
254 std::string cls {
'['};
256 if (i < n && p[i] ==
'^') { cls +=
'^'; i += 1; }
257 if (i < n && p[i] ==
']') { cls +=
"\\]"; i += 1; }
259 while (i < n && p[i] !=
']') {
260 if (p[i] ==
'[' && i + 1 < n && p[i + 1] ==
':') {
261 const std::size_t close {p.find(
":]", i + 2)};
262 if (close == std::string_view::npos) {
return false; }
264 if (ranges.empty()) {
return false; }
268 else if (p[i] ==
'[' && i + 1 < n && (p[i + 1] ==
'.' || p[i + 1] ==
'=')) {
276 if (i >= n) {
return false; }
292 const std::size_t n {p.size()};
293 const char d {p[i + 1]};
294 const auto emit_hex {[&out](
unsigned v) {
295 constexpr std::string_view hex {
"0123456789abcdef"};
297 out += hex[(v >> 4U) & 0xFU];
298 out += hex[v & 0xFU];
301 case 'b': emit_hex(0x08U); i += 2;
return true;
302 case 'a': emit_hex(0x07U); i += 2;
return true;
303 case 'n': emit_hex(0x0AU); i += 2;
return true;
304 case 't': emit_hex(0x09U); i += 2;
return true;
305 case 'r': emit_hex(0x0DU); i += 2;
return true;
306 case 'f': emit_hex(0x0CU); i += 2;
return true;
307 case 'v': emit_hex(0x0BU); i += 2;
return true;
308 case '/': out +=
'/'; i += 2;
return true;
309 case '"': out +=
'"'; i += 2;
return true;
312 if (d >=
'0' && d <=
'7') {
314 std::size_t k {i + 1};
315 std::size_t digits {0};
316 while (k < n && digits < 3 && p[k] >=
'0' && p[k] <=
'7') {
317 val = (val * 8U) +
static_cast<unsigned>(p[k] -
'0');
321 if (val > 0xFFU) {
return false; }
338 bool in_class {
false};
339 for (std::size_t i = 0; i < p.size(); ++i) {
341 if (c ==
'\\') { ++i;
continue; }
343 if (c ==
']') { in_class =
false; }
346 if (c ==
'[') { in_class =
true;
continue; }
348 const bool left_empty {i == 0 || p[i - 1] ==
'(' || p[i - 1] ==
'|'};
349 const bool right_empty {i + 1 >= p.size() || p[i + 1] ==
')' || p[i + 1] ==
'|'};
350 if (left_empty || right_empty) {
return true; }
362 [[nodiscard]]
inline std::optional<std::string>
translate_ere(std::string_view p,
368 const std::size_t n {p.size()};
372 if (i + 1 >= n) {
return std::nullopt; }
373 const char d {p[i + 1]};
378 if (d ==
']' || d ==
'}') {
return std::nullopt; }
379 if (std::string_view {
".[]{}()*+?|^$\\"}.find(d) != std::string_view::npos) {
394 std::size_t j {i + 1};
395 const std::size_t ds {j};
396 while (j < n && p[j] >=
'0' && p[j] <=
'9') { ++j; }
398 if (ok && j < n && p[j] ==
',') {
400 while (j < n && p[j] >=
'0' && p[j] <=
'9') { ++j; }
402 ok = ok && j < n && p[j] ==
'}';
403 if (!ok) {
return std::nullopt; }
404 out += p.substr(i, (j + 1) - i);
419 [[nodiscard]]
inline std::optional<std::string>
translate_bre(std::string_view p)
423 const std::size_t n {p.size()};
424 bool at_start {
true};
428 if (i + 1 >= n) {
return std::nullopt; }
429 const char d {p[i + 1]};
430 if (d ==
'(') { out +=
'('; i += 2; at_start =
true;
continue; }
431 if (d ==
')') { out +=
')'; i += 2; at_start =
false;
continue; }
433 std::size_t j {i + 2};
434 const std::size_t ds {j};
435 while (j < n && p[j] >=
'0' && p[j] <=
'9') { ++j; }
437 if (ok && j < n && p[j] ==
',') {
439 while (j < n && p[j] >=
'0' && p[j] <=
'9') { ++j; }
441 if (!ok || j + 1 >= n || p[j] !=
'\\' || p[j + 1] !=
'}') {
return std::nullopt; }
443 out += p.substr(i + 2, j - (i + 2));
449 if (d ==
']' || d ==
'}') {
return std::nullopt; }
450 if (std::string_view {
".[]*\\^$"}.find(d) != std::string_view::npos) {
459 if (c ==
'(' || c ==
')' || c ==
'{' || c ==
'}' || c ==
'|' || c ==
'+' || c ==
'?') {
467 if (i == 0) { out +=
'^'; i += 1; at_start =
false;
continue; }
472 if (i + 1 == n) { out +=
'$'; i += 1; at_start =
false;
continue; }
476 if (at_start) {
return std::nullopt; }
498 template <
typename LineFn>
500 LineFn translate_line)
503 std::size_t start {0};
506 const std::size_t nl {p.find(
'\n', start)};
507 const std::string_view line {p.substr(start, (nl == std::string_view::npos ? p.size() : nl) - start)};
508 if (line.empty()) {
return std::nullopt; }
509 const std::optional<std::string> t {translate_line(line)};
510 if (!t) {
return std::nullopt; }
511 if (!
first) { out +=
'|'; }
514 if (nl == std::string_view::npos) {
break; }
526 using namespace regex_constants;
527 if ((f & (collate | nosubs)) != ECMAScript) {
return std::nullopt; }
529 if ((f & basic) != ECMAScript) { ++grammars; }
530 if ((f & extended) != ECMAScript) { ++grammars; }
531 if ((f & awk) != ECMAScript) { ++grammars; }
532 if ((f & grep) != ECMAScript) { ++grammars; }
533 if ((f & egrep) != ECMAScript) { ++grammars; }
534 if (grammars != 1) {
return std::nullopt; }
535 if ((f & extended) != ECMAScript) {
return translate_ere(p); }
537 if ((f & awk) != ECMAScript) {
return translate_ere(p,
true); }
554 namespace sc = std::regex_constants;
555 sc::syntax_option_type s {};
556 using namespace regex_constants;
557 if ((f &
icase) != ECMAScript) { s |= sc::icase; }
558 if ((f & nosubs) != ECMAScript) { s |= sc::nosubs; }
559 if ((f & optimize) != ECMAScript) { s |= sc::optimize; }
560 if ((f & collate) != ECMAScript) { s |= sc::collate; }
561 if ((f &
multiline) != ECMAScript) { s |= sc::multiline; }
562 if ((f & basic) != ECMAScript) { s |= sc::basic; }
563 else if ((f & extended) != ECMAScript) { s |= sc::extended; }
564 else if ((f & awk) != ECMAScript) { s |= sc::awk; }
565 else if ((f & grep) != ECMAScript) { s |= sc::grep; }
566 else if ((f & egrep) != ECMAScript) { s |= sc::egrep; }
567 else { s |= sc::ECMAScript; }
578 template <
typename CharT =
char,
typename Traits = std::regex_traits<CharT>>
594 assign(std::basic_string_view<CharT>(pattern), f);
602 assign(std::basic_string_view<CharT>(pattern), f);
611 assign(std::basic_string_view<CharT>(pattern, len), f);
614 template <
typename It>
622 assign(std::basic_string_view<CharT>(pattern), f);
640 std::swap(
pattern_, other.pattern_);
641 std::swap(
flags_, other.flags_);
646 std::swap(
policy_, other.policy_);
652 return std::holds_alternative<real::regex>(
engine_);
669 [[nodiscard]]
const std::variant<std::basic_regex<CharT, Traits>,
real::regex>&
engine() const noexcept
710 [[nodiscard]]
const std::basic_regex<CharT, Traits>&
std_engine()
const
712 if (std::holds_alternative<std::basic_regex<CharT, Traits>>(
engine_)) {
713 return std::get<std::basic_regex<CharT, Traits>>(
engine_);
715 static std::mutex build_mutex;
716 const std::lock_guard lock {build_mutex};
721 catch (
const std::regex_error& std_error) {
740 mutable std::optional<std::basic_regex<CharT, Traits>>
lazy_std_;
743 void assign(std::basic_string_view<CharT> pattern,
751 if constexpr (detail::real_eligible<CharT, Traits>) {
752 const std::string_view sv {pattern.data(), pattern.size()};
765 engine_.template emplace<real::regex>(std::move(compiled));
774 reject_or_fallback(sv, f,
"the pattern uses a construct the linear engine does not represent "
775 "(a backreference, an unbounded lookaround, a POSIX class, or a "
776 "grammar that forces std)");
783 engine_.template emplace<real::regex>(std::move(compiled));
801 engine_.template emplace<std::basic_regex<CharT, Traits>>(pattern.data(), pattern.size(),
805 catch (
const std::regex_error& std_error) {
816 const std::string& reason)
822 if constexpr (detail::real_eligible<CharT, Traits>) {
824 const std::basic_regex<CharT, Traits> probe(sv.data(), sv.size(),
detail::to_std(f));
825 static_cast<void>(probe);
827 catch (
const std::regex_error& std_error) {
831 throw regex_error(std::regex_constants::error_complexity,
832 "real::compat (strict policy): this pattern requires a non-linear (backtracking) "
833 "engine — " + reason +
". Construct with policy::fallback to delegate it to "
834 "std::regex, forfeiting the linear-time / ReDoS-safe guarantee for it.");
847 catch (
const std::regex_error& std_error) {
A compiled regular expression, parameterized on its storage policy.
constexpr detail::program_view raw_program() const
The raw compiled program, for embedders (advanced).
constexpr std::size_t group_count() const
Returns the number of capturing groups (excluding group 0).
A std::basic_regex-compatible pattern, backed by real where proven, else std.
bool uses_fallback() const noexcept
True if this regex fell back to std::regex (a policy::fallback regex on an ineligible pattern) — so t...
string_type pattern_
Original pattern (for the lazy std build).
void emplace_std(std::string_view sv, flag_type f)
void assign(std::basic_string_view< CharT > pattern, flag_type f)
bool nullable_
empty_match_possible (real-backed).
std::optional< std::basic_regex< CharT, Traits > > lazy_std_
Lazy std for nullable replace/iterate.
basic_regex(const CharT *pattern, std::size_t len, flag_type f=regex_constants::ECMAScript, policy pol=policy::strict)
std::variant< std::basic_regex< CharT, Traits >, real::regex > engine_
compat::policy policy_
strict rejects ineligible, fallback delegates to std.
bool posix_longest() const noexcept
Whether this is a POSIX-ERE pattern routed to REAL: search/match must use leftmost-**longest** bounds...
basic_regex(const string_type &pattern, flag_type f=regex_constants::ECMAScript, policy pol=policy::strict)
const std::basic_regex< CharT, Traits > & std_engine() const
The std::regex for the std / lazy-std path (built once on demand for a real-backed pattern reached vi...
void swap(basic_regex &other) noexcept
void reject_or_fallback(std::string_view sv, flag_type f, const std::string &reason)
The policy branch for a pattern the linear engine cannot represent: strict throws regex_error with er...
CharT value_type
Character type.
std::size_t mark_count() const noexcept
Number of marked sub-expressions (excluding group 0), as std::basic_regex.
const std::variant< std::basic_regex< CharT, Traits >, real::regex > & engine() const noexcept
Access the active backend (engine-facing; used by the free functions).
flag_type flags() const noexcept
The flags this regex was built with.
std::basic_string< CharT > string_type
Pattern string type.
bool posix_longest_
POSIX ERE on REAL: search uses leftmost-longest bounds.
bool uses_real_traversal() const noexcept
Whether replace/iterate run on the real traversal (real-backed AND non-nullable). A nullable pattern ...
bool uses_real() const noexcept
True if this regex is backed by the real engine (vs the std fallback).
compat::policy policy() const noexcept
The drop-in policy this regex was constructed with.
basic_regex(const CharT *pattern, flag_type f=regex_constants::ECMAScript, policy pol=policy::strict)
bool nullable() const noexcept
Whether the pattern can match the empty string (real's empty_match_possible hint).
basic_regex(It begin, It end, flag_type f=regex_constants::ECMAScript, policy pol=policy::strict)
std::regex_error-compatible exception.
regex_error(const std::regex_error &error)
From a std backend error (the fallback path); keeps std's exact code.
const char * what() const noexcept override
regex_error(std::regex_constants::error_type code, std::string message)
With an explicit code and message — the strict-policy rejection of a pattern REAL cannot represent li...
std::string message_
The originating error's detailed message.
const char * what() const noexcept override
Returns the formatted error message (with position).
bool has_empty_alternation_branch(std::string_view p)
Whether p has an empty alternation branch — a | with nothing on one side: (|, |), ||,...
std::optional< std::string > translate_ere(std::string_view p, bool awk=false)
Translates a POSIX extended (ERE) — or, with awk, an awk — pattern to an equivalent REAL pattern,...
bool append_awk_escape(std::string_view p, std::size_t &i, std::string &out)
Appends the REAL translation of an awk C-escape at i (which points at the backslash),...
std::optional< std::string > translate_newline_alt(std::string_view p, LineFn translate_line)
grep / egrep: a newline in the pattern is a top-level alternation of the lines (grep = BRE lines,...
std::optional< std::string > translate_posix(std::string_view p, regex_constants::syntax_option_type f)
Dispatches a single POSIX grammar to its translator, or nullopt (→ std). Exactly one grammar bit must...
bool format_forces_std(std::string_view fmt) noexcept
Replacement format text that must route to std: $0. $0 is platform-variant (libstdc++ = the whole mat...
bool translate_bracket(std::string_view p, std::size_t &i, std::string &out)
Translates a POSIX bracket expression [...] — identical syntax in BRE and ERE, so shared by both tran...
constexpr bool real_eligible
Whether real is even eligible for this basic_regex instantiation. real runs only the char path with d...
bool grammar_forces_std(regex_constants::syntax_option_type f) noexcept
Grammars/options that force the std backend (real implements default-traits ECMAScript,...
std::string posix_class_ranges(std::string_view name)
A POSIX bracket-class name to its ASCII (C-locale) range content, appended inside a [....
std::optional< std::string > translate_bre(std::string_view p)
Translates a POSIX basic (BRE) pattern to an equivalent REAL pattern, or nullopt. BRE differs from ER...
bool pattern_forces_std(std::string_view p) noexcept
Pattern text that real accepts but matches differently from libstdc++ — a both-accept silent divergen...
real::flags to_real(regex_constants::syntax_option_type f) noexcept
Maps compat options to real::flags (always with bytes|ecma for std-char alignment).
std::regex_constants::syntax_option_type to_std(regex_constants::syntax_option_type f) noexcept
Maps compat options to std::regex syntax flags (the fallback path).
constexpr syntax_option_type operator&(syntax_option_type a, syntax_option_type b) noexcept
syntax_option_type
Grammar / option flags (own bit values; mapped to real::flags or std at construction).
@ awk
awk grammar — std backend.
@ collate
Locale-sensitive ranges; forces the std backend.
@ egrep
egrep grammar — std backend.
@ nosubs
Do not expose sub-expressions (groups still computed).
@ grep
grep grammar — std backend.
@ ECMAScript
The default grammar.
@ icase
Case-insensitive (ASCII).
@ basic
POSIX basic — std backend.
@ optimize
Hint to favour matching speed; honoured as a no-op.
@ multiline
^/$ match at line boundaries.
@ extended
POSIX extended — std backend.
constexpr syntax_option_type operator|(syntax_option_type a, syntax_option_type b) noexcept
std::regex_constants::error_type error_type
Error categories. Aliased to std's so regex_error::code() is a true drop-in.
match_flag_type
Match-control flags: the common subset.
@ match_not_null
Do not match an empty sequence.
@ match_not_bol
^ does not match the start of the sequence.
@ format_sed
sed/POSIX replacement syntax (routes to std).
@ match_not_bow
\b does not match at the start.
@ match_not_eol
$ does not match the end of the sequence.
@ format_first_only
Replace only the first match.
@ format_no_copy
Do not copy the parts of the text that did not match.
@ match_continuous
The match must start at the first character.
@ match_not_eow
\b does not match at the end.
constexpr match_flag_type operator~(match_flag_type a) noexcept
policy
The drop-in policy for a pattern the linear engine cannot represent (backreferences,...
@ strict
Reject an ineligible pattern (throws regex_error with error_complexity). The default.
@ fallback
Delegate an ineligible pattern to std::regex (backtracking — not ReDoS-safe).
@ first
Leftmost-first (Perl / Python re / the crate): source-order thread priority decides....
flags
Compilation flags, mirroring Python's re.I, re.M and re.S.
@ bytes
Binary mode: . and [^…] match raw bytes, not codepoints.
@ icase
Case-insensitive (ASCII).
@ ecma
ECMAScript compatibility: $ (no multiline) matches only at the very end (not before a final \n,...
@ multiline
^ and $ also match at line boundaries.
The public API: real::regex, real::static_regex and results.
bool empty_match_possible
The pattern can match the empty string (the nullable gate; conservative: assertions/lookarounds pass ...
pattern_hints hints
Search-acceleration hints.
REAL's version macros and the C++20 language-standard guard.