16#ifndef REAL_LAZY_DFA_HPP
17#define REAL_LAZY_DFA_HPP
41 static bool disabled {
false};
51 static bool disabled {
false};
62 static bool disabled {
false};
86 std::vector<std::pair<utf8_byte_range, std::int32_t>>
trans;
92 std::vector<utf8_trie_node>
nodes;
108 std::span<const code_range> cp_ranges)
110 std::vector<std::vector<utf8_byte_range>> seqs;
111 for (
int b = 0; b < 0x80;) {
112 if (cc.
ascii.
test(
static_cast<std::uint8_t
>(b))) {
114 while (b < 0x80 && cc.
ascii.
test(
static_cast<std::uint8_t
>(b))) {
117 seqs.push_back({
utf8_byte_range {.
lo =
static_cast<std::uint8_t
>(lo), .hi =
static_cast<std::uint8_t
>(b - 1)}});
123 for (std::uint32_t k = 0; k < cc.
range_count; ++k) {
126 std::vector<utf8_byte_range> seq;
127 for (std::size_t j = 0; j < s.length; ++j) {
128 seq.push_back(s.parts[j]);
130 seqs.push_back(std::move(seq));
140 std::vector<utf8_trie_node>& nodes;
141 std::vector<std::vector<std::int32_t>>& memo;
147 static constexpr std::uint64_t hash_trans(
const std::vector<std::pair<utf8_byte_range, std::int32_t>>& trans)
149 std::uint64_t h {1469598103934665603ULL};
150 for (
const std::pair<utf8_byte_range, std::int32_t>& t : trans) {
151 h = (h ^ t.first.lo) * 1099511628211ULL;
152 h = (h ^ t.first.hi) * 1099511628211ULL;
153 h = (h ^
static_cast<std::uint32_t
>(t.second)) * 1099511628211ULL;
158 static bool trans_equal(
const std::vector<std::pair<utf8_byte_range, std::int32_t>>& a,
159 const std::vector<std::pair<utf8_byte_range, std::int32_t>>& b)
161 if (a.size() != b.size()) {
164 for (std::size_t i = 0; i < a.size(); ++i) {
165 if (a[i].
first.lo != b[i].first.lo || a[i].first.hi != b[i].first.hi || a[i].second != b[i].second) {
172 std::int32_t build(
const std::vector<std::vector<utf8_byte_range>>& in)
174 std::vector<int> bounds;
175 for (
const std::vector<utf8_byte_range>& s : in) {
176 bounds.push_back(s[0].lo);
177 bounds.push_back(s[0].hi + 1);
179 std::sort(bounds.begin(), bounds.end());
180 bounds.erase(std::unique(bounds.begin(), bounds.end()), bounds.end());
183 for (std::size_t i = 0; i + 1 < bounds.size(); ++i) {
184 const int lo {bounds[i]};
185 const int hi {bounds[i + 1] - 1};
186 std::vector<std::vector<utf8_byte_range>> tails;
187 bool all_empty {
true};
188 for (
const std::vector<utf8_byte_range>& s : in) {
189 if (s[0].lo <= lo && hi <= s[0].hi) {
190 std::vector<utf8_byte_range> tail(s.begin() + 1, s.end());
191 all_empty = all_empty && tail.empty();
192 tails.push_back(std::move(tail));
198 std::int32_t child {-1};
200 std::vector<std::vector<utf8_byte_range>> nonempty;
201 for (std::vector<utf8_byte_range>& t : tails) {
203 nonempty.push_back(std::move(t));
206 child = build(nonempty);
208 node.
trans.emplace_back(
utf8_byte_range {.
lo =
static_cast<std::uint8_t
>(lo), .hi =
static_cast<std::uint8_t
>(hi)}, child);
211 std::vector<std::int32_t>& bucket {memo[hash_trans(node.
trans) % memo.size()]};
212 for (
const std::int32_t existing : bucket) {
213 if (trans_equal(nodes[
static_cast<std::size_t
>(existing)].trans, node.
trans)) {
217 const auto id {
static_cast<std::int32_t
>(nodes.size())};
218 nodes.push_back(std::move(node));
219 bucket.push_back(
id);
223 constexpr std::size_t trie_memo_buckets {1024};
224 std::vector<std::vector<std::int32_t>> memo(trie_memo_buckets);
225 builder b {trie.
nodes, memo};
226 trie.
root = b.build(seqs);
239 n += (3 * node.trans.size()) - 1;
256 const auto base {
static_cast<std::int32_t
>(bp.
code.size())};
257 std::vector<std::int32_t> order {trie.
root};
258 for (std::int32_t i = 0; i < static_cast<std::int32_t>(trie.
nodes.size()); ++i) {
259 if (i != trie.
root) {
263 std::vector<std::int32_t> node_pc(trie.
nodes.size(), 0);
264 std::int32_t off {base};
265 for (
const std::int32_t
id : order) {
266 node_pc[
static_cast<std::size_t
>(id)] = off;
267 off += (3 *
static_cast<std::int32_t
>(trie.
nodes[
static_cast<std::size_t
>(id)].trans.size())) - 1;
269 for (
const std::int32_t
id : order) {
271 const auto k {
static_cast<std::int32_t
>(node.trans.size())};
272 for (std::int32_t j = 0; j < k; ++j) {
273 const auto& edge {node.trans[
static_cast<std::size_t
>(j)]};
274 const std::int32_t target {edge.second < 0 ? after : node_pc[
static_cast<std::size_t
>(edge.second)]};
275 const auto here {
static_cast<std::int32_t
>(bp.
code.size())};
277 bp.
code.push_back({.op =
opcode::split, .primary_target = here + 1, .secondary_target = here + 3});
280 cc.
set_range(edge.first.lo, edge.first.hi);
296 bool keep_assertions =
false)
306 if (!keep_assertions || in.arg16 != 0) {
315 const std::size_t n {prog.
code.size()};
316 std::vector<std::int32_t> map(n + 1, 0);
317 std::vector<utf8_trie> tries(n);
319 for (std::size_t pc = 0; pc < n; ++pc) {
320 map[pc] =
static_cast<std::int32_t
>(cur);
324 map[pc + 1] = map[pc + 2] = map[pc + 3] =
static_cast<std::int32_t
>(cur);
331 map[n] =
static_cast<std::int32_t
>(cur);
333 const auto remap {[&](std::int32_t t) {
334 return (t >= 0 &&
static_cast<std::size_t
>(t) <= n) ? map[
static_cast<std::size_t
>(t)] : t;
336 for (std::size_t pc = 0; pc < n; ++pc) {
345 out.secondary_target = remap(in.secondary_target);
346 bp.
code.push_back(out);
359 std::array<std::uint8_t, 256>
of {};
366 std::span<const char_class> classes)
368 std::vector<char_class> class_preds;
369 std::vector<std::uint16_t> literal_preds;
370 const auto push_unique {[](
auto& vec,
const auto& value) {
371 for (
const auto& existing : vec) {
372 if (existing == value) {
376 vec.push_back(value);
378 for (
const instr& in : code) {
380 push_unique(class_preds, classes[in.arg16]);
383 push_unique(literal_preds,
static_cast<std::uint16_t
>(in.arg8));
386 const auto sig_equal {[&](
unsigned a,
unsigned b) {
388 if (cc.test(
static_cast<std::uint8_t
>(a)) != cc.test(
static_cast<std::uint8_t
>(b))) {
392 for (
const std::uint16_t lit : literal_preds) {
393 if ((a == lit) != (b == lit)) {
400 std::array<std::uint8_t, 256> rep {};
401 for (
unsigned b = 0; b < 256U; ++b) {
402 bool assigned {
false};
403 for (std::uint16_t c = 0; c < alpha.
count; ++c) {
404 if (sig_equal(b, rep[c])) {
405 alpha.
of[b] =
static_cast<std::uint8_t
>(c);
411 rep[alpha.
count] =
static_cast<std::uint8_t
>(b);
412 alpha.
of[b] =
static_cast<std::uint8_t
>(alpha.
count);
430 std::vector<std::vector<std::uint32_t>>
buckets;
436 static constexpr std::size_t
hash(
const std::vector<std::int32_t>& v)
441 std::uint64_t h {1469598103934665603ULL};
442 for (
const std::int32_t x : v) {
443 h = (h ^
static_cast<std::uint64_t
>(
static_cast<std::uint32_t
>(x))) * 1099511628211ULL;
445 return static_cast<std::size_t
>(h);
448 [[nodiscard]]
constexpr std::uint32_t
find(
const std::vector<std::int32_t>& pcs,
449 const std::vector<std::vector<std::int32_t>>& state_pcs)
const
452 if (state_pcs[
id] == pcs) {
459 constexpr void insert(
const std::vector<std::int32_t>& pcs,
467 for (std::vector<std::uint32_t>& b :
buckets) {
518 explicit constexpr lazy_dfa(std::span<const instr> code,
519 std::span<const char_class> classes,
580 std::size_t best_end {
npos};
581 bool matched {
false};
593 if (pos >= text.size() || state ==
dead_state) {
596 const std::uint8_t
byte {
static_cast<std::uint8_t
>(text[pos])};
605 [[nodiscard]]
bool is_match(std::uint32_t state)
const
614 std::uint32_t
step(std::uint32_t state,
618 const std::uint32_t cached {
trans_[(
static_cast<std::size_t
>(state) *
alpha_.
count) + cls]};
624 std::vector<std::int32_t> next;
625 std::vector<char> seen(
code_.size(), 0);
626 for (
const std::int32_t pc :
state_pcs_[state]) {
632 const std::uint32_t result {
intern(next)};
656 const std::vector<std::int32_t> pcs {
state_pcs_[state]};
657 std::vector<std::int32_t> next;
658 std::vector<char> seen(
code_.size(), 0);
659 for (
const std::int32_t pc : pcs) {
666 const std::uint32_t result {
intern(next)};
675 std::uint32_t
cut(std::uint32_t state,
681 std::vector<std::int32_t>
prefix;
683 for (std::uint32_t i = 0; i < m; ++i) {
709 for (
const instr& in : code) {
719 std::uint8_t
byte)
const
721 const instr& in {
code_[
static_cast<std::size_t
>(pc)]};
723 return static_cast<std::uint8_t
>(in.arg8) ==
byte;
726 return classes_[in.arg16].test(
byte);
739 std::vector<std::int32_t>& out,
740 std::vector<char>& seen)
const
742 if (pc < 0 ||
static_cast<std::size_t
>(pc) >=
code_.size()) {
745 std::vector<std::int32_t> stack {pc};
746 while (!stack.empty()) {
747 const std::int32_t cur {stack.back()};
749 if (cur < 0 ||
static_cast<std::size_t
>(cur) >=
code_.size() || seen[
static_cast<std::size_t
>(cur)] != 0) {
752 seen[
static_cast<std::size_t
>(cur)] = 1;
753 const instr& in {
code_[
static_cast<std::size_t
>(cur)]};
761 stack.push_back(in.secondary_target);
762 stack.push_back(in.primary_target);
765 stack.push_back(in.primary_target);
768 stack.push_back(cur + 1);
777 constexpr std::uint32_t
intern(
const std::vector<std::int32_t>& pcs)
793 constexpr std::uint32_t
intern_fresh(
const std::vector<std::int32_t>& pcs)
795 const auto id {
static_cast<std::uint32_t
>(
state_pcs_.size())};
800 for (std::size_t i = 0; i < pcs.size(); ++i) {
802 match_idx =
static_cast<std::uint32_t
>(i);
835 std::vector<std::int32_t> start;
836 std::vector<char> seen(
code_.size(), 0);
876 std::span<const char_class> classes,
887 for (std::int32_t pc = 0; pc < static_cast<std::int32_t>(code.size()); ++pc) {
888 const instr& in {code[
static_cast<std::size_t
>(pc)]};
892 rev_consume_[
static_cast<std::size_t
>(pc) + 1].push_back(pc);
895 rev_eps_[
static_cast<std::size_t
>(in.primary_target)].push_back(pc);
896 rev_eps_[
static_cast<std::size_t
>(in.secondary_target)].push_back(pc);
899 rev_eps_[
static_cast<std::size_t
>(in.primary_target)].push_back(pc);
902 rev_eps_[
static_cast<std::size_t
>(pc) + 1].push_back(pc);
929 std::size_t best {
npos};
939 state =
step(state,
static_cast<std::uint8_t
>(text[pos]));
947 std::vector<char>& seen)
const
949 std::vector<std::int32_t> stack {set};
950 while (!stack.empty()) {
951 const std::int32_t pc {stack.back()};
953 for (
const std::int32_t pred :
rev_eps_[
static_cast<std::size_t
>(pc)]) {
954 if (seen[
static_cast<std::size_t
>(pred)] == 0) {
955 seen[
static_cast<std::size_t
>(pred)] = 1;
957 stack.push_back(pred);
961 std::sort(set.begin(), set.end());
964 std::uint32_t
step(std::uint32_t state,
968 const std::uint32_t cached {
trans_[(
static_cast<std::size_t
>(state) *
alpha_.
count) + cls]};
972 const std::vector<std::int32_t> pcs {
state_pcs_[state]};
973 std::vector<std::int32_t> next;
974 std::vector<char> seen(
code_.size(), 0);
975 for (
const std::int32_t pc : pcs) {
976 for (
const std::int32_t pred :
rev_consume_[
static_cast<std::size_t
>(pc)]) {
977 if (
consumes(pred,
byte) && seen[
static_cast<std::size_t
>(pred)] == 0) {
978 seen[
static_cast<std::size_t
>(pred)] = 1;
979 next.push_back(pred);
984 const std::uint32_t result {
intern(next)};
990 std::uint8_t
byte)
const
992 const instr& in {
code_[
static_cast<std::size_t
>(pc)]};
994 return static_cast<std::uint8_t
>(in.arg8) ==
byte;
1001 for (
const instr& in : code) {
1010 constexpr std::uint32_t
intern(
const std::vector<std::int32_t>& pcs)
1022 const auto id {
static_cast<std::uint32_t
>(
state_pcs_.size())};
1025 bool has_start {
false};
1026 for (
const std::int32_t pc : pcs) {
1046 std::vector<std::int32_t> start;
1047 std::vector<char> seen(
code_.size(), 0);
1049 seen[
static_cast<std::size_t
>(
match_pc_)] = 1;
A lazy priority-preserving forward DFA over a Pike program (the kFirstMatch forward pass).
std::span< const char_class > classes_
std::vector< std::uint32_t > state_cut_
state id -> memoized priority-cut result (no_transition = not yet computed).
static constexpr bool compute_eligibility(std::span< const instr > code)
static constexpr std::uint32_t no_transition
A not-yet-computed cached transition.
constexpr std::uint32_t intern_fresh(const std::vector< std::int32_t > &pcs)
std::uint32_t step(std::uint32_t state, std::uint8_t byte)
Transition state on byte to the next DFA state, computing and caching it on first use....
std::size_t forward_end(std::string_view text)
The end offset of the leftmost-first match in text (kFirstMatch), or real::npos.
std::span< const instr > code_
std::uint32_t cut(std::uint32_t state, std::uint32_t m)
The priority-cut at an accept: intern the prefix of state's ordered pc-set before index m (dropping t...
static constexpr std::size_t state_budget
Cached states before a flush (the memory cap).
std::uint32_t cut_cached(std::uint32_t state)
The priority-cut of state at its own accept, memoized. cut is O(state size) — it rebuilds and re-inte...
std::uint32_t start_state_
std::vector< std::vector< std::int32_t > > state_pcs_
state id -> ordered pc-set.
static std::int32_t consumed_width(std::int32_t)
std::vector< std::uint32_t > trans_
flat [state*stride + class] -> next, unseeded (post-match); stride = alpha_.count.
void begin_scan()
Begin a search: clear the per-scan flush counter and the thrash flag. (No cache flush — the states ca...
bool is_match(std::uint32_t state) const
Whether state accepts here (its ordered set contains a match PC).
constexpr void flush()
Empty the cache back to the dead + start states (the eviction: bounded memory).
constexpr std::uint32_t intern(const std::vector< std::int32_t > &pcs)
Intern an ordered pc-set into a state id (cached). Flushes the cache when the budget is hit.
lazy_byte_alphabet alpha_
std::uint32_t step_seeded(std::uint32_t state, std::uint8_t byte)
Like step, but re-seeds: the unanchored-search variant appends pc 0's closure at the lowest priority,...
static constexpr std::uint32_t no_match_idx
A state whose ordered set holds no accept.
constexpr void close_into(std::int32_t pc, std::vector< std::int32_t > &out, std::vector< char > &seen) const
Append the ordered epsilon-closure of pc to out (split priority; save/jump crossed),...
bool consumes(std::int32_t pc, std::uint8_t byte) const
std::vector< std::uint32_t > trans_seeded_
flat [state*stride + class] -> next, re-seeding (pre-match).
std::uint16_t num_classes() const
std::vector< std::uint32_t > state_match_idx_
state id -> index of its first accept, or no_match_idx.
std::uint32_t start_state() const
const counters & stats() const
static constexpr std::size_t thrash_flushes
Flushes within one scan that trip thrashing.
constexpr lazy_dfa(std::span< const instr > code, std::span< const char_class > classes, std::size_t budget=state_budget, const lazy_byte_alphabet *shared_alpha=nullptr)
Builds the (initially empty) lazy DFA over a Pike program.
static constexpr std::uint32_t dead_state
The empty state: every transition from it stays here.
The start-finder companion to lazy_dfa. Given a match end, it finds the leftmost start (the design gu...
bool consumes(std::int32_t pc, std::uint8_t byte) const
std::vector< std::vector< std::int32_t > > state_pcs_
static constexpr std::uint32_t dead_state
std::size_t reverse_start(std::string_view text, std::size_t e, std::size_t resume)
The leftmost start of the match ending at e, not before resume. Scans the text backward from e over t...
std::span< const instr > code_
static constexpr bool compute_eligibility(std::span< const instr > code)
std::vector< std::vector< std::int32_t > > rev_eps_
transposed epsilon edges.
constexpr reverse_dfa(std::span< const instr > code, std::span< const char_class > classes, std::size_t budget=state_budget, const lazy_byte_alphabet *shared_alpha=nullptr)
constexpr std::uint32_t intern(const std::vector< std::int32_t > &pcs)
std::uint32_t step(std::uint32_t state, std::uint8_t byte)
lazy_byte_alphabet alpha_
std::vector< std::uint32_t > trans_
flat [state*stride + class] -> next.
static constexpr std::size_t state_budget
std::vector< char > state_has_start_
state -> reaches the program start (an accept).
constexpr void rev_closure(std::vector< std::int32_t > &set, std::vector< char > &seen) const
static constexpr std::uint32_t no_transition
std::uint32_t start_state_
std::span< const char_class > classes_
std::vector< std::vector< std::int32_t > > rev_consume_
transposed consuming edges (the pred consuming pcs).
constexpr std::vector< utf8_byte_seq > utf8_range_sequences(std::uint32_t lo, std::uint32_t hi)
Canonical UTF-8 byte-range sequences for the code-point range [lo, hi], excluding the surrogate block...
std::size_t utf8_trie_emit_size(const utf8_trie &trie)
The instruction count emit_utf8_trie writes: an empty class is one dead klass; otherwise each node is...
bool & lazy_dfa_route_disabled()
Test seam: force the matcher off the lazy-DFA route onto the pure Pike VM, so a differential can asse...
void emit_utf8_trie(byte_program &bp, const utf8_trie &trie, std::int32_t after)
Emits trie into bp as a deterministic split/klass/jump fragment; accept edges jump to after (the cons...
bool & inner_literal_guard_disabled()
Test seam: force the inner-literal small-haystack guard off, so the route fires on any size....
bool & inner_literal_route_disabled()
Test seam: force the matcher off the inner-literal search route (IL.2) onto the core search,...
utf8_trie build_utf8_trie(const cp_class &cc, std::span< const code_range > cp_ranges)
Builds the minimal deterministic trie recognising a code-point class's UTF-8 byte sequences.
@ prefix
Anchored at the start position (Python re.match).
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)....
byte_program build_byte_program(const program_view &prog, bool keep_assertions=false)
Builds the byte-level DFA program for prog (see byte_program). A klass_cp at P (a four- instruction c...
@ klass_cp
Consume one code point tested against cp_classes[arg16] (decode + range bsearch); enters a 3-instr co...
@ 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.
@ assert_lookaround
Epsilon; proceeds only if the lookaround sub-program arg16 holds here.
@ assert_position
Epsilon; proceeds only if assertion arg8 holds here.
@ split
Epsilon-branch to x (preferred) and y.
constexpr std::size_t npos
Sentinel for "no position" / unset capture slot (akin to std::string::npos).
@ first
Leftmost-first (Perl / Python re / the crate): source-order thread priority decides....
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...
bool eligible
Representable by the byte DFAs / Tier-A one-pass.
bool has_assertions
A Tier-B build kept assert_position ops (else stripped/declined).
std::vector< char_class > classes
bool unicode_word
Program default word-ness for \b \B < > (Tier-B edge conditions).
std::vector< instr > code
A set of byte values (0–255) as a 256-bit bitmap.
constexpr void set_range(std::uint8_t low, std::uint8_t high)
Adds the inclusive byte range [low, high] to the set.
constexpr bool test(std::uint8_t byte) const
Tests membership of byte byte.
An inclusive code-point range [lo, hi]. Shared by character classes (ast.hpp) and the generated Unico...
A match-time code-point class for the klass_cp opcode: an ASCII bitmap for code points < 0x80 plus a ...
std::uint32_t range_count
Number of ranges belonging to this class.
std::uint32_t range_begin
First range in the program's cp_ranges buffer.
char_class ascii
Members < 0x80.
One NFA instruction. Field meaning depends on op.
std::int32_t primary_target
Primary branch target (split/jump).
Byte-class alphabet over a Pike program: bytes that satisfy exactly the same byte/klass predicates sh...
std::uint16_t count
number of distinct classes.
std::array< std::uint8_t, 256 > of
byte -> class index.
Cache-behaviour counters, for the policy tests and later tuning.
std::size_t scan_flushes
flushes in the current scan (reset by begin_scan).
A tiny open-chaining hash set of interned PC-set state ids, keyed by their pc-set....
std::vector< std::vector< std::uint32_t > > buckets
constexpr std::uint32_t find(const std::vector< std::int32_t > &pcs, const std::vector< std::vector< std::int32_t > > &state_pcs) const
static constexpr std::size_t hash(const std::vector< std::int32_t > &v)
constexpr void insert(const std::vector< std::int32_t > &pcs, std::uint32_t id)
static constexpr std::size_t bucket_count
static constexpr std::uint32_t not_found
std::span< const code_range > cp_ranges
Flat range buffer the cp_class slices index into.
bool unicode_word
\b \B < > use Unicode word-ness (text mode, not bytes / re.A).
std::span< const char_class > classes
Interned character classes.
std::span< const cp_class > cp_classes
Match-time code-point classes (for klass_cp).
std::span< const instr > code
The instruction stream (main + lookaround regions).
One byte-range step [lo, hi] of a UTF-8 sequence produced by the code-point-range algorithm.
std::uint8_t lo
Low byte (inclusive).
A canonical UTF-8 byte-range sequence (1–4 steps) covering part of a code-point range.
One node of a minimal deterministic UTF-8 trie for a code-point class. Its transitions are byte range...
std::vector< std::pair< utf8_byte_range, std::int32_t > > trans
A minimal deterministic UTF-8 trie for a code-point class. root == -1 means the class is empty.
std::vector< utf8_trie_node > nodes
Code-point range → canonical UTF-8 byte-range sequences (RE2 / rust regex-syntax Utf8Sequences).