12#ifndef REAL_PREFILTER_HPP
13#define REAL_PREFILTER_HPP
46 const std::size_t code_size {code.size()};
51 const std::size_t exit {code_size - 2};
53 std::int32_t branches {};
56 std::size_t branch_end {is_split ?
static_cast<std::size_t
>(code[pc].primary_target) : pc};
57 std::int32_t branch_width {};
62 if (branch_width == 0) {
68 if (branch_end >= exit || code[branch_end].op !=
opcode::jump ||
69 code[branch_end].primary_target !=
static_cast<std::int32_t
>(exit)) {
72 pc =
static_cast<std::size_t
>(code[pc].secondary_target);
79 return branch_end == exit && branches >= 2;
96 const auto kind {
static_cast<assert_kind>(code[pc].arg8)};
115 std::size_t prefix_pc {};
130 bool has_inter_or_trailing_assert {};
132 for (std::size_t i = 0; i < code.size() && !has_inter_or_trailing_assert; ++i) {
137 has_inter_or_trailing_assert =
true;
143 if (!has_inter_or_trailing_assert) {
144 std::size_t q {prefix_pc};
163 std::span<const char_class> classes,
164 std::span<const cp_class> cp_classes,
167 std::vector<unsigned char> visited(code.size(), 0);
168 std::vector<std::int32_t> stack;
170 bool empty_match_possible {};
171 while (!stack.empty()) {
172 const std::int32_t current_pc {stack.back()};
174 if (visited[
static_cast<std::size_t
>(current_pc)] != 0) {
177 visited[
static_cast<std::size_t
>(current_pc)] = 1;
178 const instr& instruction {code[
static_cast<std::size_t
>(current_pc)]};
179 switch (instruction.op) {
183 stack.push_back(current_pc + 1);
186 stack.push_back(instruction.primary_target);
189 stack.push_back(instruction.primary_target);
190 stack.push_back(instruction.secondary_target);
202 const cp_class& cc {cp_classes[
static_cast<std::size_t
>(instruction.arg16)]};
210 empty_match_possible =
true;
224 std::span<const char_class> classes,
225 std::int32_t cp_mark_ascii,
226 std::int32_t cp_mark_offset,
238 std::int16_t gs {-1};
242 gs =
static_cast<std::int16_t
>(code[p].arg16);
246 code[p + 1].primary_target ==
static_cast<std::int32_t
>(p) &&
247 code[p + 1].secondary_target ==
static_cast<std::int32_t
>(p + 2)) {
248 const std::int32_t cls {code[p].arg16};
249 std::size_t q {p + 2};
250 std::int16_t ge {-1};
252 if (gs >= 0 && q < code.size() && code[q].op ==
opcode::save) {
253 ge =
static_cast<std::int16_t
>(code[q].arg16);
258 q + 2 == code.size()) {
272 std::int16_t gs {-1};
276 gs =
static_cast<std::int16_t
>(code[p].arg16);
281 const std::int32_t cp_idx {code[p].arg16};
282 const std::size_t loop_pc {p};
283 std::size_t q {p + 4};
286 code[q].primary_target ==
static_cast<std::int32_t
>(loop_pc) &&
287 code[q].secondary_target ==
static_cast<std::int32_t
>(q + 1)) {
291 std::int16_t ge {-1};
293 if (gs >= 0 && q < code.size() && code[q].op ==
opcode::save) {
294 ge =
static_cast<std::int16_t
>(code[q].arg16);
299 q + 2 == code.size()) {
319 std::int32_t width {};
320 std::int32_t open_groups {};
323 if (i < code.size() && code[i].op ==
opcode::save && code[i].arg16 == 0) {
325 while (i < code.size()) {
326 const opcode op {code[i].op};
332 const std::int32_t slot {code[i].arg16};
336 else if (slot >= 2 && (slot % 2) == 0) {
337 if (open_groups > 0) {
342 else if (slot >= 3) {
351 if (width >= 1 && closed && !nested && i + 1 == code.size() && code[i].op ==
opcode::match) {
362 if ((code.size() == 19 || code.size() == 20) && code[0].op ==
opcode::save) {
368 std::int32_t
ascii {(cp_mark_ascii >= 0 && cp_mark_offset == 1
369 &&
static_cast<std::size_t
>(cp_mark_ascii) < classes.size())
381 const char_class& ascii_class {classes[
static_cast<std::size_t
>(
ascii)]};
382 for (
int byte {0x80};
byte <= 0xFF; ++
byte) {
383 if (ascii_class.test(
static_cast<std::uint8_t
>(
byte))) {
389 const bool bare {code.size() == 19 && code[17].op ==
opcode::save &&
391 const bool plus {code.size() == 20 && code[17].op ==
opcode::split && code[17].primary_target == 1 &&
393 if (
ascii >= 0 && (bare || plus)) {
414 constexpr std::array<std::uint16_t, 26> lower {
415 650, 150, 300, 350, 1000, 200, 180, 450, 550, 15, 80, 350, 250,
416 550, 600, 170, 10, 450, 500, 700, 250, 100, 200, 15, 180, 8};
417 if (b >=
'a' && b <=
'z') {
418 return lower[b -
'a'];
420 if (b >=
'A' && b <=
'Z') {
421 return static_cast<std::uint16_t
>((lower[b -
'A'] / 6) + 3);
423 if (b >=
'0' && b <=
'9') {
427 case ' ':
return 1500;
428 case '.':
return 120;
429 case '\n':
case ',':
return 100;
431 case '(':
case ')':
case '_':
return 50;
432 case '"':
case '\'':
return 45;
433 case '\t':
case '=':
case '-':
return 40;
435 case '>':
case '<':
return 30;
436 case ';':
case '*':
return 25;
437 case '{':
case '}':
return 22;
438 case '[':
case ']':
case '+':
return 20;
439 case '?':
case '!':
case '|':
return 15;
442 case '#':
case '\\':
case '$':
return 8;
443 case '~':
case '@':
case '^':
case '`':
return 3;
467 std::size_t offset {0};
468 std::int16_t best_byte {-1};
469 std::uint8_t best_off {0};
470 std::uint16_t best_freq {0xFFFFU};
471 while (pc < code.size() && offset <= 0xFFU) {
472 const opcode op {code[pc].op};
478 const auto freq {
byte_frequency(
static_cast<std::uint8_t
>(code[pc].arg8))};
479 if (freq < best_freq) {
481 best_byte =
static_cast<std::int16_t
>(
static_cast<std::uint8_t
>(code[pc].arg8));
482 best_off =
static_cast<std::uint8_t
>(offset);
500 std::uint32_t first_freq {0};
505 for (
int b = 0; b < 256; ++b) {
511 if (best_freq < 100U &&
static_cast<std::uint32_t
>(best_freq) * 4U < first_freq) {
530 std::span<const char_class> classes,
531 std::span<const cp_class> cp_classes,
532 std::int32_t cp_mark_ascii,
533 std::int32_t cp_mark_offset)
541 bool has_lookaround {
false};
542 for (
const instr& in : code) {
544 has_lookaround =
true;
561 if (has_lookaround) {
575 std::array<char, 4> members {};
577 for (
unsigned byte = 0;
byte < 256; ++
byte) {
580 members[
static_cast<std::size_t
>(
count)] =
static_cast<char>(
byte);
589 hints.
single_first =
static_cast<std::int16_t
>(
static_cast<unsigned char>(members[0]));
603 std::array<char, 6> stops {};
605 for (
unsigned byte = 0;
byte < 256; ++
byte) {
606 if (!accepted.test(
static_cast<std::uint8_t
>(
byte))) {
607 if (stop_count < 6) {
608 stops[
static_cast<std::size_t
>(stop_count)] =
static_cast<char>(
byte);
611 if (stop_count > 6) {
616 if (stop_count >= 1 && stop_count <= 6) {
629 std::array<char, 6> stops {};
631 for (
unsigned byte = 0;
byte < 0x80; ++
byte) {
632 if (!accepted.test(
static_cast<std::uint8_t
>(
byte))) {
633 if (stop_count < 6) {
634 stops[
static_cast<std::size_t
>(stop_count)] =
static_cast<char>(
byte);
637 if (stop_count > 6) {
642 if (stop_count >= 1 && stop_count <= 6) {
669 if (pos >= text.size()) {
672 if (!std::is_constant_evaluated()) {
673 const void* hit {std::memchr(text.data() + pos,
byte, text.size() - pos)};
674 return hit ==
nullptr
676 :
static_cast<std::size_t
>(
static_cast<const char*
>(hit) - text.data());
678 for (std::size_t i = pos; i < text.size(); ++i) {
679 if (text[i] ==
byte) {
696 std::string_view literal)
698 if (literal.empty()) {
699 return pos <= text.size() ? pos :
npos;
701 if (literal.size() == 1U) {
702 return find_byte(text, pos, literal.front());
704 if (text.size() < literal.size()) {
707 const std::size_t last_start {text.size() - literal.size()};
709 while (i <= last_start) {
710 const std::size_t hit {
find_byte(text, i, literal.front())};
711 if (hit ==
npos || hit > last_start) {
714 if (text.compare(hit, literal.size(), literal) == 0) {
740 if (pos >= text.size()) {
743 const auto off {text.substr(pos).find(
prefix)};
744 if (off == std::string_view::npos) {
771 if (pos >= text.size()) {
774 if (!std::is_constant_evaluated()) {
775 const char*
const base {text.data()};
776 std::size_t window {text.size() - pos};
777 std::size_t best {
npos};
778 for (std::uint8_t i = 0; i < n; ++i) {
779 const void* hit {std::memchr(base + pos, set[i], window)};
780 if (hit !=
nullptr) {
781 const std::size_t idx {
static_cast<std::size_t
>(
static_cast<const char*
>(hit) - base)};
790 for (std::size_t i = pos; i < text.size(); ++i) {
791 for (std::uint8_t j = 0; j < n; ++j) {
792 if (text[i] == set[j]) {
817 if (!std::is_constant_evaluated()) {
818 const char*
const base {text.data()};
820 for (; i + 8 <= end; i += 8) {
821 std::uint64_t word {};
822 std::memcpy(&word, base + i, 8);
823 if ((word & 0x8080808080808080ULL) != 0U) {
824 for (std::size_t b = 0; b < 8; ++b) {
825 if (
static_cast<std::uint8_t
>(base[i + b]) >= 0x80U) {
831 for (; i < end; ++i) {
832 if (
static_cast<std::uint8_t
>(base[i]) >= 0x80U) {
838 for (std::size_t i = pos; i < end; ++i) {
839 if (
static_cast<std::uint8_t
>(text[i]) >= 0x80U) {
256-bit byte set with O(1) membership, fully constexpr.
constexpr char_class utf8_lead3_set()
The lead-byte set of a 3-byte UTF-8 sequence.
constexpr void detect_fast_shapes(std::span< const instr > code, std::span< const char_class > classes, std::int32_t cp_mark_ascii, std::int32_t cp_mark_offset, pattern_hints &hints)
Detects the whole-pattern fast-path shapes and sets their hint flags: class+, fixed-shape straight ru...
constexpr std::size_t find_literal(std::string_view text, std::size_t pos, std::string_view literal)
Index of the first occurrence of literal in text[pos..), or real::npos.
constexpr std::size_t find_prefix(std::string_view text, std::size_t pos, std::string_view prefix)
First position >= pos where prefix occurs in text, or npos.
constexpr std::size_t find_bytes_cascade(std::string_view text, std::size_t pos, const char *set, std::uint8_t n)
Index of the first byte in text[pos..) that belongs to a small (2..4) first-byte set.
constexpr char_class utf8_lead2_set()
The lead-byte set of a 2-byte UTF-8 sequence.
constexpr char_class utf8_lead4_set()
The lead-byte set of a 4-byte UTF-8 sequence.
constexpr void extract_anchoring(std::span< const instr > code, pattern_hints &hints)
Records start anchoring: the first non-save instruction tells whether every match must begin at posit...
constexpr void extract_rare_byte(std::span< const instr > code, pattern_hints &hints)
Finds a required literal byte at a FIXED offset that is statically far rarer than the pattern's first...
constexpr std::size_t first_high_byte(std::string_view text, std::size_t pos, std::size_t end)
Index of the first byte >= 0x80 in text[pos, end), or end if the range is pure ASCII.
constexpr void compute_first_bytes(std::span< const instr > code, std::span< const char_class > classes, std::span< const cp_class > cp_classes, pattern_hints &hints)
Computes the possible first-byte set by a DFS over the epsilon closure of pc 0.
constexpr std::size_t find_byte(std::string_view text, std::size_t pos, char byte)
Index of byte in text[pos..), or real::npos.
constexpr std::uint16_t byte_frequency(std::uint8_t b)
Approximate static frequency of a byte in mixed English + source text (occurrences per 10000; higher ...
constexpr pattern_hints analyze_program(std::span< const instr > code, std::span< const char_class > classes, std::span< const cp_class > cp_classes, std::int32_t cp_mark_ascii, std::int32_t cp_mark_offset)
Walks a compiled program once to derive its search hints.
@ prefix
Anchored at the start position (Python re.match).
assert_kind
Kind of zero-width assertion carried in assert_position's arg8.
@ line_start
^ with multiline.
@ text_start
\A, and ^ without multiline.
constexpr void extract_prefix(std::span< const instr > code, pattern_hints &hints)
Collects the required literal prefix and the exact-literal fast-path length.
constexpr bool is_fixed_alternation(std::span< const instr > code)
Tests whether the whole program is an alternation of straight-line branches (e.g. the|fox|dog).
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.
constexpr std::size_t npos
Sentinel for "no position" / unset capture slot (akin to std::string::npos).
@ ascii
ASCII mode (re.A): \d \w \s \b stay ASCII and icase folds ASCII only, even in text mode....
Compiled form of a pattern and the public flags / error types.
A set of byte values (0–255) as a 256-bit bitmap.
constexpr void merge(const char_class &other)
Unions other into this set.
constexpr bool test(std::uint8_t byte) const
Tests membership of byte byte.
constexpr void set(std::uint8_t byte)
Adds byte byte to the set.
constexpr bool empty() const
Reports whether the set has no members.
A match-time code-point class for the klass_cp opcode: an ASCII bitmap for code points < 0x80 plus a ...
One NFA instruction. Field meaning depends on op.
Search-acceleration hints extracted from a compiled program.
std::uint8_t exact_literal_len
Length of the pure-literal match, or 0.
bool greedy_cp_class_plus
The greedy_cp_class pattern is a greedy + loop (vs a single code point).
std::int16_t rare_byte
A required literal byte at a fixed offset from the match start that is statically rarer than the patt...
std::array< char, 16 > prefix
Required literal prefix (possibly truncated).
bool fixed_alternation
Whole pattern is an alternation of straight-line branches (no captures/asserts).
bool anchored_start
\A / ^ (no multiline): only position 0.
std::uint8_t prefix_size
Valid bytes in prefix.
std::int16_t greedy_group_start
For a class-loop wrapped in one capturing group ((\w+), ([a-z]+)): the group's start slot to mirror t...
std::array< char, 6 > stop_set
For a whole-pattern class+ run whose accepted set has a small (<= 6-byte) complement: the STOP bytes ...
bool line_anchored
^ multiline: position 0 or after \n.
std::int16_t greedy_group_end
The enveloping group's end slot (mirrors the whole-match end).
std::int32_t greedy_cp_class
cp_class index if the whole pattern is a code-point class klass_cp (optionally +),...
bool empty_match_possible
The pattern can match the empty string (the nullable gate; conservative: assertions/lookarounds pass ...
std::int32_t greedy_class_loop
Class index if the whole pattern is "class+", else -1.
std::uint8_t small_set_size
Number of valid members in small_set — 0 when not a small set, else 2..4. Drives the memchr-cascade s...
bool fixed_shape
Whole pattern is a fixed-width byte/klass sequence (no branches/asserts/captures).
char_class first_bytes
All possible first bytes.
bool codepoint_class_plus
The codepoint_class_ascii pattern is a greedy + loop (vs a single codepoint).
std::uint8_t stop_set_size
Members in stop_set — 0 when the complement is too large, else 1..6.
bool first_bytes_valid
False when an empty match is possible.
std::array< char, 4 > small_set
The 2..4 possible first bytes, enumerated (the memchr-cascade set). Empty unless small_set_size is se...
std::uint8_t rare_offset
The fixed byte offset of rare_byte from the match start.
std::int32_t codepoint_class_ascii
ASCII-class index when the whole pattern is ./negated-class (optionally +), else -1.
std::int16_t single_first
The unique possible first byte, or -1.
REAL's version macros and the C++20 language-standard guard.