REAL
Regular Expression Algorithmic Library — constexpr C++20 regex
Loading...
Searching...
No Matches
onepass.hpp
Go to the documentation of this file.
1
20#ifndef REAL_ONEPASS_HPP
21#define REAL_ONEPASS_HPP
22
23// Internal — do not include directly.
24// Users: #include <real/real.hpp> (or the documented opt-ins <real/dfa.hpp>, <real/std/regex.hpp>).
25
26#include <algorithm>
27#include <array>
28#include <bit>
29#include <mutex>
30#include <optional>
31#include <cstddef>
32#include <cstdint>
33#include <string>
34#include <string_view>
35#include <vector>
36
39#include "real/core/program.hpp"
40
41namespace real::detail {
42
47 {
48 std::uint32_t next {0};
49 std::uint64_t cap_mask {0};
50 std::uint32_t assert_mask {0};
51 bool assigned {false};
52 };
53
57 {
58 std::vector<onepass_edge> edge;
59 bool matches {false};
60 std::uint64_t match_cap_mask {0};
61 std::uint32_t match_assert_mask {0};
62 };
63
74 class onepass
75 {
76 public:
77
78 static constexpr std::uint32_t no_node {0xFFFFFFFFU};
79 static constexpr std::size_t max_nodes {65000};
80 static constexpr std::size_t max_slots {10};
81 static constexpr std::size_t minimize_buckets {4096};
82 static constexpr std::size_t max_table_bytes {8U << 20};
83
87 explicit constexpr onepass(const byte_program& bp,
88 std::size_t max_bytes = max_table_bytes)
89 : max_bytes_ {max_bytes},
90 ascii_word_ {!bp.unicode_word} // word-ness mode for \b \B < > in edge conditions (Tier-B)
91 {
92 if (!bp.eligible) {
93 bail("the byte-program is itself ineligible (a lookaround, or a word-ness-flipped assertion)");
94 return;
95 }
96 build(bp);
97 }
98
99 [[nodiscard]] bool eligible() const
100 {
101 return eligible_;
102 }
103
104 [[nodiscard]] const std::string& bail_reason() const
105 {
106 return bail_reason_;
107 }
108
109 [[nodiscard]] std::size_t node_count() const
110 {
111 return nodes_.size();
112 }
113
114 [[nodiscard]] std::uint16_t num_classes() const
115 {
116 return alpha_.count;
117 }
118
119 [[nodiscard]] const std::vector<onepass_node>& nodes() const
120 {
121 return nodes_;
122 }
123
125 [[nodiscard]] std::uint8_t class_of(std::uint8_t byte) const
126 {
127 return alpha_.of[byte];
128 }
129
131 [[nodiscard]] std::size_t slot_count() const
132 {
133 return slot_count_;
134 }
135
150 template <typename OutSlots>
151 [[nodiscard]] bool extract(std::string_view text,
152 std::size_t s,
153 std::size_t e,
154 OutSlots& out) const
155 {
156 if (!eligible_) {
157 return false;
158 }
159 out.assign(slot_count_, npos);
160 std::uint32_t node {0}; // node 0 is the start (the closure of pc 0)
161 for (std::size_t pos = s; pos < e; ++pos) {
162 const std::uint8_t cls {alpha_.of[static_cast<std::uint8_t>(text[pos])]};
163 const onepass_edge& edge {nodes_[node].edge[cls]};
164 if (!edge.assigned) {
165 return false; // no outgoing edge for this byte — the span does not match
166 }
167 if (edge.assert_mask != 0 && !asserts_hold(edge.assert_mask, text, pos)) {
168 return false; // an assertion on this edge does not hold here (Tier-B)
169 }
170 for (std::uint64_t m = edge.cap_mask; m != 0; m &= m - 1) {
171 out[static_cast<std::size_t>(std::countr_zero(m))] = pos; // saves crossed before this byte take pos
172 }
173 node = edge.next;
174 }
175 if (!nodes_[node].matches) {
176 return false; // reached e but not at an accept
177 }
178 if (nodes_[node].match_assert_mask != 0 && !asserts_hold(nodes_[node].match_assert_mask, text, e)) {
179 return false; // an end assertion ($, \b, \Z…) does not hold at e
180 }
181 for (std::uint64_t m = nodes_[node].match_cap_mask; m != 0; m &= m - 1) {
182 out[static_cast<std::size_t>(std::countr_zero(m))] = e; // saves crossed to the match take e
183 }
184 return true;
185 }
186
188 [[nodiscard]] bool asserts_hold(std::uint32_t mask,
189 std::string_view text,
190 std::size_t pos) const
191 {
192 for (std::uint32_t m = mask; m != 0; m &= m - 1) {
193 const auto kind {static_cast<assert_kind>(std::countr_zero(m))};
194 if (!assertion_holds(kind, text, pos, ascii_word_)) {
195 return false;
196 }
197 }
198 return true;
199 }
200
201 private:
202
206 constexpr void bail(const char* reason,
207 std::int32_t node = -1,
208 std::int32_t klass = -1,
209 std::int32_t pc = -1)
210 {
211 eligible_ = false;
212 bail_reason_ = reason;
213 bail_node_ = node;
215 bail_pc_ = pc;
216 }
217
219 constexpr std::uint32_t node_of(std::int32_t pc,
220 std::vector<std::int32_t>& queue)
221 {
222 std::uint32_t& id {pc_to_node_[static_cast<std::size_t>(pc)]};
223 if (id == no_node) {
224 id = static_cast<std::uint32_t>(nodes_.size());
225 onepass_node fresh;
226 fresh.edge.assign(alpha_.count, onepass_edge {});
227 nodes_.push_back(std::move(fresh));
228 queue.push_back(pc);
229 }
230 return id;
231 }
232
233 constexpr void build(const byte_program& bp)
234 {
235 code_ = bp.code;
236 classes_ = bp.classes;
237 alpha_ = compute_lazy_alphabet(bp.code, bp.classes);
238
239 // For each interned char-class, the byte-classes it consumes — so a `klass` instruction writes only
240 // those edges instead of scanning the whole alphabet. Computed once here (O(classes x 256)) rather
241 // than per instruction (which was O(nodes x classes) and dominated a Unicode \w build).
242 class_cover_.assign(classes_.size(), {});
243 for (std::size_t i = 0; i < classes_.size(); ++i) {
244 std::vector<std::uint16_t>& cover {class_cover_[i]};
245 for (unsigned b = 0; b < 256U; ++b) {
246 if (classes_[i].test(static_cast<std::uint8_t>(b))) {
247 cover.push_back(alpha_.of[b]);
248 }
249 }
250 std::ranges::sort(cover);
251 cover.erase(std::ranges::unique(cover).begin(), cover.end());
252 }
253
254 std::size_t max_slot {0};
255 for (const instr& in : bp.code) {
256 if (in.op == opcode::save) {
257 max_slot = std::max(max_slot, static_cast<std::size_t>(in.arg16));
258 }
259 }
260 slot_count_ = max_slot + 1;
261 if (slot_count_ > max_slots) {
262 bail("too many capture slots: the general Pike VM keeps these", static_cast<std::int32_t>(slot_count_));
263 return;
264 }
265
266 pc_to_node_.assign(bp.code.size(), no_node);
267 std::vector<std::int32_t> queue;
268 node_of(0, queue); // the start node
269 while (!queue.empty() && eligible_) {
270 const std::int32_t pc {queue.back()};
271 queue.pop_back();
272 std::vector<char> on_path(bp.code.size(), 0);
273 build_edges(pc, 0, 0, on_path, pc_to_node_[static_cast<std::size_t>(pc)], queue);
274 if (nodes_.size() > max_nodes) {
275 bail("node cap exceeded", static_cast<std::int32_t>(nodes_.size()));
276 return;
277 }
278 }
279 if (!eligible_) {
280 return;
281 }
282 minimize(); // (i) collapse equivalent nodes (the byte-program's trie sharing, lost in the flood, recovered)
283 // (ii) memory cap: even minimized, a pathological table declines to the Pike VM rather than bloat the regex.
284 std::size_t bytes {0};
285 for (const onepass_node& nd : nodes_) {
286 bytes += nd.edge.capacity() * sizeof(onepass_edge);
287 }
288 if (bytes > max_bytes_) {
289 bail("one-pass table too large after minimization (MB)", static_cast<std::int32_t>(bytes >> 20));
290 }
291 }
292
299 constexpr void minimize()
300 {
301 const std::size_t n {nodes_.size()};
302 std::vector<std::uint32_t> cls(n, 0);
303 std::uint32_t classes {0};
304 while (true) {
305 std::vector<std::vector<std::uint64_t>> sigs(n);
306 for (std::size_t i = 0; i < n; ++i) {
307 std::vector<std::uint64_t>& s {sigs[i]};
308 s.push_back(cls[i]);
309 s.push_back(nodes_[i].matches ? 1U : 0U);
310 s.push_back(nodes_[i].match_cap_mask);
311 s.push_back(nodes_[i].match_assert_mask);
312 for (const onepass_edge& e : nodes_[i].edge) {
313 s.push_back(e.assigned ? static_cast<std::uint64_t>(cls[e.next]) + 1U : 0U);
314 s.push_back(e.cap_mask);
315 s.push_back(e.assert_mask);
316 }
317 }
318 std::vector<std::uint32_t> next_cls(n, 0);
319 std::vector<std::vector<std::uint32_t>> buckets(minimize_buckets);
320 std::uint32_t next {0};
321 for (std::size_t i = 0; i < n; ++i) {
322 std::vector<std::uint32_t>& bucket {buckets[sig_hash(sigs[i]) % minimize_buckets]};
323 std::uint32_t id {no_node};
324 for (const std::uint32_t j : bucket) {
325 if (sigs[j] == sigs[i]) {
326 id = next_cls[j];
327 break;
328 }
329 }
330 if (id == no_node) {
331 next_cls[i] = next++;
332 bucket.push_back(static_cast<std::uint32_t>(i));
333 }
334 else {
335 next_cls[i] = id;
336 }
337 }
338 cls = std::move(next_cls);
339 if (next == classes) {
340 break; // fixpoint: the partition stopped refining
341 }
342 classes = next;
343 }
344
345 // Rebuild with one node per class, the start (old node 0) renumbered to node 0.
346 std::vector<std::int32_t> rep(classes, -1);
347 for (std::size_t i = 0; i < n; ++i) {
348 if (rep[cls[i]] < 0) {
349 rep[cls[i]] = static_cast<std::int32_t>(i);
350 }
351 }
352 std::vector<std::uint32_t> class_id(classes, no_node);
353 class_id[cls[0]] = 0;
354 std::uint32_t assigned {1};
355 for (std::uint32_t c = 0; c < classes; ++c) {
356 if (class_id[c] == no_node) {
357 class_id[c] = assigned++;
358 }
359 }
360 std::vector<onepass_node> merged(classes);
361 for (std::uint32_t c = 0; c < classes; ++c) {
362 const onepass_node& r {nodes_[static_cast<std::size_t>(rep[c])]};
363 onepass_node& nn {merged[class_id[c]]};
364 nn.matches = r.matches;
365 nn.match_cap_mask = r.match_cap_mask;
366 nn.match_assert_mask = r.match_assert_mask;
367 nn.edge.assign(alpha_.count, onepass_edge {});
368 for (std::uint16_t x = 0; x < alpha_.count; ++x) {
369 if (r.edge[x].assigned) {
370 nn.edge[x] = onepass_edge {.next = class_id[cls[r.edge[x].next]],
371 .cap_mask = r.edge[x].cap_mask,
372 .assert_mask = r.edge[x].assert_mask,
373 .assigned = true};
374 }
375 }
376 }
377 nodes_ = std::move(merged);
378 }
379
383 static constexpr std::size_t sig_hash(const std::vector<std::uint64_t>& v)
384 {
385 std::uint64_t h {1469598103934665603ULL};
386 for (const std::uint64_t x : v) {
387 h = (h ^ x) * 1099511628211ULL;
388 }
389 return static_cast<std::size_t>(h);
390 }
391
394 constexpr void build_edges(std::int32_t pc,
395 std::uint64_t cap_mask,
396 std::uint32_t assert_mask,
397 std::vector<char>& on_path,
398 std::uint32_t node_id,
399 std::vector<std::int32_t>& queue)
400 {
401 if (!eligible_) {
402 return;
403 }
404 if (on_path[static_cast<std::size_t>(pc)] != 0) {
405 bail("epsilon cycle (a nullable loop): not one-pass", -1, -1, pc);
406 return;
407 }
408 on_path[static_cast<std::size_t>(pc)] = 1;
409 const instr& in {code_[static_cast<std::size_t>(pc)]};
410 switch (in.op) {
411 case opcode::byte:
412 case opcode::klass: {
413 const std::uint32_t next {node_of(pc + 1, queue)};
414 onepass_node& node {nodes_[node_id]};
415 // Only the byte-classes this instruction actually consumes (one for `byte`, a precomputed few
416 // for `klass`) — never a scan over the whole alphabet, which made the build O(nodes x classes)
417 // and dominated a Unicode \w find_iter.
418 const auto write_edge {[&](std::uint16_t cls) {
419 onepass_edge& slot {node.edge[cls]};
420 if (slot.assigned
421 && (slot.next != next || slot.cap_mask != cap_mask
422 || slot.assert_mask != assert_mask)) {
423 bail("byte-class conflict: not one-pass", static_cast<std::int32_t>(node_id),
424 cls, pc);
425 return false;
426 }
427 slot = onepass_edge {.next = next,
428 .cap_mask = cap_mask,
429 .assert_mask = assert_mask,
430 .assigned = true};
431 return true;
432 }};
433 if (in.op == opcode::byte) {
434 if (!write_edge(alpha_.of[static_cast<std::uint8_t>(in.arg8)])) {
435 return;
436 }
437 }
438 else {
439 for (const std::uint16_t cls : class_cover_[in.arg16]) {
440 if (!write_edge(cls)) {
441 return;
442 }
443 }
444 }
445 break; // a consuming instruction ends this epsilon path
446 }
447 case opcode::match: {
448 onepass_node& node {nodes_[node_id]};
449 if (node.matches && (node.match_cap_mask != cap_mask || node.match_assert_mask != assert_mask)) {
450 bail("second distinct match: not one-pass", static_cast<std::int32_t>(node_id));
451 return;
452 }
453 node.matches = true;
454 node.match_cap_mask = cap_mask;
455 node.match_assert_mask = assert_mask;
456 break;
457 }
458 case opcode::split:
459 build_edges(in.primary_target, cap_mask, assert_mask, on_path, node_id, queue);
460 build_edges(in.secondary_target, cap_mask, assert_mask, on_path, node_id, queue);
461 break;
462 case opcode::jump:
463 build_edges(in.primary_target, cap_mask, assert_mask, on_path, node_id, queue);
464 break;
465 case opcode::save:
466 build_edges(pc + 1, cap_mask | (std::uint64_t {1} << in.arg16), assert_mask, on_path, node_id, queue);
467 break;
468 case opcode::assert_position:
469 // Tier-B: the assertion becomes a condition on whatever edge (or match) this epsilon path reaches.
470 // The build_byte_program Tier-B pass already rejected a word-ness-flipped assert, so arg16 is 0.
471 build_edges(pc + 1, cap_mask, assert_mask | (std::uint32_t {1} << in.arg8), on_path, node_id, queue);
472 break;
473 default:
474 bail("unexpected op in byte-program (lookaround/klass_cp should be absent)");
475 return;
476 }
477 on_path[static_cast<std::size_t>(pc)] = 0; // backtrack: only a cycle bails, a diamond is fine
478 }
479
480 std::span<const instr> code_;
481 std::span<const char_class> classes_;
483 std::vector<std::vector<std::uint16_t>> class_cover_;
484 std::vector<std::uint32_t> pc_to_node_;
485 std::vector<onepass_node> nodes_;
486 std::size_t slot_count_ {0};
487 std::size_t max_bytes_ {max_table_bytes};
488 bool ascii_word_ {true};
489 std::int32_t bail_node_ {-1};
490 std::int32_t bail_class_ {-1};
491 std::int32_t bail_pc_ {-1};
492 bool eligible_ {true};
493 std::string bail_reason_;
494 };
495
502 {
505 std::optional<onepass> op_table;
507 std::size_t il_min_haystack {};
508 std::once_flag once;
509
510 // A copied regex is an independent regex: it gets its own fresh, unbuilt cache rather than sharing
511 // (std::once_flag is not copyable anyway). Copy/move reset rather than transfer — the built state is a
512 // pure runtime accelerator, cheap to rebuild.
513 regex_immutables() = default;
514 regex_immutables(const regex_immutables& /*other*/) noexcept
515 {}
516
517 regex_immutables(regex_immutables&& /*other*/) noexcept
518 {}
519
520 // Assignment keeps this object's own (fresh) cache — it deliberately copies/moves nothing, which is
521 // inherently self-assignment-safe (there is no state to guard). NOLINT: the empty body is the contract.
522 // NOLINTNEXTLINE(cert-oop54-cpp)
523 regex_immutables& operator=(const regex_immutables& /*other*/) noexcept
524 {
525 return *this;
526 }
527
528 // NOLINTNEXTLINE(cert-oop54-cpp)
530 {
531 return *this;
532 }
533
534 ~regex_immutables() = default;
535 };
536} // namespace real::detail
537
538#endif // REAL_ONEPASS_HPP
Zero-width assertion evaluation (^ $ \b \B \A \Z < >) as free functions over a subject and a positi...
Builds and holds the one-pass classification (and table, when eligible) of a byte-program.
Definition onepass.hpp:75
std::uint16_t num_classes() const
Definition onepass.hpp:114
std::size_t slot_count() const
The number of capture slots (group 0 start/end plus each group's).
Definition onepass.hpp:131
const std::string & bail_reason() const
Definition onepass.hpp:104
std::uint8_t class_of(std::uint8_t byte) const
The byte-class of byte, for a runtime that walks this table.
Definition onepass.hpp:125
std::int32_t bail_node_
Definition onepass.hpp:489
bool asserts_hold(std::uint32_t mask, std::string_view text, std::size_t pos) const
Whether every assertion in mask (a set of assert_kind bits) holds at pos in text.
Definition onepass.hpp:188
static constexpr std::size_t max_slots
Slot-pointer cap: group 0 + four user groups.
Definition onepass.hpp:80
constexpr void build(const byte_program &bp)
Definition onepass.hpp:233
std::span< const char_class > classes_
Definition onepass.hpp:481
std::int32_t bail_pc_
Definition onepass.hpp:491
std::string bail_reason_
Definition onepass.hpp:493
bool eligible() const
Definition onepass.hpp:99
std::int32_t bail_class_
Definition onepass.hpp:490
constexpr void build_edges(std::int32_t pc, std::uint64_t cap_mask, std::uint32_t assert_mask, std::vector< char > &on_path, std::uint32_t node_id, std::vector< std::int32_t > &queue)
Walk the epsilon-closure from pc, writing this node's edges. on_path detects epsilon cycles (a nullab...
Definition onepass.hpp:394
static constexpr std::size_t sig_hash(const std::vector< std::uint64_t > &v)
FNV-1a hash of a partition signature (for the constexpr-friendly bucket dedup). Accumulates in a fixe...
Definition onepass.hpp:383
std::size_t max_bytes_
Definition onepass.hpp:487
std::vector< std::uint32_t > pc_to_node_
pc -> node id (or no_node).
Definition onepass.hpp:484
std::span< const instr > code_
Definition onepass.hpp:480
std::size_t node_count() const
Definition onepass.hpp:109
constexpr void bail(const char *reason, std::int32_t node=-1, std::int32_t klass=-1, std::int32_t pc=-1)
Reject as not one-pass, recording a category and the offending node / byte-class / pc (kept as intege...
Definition onepass.hpp:206
static constexpr std::size_t minimize_buckets
Hash buckets for the Moore-refinement dedup.
Definition onepass.hpp:81
static constexpr std::uint32_t no_node
"No node yet" sentinel in the pc->node map.
Definition onepass.hpp:78
std::size_t slot_count_
Definition onepass.hpp:486
std::vector< onepass_node > nodes_
Definition onepass.hpp:485
constexpr void minimize()
Moore partition refinement of the one-pass automaton: merge nodes that are behaviourally identical (s...
Definition onepass.hpp:299
constexpr onepass(const byte_program &bp, std::size_t max_bytes=max_table_bytes)
Definition onepass.hpp:87
const std::vector< onepass_node > & nodes() const
Definition onepass.hpp:119
static constexpr std::size_t max_nodes
Node cap (RE2's), a memory/DoS bound.
Definition onepass.hpp:79
lazy_byte_alphabet alpha_
Definition onepass.hpp:482
bool extract(std::string_view text, std::size_t s, std::size_t e, OutSlots &out) const
Fills out with the capture slots of the one-pass match on text[s, e) — the single left-to- right pass...
Definition onepass.hpp:151
constexpr std::uint32_t node_of(std::int32_t pc, std::vector< std::int32_t > &queue)
Get-or-create the node whose entry pc is pc, enqueueing a fresh one for the flood.
Definition onepass.hpp:219
static constexpr std::size_t max_table_bytes
Table-memory cap (~8 MB): larger declines to the VM.
Definition onepass.hpp:82
std::vector< std::vector< std::uint16_t > > class_cover_
char-class index -> the byte-classes it consumes.
Definition onepass.hpp:483
A lazy, priority-preserving forward DFA over the Pike program (the kFirstMatch forward pass + cache).
constexpr bool assertion_holds(assert_kind kind, std::string_view text, std::size_t pos, bool ascii_word)
Evaluates a zero-width assertion at pos in text.
assert_kind
Kind of zero-width assertion carried in assert_position's arg8.
Definition program.hpp:208
constexpr lazy_byte_alphabet compute_lazy_alphabet(std::span< const instr > code, std::span< const char_class > classes)
Partition 0..255 by the program's consuming predicates (every klass test, every byte literal)....
Definition lazy_dfa.hpp:365
@ byte
Consume one byte equal to arg8; fall through to pc+1.
@ save
Store current position in slot arg16; fall through (epsilon).
@ klass
Consume one byte in classes[arg16]; fall through to pc+1.
constexpr std::size_t npos
Sentinel for "no position" / unset capture slot (akin to std::string::npos).
Definition program.hpp:33
@ bytes
Binary mode: . and [^…] match raw bytes, not codepoints.
Compiled form of a pattern and the public flags / error types.
A byte-level program derived from a Pike program for the DFA passes: every klass_cp construct is expa...
Definition lazy_dfa.hpp:72
bool eligible
Representable by the byte DFAs / Tier-A one-pass.
Definition lazy_dfa.hpp:75
std::vector< char_class > classes
Definition lazy_dfa.hpp:74
std::vector< instr > code
Definition lazy_dfa.hpp:73
One NFA instruction. Field meaning depends on op.
Definition program.hpp:251
Byte-class alphabet over a Pike program: bytes that satisfy exactly the same byte/klass predicates sh...
Definition lazy_dfa.hpp:358
std::uint16_t count
number of distinct classes.
Definition lazy_dfa.hpp:360
std::array< std::uint8_t, 256 > of
byte -> class index.
Definition lazy_dfa.hpp:359
One outgoing edge of a one-pass node, for a byte-class: the next node and the capture slots that take...
Definition onepass.hpp:47
std::uint64_t cap_mask
Bit i set => write the current position into slot i on this edge.
Definition onepass.hpp:49
bool assigned
Whether this byte-class has an edge from this node.
Definition onepass.hpp:51
std::uint32_t assert_mask
Bit k (an assert_kind) set => that assertion must hold at the position to take this edge (Tier-B).
Definition onepass.hpp:50
std::uint32_t next
Next node id (valid only when assigned).
Definition onepass.hpp:48
A one-pass node: one edge per byte-class, plus whether the run may end here and with what captures....
Definition onepass.hpp:57
std::uint32_t match_assert_mask
Assertions that must hold at the end for the match (Tier-B).
Definition onepass.hpp:61
std::vector< onepass_edge > edge
Indexed by byte-class.
Definition onepass.hpp:58
std::uint64_t match_cap_mask
Slots written when the match is taken.
Definition onepass.hpp:60
bool matches
Reaching match from here (via epsilon).
Definition onepass.hpp:59
The per-regex immutable cache the router shares across every find_iter on a regex: the byte- program ...
Definition onepass.hpp:502
lazy_byte_alphabet alphabet
byte-class alphabet of byte_prog (shared by both DFAs, else recomputed per scan).
Definition onepass.hpp:504
std::once_flag once
guards the one-time build.
Definition onepass.hpp:508
regex_immutables(regex_immutables &&) noexcept
Definition onepass.hpp:517
std::optional< onepass > op_table
one-pass extractor, present iff the pattern is one-pass.
Definition onepass.hpp:505
regex_immutables & operator=(regex_immutables &&) noexcept
Definition onepass.hpp:529
byte_program il_prefix_prog
IL: the inner-literal prefix's byte program (ineligible until built). Per-regex so the reverse DFA th...
Definition onepass.hpp:506
regex_immutables & operator=(const regex_immutables &) noexcept
Definition onepass.hpp:523
regex_immutables(const regex_immutables &) noexcept
Definition onepass.hpp:514
byte_program byte_prog
klass_cp-expanded byte program (empty until built).
Definition onepass.hpp:503