77 template <
typename DataVec,
typename RefVec,
typename FreeVec>
90 constexpr void reset(std::uint16_t slot_count)
99 [[nodiscard]]
constexpr std::size_t*
slots(std::uint32_t b)
101 return &
data[
static_cast<std::size_t
>(b) *
width];
108 const std::uint32_t b {
free_list.back()};
113 const auto b {
static_cast<std::uint32_t
>(
refcount.size())};
115 for (std::uint16_t s = 0; s <
width; ++s) {
135 [[nodiscard]]
constexpr std::uint32_t
cow_write(std::uint32_t b,
140 const std::uint32_t b2 {
allocate()};
141 std::size_t*
const dst {
slots(b2)};
142 const std::size_t*
const src {
slots(b)};
143 for (std::uint16_t s = 0; s <
width; ++s) {
150 slots(b)[slot] = value;
159 for (std::size_t b = 0; b <
refcount.size(); ++b) {
169 std::vector<std::int32_t>,
170 std::vector<std::uint32_t>>;
181 template <
typename PcVec,
typename SlotVec,
typename MarkVec>
193 constexpr void reset(std::size_t code_size)
195 if (
mark.size() != code_size) {
196 mark.assign(code_size, 0);
209 [[nodiscard]]
constexpr bool seen(std::int32_t pc)
const
234 template <
typename ThreadList,
typename EpsVec>
252 std::array<std::uint8_t, 256>
table {};
304 template <
typename State>
341 template <
bool Cascade = false,
typename OutSlots>
342 constexpr bool run(std::string_view text,
346 std::size_t forbid_empty_until = 0,
359 return run_class_loop<Cascade>(text, start, mode, out_slots);
372 if constexpr (
requires(State & s) {
382 if (
state_.il_text !=
static_cast<const void*
>(text.data())) {
383 state_.il_abandoned =
false;
384 state_.il_text =
static_cast<const void*
>(text.data());
386 if (!
state_.il_abandoned) {
387 bool abandon {
false};
392 state_.il_abandoned =
true;
401 return run_codepoint_class<Cascade>(text, start, mode, out_slots);
415 if constexpr (
requires(State & s) {
435 if (
state_.dfa_program !=
static_cast<const void*
>(
prog_.
code.data())) {
438 if (
state_.fwd_dfa.has_value() &&
state_.rev_dfa.has_value() &&
state_.fwd_dfa->eligible()
439 && !
state_.fwd_dfa->thrashing()) {
440 const std::size_t match_end {
state_.fwd_dfa->forward_end(text.substr(start))};
441 if (match_end ==
npos) {
445 const std::size_t abs_end {start + match_end};
446 const std::size_t abs_start {
state_.rev_dfa->reverse_start(text, abs_end, start)};
454 return run_general<Cascade>(text.substr(0, abs_end), abs_start, mode, out_slots);
461 return run_general<false>(text, start, mode, out_slots);
463 return run_general<Cascade>(text, start, mode, out_slots);
470 template <
bool Cascade = false,
typename OutSlots>
475 std::size_t* forward_stop =
nullptr)
478 const std::size_t code_size {
prog_.
code.size()};
479 auto* clist {&
state_.lists[0]};
480 auto* nlist {&
state_.lists[1]};
481 clist->reset(code_size);
482 nlist->reset(code_size);
487 std::size_t pos {start};
488 while (pos <= text.size()) {
489 const bool seeding = (pos == start) || (mode ==
run_mode::search && !matched);
490 if (seeding && mode ==
run_mode::search && !matched && clist->pcs.empty()) {
495 if (pos > text.size()) {
502 clist->reset(code_size);
507 state_.pool.incref(pool_type::npos_block);
508 add_thread(*clist, 0, pos, pool_type::npos_block);
510 if (clist->pcs.empty()) {
517 clist->reset(code_size);
521 step(*clist, *nlist, pos, mode, matched, out_slots);
526 nlist->reset(code_size);
533 assert(
state_.pool.total_refs() == 1 &&
"OPT-D1 capture-block refcount leak or imbalance");
534 if (forward_stop !=
nullptr) {
548 template <
typename OutSlots>
555 if constexpr (
requires(State & st) {
559 if (
state_.dfa_program !=
static_cast<const void*
>(
prog_.
code.data())) {
562 if (
state_.fwd_dfa.has_value() &&
state_.fwd_dfa->eligible() && !
state_.fwd_dfa->thrashing()) {
563 const std::size_t match_end {
state_.fwd_dfa->forward_end(text.substr(s))};
564 if (match_end ==
npos) {
569 const std::size_t e {s + match_end};
577 return run_general<false>(text.substr(0, e), s,
run_mode::prefix, out_slots, &stop);
593 template <
typename OutSlots>
600 std::array<char, 16> lit_buf {};
607 std::size_t pos {start};
608 const std::size_t min_match_start {start};
609 std::size_t min_pre_start {start};
610 bool first_candidate {
true};
617 if (first_candidate) {
618 first_candidate =
false;
630 if (h < min_pre_start) {
648 if (
state_.il_prefix_rev.has_value()) {
649 s =
state_.il_prefix_rev->reverse_start(text, h, min_match_start);
656 std::size_t stop {s};
660 if (stop > min_pre_start) {
661 min_pre_start = stop;
698 using list_type = std::remove_reference_t<decltype(std::declval<State&>().lists[0])>;
716 if (immut ==
nullptr) {
719 std::call_once(immut->once, [&] {
720 immut->byte_prog = build_byte_program(prog_);
721 if (immut->byte_prog.eligible) {
723 compute_lazy_alphabet(immut->byte_prog.code, immut->byte_prog.classes);
725 const byte_program tier_b {build_byte_program(prog_, true)};
726 if (tier_b.eligible) {
727 immut->op_table.emplace(tier_b);
745 const std::size_t sz {immut->il_prefix_prog.code.size()};
746 immut->il_min_haystack = std::min<std::size_t>(512UL * 1024, std::max<std::size_t>(64UL * 1024, sz * 64));
759 if (immut ==
nullptr) {
765 const auto*
const program {
static_cast<const void*
>(prog_.code.data())};
766 if (state_.dfa_program != program) {
767 if (immut->byte_prog.eligible) {
774 state_.fwd_dfa.reset();
775 state_.rev_dfa.reset();
777 state_.dfa_program = program;
783 using pool_type = std::remove_reference_t<decltype(std::declval<State&>().pool)>;
797 constexpr const std::uint8_t*
class_table(std::size_t class_index)
799 if (state_.table_class !=
static_cast<std::int32_t
>(class_index)) {
801 for (std::size_t b {0}; b < 256; ++b) {
802 state_.table[b] =
klass.
test(
static_cast<std::uint8_t
>(b)) ? 1U : 0U;
804 state_.table_class =
static_cast<std::int32_t
>(class_index);
806 return state_.table.data();
819 const std::int32_t key {-2 -
static_cast<std::int32_t
>(cp_index)};
820 if (state_.table_class != key) {
822 for (std::size_t b {0}; b < 256; ++b) {
823 state_.table[b] =
klass.
test(
static_cast<std::uint8_t
>(b)) ? 1U : 0U;
825 state_.table_class = key;
827 return state_.table.data();
831 static constexpr std::uint32_t cp_page_max {0x7FFU};
835 static constexpr int max_loop_hops {8};
840 static constexpr std::size_t cascade_run_threshold {32};
851 const std::int32_t key {-2 -
static_cast<std::int32_t
>(cp_index)};
852 if (state_.cp_page_class != key) {
853 state_.cp_page.fill(0);
855 for (std::uint32_t k {0}; k < cc.range_count; ++k) {
857 if (r.lo > cp_page_max) {
860 const std::uint32_t lo {r.
lo < 0x80U ? 0x80U : r.lo};
861 const std::uint32_t hi {r.hi > cp_page_max ? cp_page_max : r.hi};
862 for (std::uint32_t c {lo}; c <= hi; ++c) {
863 const std::uint32_t bit {c - 0x80U};
864 state_.cp_page[bit >> 6U] |= std::uint64_t {1} << (bit & 63U);
867 state_.cp_page_class = key;
869 return state_.cp_page.data();
878 template <
typename OutSlots>
880 std::size_t match_start,
881 std::size_t match_end)
const
883 out_slots[0] = match_start;
884 out_slots[1] = match_end;
885 if (prog_.hints.greedy_group_start >= 0) {
886 out_slots[
static_cast<std::size_t
>(prog_.hints.greedy_group_start)] = match_start;
887 out_slots[
static_cast<std::size_t
>(prog_.hints.greedy_group_end)] = match_end;
896 std::size_t from)
const
898 const std::size_t stop {
899 find_bytes_cascade(text, from, prog_.hints.stop_set.data(), prog_.hints.stop_set_size)};
900 return stop ==
npos ? text.size() : stop;
917 template <
bool Cascade,
typename OutSlots>
923 const std::uint8_t*
const tbl =
924 class_table(
static_cast<std::size_t
>(prog_.hints.greedy_class_loop));
925 const auto in_class = [&](std::size_t i) {
926 return tbl[
static_cast<std::uint8_t
>(text[i])] != 0U;
928 std::size_t match_start {start};
930 while (match_start < text.size() && !in_class(match_start)) {
934 if (match_start >= text.size() || !in_class(match_start)) {
935 out_slots.assign(prog_.slot_count,
npos);
945 std::size_t match_end {match_start + 1};
946 if constexpr (Cascade) {
947 if (!std::is_constant_evaluated()) {
948 while (match_end < text.size() && in_class(match_end)) {
950 if (match_end - match_start == cascade_run_threshold) {
951 match_end = run_cascade_stop(text, match_end);
957 while (match_end < text.size() && in_class(match_end)) {
963 while (match_end < text.size() && in_class(match_end)) {
968 out_slots.assign(prog_.slot_count,
npos);
971 out_slots.assign(prog_.slot_count,
npos);
972 fill_span_slots(out_slots, match_start, match_end);
991 template <
typename OutSlots>
997 const std::size_t cp_index {
static_cast<std::size_t
>(prog_.hints.greedy_cp_class)};
999 const std::uint8_t*
const asc {cp_ascii_table(cp_index)};
1003 const auto member_hi = [&](
char32_t cp) ->
bool {
1004 if (cp <= cp_page_max) {
1005 const std::uint64_t*
const page {cp_page_table(cp_index)};
1006 const std::uint32_t bit {
static_cast<std::uint32_t
>(cp) - 0x80U};
1007 return ((page[bit >> 6U] >> (bit & 63U)) & std::uint64_t {1}) != 0U;
1009 return cp_class_matches(cc, cp);
1013 const auto width = [&](std::size_t i) -> std::size_t {
1018 const bool m {dc.
cp < 0x80U ? asc[dc.cp] != 0U : member_hi(dc.cp)};
1019 return m ? dc.length : 0;
1021 out_slots.assign(prog_.slot_count,
npos);
1022 std::size_t match_start {start};
1024 while (match_start < text.size() && width(match_start) == 0) {
1028 if (match_start >= text.size()) {
1034 const std::size_t
first {width(match_start)};
1038 std::size_t match_end {match_start +
first};
1039 if (prog_.hints.greedy_cp_class_plus) {
1040 while (match_end < text.size()) {
1043 const auto lead {
static_cast<std::uint8_t
>(text[match_end])};
1045 if (asc[lead] == 0U) {
1052 if (!dc.valid || !member_hi(dc.cp)) {
1061 fill_span_slots(out_slots, match_start, match_end);
1077 template <
bool SkipSaves = false>
1080 std::size_t s)
const
1082 std::size_t consumed {};
1083 while (pc < prog_.code.size()) {
1084 const instr& instruction {prog_.code[pc]};
1085 if constexpr (SkipSaves) {
1096 if (s + consumed >= text.size()) {
1099 const auto byte_value {
static_cast<std::uint8_t
>(text[s + consumed])};
1100 const bool ok {instruction.
op ==
opcode::byte ? byte_value == instruction.arg8
1101 : prog_.classes[instruction.arg16].test(byte_value)};
1108 return s + consumed;
1127 template <
typename MatchAt,
typename OutSlots>
1131 OutSlots& out_slots)
1133 std::size_t match_start {start};
1134 while (match_start <= text.size()) {
1135 match_start = next_candidate(text, match_start, start);
1136 if (match_start > text.size()) {
1139 const std::size_t match_end {match_at(match_start)};
1140 if (match_end !=
npos) {
1141 out_slots[0] = match_start;
1142 out_slots[1] = match_end;
1165 template <
typename OutSlots>
1169 OutSlots& out_slots)
1172 if (prog_.slot_count <= 2) {
1173 out_slots.assign(2,
npos);
1174 const auto at {[&](std::size_t s) {
return match_byte_klass_run<false>(text, 1, s); }};
1176 const std::size_t match_end {at(start)};
1180 out_slots[0] = start;
1181 out_slots[1] = match_end;
1184 return fast_search(text, start, at, out_slots);
1190 out_slots.assign(prog_.slot_count,
npos);
1191 const auto at {[&](std::size_t s) {
return match_byte_klass_run<true>(text, 1, s); }};
1193 const std::size_t match_end {at(start)};
1197 out_slots[0] = start;
1198 out_slots[1] = match_end;
1199 fill_fixed_saves(start, out_slots);
1202 if (!fast_search(text, start, at, out_slots)) {
1205 fill_fixed_saves(out_slots[0], out_slots);
1217 template <
typename OutSlots>
1219 OutSlots& out_slots)
const
1221 if (prog_.slot_count <= 2) {
1224 std::size_t offset {};
1225 for (std::size_t pc {1}; pc < prog_.code.size(); ++pc) {
1226 const instr& instruction {prog_.code[pc]};
1231 out_slots[
static_cast<std::size_t
>(instruction.arg16)] = match_start + offset;
1255 template <
bool Cascade,
typename OutSlots>
1259 OutSlots& out_slots)
1261 const std::uint8_t*
const ascii {
1262 class_table(
static_cast<std::size_t
>(prog_.hints.codepoint_class_ascii))};
1263 out_slots.assign(2,
npos);
1265 const auto cont = [&](std::size_t i) {
1266 const auto cont_byte {
static_cast<std::uint8_t
>(text[i])};
1267 return cont_byte >= 0x80 && cont_byte <= 0xBF;
1270 const auto width = [&](std::size_t i) -> std::size_t {
1271 const auto byte_value {
static_cast<std::uint8_t
>(text[i])};
1272 if (byte_value < 0x80) {
1273 return ascii[byte_value] != 0U ? 1 : 0;
1275 if (byte_value >= 0xC2 && byte_value <= 0xDF) {
1276 return i + 1 < text.size() && cont(i + 1) ? 2 : 0;
1278 if (byte_value >= 0xE0 && byte_value <= 0xEF) {
1279 return i + 2 < text.size() && cont(i + 1) && cont(i + 2) ? 3 : 0;
1281 if (byte_value >= 0xF0 && byte_value <= 0xF4) {
1282 return i + 3 < text.size() && cont(i + 1) && cont(i + 2) && cont(i + 3) ? 4 : 0;
1287 std::size_t match_start {start};
1289 while (match_start < text.size() && width(match_start) == 0) {
1293 if (match_start >= text.size()) {
1296 const std::size_t first_width {width(match_start)};
1297 if (first_width == 0) {
1300 std::size_t match_end {match_start + first_width};
1301 if (prog_.hints.codepoint_class_plus) {
1302 const auto scalar_scan = [&]() {
1303 while (match_end < text.size()) {
1304 const std::size_t codepoint_width {width(match_end)};
1305 if (codepoint_width == 0) {
1308 match_end += codepoint_width;
1311 if constexpr (Cascade) {
1312 if (!std::is_constant_evaluated()) {
1318 const std::size_t stop {
find_bytes_cascade(text, match_end, prog_.hints.stop_set.data(),
1319 prog_.hints.stop_set_size)};
1320 const std::size_t p1 {stop ==
npos ? text.size() : stop};
1321 bool malformed {
false};
1322 while (match_end < p1) {
1328 const std::size_t w {width(match_end)};
1350 out_slots[0] = match_start;
1351 out_slots[1] = match_end;
1370 template <
typename OutSlots>
1374 OutSlots& out_slots)
1376 out_slots.assign(2,
npos);
1377 const auto& code {prog_.code};
1382 const auto match_at = [&](std::size_t match_start,
bool require_full) -> std::size_t {
1386 const std::size_t branch {is_split ?
static_cast<std::size_t
>(code[pc].primary_target) : pc};
1387 const std::size_t match_end {match_byte_klass_run(text, branch, match_start)};
1388 if (match_end !=
npos && (!require_full || match_end == text.size())) {
1394 pc =
static_cast<std::size_t
>(code[pc].secondary_target);
1399 const std::size_t match_end {match_at(start, mode ==
run_mode::full)};
1400 if (match_end ==
npos) {
1403 out_slots[0] = start;
1404 out_slots[1] = match_end;
1407 return fast_search(text, start, [&](std::size_t match_start) {
return match_at(match_start,
false); }, out_slots);
1419 std::size_t len)
const
1421 if (cand + len > text.size()) {
1424 const auto pfx {std::string_view(prog_.hints.prefix.data(), len)};
1425 if (std::is_constant_evaluated()) {
1426 return text.substr(cand, len) == pfx;
1428 return std::memcmp(text.data() + cand, pfx.data(), len) == 0;
1444 template <
typename OutSlots>
1447 OutSlots& out_slots)
const
1449 out_slots.assign(prog_.slot_count,
npos);
1450 std::size_t consumed {};
1451 for (std::size_t pc = 0; pc < prog_.code.size(); ++pc) {
1452 const instr& instruction {prog_.code[pc]};
1454 out_slots[instruction.arg16] = cand + consumed;
1458 out_slots.assign(prog_.slot_count,
npos);
1469 if (prog_.slot_count >= 2 && out_slots[1] ==
npos) {
1470 out_slots[1] = cand + len;
1492 template <
typename OutSlots>
1496 OutSlots& out_slots)
1498 const std::size_t len {
static_cast<std::size_t
>(prog_.hints.exact_literal_len)};
1500 out_slots.assign(prog_.slot_count,
npos);
1504 const bool full_ok = mode !=
run_mode::full || start + len == text.size();
1505 const bool ok = literal_at(text, start, len) && full_ok &&
1506 replay_literal(start, len, out_slots);
1508 out_slots.assign(prog_.slot_count,
npos);
1512 std::size_t from {start};
1514 const std::size_t cand {next_candidate(text, from, start)};
1515 if (cand > text.size() || cand + len > text.size()) {
1516 out_slots.assign(prog_.slot_count,
npos);
1519 if (literal_at(text, cand, len) && replay_literal(cand, len, out_slots)) {
1540 std::size_t start)
const
1543 if (hints.anchored_start) {
1544 return pos == start ? pos :
npos;
1546 if (hints.prefix_size >= 2) {
1547 return find_prefix(text, pos, std::string_view(hints.prefix.data(), hints.prefix_size));
1549 if (hints.rare_byte >= 0) {
1553 const std::size_t from {pos + hints.rare_offset};
1554 if (from > text.size()) {
1557 const std::size_t hit {
find_byte(text, from,
static_cast<char>(hints.rare_byte))};
1558 return hit ==
npos ?
npos : hit - hints.rare_offset;
1560 if (hints.single_first >= 0) {
1561 return find_byte(text, pos,
static_cast<char>(hints.single_first));
1563 if (hints.line_anchored && pos != start) {
1564 const std::size_t nl {
find_byte(text, pos - 1,
'\n')};
1567 if (hints.small_set_size >= 2) {
1572 constexpr std::size_t probe {32};
1573 const std::size_t window_end {pos + probe < text.size() ? pos + probe : text.size()};
1574 std::size_t p {pos};
1575 while (p < window_end && !hints.first_bytes.test(
static_cast<std::uint8_t
>(text[p]))) {
1578 if (p < window_end) {
1581 if (window_end == text.size()) {
1584 return find_bytes_cascade(text, window_end, hints.small_set.data(), hints.small_set_size);
1586 if (hints.first_bytes_valid) {
1587 while (pos < text.size() &&
1588 !hints.first_bytes.test(
static_cast<std::uint8_t
>(text[pos]))) {
1591 return pos < text.size() ? pos :
npos;
1611 std::size_t start)
const
1614 if (hints.anchored_start && pos != start) {
1621 if (!prog_.byte_mode && pos < text.size() &&
1622 (
static_cast<std::uint8_t
>(text[pos]) & 0xC0U) == 0x80U) {
1625 if (!hints.first_bytes_valid) {
1628 return pos < text.size() && hints.
first_bytes.
test(
static_cast<std::uint8_t
>(text[pos]));
1639 bool ascii_word)
const
1647 bool ascii_word)
const
1662 bool word_ness_flipped)
const
1667 const bool ascii_word {prog_.unicode_word == word_ness_flipped};
1686 template <
typename OutSlots>
1692 OutSlots& out_slots)
1694 const std::uint16_t slot_count {prog_.slot_count};
1695 for (std::size_t i = 0; i < clist.pcs.size(); ++i) {
1696 const std::int32_t pc {clist.pcs[i]};
1697 const instr& instruction {prog_.code[
static_cast<std::size_t
>(pc)]};
1698 switch (instruction.op) {
1700 if (pos < text_.size() &&
1701 static_cast<std::uint8_t
>(text_[pos]) == instruction.arg8) {
1702 advance_thread(clist, nlist, i, pc + 1, pos + 1);
1706 if (pos < text_.size() &&
1707 prog_.classes[instruction.arg16].test(
static_cast<std::uint8_t
>(text_[pos]))) {
1708 advance_thread(clist, nlist, i, pc + 1, pos + 1);
1712 if (pos < text_.size()) {
1715 cp_class_matches(prog_.cp_classes[instruction.arg16], dc.cp)) {
1716 advance_thread(clist, nlist, i,
1717 pc + 1 +
static_cast<std::int32_t
>(4 - dc.length), pos + 1);
1727 const std::size_t*
const won {thread_slots(clist, i)};
1730 if (pos == won[0] && won[0] < forbid_empty_until_) {
1738 const bool better {!matched
1739 || won[0] < out_slots[0]
1740 || (won[0] == out_slots[0] && won[1] > out_slots[1])};
1742 for (std::uint16_t s = 0; s < slot_count; ++s) {
1743 out_slots[s] = won[s];
1749 for (std::uint16_t s = 0; s < slot_count; ++s) {
1750 out_slots[s] = won[s];
1773 std::int32_t next_pc,
1774 std::size_t next_pos)
1776 const std::uint32_t block {
static_cast<std::uint32_t
>(clist.slots[i])};
1777 state_.pool.incref(block);
1778 add_thread(nlist, next_pc, next_pos, block);
1788 return state_.pool.slots(
static_cast<std::uint32_t
>(clist.slots[i]));
1804 member = cc.
ascii.
test(
static_cast<std::uint8_t
>(cp));
1810 const std::size_t mid {lo + ((hi - lo) / 2)};
1811 if (prog_.cp_ranges[mid].hi < cp) {
1819 cp >= prog_.cp_ranges[lo].lo && cp <= prog_.cp_ranges[lo].hi;
1843 std::uint32_t initial_block)
1845 auto& pool {state_.pool};
1846 auto& stack {state_.stack};
1848 stack.push_back({.pc = pc0, .block = initial_block});
1849 while (!stack.empty()) {
1850 const auto entry {stack.back()};
1852 const std::int32_t pc {entry.pc};
1853 const std::uint32_t block {entry.block};
1854 if (list.seen(pc)) {
1859 const instr& instruction {prog_.code[
static_cast<std::size_t
>(pc)]};
1860 switch (instruction.op) {
1865 for (
int hops = 0; hops < max_loop_hops && list.seen(head)
1866 && prog_.code[
static_cast<std::size_t
>(head)].op ==
opcode::jump; ++hops) {
1867 head = prog_.code[
static_cast<std::size_t
>(head)].primary_target;
1869 const instr& head_instruction {prog_.code[
static_cast<std::size_t
>(head)]};
1870 const std::int32_t target {list.seen(head) && head_instruction.
op ==
opcode::split
1871 ? head_instruction.secondary_target
1872 : instruction.primary_target};
1873 stack.push_back({.pc = target, .block = block});
1878 stack.push_back({.pc = instruction.secondary_target, .block = block});
1879 stack.push_back({.pc = instruction.primary_target, .block = block});
1884 const std::uint32_t written {pool.cow_write(block, instruction.arg16, pos)};
1885 stack.push_back({.pc = pc + 1, .block = written});
1890 stack.push_back({.pc = pc + 1, .block = block});
1897 if constexpr (
requires(State & s) {
1900 if (lookaround_holds(instruction.arg16, pos)) {
1901 stack.push_back({.pc = pc + 1, .block = block});
1915 list.pcs.push_back(pc);
1916 list.slots.push_back(block);
1929 for (std::size_t i = 0; i < list.pcs.size(); ++i) {
1930 state_.pool.decref(
static_cast<std::uint32_t
>(list.slots[i]));
1953 ? lookbehind_matches(sub, pos)
1954 : lookahead_matches(sub, pos)};
1955 return sub.negative ? !matched : matched;
1967 const std::size_t code_size {prog_.code.size()};
1970 clist->
reset(code_size);
1971 nlist->reset(code_size);
1973 sub_add_thread(*clist, sub.
code_offset, pos, matched);
1974 const std::size_t limit {pos +
static_cast<std::size_t
>(sub.
l_max)};
1975 for (std::size_t p {pos}; !matched && !clist->pcs.empty() && p < text_.size() && p < limit; ++p) {
1976 const auto byte_value {
static_cast<std::uint8_t
>(text_[p])};
1977 for (
const std::int32_t pc : clist->pcs) {
1978 const instr& in {prog_.code[
static_cast<std::size_t
>(pc)]};
1983 if (dc.valid && cp_class_matches(prog_.cp_classes[in.arg16], dc.cp)) {
1984 sub_add_thread(*nlist, pc + 1 +
static_cast<std::int32_t
>(4 - dc.length), p + 1, matched);
1990 const bool consume {in.op ==
opcode::byte ? byte_value == in.arg8
1991 : prog_.classes[in.arg16].test(byte_value)};
1993 sub_add_thread(*nlist, pc + 1, p + 1, matched);
1999 nlist->
reset(code_size);
2016 const std::size_t lmax {
static_cast<std::size_t
>(sub.
l_max)};
2017 const std::size_t window_start {pos > lmax ? pos - lmax : 0};
2018 for (std::size_t s {pos};; --s) {
2021 const bool aligned {prog_.byte_mode || s >= pos
2022 || (
static_cast<std::uint8_t
>(text_[s]) & 0xC0U) != 0x80U};
2023 if (aligned && sub_fullmatch_window(sub.
code_offset, s, pos)) {
2026 if (s == window_start) {
2044 const std::size_t code_size {prog_.code.size()};
2047 clist->
reset(code_size);
2048 nlist->reset(code_size);
2050 sub_add_thread(*clist, code_offset, start, here);
2055 for (std::size_t p {start}; p < pos; ++p) {
2056 if (clist->pcs.empty()) {
2059 const bool last {p + 1 == pos};
2060 bool at_pos {
false};
2061 const auto byte_value {
static_cast<std::uint8_t
>(text_[p])};
2062 for (
const std::int32_t pc : clist->pcs) {
2063 const instr& in {prog_.code[
static_cast<std::size_t
>(pc)]};
2066 if (dc.valid && cp_class_matches(prog_.cp_classes[in.arg16], dc.cp)) {
2067 sub_add_thread(*nlist, pc + 1 +
static_cast<std::int32_t
>(4 - dc.length), p + 1,
2068 last ? at_pos : sink);
2074 const bool consume {in.op ==
opcode::byte ? byte_value == in.arg8
2075 : prog_.classes[in.arg16].test(byte_value)};
2077 sub_add_thread(*nlist, pc + 1, p + 1, last ? at_pos : sink);
2086 nlist->
reset(code_size);
2112 auto& stack {state_.lookaround.stack};
2114 stack.push_back({.pc = pc0, .block = 0});
2115 while (!stack.empty()) {
2116 const std::int32_t pc {stack.back().pc};
2118 if (list.
seen(pc)) {
2122 const instr& in {prog_.code[
static_cast<std::size_t
>(pc)]};
2125 stack.push_back({.pc = in.primary_target, .block = 0});
2128 stack.push_back({.pc = in.secondary_target, .block = 0});
2129 stack.push_back({.pc = in.primary_target, .block = 0});
2134 stack.push_back({.pc = pc + 1, .block = 0});
2138 stack.push_back({.pc = pc + 1, .block = 0});
2147 list.
pcs.push_back(pc);
256-bit byte set with O(1) membership, fully constexpr.
static constexpr std::size_t state_budget
Cached states before a flush (the memory cap).
The Pike VM, generic over the scratch-state container policy.
void ensure_lazy_dfa()
Ensure this iterator's lazy DFA caches are warm for the current program. Builds the shared immutables...
std::remove_reference_t< decltype(std::declval< State & >().lists[0])> list_type
The concrete thread-list type taken from the bound State.
bool confirm_at(std::string_view text, std::size_t s, OutSlots &out_slots, std::size_t &stop)
Confirm a match anchored at s: find its end with the forward DFA and fill captures with the one-pass ...
constexpr const std::uint64_t * cp_page_table(std::size_t cp_index)
Builds (once, cached) and returns the cp_class's membership bitmap over [U+0080, U+07FF] — a one-load...
constexpr bool lookbehind_matches(const lookaround_sub &sub, std::size_t pos)
Lookbehind: does the sub-pattern match a window ENDING EXACTLY at pos?
constexpr bool word_after(std::size_t pos, bool ascii_word) const
Word-ness of the code point starting at pos — the right side of a boundary. False at the text end or ...
constexpr pike_vm(const program_view &prog, State &state)
Binds the VM to a program and caller-owned scratch state.
constexpr bool run_codepoint_class(std::string_view text, std::size_t start, run_mode mode, OutSlots &out_slots)
Fast path for . / a negated class, optionally a greedy +.
constexpr std::size_t next_candidate(std::string_view text, std::size_t pos, std::size_t start) const
First position >= pos that could start a match, per the hints.
match_semantics sem_
Match semantics for the current run (match_semantics::first by default; match_semantics::longest is t...
constexpr bool lookahead_matches(const lookaround_sub &sub, std::size_t pos)
Lookahead: does the sub-pattern match a prefix starting at pos?
constexpr void add_thread(list_type &list, std::int32_t pc0, std::size_t pos, std::uint32_t initial_block)
Adds pc0 and its whole epsilon closure to list — the one closure walk (OPT D1). Each DFS frame carrie...
constexpr bool run_exact_literal(std::string_view text, std::size_t start, run_mode mode, OutSlots &out_slots)
Fast path for a pure-literal pattern.
std::remove_reference_t< decltype(std::declval< State & >().pool)> pool_type
The capture-block pool type of the bound State (OPT D1) — heap-backed for dynamic,...
constexpr const std::uint8_t * class_table(std::size_t class_index)
Returns a flat 256-byte membership table for class class_index.
constexpr void fill_span_slots(OutSlots &out_slots, std::size_t match_start, std::size_t match_end) const
Writes a class-loop fast-path result into out_slots: the whole-match span in slots 0/1,...
std::string_view text_
The subject text for the current run.
constexpr bool lookaround_holds(std::uint16_t sub_id, std::size_t pos)
Evaluates a bounded lookaround at pos (true if the thread should proceed).
constexpr std::size_t run_cascade_stop(std::string_view text, std::size_t from) const
OPT-C run tail: the next stop byte at or after from, or the text end. Kept in its own function so the...
constexpr void cow_release_blocks(list_type &list)
Releases the block references a list's threads hold (OPT D1), before the list is reset or the run ret...
constexpr bool seed_viable(std::string_view text, std::size_t pos, std::size_t start) const
Cheap pre-check before seeding a new thread at pos.
constexpr bool run_fixed_shape(std::string_view text, std::size_t start, run_mode mode, OutSlots &out_slots)
Fast path for a whole-pattern fixed-width byte/klass sequence.
constexpr void step(list_type &clist, list_type &nlist, std::size_t pos, run_mode mode, bool &matched, OutSlots &out_slots)
Advances every thread of clist by the byte at pos.
constexpr std::size_t match_byte_klass_run(std::string_view text, std::size_t pc, std::size_t s) const
Matches the run of byte/klass instructions starting at pc.
constexpr bool fast_search(std::string_view text, std::size_t start, MatchAt match_at, OutSlots &out_slots)
Leftmost search by scanning candidate positions (first-byte hints).
constexpr bool run_cp_class_loop(std::string_view text, std::size_t start, run_mode mode, OutSlots &out_slots)
Fast path for a whole-pattern code-point class klass_cp, optionally a greedy +.
static constexpr std::size_t lazy_dfa_min_input
Below this input length the lazy-DFA routing is skipped (the two-pass setup does not amortise on a sh...
constexpr bool run_class_loop(std::string_view text, std::size_t start, run_mode mode, OutSlots &out_slots)
Fast path for a whole-pattern "class+".
constexpr bool replay_literal(std::size_t cand, std::size_t len, OutSlots &out_slots) const
Fills capture slots for a literal match at cand.
constexpr bool run_general(std::string_view text, std::size_t start, run_mode mode, OutSlots &out_slots, std::size_t *forward_stop=nullptr)
The general Pike VM search loop (the match semantics), factored so the lazy-DFA routing can run it on...
constexpr void sub_add_thread(thread_list &list, std::int32_t pc0, std::size_t pos, bool &matched)
Epsilon-closure for the lookaround sub-VM, on the isolated sub-scratch.
State & state_
Borrowed reusable scratch state.
constexpr const std::size_t * thread_slots(list_type &clist, std::size_t i)
Pointer to thread i's slot_count capture values — its COW block's slots (OPT D1). Used by the match c...
const program_view & prog_
The program being executed (borrowed; a stable lvalue that outlives the VM).
constexpr bool sub_fullmatch_window(std::int32_t code_offset, std::size_t start, std::size_t pos)
Reports whether the sub-program, run from start, reaches match EXACTLY at pos (a fullmatch of [start,...
constexpr const std::uint8_t * cp_ascii_table(std::size_t cp_index)
Byte-indexed membership table for a cp_class's ASCII bitmap — the same one-load trick as class_table,...
constexpr bool assertion_holds(assert_kind kind, std::size_t pos, bool word_ness_flipped) const
Evaluates a zero-width assertion at pos in the current text.
constexpr void fill_fixed_saves(std::size_t match_start, OutSlots &out_slots) const
Fills the capturing-group slots of a fixed-shape match. Every consuming op is one byte wide,...
bool run_inner_literal(std::string_view text, std::size_t start, OutSlots &out_slots, bool &abandon)
The inner-literal search: memmem a required literal, reverse-match the prefix to the match start,...
constexpr bool word_before(std::size_t pos, bool ascii_word) const
Word-ness of the code point ending exactly at pos — the left side of a \b/\B/ </> boundary....
constexpr bool cp_class_matches(const detail::cp_class &cc, char32_t cp) const
Tests a decoded code point against a klass_cp class: ASCII bitmap below 0x80, a binary search of the ...
std::size_t forbid_empty_until_
Reject empty matches whose start is below this offset.
constexpr bool literal_at(std::string_view text, std::size_t cand, std::size_t len) const
Tests whether the fixed literal prefix occurs at cand.
constexpr bool run_alternation(std::string_view text, std::size_t start, run_mode mode, OutSlots &out_slots)
Fast path for an alternation of straight-line branches.
void ensure_immutables()
Build the forward/reverse DFAs into the reusable state on first eligible use, or rebuild them if the ...
constexpr bool run(std::string_view text, std::size_t start, run_mode mode, OutSlots &out_slots, std::size_t forbid_empty_until=0, match_semantics sem=match_semantics::first)
Runs the VM over text starting at start.
constexpr void advance_thread(list_type &clist, list_type &nlist, std::size_t i, std::int32_t next_pc, std::size_t next_pos)
Advances thread i of clist by one consumed byte, seeding its continuation's closure into nlist (OPT D...
static constexpr std::size_t state_budget
A lazy, priority-preserving forward DFA over the Pike program (the kFirstMatch forward pass + cache).
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 decoded_codepoint decode_codepoint_strict(std::string_view text, std::size_t pos)
Strictly decodes and validates the UTF-8 sequence at text[pos].
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.
@ behind
(?<= / (?<! — the sub matches ending exactly at the position.
constexpr bool word_after(std::string_view text, std::size_t pos, bool ascii_word)
Word-ness of the code point starting at pos — the right side of a boundary. False at the text end or ...
constexpr bool word_before(std::string_view text, std::size_t pos, bool ascii_word)
Word-ness of the code point ending at pos — the left side of a boundary. False at the text start or o...
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...
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,...
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 std::size_t find_byte(std::string_view text, std::size_t pos, char byte)
Index of byte in text[pos..), or real::npos.
run_mode
How a VM run is anchored.
@ search
First match anywhere (Python re.search).
@ prefix
Anchored at the start position (Python re.match).
@ full
Anchored at both ends (Python re.fullmatch).
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.
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).
match_semantics
Which match a search returns among those starting at the leftmost position (an experimental,...
@ longest
Leftmost-longest (POSIX / RE2 set_longest_match): the longest overall match wins the bounds.
@ first
Leftmost-first (Perl / Python re / the crate): source-order thread priority decides....
@ ascii
ASCII mode (re.A): \d \w \s \b stay ASCII and icase folds ASCII only, even in text mode....
The one-pass builder: decides whether a pattern is one-pass and, if so, tabulates a deterministic cap...
Search acceleration: pattern analysis and candidate-finding.
Compiled form of a pattern and the public flags / error types.
Copy-on-write pool of capture blocks (OPT D1) — the one capture-slot mechanism for both storages.
constexpr std::uint32_t cow_write(std::uint32_t b, std::uint16_t slot, std::size_t value)
Write slots(b)[slot] = value, detaching first if b is shared. Returns the block that now holds the wr...
std::uint16_t width
slot_count (values per block).
constexpr long long total_refs() const
Sum of all live refcounts — the debug Σ-invariant checks this equals the references the VM actually h...
constexpr void decref(std::uint32_t b)
FreeVec free_list
Recycled block indices (refcount hit 0).
RefVec refcount
Live references per block (non-atomic — the VM is single-threaded).
static constexpr std::uint32_t npos_block
Canonical all-npos block, shared by every seed.
constexpr std::uint32_t allocate()
A fresh block with refcount 1 (a recycled index if available, else a grown one).
constexpr std::size_t * slots(std::uint32_t b)
Pointer to block b's width slots. Invalidated by any allocate that grows data.
constexpr void reset(std::uint16_t slot_count)
Reset for a new match run: block 0 = all-npos, held by a permanent sentinel ref. The storage grows by...
constexpr void incref(std::uint32_t b)
DataVec data
Flat slot storage: block b's slots at [b*width, b*width+width).
Reusable VM scratch state.
std::int32_t table_class
Flat 256-byte membership table for the hot single-class scan, and the class index it was built for (-...
std::array< std::uint64_t, 30 > cp_page
1 where the code point (U+0080..U+07FF) is a member.
std::array< std::uint8_t, 256 > table
1 where the byte is in table_class.
EpsVec stack
Epsilon-closure DFS stack.
std::int32_t cp_page_class
Membership bitmap for a cp_class over the 2-byte UTF-8 range [U+0080, U+07FF], and the class it was b...
ThreadList lists[2]
Current and next thread lists (flipped by index).
One priority-ordered list of NFA threads (leftmost-greedy semantics).
constexpr bool seen(std::int32_t pc) const
Returns true if pc is already in this generation.
PcVec pcs
Live program counters, in priority order.
SlotVec slots
Flattened capture slots, parallel to pcs.
std::uint64_t generation
Current generation; bumped by reset.
MarkVec mark
Per-pc generation stamp (see seen).
constexpr void reset(std::size_t code_size)
Clears the list in O(1) by bumping the generation.
constexpr void mark_seen(std::int32_t pc)
Marks pc as present in the current generation.
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.
std::vector< char_class > classes
std::vector< instr > code
A set of byte values (0–255) as a 256-bit bitmap.
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...
std::uint32_t lo
First code point (inclusive).
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.
The result of a strict UTF-8 decode: the code point, its byte length, and validity.
std::uint32_t cp
The decoded code point (meaningful only when valid).
std::size_t length
Bytes consumed by the sequence (1–4), or the bytes examined on failure.
One frame on the epsilon-closure DFS stack (OPT D1): a program counter to explore,...
std::int32_t pc
The program counter to explore.
std::uint32_t block
Index of the capture block this branch carries (into the capture pool).
One NFA instruction. Field meaning depends on op.
std::int32_t primary_target
Primary branch target (split/jump).
Reusable, isolated scratch for one level of lookaround evaluation (dynamic only).
thread_list lists[2]
Sub-VM thread lists (pcs only; the sub is capture-free).
std::vector< eps_entry > stack
Sub-VM epsilon-closure stack.
A bounded lookaround sub-program, referenced by assert_lookaround's arg16.
std::int32_t code_offset
First instruction of the sub-program in code.
std::int32_t l_max
Max bytes the sub-pattern can consume (bounded).
look_dir direction
Ahead or behind.
Search-acceleration hints extracted from a compiled program.
std::uint8_t exact_literal_len
Length of the pure-literal match, or 0.
std::int32_t inner_literal_prefix
bool fixed_alternation
Whole pattern is an alternation of straight-line branches (no captures/asserts).
std::int32_t greedy_cp_class
cp_class index if the whole pattern is a code-point class klass_cp (optionally +),...
std::int32_t greedy_class_loop
Class index if the whole pattern is "class+", else -1.
bool fixed_shape
Whole pattern is a fixed-width byte/klass sequence (no branches/asserts/captures).
char_class first_bytes
All possible first bytes.
std::uint8_t inner_literal_len
std::array< std::uint8_t, 16 > inner_literal
A required inner literal every match must contain (the memmem candidate the inner-literal prefilter s...
std::int32_t codepoint_class_ascii
ASCII-class index when the whole pattern is ./negated-class (optionally +), else -1.
VM scratch state for the dynamic storage mode, plus the lookaround sub-scratch.
lookaround_scratch lookaround
Isolated sub-scratch for bounded lookaround evaluation.
std::optional< reverse_dfa > rev_dfa
OPT lazy-DFA: the reverse start-finder.
capture_pool pool
OPT D1: copy-on-write capture blocks (heap-backed).
const void * il_text
IL: the haystack il_abandoned refers to (reset the flag when it changes).
const void * il_prefix_for
IL: the prefix program il_prefix_rev was built for.
std::optional< lazy_dfa > fwd_dfa
OPT lazy-DFA: forward pass (built lazily, cache persists across a find_iter).
bool il_abandoned
IL: a linearity guard tripped on this haystack — stay on the core.
std::optional< reverse_dfa > il_prefix_rev
IL: the inner-literal prefix reverse DFA (built once per program).
const void * dfa_program
The program the DFAs were built for (rebuild if it changes).
std::span< const instr > prefix_code
IL: inner-literal prefix sub-program (the reverse start-finder). Empty unless there is a required lit...
regex_immutables * immut
Per-regex DFA/one-pass cache (dynamic storage only; else null).
std::span< const code_range > prefix_cp_ranges
IL: flat range buffer for prefix_cp_classes.
std::uint16_t slot_count
2 * (capture groups + 1).
std::span< const cp_class > prefix_cp_classes
IL: code-point classes for prefix_code.
pattern_hints hints
Search-acceleration hints.
bool unicode_word
\b \B < > use Unicode word-ness (text mode, not bytes / re.A).
std::span< const char_class > prefix_classes
IL: classes for prefix_code.
std::span< const instr > code
The instruction stream (main + lookaround regions).
The per-regex immutable cache the router shares across every find_iter on a regex: the byte- program ...
std::optional< onepass > op_table
one-pass extractor, present iff the pattern is one-pass.
byte_program il_prefix_prog
IL: the inner-literal prefix's byte program (ineligible until built). Per-regex so the reverse DFA th...
std::size_t il_min_haystack
IL: on a haystack that HAS a candidate, the route only fires at or above this size (0 = always)....
Unicode \w / \d / \s property ranges and their lookups.
UTF-8 position arithmetic for match iteration.
REAL's version macros and the C++20 language-standard guard.