66 : std::runtime_error(message)
94 std::vector<dfa_instr>
code;
109 for (std::size_t r = 0; r < programs.size(); ++r) {
111 const std::size_t base {nfa.
code.size()};
112 const std::size_t cbase {nfa.
classes.size()};
113 nfa.
entry.push_back(base);
117 for (std::size_t i = 0; i < prog.code.size(); ++i) {
118 const instr& in {prog.code[i]};
121 out.klass =
static_cast<std::uint32_t
>(cbase + in.arg16);
124 out.primary = in.primary_target +
static_cast<std::int64_t
>(base);
125 out.secondary = in.secondary_target +
static_cast<std::int64_t
>(base);
128 out.primary = in.primary_target +
static_cast<std::int64_t
>(base);
135 throw dfa_error(
"pattern has a zero-width assertion that no DFA can represent "
136 "(only \\A/^ is allowed)");
139 throw dfa_error(
"pattern has a lookaround, which no DFA can represent");
144 throw dfa_error(
"pattern has a Unicode code-point class (\\w/\\d/\\s in text mode), "
145 "which no DFA can represent");
147 nfa.
code.push_back(out);
160 const std::size_t word {i >> 6U};
161 if (word < s.size()) {
162 s[word] |= (std::uint64_t {1} << (i & 63U));
169 const std::size_t word {i >> 6U};
170 return word < s.size() && ((s[word] >> (i & 63U)) & 1U) != 0;
176 const std::vector<std::uint32_t>& seeds,
179 const std::size_t words {(nfa.
code.size() + 63U) / 64U};
181 std::vector<std::uint32_t> stack;
182 for (
const std::uint32_t pc : seeds) {
188 while (!stack.empty()) {
189 const std::uint32_t pc {stack.back()};
192 const auto visit {[&](std::int64_t target) {
193 if (target >= 0 && !
dfa_test_bit(present,
static_cast<std::size_t
>(target))) {
194 dfa_set_bit(present,
static_cast<std::size_t
>(target));
195 stack.push_back(
static_cast<std::uint32_t
>(target));
199 case opcode::split: visit(in.primary); visit(in.secondary);
break;
201 case opcode::save: visit(
static_cast<std::int64_t
>(pc) + 1);
break;
203 if (at_start) { visit(
static_cast<std::int64_t
>(pc) + 1); }
221 std::vector<std::uint32_t> seeds;
222 for (std::size_t pc = 0; pc < nfa.
code.size(); ++pc) {
227 const bool consume {(in.op ==
opcode::byte && in.arg8 == rep)
230 seeds.push_back(
static_cast<std::uint32_t
>(pc + 1));
241 std::int64_t best {-1};
242 for (std::size_t pc = 0; pc < nfa.
code.size(); ++pc) {
256 std::array<std::uint8_t, 256>
of {};
257 std::array<std::uint8_t, 256>
rep {};
266 std::vector<char_class> class_preds;
267 std::vector<std::uint16_t> literal_preds;
268 const auto push_unique {[](
auto& vec,
const auto& value) {
269 for (std::size_t i = 0; i < vec.size(); ++i) {
270 if (vec[i] == value) {
return; }
272 vec.push_back(value);
274 for (std::size_t pc = 0; pc < nfa.
code.size(); ++pc) {
277 push_unique(class_preds, nfa.
classes[in.klass]);
280 push_unique(literal_preds,
static_cast<std::uint16_t
>(in.arg8));
283 const auto sig_equal {[&](
unsigned a,
unsigned b) {
284 for (std::size_t i = 0; i < class_preds.size(); ++i) {
285 if (class_preds[i].test(
static_cast<std::uint8_t
>(a))
286 != class_preds[i].test(
static_cast<std::uint8_t
>(b))) {
290 for (std::size_t i = 0; i < literal_preds.size(); ++i) {
291 const unsigned lit {literal_preds[i]};
292 if ((a == lit) != (b == lit)) {
return false; }
297 for (
unsigned b = 0; b < 256U; ++b) {
298 bool assigned {
false};
299 for (std::size_t c = 0; c < bc.
count; ++c) {
300 if (sig_equal(b, bc.
rep[c])) {
301 bc.
of[b] =
static_cast<std::uint8_t
>(c);
307 bc.
rep[bc.
count] =
static_cast<std::uint8_t
>(b);
308 bc.
of[b] =
static_cast<std::uint8_t
>(bc.
count);
327 inline constexpr std::uint32_t
dfa_no_rule {std::numeric_limits<std::uint32_t>::max()};
339 const std::size_t nc {bc.
count};
345 std::vector<dfa_set> sets;
346 std::vector<std::int64_t> accept_pre;
347 const auto find_or_add {[&](
dfa_set s) -> std::uint32_t {
348 for (std::size_t i = 0; i < sets.size(); ++i) {
349 if (sets[i] == s) {
return static_cast<std::uint32_t
>(i); }
355 sets.push_back(std::move(s));
356 accept_pre.push_back(acc);
357 if (sets.size() > state_cap) {
358 throw dfa_error(
"DFA state count exceeded max_dfa_states; "
359 "pattern is too complex for a DFA");
361 return static_cast<std::uint32_t
>(sets.size() - 1);
364 const std::size_t words {(nfa.code.size() + 63U) / 64U};
365 sets.emplace_back(words, 0);
366 accept_pre.push_back(-1);
367 std::vector<std::uint32_t> entry_seeds;
368 for (
const std::size_t e : nfa.entry) {
369 entry_seeds.push_back(
static_cast<std::uint32_t
>(e));
373 std::vector<std::uint32_t> trans_pre;
374 for (std::size_t s = 0; s < sets.size(); ++s) {
375 for (std::size_t c = 0; c < nc; ++c) {
376 trans_pre.push_back(find_or_add(
dfa_move(nfa, sets[s], bc.rep[c])));
379 const std::size_t n_pre {sets.size()};
382 std::vector<std::int64_t> block(n_pre, 0);
383 for (std::size_t s = 0; s < n_pre; ++s) {
384 block[s] = accept_pre[s] < 0 ? 0 : accept_pre[s] + 1;
386 std::size_t num_blocks {0};
388 std::vector<std::int64_t> seen;
389 for (std::size_t s = 0; s < n_pre; ++s) {
391 for (
const std::int64_t b : seen) {
392 if (b == block[s]) { found =
true;
break; }
394 if (!found) { seen.push_back(block[s]); }
396 num_blocks = seen.size();
398 for (
bool changed =
true; changed;) {
400 std::vector<std::vector<std::int64_t>> sigs;
401 std::vector<std::int64_t> new_block(n_pre, 0);
402 for (std::size_t s = 0; s < n_pre; ++s) {
403 std::vector<std::int64_t> sig;
405 sig.push_back(block[s]);
406 for (std::size_t c = 0; c < nc; ++c) {
407 sig.push_back(block[trans_pre[(s * nc) + c]]);
409 std::int64_t
id {-1};
410 for (std::size_t i = 0; i < sigs.size(); ++i) {
411 if (sigs[i] == sig) {
id =
static_cast<std::int64_t
>(i);
break; }
414 id =
static_cast<std::int64_t
>(sigs.size());
415 sigs.push_back(std::move(sig));
419 if (sigs.size() != num_blocks) {
421 num_blocks = sigs.size();
422 block = std::move(new_block);
428 std::vector<std::int64_t> rep_of_block(num_blocks, -1);
429 for (std::size_t s = 0; s < n_pre; ++s) {
430 std::int64_t& slot {rep_of_block[
static_cast<std::size_t
>(block[s])]};
431 if (slot < 0) { slot =
static_cast<std::int64_t
>(s); }
433 out.
trans.reserve(num_blocks * nc);
434 out.
accept.reserve(num_blocks);
435 for (std::size_t b = 0; b < num_blocks; ++b) {
436 const std::size_t rep {
static_cast<std::size_t
>(rep_of_block[b])};
437 out.
accept.push_back(accept_pre[rep] < 0 ?
dfa_no_rule :
static_cast<std::uint32_t
>(accept_pre[rep]));
438 for (std::size_t c = 0; c < nc; ++c) {
439 out.
trans.push_back(
static_cast<std::uint32_t
>(block[trans_pre[(rep * nc) + c]]));
442 out.
start =
static_cast<std::uint32_t
>(block[out.
start]);
463 explicit dfa(std::span<const detail::program_view> programs)
464 :
tables_(detail::dfa_build(programs))
472 explicit dfa(std::span<const regex> patterns)
486 [[nodiscard]] std::optional<dfa_match>
match(std::string_view rest)
const noexcept
489 std::optional<dfa_match> best;
490 for (std::size_t i = 0; i < rest.size();) {
491 const auto byte {
static_cast<std::uint8_t
>(rest[i])};
527 static std::vector<detail::program_view>
views_of(std::span<const regex> patterns)
529 std::vector<detail::program_view> views;
530 views.reserve(patterns.size());
531 for (
const regex& pattern : patterns) {
532 views.push_back(pattern.raw_program());
A compiled regular expression, parameterized on its storage policy.
Thrown when a pattern cannot be represented as a DFA.
dfa_error(const std::string &message)
Builds the error.
A maximal-munch DFA over an ordered set of patterns.
static std::vector< detail::program_view > views_of(std::span< const regex > patterns)
Materializes program views from patterns (helper for the regex ctor).
detail::dfa_tables tables_
The immutable baked tables.
std::size_t rule_count() const noexcept
The number of patterns the DFA was built from.
dfa(std::span< const detail::program_view > programs)
Builds the DFA from compiled programs (the embedder path).
std::optional< dfa_match > match(std::string_view rest) const noexcept
Matches the longest pattern anchored at the start of rest.
std::size_t class_count() const noexcept
The number of byte-equivalence classes (the reduced alphabet width).
std::size_t state_count() const noexcept
The number of states in the minimized automaton (includes the dead state).
dfa(std::span< const regex > patterns)
Builds the DFA from regexes (a convenience over regex::raw_program).
Resource limits guarding against pattern-driven resource exhaustion.
void dfa_set_bit(dfa_set &s, std::size_t i)
bool dfa_test_bit(const dfa_set &s, std::size_t i)
std::vector< std::uint64_t > dfa_set
A set of NFA PCs as a bitset (one per DFA state during construction).
std::int64_t dfa_accept_of(const dfa_nfa &nfa, const dfa_set &set)
The accepting rule of a state set: the SMALLEST rule index among its match PCs (the order tie-break),...
dfa_tables dfa_build(std::span< const program_view > programs, std::size_t state_cap=max_dfa_states)
Subset construction over byte-classes, then Moore minimization (initial partition by accept tag,...
constexpr std::uint32_t dfa_no_rule
dfa_byte_classes dfa_compute_classes(const dfa_nfa &nfa)
dfa_nfa dfa_flatten(std::span< const program_view > programs)
Flattens programs into one union NFA, auditing DFA-ability.
constexpr std::size_t max_dfa_states
Maximum DFA states (opt-in real::dfa). Subset construction is 2^NFA in the worst case; this caps it s...
@ text_start
\A, and ^ without multiline.
dfa_set dfa_closure(const dfa_nfa &nfa, const std::vector< std::uint32_t > &seeds, bool at_start)
The epsilon-closure of seeds (a PC list), as a canonical PC bitset. at_start follows a text_start ass...
dfa_set dfa_move(const dfa_nfa &nfa, const dfa_set &set, std::uint8_t rep)
The move on the byte rep: ε-closure of the successors of every PC in set that consumes rep.
opcode
NFA instruction opcodes executed by the Pike VM.
@ 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.
The public API: real::regex, real::static_regex and results.
A set of byte values (0–255) as a 256-bit bitmap.
Computes byte-equivalence classes: two bytes are equivalent iff they satisfy the same consuming predi...
std::array< std::uint8_t, 256 > rep
std::array< std::uint8_t, 256 > of
A flattened NFA instruction (global PCs, global class index).
std::uint32_t klass
Global class index (op == klass).
std::int64_t primary
Global target (split/jump).
std::int64_t secondary
Global secondary target (split).
The union NFA over all the patterns, flattened into one address space.
std::vector< char_class > classes
std::vector< std::int64_t > accept_rule
accept_rule[pc] = rule index if match, else -1.
std::vector< std::size_t > entry
entry[rule] = global pc of its pc 0.
std::vector< dfa_instr > code
The baked DFA tables produced by dfa_build.
std::vector< std::uint32_t > trans
[state*num_classes + cls] -> next state (0 = dead).
std::array< std::uint8_t, 256 > byte_class
std::vector< std::uint32_t > accept
accept[state] = rule index, or NO_RULE.
One NFA instruction. Field meaning depends on op.
The outcome of dfa::match — which rule won, and how many bytes it spans.
std::uint32_t rule_index
Index of the winning pattern, in the order passed to the ctor.
std::size_t length
Byte length of the (non-empty) match.
REAL's version macros and the C++20 language-standard guard.