/// @file dpf/keyword2.hpp /// @brief `dpf::keyword2`, a ranked pattern language meant to replace `dpf::keyword` /// @details A pattern is an anchored expression: literals, classes, `.`, /// concatenation, alternation, `?`, and `{n}` / `{n,m}`. There is no /// unbounded `*` or `+`. Each value in the language has one rank in /// `0 .. |L|-1`, and that rank is the DPF input. /// /// Significant characters keep their own spelling. A pad field is /// opted into explicitly: /// /// pad('0')[0-9]{0,4} /// pad('\0')[a-z]{0,3} /// /// The quoted character is digit 0. The other characters of the class, /// in the order written, take digits 1, 2, …. Leading pads do not /// change the rank, and the domain size is still `radix^max`. /// `dpf::keyword` is unchanged: its implicit `alphabet[0]` pad stays. /// /// Rank order is structural. Concatenation is mixed-radix with the /// left piece in the high place, so a fixed-width spelling sorts in /// lexicographic byte order. A pad field sorts by its integer value /// (left-pad order: in `pad('\0')[a-z]{0,3}`, `b` is before `aa`). /// A variable repetition `{n,m}` is shortlex: shorter sequences occupy /// the lower ranks. Alternatives occupy contiguous blocks in source /// order, and must be unambiguous. /// /// The pattern rejects itself when a pad field or a variable /// repetition is not delimited, when two spellings share a value by /// accident, when the token automaton would exceed 64 states, when a /// spelling is longer than 64, or when `|L|` does not fit in 256 bits. /// @copyright Copyright (c) 2019-2026 Ryan Henry and [others](@ref authors) /// @license Released under a GNU General Public v2.0 (GPLv2) license; /// see [LICENSE.md](@ref license) for details. #ifndef LIBDPF_INCLUDE_DPF_KEYWORD2_HPP__ #define LIBDPF_INCLUDE_DPF_KEYWORD2_HPP__ #include #include #include #include #include #include #include #include #include #include "hedley/hedley.h" #include "dpf/modint.hpp" #include "dpf/utils.hpp" namespace dpf { /// @brief Why `dpf::keyword2` refused a pattern. enum class keyword2_error : std::uint8_t { ok = 0, syntax, delim, overlap, states, length, width }; namespace detail { inline constexpr std::uint16_t kw2_nodes = 64; inline constexpr std::uint16_t kw2_kids = 192; inline constexpr std::uint16_t kw2_len_cap = 64; inline constexpr std::uint16_t kw2_dfa_cap = 64; inline constexpr std::uint16_t kw2_nfa_cap = 160; inline constexpr std::uint16_t kw2_none = 0xffff; struct u256 { std::uint64_t w[4]{}; constexpr bool zero() const { return w[0] == 0 && w[1] == 0 && w[2] == 0 && w[3] == 0; } constexpr bool one() const { return w[0] == 1 && w[1] == 0 && w[2] == 0 && w[3] == 0; } }; struct card { u256 v{}; bool all = false; bool bad = false; constexpr bool zero() const { return !all && !bad && v.zero(); } constexpr bool one() const { return !all && !bad && v.one(); } }; constexpr card card_u64(std::uint64_t x) { card c; c.v.w[0] = x; return c; } constexpr card card_one() { return card_u64(1); } constexpr int cmp_u(u256 a, u256 b) { for (int i = 3; i >= 0; --i) { if (a.w[i] < b.w[i]) return -1; if (a.w[i] > b.w[i]) return 1; } return 0; } constexpr u256 sub_u(u256 a, u256 b) { u256 r; std::uint64_t borrow = 0; for (int i = 0; i < 4; ++i) { unsigned __int128 aa = a.w[i]; unsigned __int128 bb = static_cast(b.w[i]) + borrow; if (aa >= bb) { r.w[i] = static_cast(aa - bb); borrow = 0; } else { r.w[i] = static_cast( (aa + (static_cast(1) << 64)) - bb); borrow = 1; } } return r; } constexpr u256 dec_u(u256 a) { return sub_u(a, card_one().v); } constexpr card add_card(card a, card b) { if (a.bad || b.bad) { card r; r.bad = true; return r; } if (a.zero()) return b; if (b.zero()) return a; if (a.all || b.all) { card r; r.bad = true; return r; } card r; std::uint64_t carry = 0; for (int i = 0; i < 4; ++i) { unsigned __int128 s = static_cast(a.v.w[i]) + b.v.w[i] + carry; r.v.w[i] = static_cast(s); carry = static_cast(s >> 64); } if (carry) { if (r.v.zero()) r.all = true; else r.bad = true; } return r; } constexpr card mul_card(card a, card b) { if (a.bad || b.bad) { card r; r.bad = true; return r; } if (a.zero() || b.zero()) return {}; if (a.one()) return b; if (b.one()) return a; if (a.all || b.all) { card r; r.bad = true; return r; } std::uint64_t out[8]{}; for (int i = 0; i < 4; ++i) { unsigned __int128 carry = 0; for (int j = 0; j < 4; ++j) { unsigned __int128 cur = out[i + j] + static_cast(a.v.w[i]) * b.v.w[j] + carry; out[i + j] = static_cast(cur); carry = cur >> 64; } out[i + 4] = static_cast(carry); } card r; bool hi = out[4] | out[5] | out[6] | out[7]; if (hi) { bool exact = out[4] == 1 && out[5] == 0 && out[6] == 0 && out[7] == 0 && out[0] == 0 && out[1] == 0 && out[2] == 0 && out[3] == 0; if (exact) r.all = true; else r.bad = true; return r; } for (int i = 0; i < 4; ++i) r.v.w[i] = out[i]; return r; } constexpr u256 mul_add_u(u256 rank, card place, u256 extra) { if (place.one()) return add_card(card{rank, false, false}, card{extra, false, false}).v; if (place.zero()) return extra; if (rank.zero()) return extra; card prod = mul_card(card{rank, false, false}, place); return add_card(prod, card{extra, false, false}).v; } constexpr void divmod_u(u256 n, u256 d, u256 & q, u256 & r) { q = {}; std::uint64_t rw[5]{}; for (int bit = 255; bit >= 0; --bit) { std::uint64_t c = 0; for (int i = 0; i < 5; ++i) { std::uint64_t nc = rw[i] >> 63; rw[i] = (rw[i] << 1) | c; c = nc; } if ((n.w[bit / 64] >> (bit % 64)) & 1u) rw[0] |= 1u; bool ge = rw[4] != 0; if (!ge) { ge = true; for (int i = 3; i >= 0; --i) { if (rw[i] > d.w[i]) break; if (rw[i] < d.w[i]) { ge = false; break; } } } if (!ge) continue; std::uint64_t borrow = 0; for (int i = 0; i < 4; ++i) { unsigned __int128 aa = rw[i]; unsigned __int128 bb = static_cast(d.w[i]) + borrow; if (aa >= bb) { rw[i] = static_cast(aa - bb); borrow = 0; } else { rw[i] = static_cast( (aa + (static_cast(1) << 64)) - bb); borrow = 1; } } rw[4] -= borrow; q.w[bit / 64] |= std::uint64_t{1} << (bit % 64); } r = {}; for (int i = 0; i < 4; ++i) r.w[i] = rw[i]; } constexpr void divmod_card(u256 n, card d, u256 & q, u256 & r) { if (d.all) { q = {}; r = n; return; } divmod_u(n, d.v, q, r); } constexpr int cmp_card_u(u256 n, card d) { if (d.all) return -1; if (d.bad) return 1; return cmp_u(n, d.v); } constexpr int bit_length_u(u256 a) { for (int i = 3; i >= 0; --i) { if (a.w[i] == 0) continue; int b = 63; while (((a.w[i] >> b) & 1u) == 0) --b; return i * 64 + b + 1; } return 0; } constexpr int popcount64(std::uint64_t x) { int n = 0; while (x) { n += static_cast(x & 1u); x >>= 1; } return n; } constexpr int popcount_bits(const std::uint64_t b[4]) { return popcount64(b[0]) + popcount64(b[1]) + popcount64(b[2]) + popcount64(b[3]); } constexpr bool bit_test(const std::uint64_t b[4], unsigned ch) { return (b[ch / 64] >> (ch % 64)) & 1u; } constexpr void bit_set(std::uint64_t b[4], unsigned ch) { b[ch / 64] |= std::uint64_t{1} << (ch % 64); } constexpr void bit_or(std::uint64_t d[4], const std::uint64_t s[4]) { for (int i = 0; i < 4; ++i) d[i] |= s[i]; } constexpr void bit_fill(std::uint64_t b[4]) { for (int i = 0; i < 4; ++i) b[i] = ~std::uint64_t{0}; } constexpr bool bit_overlap(const std::uint64_t a[4], const std::uint64_t b[4]) { return (a[0] & b[0]) || (a[1] & b[1]) || (a[2] & b[2]) || (a[3] & b[3]); } constexpr int rank_below(const std::uint64_t b[4], unsigned ch) { int r = 0; unsigned wi = ch / 64; unsigned bi = ch % 64; for (unsigned i = 0; i < wi; ++i) r += popcount64(b[i]); if (bi) { std::uint64_t mask = ~std::uint64_t{0} >> (64 - bi); r += popcount64(b[wi] & mask); } return r; } constexpr unsigned char bit_at_index(const std::uint64_t b[4], int index) { for (unsigned ch = 0; ch < 256; ++ch) if (bit_test(b, ch) && index-- == 0) return static_cast(ch); return 0; } enum class node_kind : std::uint8_t { empty, lit, cls, dot, pad, cat, alt, rep }; struct node { node_kind k = node_kind::empty; std::uint16_t a = 0; std::uint16_t n = 0; std::uint16_t minv = 0; std::uint16_t maxv = 0; std::uint8_t ch = 0; std::uint16_t radix = 0; std::uint64_t bits[4]{}; std::uint8_t digs[256]{}; card lang{}; bool nullable = false; std::uint16_t min_len = 0; std::uint16_t max_len = 0; std::uint64_t first[4]{}; }; struct ast { node nodes[kw2_nodes]{}; std::uint16_t kids[kw2_kids]{}; std::uint16_t nnodes = 0; std::uint16_t nkids = 0; std::uint16_t root = 0; }; struct program { ast tree{}; card lang{}; std::uint16_t bits = 1; keyword2_error error = keyword2_error::ok; std::uint16_t err_pos = 0; }; constexpr void fail(program & p, keyword2_error e, std::uint16_t pos) { if (p.error == keyword2_error::ok) { p.error = e; p.err_pos = pos; } } struct parser { program * p; const char * s; std::uint16_t i = 0; std::uint16_t n = 0; constexpr bool alive() const { return p->error == keyword2_error::ok; } constexpr char peek() const { return i < n ? s[i] : '\0'; } constexpr bool eat(char c) { if (peek() != c) return false; ++i; return true; } constexpr int hexval(char c) const { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; return -1; } constexpr bool parse_escape(unsigned char & out) { if (!eat('\\')) return false; char c = peek(); if (c == '\0') { fail(*p, keyword2_error::syntax, i); return false; } ++i; switch (c) { case '0': out = 0; return true; case 'n': out = '\n'; return true; case 't': out = '\t'; return true; case 'r': out = '\r'; return true; case '\\': out = '\\'; return true; case '\'': out = '\''; return true; case '"': out = '"'; return true; case '-': out = '-'; return true; case ']': out = ']'; return true; case '[': out = '['; return true; case '(': out = '('; return true; case ')': out = ')'; return true; case '{': out = '{'; return true; case '}': out = '}'; return true; case '|': out = '|'; return true; case '.': out = '.'; return true; case '?': out = '?'; return true; case 'x': { int h = hexval(peek()); if (h < 0) { fail(*p, keyword2_error::syntax, i); return false; } ++i; int l = hexval(peek()); if (l < 0) { fail(*p, keyword2_error::syntax, i); return false; } ++i; out = static_cast((h << 4) | l); return true; } default: fail(*p, keyword2_error::syntax, i); return false; } } constexpr bool parse_quoted(unsigned char & out) { if (!eat('\'')) { fail(*p, keyword2_error::syntax, i); return false; } if (peek() == '\\') { if (!parse_escape(out)) return false; } else { if (peek() == '\0' || peek() == '\'') { fail(*p, keyword2_error::syntax, i); return false; } out = static_cast(peek()); ++i; } if (!eat('\'')) { fail(*p, keyword2_error::syntax, i); return false; } return true; } constexpr std::uint16_t parse_num() { if (peek() < '0' || peek() > '9') { fail(*p, keyword2_error::syntax, i); return 0; } unsigned v = 0; while (peek() >= '0' && peek() <= '9') { v = v * 10u + static_cast(peek() - '0'); ++i; if (v > kw2_len_cap) { fail(*p, keyword2_error::length, i); return 0; } } return static_cast(v); } constexpr bool parse_quant(std::uint16_t & lo, std::uint16_t & hi) { if (eat('?')) { lo = 0; hi = 1; return true; } if (peek() == '*' || peek() == '+') { fail(*p, keyword2_error::syntax, i); return false; } if (!eat('{')) return false; lo = parse_num(); if (!alive()) return false; if (eat(',')) { if (peek() == '}') { fail(*p, keyword2_error::syntax, i); return false; } hi = parse_num(); } else hi = lo; if (!eat('}')) { fail(*p, keyword2_error::syntax, i); return false; } if (lo > hi) { fail(*p, keyword2_error::syntax, i); return false; } return true; } constexpr std::uint16_t add_node() { if (p->tree.nnodes >= kw2_nodes) { fail(*p, keyword2_error::states, i); return 0; } return p->tree.nnodes++; } constexpr std::uint16_t add_kid(std::uint16_t id) { if (p->tree.nkids >= kw2_kids) { fail(*p, keyword2_error::states, i); return 0; } std::uint16_t slot = p->tree.nkids++; p->tree.kids[slot] = id; return slot; } constexpr void add_class_char(node & nd, unsigned char ch, bool ordered) { if (!bit_test(nd.bits, ch)) { bit_set(nd.bits, ch); if (ordered && ch != nd.ch && nd.radix < 256) nd.digs[nd.radix++] = ch; } } constexpr bool parse_class_body(node & nd, bool ordered) { if (!eat('[')) { fail(*p, keyword2_error::syntax, i); return false; } bool neg = eat('^'); if (neg && ordered) { fail(*p, keyword2_error::syntax, i); return false; } if (peek() == ']') { fail(*p, keyword2_error::syntax, i); return false; } while (alive() && peek() != ']' && peek() != '\0') { unsigned char lo = 0; if (peek() == '\\') { if (!parse_escape(lo)) return false; } else { lo = static_cast(peek()); ++i; } if (peek() == '-' && i + 1 < n && s[i + 1] != ']') { ++i; unsigned char hi = 0; if (peek() == '\\') { if (!parse_escape(hi)) return false; } else { hi = static_cast(peek()); ++i; } if (hi < lo) { fail(*p, keyword2_error::syntax, i); return false; } for (unsigned c = lo; c <= hi && alive(); ++c) add_class_char(nd, static_cast(c), ordered); } else add_class_char(nd, lo, ordered); } if (!eat(']')) { fail(*p, keyword2_error::syntax, i); return false; } if (neg) { for (int w = 0; w < 4; ++w) nd.bits[w] = ~nd.bits[w]; } if (popcount_bits(nd.bits) == 0) { fail(*p, keyword2_error::syntax, i); return false; } return alive(); } constexpr std::uint16_t wrap_rep(std::uint16_t unit, std::uint16_t lo, std::uint16_t hi) { std::uint16_t id = add_node(); if (!alive()) return 0; node & nd = p->tree.nodes[id]; nd.k = node_kind::rep; nd.minv = lo; nd.maxv = hi; nd.a = p->tree.nkids; nd.n = 1; add_kid(unit); return id; } constexpr std::uint16_t parse_pad() { i = static_cast(i + 4); std::uint16_t id = add_node(); if (!alive()) return 0; node & nd = p->tree.nodes[id]; nd.k = node_kind::pad; unsigned char padc = 0; if (!parse_quoted(padc)) return 0; if (!eat(')')) { fail(*p, keyword2_error::syntax, i); return 0; } nd.ch = padc; nd.digs[0] = padc; nd.radix = 1; if (!parse_class_body(nd, true)) return 0; std::uint16_t lo = 0, hi = 0; if (!parse_quant(lo, hi) || p->error != keyword2_error::ok) { if (p->error == keyword2_error::ok) fail(*p, keyword2_error::syntax, i); return 0; } nd.minv = lo; nd.maxv = hi; bit_set(nd.bits, padc); return id; } constexpr bool is_meta(char c) const { return c == '.' || c == '|' || c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' || c == '?' || c == '\\' || c == '*' || c == '+'; } constexpr std::uint16_t parse_atom() { if (!alive()) return 0; if (eat('(')) { std::uint16_t inner = parse_alt(); if (!eat(')')) { fail(*p, keyword2_error::syntax, i); return 0; } return inner; } if (eat('.')) { std::uint16_t id = add_node(); if (!alive()) return 0; p->tree.nodes[id].k = node_kind::dot; return id; } if (peek() == '[') { std::uint16_t id = add_node(); if (!alive()) return 0; p->tree.nodes[id].k = node_kind::cls; if (!parse_class_body(p->tree.nodes[id], false)) return 0; return id; } unsigned char ch = 0; if (peek() == '\\') { if (!parse_escape(ch)) return 0; } else if (peek() == '\0' || is_meta(peek())) { fail(*p, keyword2_error::syntax, i); return 0; } else { ch = static_cast(peek()); ++i; } std::uint16_t id = add_node(); if (!alive()) return 0; p->tree.nodes[id].k = node_kind::lit; p->tree.nodes[id].ch = ch; return id; } constexpr bool starts_pad() const { return i + 3 < n && s[i] == 'p' && s[i + 1] == 'a' && s[i + 2] == 'd' && s[i + 3] == '('; } constexpr bool piece_starts() const { if (i >= n) return false; char c = s[i]; if (c == '|' || c == ')') return false; return true; } constexpr std::uint16_t parse_piece() { if (!alive()) return 0; if (starts_pad()) return parse_pad(); std::uint16_t id = parse_atom(); if (!alive()) return 0; std::uint16_t lo = 0, hi = 0; std::uint16_t before = i; if (parse_quant(lo, hi)) { if (!alive()) return 0; if (p->tree.nodes[id].k == node_kind::pad) { fail(*p, keyword2_error::syntax, before); return 0; } return wrap_rep(id, lo, hi); } return id; } constexpr std::uint16_t parse_concat() { if (!alive()) return 0; if (!piece_starts()) { std::uint16_t id = add_node(); if (!alive()) return 0; p->tree.nodes[id].k = node_kind::empty; return id; } std::uint16_t local[kw2_nodes]{}; std::uint16_t ln = 0; local[ln++] = parse_piece(); if (!alive()) return 0; if (!piece_starts()) return local[0]; while (alive() && piece_starts() && ln < kw2_nodes) local[ln++] = parse_piece(); std::uint16_t id = add_node(); if (!alive()) return 0; node & nd = p->tree.nodes[id]; nd.k = node_kind::cat; nd.a = p->tree.nkids; nd.n = ln; for (std::uint16_t i = 0; i < ln; ++i) add_kid(local[i]); return id; } constexpr std::uint16_t parse_alt() { if (!alive()) return 0; std::uint16_t local[kw2_nodes]{}; std::uint16_t ln = 0; local[ln++] = parse_concat(); if (!alive() || peek() != '|') return local[0]; while (alive() && eat('|') && ln < kw2_nodes) local[ln++] = parse_concat(); std::uint16_t id = add_node(); if (!alive()) return 0; node & nd = p->tree.nodes[id]; nd.k = node_kind::alt; nd.a = p->tree.nkids; nd.n = ln; for (std::uint16_t i = 0; i < ln; ++i) add_kid(local[i]); return id; } }; constexpr card pow_small(std::uint16_t radix, std::uint16_t exp) { card r = card_one(); card f = card_u64(radix); for (std::uint16_t i = 0; i < exp; ++i) r = mul_card(r, f); return r; } constexpr card rep_card(card s, std::uint16_t lo, std::uint16_t hi) { card sum{}; card p = card_one(); for (std::uint16_t k = 0; k <= hi; ++k) { if (k >= lo) sum = add_card(sum, p); if (k != hi) p = mul_card(p, s); if (sum.bad || p.bad) return sum.bad ? sum : p; } return sum; } constexpr void saturate_len(std::uint16_t & dst, std::uint32_t v) { dst = v > kw2_len_cap ? static_cast(kw2_len_cap + 1) : static_cast(v); } constexpr void annotate(program & p, std::uint16_t id) { if (p.error != keyword2_error::ok) return; node & nd = p.tree.nodes[id]; switch (nd.k) { case node_kind::empty: nd.lang = card_one(); nd.nullable = true; nd.min_len = 0; nd.max_len = 0; break; case node_kind::lit: nd.lang = card_one(); bit_set(nd.first, nd.ch); nd.min_len = 1; nd.max_len = 1; break; case node_kind::cls: nd.lang = card_u64(static_cast(popcount_bits(nd.bits))); bit_or(nd.first, nd.bits); nd.min_len = 1; nd.max_len = 1; break; case node_kind::dot: nd.lang = card_u64(256); bit_fill(nd.first); bit_fill(nd.bits); nd.min_len = 1; nd.max_len = 1; break; case node_kind::pad: nd.lang = pow_small(nd.radix == 0 ? 1 : nd.radix, nd.maxv); bit_or(nd.first, nd.bits); nd.nullable = nd.minv == 0; saturate_len(nd.min_len, nd.minv); saturate_len(nd.max_len, nd.maxv); break; case node_kind::cat: { nd.lang = card_one(); std::uint32_t mn = 0, mx = 0; for (std::uint16_t c = 0; c < nd.n; ++c) { std::uint16_t cid = p.tree.kids[nd.a + c]; annotate(p, cid); if (p.error != keyword2_error::ok) return; const node & ch = p.tree.nodes[cid]; nd.lang = mul_card(nd.lang, ch.lang); mn += ch.min_len; mx += ch.max_len; } for (int w = 0; w < 4; ++w) nd.first[w] = 0; for (std::uint16_t c = 0; c < nd.n; ++c) { const node & ch = p.tree.nodes[p.tree.kids[nd.a + c]]; bit_or(nd.first, ch.first); if (!ch.nullable) break; } nd.nullable = true; for (std::uint16_t c = 0; c < nd.n; ++c) if (!p.tree.nodes[p.tree.kids[nd.a + c]].nullable) nd.nullable = false; saturate_len(nd.min_len, mn); saturate_len(nd.max_len, mx); break; } case node_kind::alt: { nd.lang = {}; nd.nullable = false; std::uint16_t mn = kw2_len_cap + 1, mx = 0; for (std::uint16_t c = 0; c < nd.n; ++c) { std::uint16_t cid = p.tree.kids[nd.a + c]; annotate(p, cid); if (p.error != keyword2_error::ok) return; const node & ch = p.tree.nodes[cid]; nd.lang = add_card(nd.lang, ch.lang); bit_or(nd.first, ch.first); if (ch.nullable) nd.nullable = true; if (ch.min_len < mn) mn = ch.min_len; if (ch.max_len > mx) mx = ch.max_len; } nd.min_len = mn > kw2_len_cap ? static_cast(kw2_len_cap + 1) : mn; nd.max_len = mx; break; } case node_kind::rep: { std::uint16_t uid = p.tree.kids[nd.a]; annotate(p, uid); if (p.error != keyword2_error::ok) return; const node & u = p.tree.nodes[uid]; if (u.nullable) { fail(p, keyword2_error::delim, 0); return; } nd.lang = rep_card(u.lang, nd.minv, nd.maxv); bit_or(nd.first, u.first); nd.nullable = nd.minv == 0; saturate_len(nd.min_len, static_cast(nd.minv) * u.min_len); saturate_len(nd.max_len, static_cast(nd.maxv) * u.max_len); break; } } if (nd.lang.bad) fail(p, keyword2_error::width, 0); } constexpr void check_delim(program & p, std::uint16_t id, const std::uint64_t follow[4]) { if (p.error != keyword2_error::ok) return; node & nd = p.tree.nodes[id]; switch (nd.k) { case node_kind::pad: if (bit_overlap(nd.bits, follow)) fail(p, keyword2_error::delim, 0); break; case node_kind::rep: { std::uint16_t uid = p.tree.kids[nd.a]; std::uint64_t inner[4]{}; bit_or(inner, follow); if (nd.maxv > 1) bit_or(inner, p.tree.nodes[uid].first); if (nd.minv != nd.maxv && bit_overlap(p.tree.nodes[uid].first, follow)) fail(p, keyword2_error::delim, 0); check_delim(p, uid, inner); break; } case node_kind::cat: { std::uint64_t fol[4]{}; bit_or(fol, follow); for (int c = static_cast(nd.n) - 1; c >= 0; --c) { std::uint16_t cid = p.tree.kids[nd.a + static_cast(c)]; check_delim(p, cid, fol); const node & ch = p.tree.nodes[cid]; if (ch.nullable) bit_or(fol, ch.first); else { for (int w = 0; w < 4; ++w) fol[w] = 0; bit_or(fol, ch.first); } } break; } case node_kind::alt: for (std::uint16_t c = 0; c < nd.n; ++c) check_delim(p, p.tree.kids[nd.a + c], follow); break; default: break; } } struct nfa { struct st { std::uint16_t eps0 = kw2_none; std::uint16_t eps1 = kw2_none; std::uint16_t dest = kw2_none; std::uint64_t bits[4]{}; bool accept = false; }; st st[kw2_nfa_cap]{}; std::uint16_t n = 0; std::uint16_t start = 0; }; struct frag { std::uint16_t in = 0, out = 0; }; constexpr std::uint16_t nfa_new(nfa & n, bool & bad) { if (n.n >= kw2_nfa_cap) { bad = true; return 0; } std::uint16_t id = n.n++; n.st[id].eps0 = kw2_none; n.st[id].eps1 = kw2_none; n.st[id].dest = kw2_none; n.st[id].accept = false; for (int w = 0; w < 4; ++w) n.st[id].bits[w] = 0; return id; } constexpr void nfa_link(nfa & n, bool & bad, std::uint16_t from, std::uint16_t to) { if (bad) return; auto & s = n.st[from]; if (s.eps0 == kw2_none) { s.eps0 = to; return; } if (s.eps1 == kw2_none) { s.eps1 = to; return; } std::uint16_t r = nfa_new(n, bad); if (bad) return; n.st[r].eps0 = s.eps0; n.st[r].eps1 = s.eps1; s.eps0 = r; s.eps1 = to; } constexpr frag nfa_byte_chain(nfa & n, bool & bad, const std::uint64_t bits[4], std::uint16_t lo, std::uint16_t hi) { frag f; f.in = nfa_new(n, bad); if (bad) return f; if (hi == 0) { f.out = f.in; return f; } f.out = nfa_new(n, bad); if (bad) return f; if (lo == 0) nfa_link(n, bad, f.in, f.out); std::uint16_t prev = f.in; for (std::uint16_t i = 0; i < hi && !bad; ++i) { std::uint16_t nxt = (i + 1 == hi) ? f.out : nfa_new(n, bad); if (bad) return f; n.st[prev].dest = nxt; for (int w = 0; w < 4; ++w) n.st[prev].bits[w] = bits[w]; if (i + 1 >= lo && nxt != f.out) nfa_link(n, bad, nxt, f.out); prev = nxt; } return f; } constexpr frag nfa_build(nfa & n, bool & bad, const program & p, std::uint16_t id); constexpr frag nfa_build(nfa & n, bool & bad, const program & p, std::uint16_t id) { const node & nd = p.tree.nodes[id]; if (nd.k == node_kind::empty) { frag f; f.in = f.out = nfa_new(n, bad); return f; } if (nd.k == node_kind::lit || nd.k == node_kind::cls || nd.k == node_kind::dot) { std::uint64_t bits[4]{}; if (nd.k == node_kind::lit) bit_set(bits, nd.ch); else if (nd.k == node_kind::dot) bit_fill(bits); else for (int w = 0; w < 4; ++w) bits[w] = nd.bits[w]; return nfa_byte_chain(n, bad, bits, 1, 1); } if (nd.k == node_kind::pad) return nfa_byte_chain(n, bad, nd.bits, nd.minv, nd.maxv); if (nd.k == node_kind::rep) { std::uint16_t uid = p.tree.kids[nd.a]; const node & u = p.tree.nodes[uid]; if (u.k == node_kind::lit || u.k == node_kind::cls || u.k == node_kind::dot) { std::uint64_t bits[4]{}; if (u.k == node_kind::lit) bit_set(bits, u.ch); else if (u.k == node_kind::dot) bit_fill(bits); else for (int w = 0; w < 4; ++w) bits[w] = u.bits[w]; return nfa_byte_chain(n, bad, bits, nd.minv, nd.maxv); } frag f; f.in = nfa_new(n, bad); f.out = nfa_new(n, bad); if (nd.maxv == 0) { nfa_link(n, bad, f.in, f.out); return f; } std::uint16_t prev = f.in; for (std::uint16_t i = 0; i < nd.maxv && !bad; ++i) { if (i >= nd.minv) nfa_link(n, bad, prev, f.out); frag c = nfa_build(n, bad, p, uid); nfa_link(n, bad, prev, c.in); prev = c.out; } nfa_link(n, bad, prev, f.out); return f; } if (nd.k == node_kind::cat) { frag cur = nfa_build(n, bad, p, p.tree.kids[nd.a]); for (std::uint16_t c = 1; c < nd.n && !bad; ++c) { frag nxt = nfa_build(n, bad, p, p.tree.kids[nd.a + c]); nfa_link(n, bad, cur.out, nxt.in); cur.out = nxt.out; } return cur; } frag f; f.in = nfa_new(n, bad); f.out = nfa_new(n, bad); for (std::uint16_t c = 0; c < nd.n && !bad; ++c) { frag b = nfa_build(n, bad, p, p.tree.kids[nd.a + c]); nfa_link(n, bad, f.in, b.in); nfa_link(n, bad, b.out, f.out); } return f; } constexpr bool nfa_intersect(const nfa & n, std::uint16_t a0, std::uint16_t b0) { if (n.n == 0 || a0 == kw2_none || b0 == kw2_none) return false; constexpr std::uint16_t cap = kw2_nfa_cap; unsigned char seen[(cap * cap + 7) / 8]{}; auto mark = [&](std::uint16_t a, std::uint16_t b) { std::uint32_t bit = static_cast(a) * cap + b; seen[bit / 8] = static_cast(seen[bit / 8] | (1u << (bit % 8))); }; auto test = [&](std::uint16_t a, std::uint16_t b) { std::uint32_t bit = static_cast(a) * cap + b; return (seen[bit / 8] >> (bit % 8)) & 1u; }; std::uint16_t qa[cap * cap]{}; std::uint16_t qb[cap * cap]{}; int qh = 0, qt = 0; // Explicit stack for epsilon product, then byte steps via a queue of concrete states. std::uint16_t sa[cap * 4]{}, sb[cap * 4]{}; int sp = 0; auto consider = [&](std::uint16_t a, std::uint16_t b) { if (a >= n.n || b >= n.n || test(a, b) || sp >= cap * 4) return; sa[sp] = a; sb[sp] = b; ++sp; }; consider(a0, b0); // We collect byte-ready pairs in qa after closing eps. while (sp) { --sp; std::uint16_t a = sa[sp], b = sb[sp]; if (test(a, b)) continue; mark(a, b); if (qt >= cap * cap) return true; qa[qt] = a; qb[qt] = b; ++qt; if (n.st[a].eps0 != kw2_none) consider(n.st[a].eps0, b); if (n.st[a].eps1 != kw2_none) consider(n.st[a].eps1, b); if (n.st[b].eps0 != kw2_none) consider(a, n.st[b].eps0); if (n.st[b].eps1 != kw2_none) consider(a, n.st[b].eps1); } while (qh < qt) { std::uint16_t a = qa[qh], b = qb[qh]; ++qh; if (n.st[a].accept && n.st[b].accept) return true; if (n.st[a].dest == kw2_none || n.st[b].dest == kw2_none) continue; if (!bit_overlap(n.st[a].bits, n.st[b].bits)) continue; // Step both, then epsilon-close into the queue by resetting the stack search // only for the new pair. Reuse consider on a fresh seen? The pair (dest,dest) // may already be seen. Push dests through a nested eps walk that respects seen. std::uint16_t da = n.st[a].dest, db = n.st[b].dest; sp = 0; consider(da, db); while (sp) { --sp; std::uint16_t x = sa[sp], y = sb[sp]; if (test(x, y)) continue; mark(x, y); if (qt >= cap * cap) return true; qa[qt] = x; qb[qt] = y; ++qt; if (n.st[x].eps0 != kw2_none) consider(n.st[x].eps0, y); if (n.st[x].eps1 != kw2_none) consider(n.st[x].eps1, y); if (n.st[y].eps0 != kw2_none) consider(x, n.st[y].eps0); if (n.st[y].eps1 != kw2_none) consider(x, n.st[y].eps1); } } return false; } struct subset { std::uint64_t w[3]{}; constexpr bool operator==(subset o) const { return w[0] == o.w[0] && w[1] == o.w[1] && w[2] == o.w[2]; } }; constexpr bool sub_test(subset s, unsigned i) { return (s.w[i / 64] >> (i % 64)) & 1u; } constexpr void sub_set(subset & s, unsigned i) { s.w[i / 64] |= std::uint64_t{1} << (i % 64); } constexpr int eps_accept_ways(const nfa & n, const std::uint16_t * seeds, int nseeds) { int ways[kw2_nfa_cap]{}; unsigned char color[kw2_nfa_cap]{}; std::uint16_t order[kw2_nfa_cap]{}; int on = 0; std::uint16_t stack[kw2_nfa_cap]{}; int sp = 0; for (int i = 0; i < nseeds; ++i) { if (color[seeds[i]] == 0) { stack[sp++] = seeds[i]; color[seeds[i]] = 1; } ways[seeds[i]] += 1; } // color 1 = in stack, 2 = done. The stack above only seeds discovery. for (int i = 0; i < kw2_nfa_cap; ++i) color[i] = 0; sp = 0; for (int i = 0; i < nseeds; ++i) stack[sp++] = seeds[i]; while (sp) { std::uint16_t s = stack[sp - 1]; if (color[s] == 0) { color[s] = 1; auto push = [&](std::uint16_t t) { if (t == kw2_none) return; if (color[t] == 1) return; if (color[t] == 0) stack[sp++] = t; }; push(n.st[s].eps0); push(n.st[s].eps1); continue; } if (color[s] == 1) { color[s] = 2; order[on++] = s; --sp; } else --sp; } // `ways` currently counts seeds. Rebuild from zero and propagate in topo. int seed_ways[kw2_nfa_cap]{}; for (int i = 0; i < nseeds; ++i) seed_ways[seeds[i]] += 1; for (int i = 0; i < kw2_nfa_cap; ++i) ways[i] = seed_ways[i]; for (int i = on - 1; i >= 0; --i) { std::uint16_t s = order[i]; auto add = [&](std::uint16_t t) { if (t == kw2_none) return; ways[t] += ways[s]; }; add(n.st[s].eps0); add(n.st[s].eps1); } int acc = 0; for (std::uint16_t s = 0; s < n.n; ++s) if (n.st[s].accept) acc += ways[s]; return acc; } constexpr void check_ambiguous(program & p) { if (p.error != keyword2_error::ok) return; nfa n{}; bool bad = false; frag root = nfa_build(n, bad, p, p.tree.root); if (bad) { fail(p, keyword2_error::states, 0); return; } n.st[root.out].accept = true; n.start = root.in; subset states[kw2_dfa_cap]{}; int ns = 0; auto intern = [&](subset s) -> int { for (int i = 0; i < ns; ++i) if (states[i] == s) return i; if (ns >= kw2_dfa_cap) return -1; states[ns] = s; return ns++; }; auto close_seeds = [&](const std::uint16_t * seeds, int nseeds, subset & dst) { std::uint16_t stack[kw2_nfa_cap]{}; int sp = 0; for (int i = 0; i < nseeds; ++i) stack[sp++] = seeds[i]; while (sp) { std::uint16_t s = stack[--sp]; if (s >= n.n || sub_test(dst, s)) continue; sub_set(dst, s); if (n.st[s].eps0 != kw2_none) stack[sp++] = n.st[s].eps0; if (n.st[s].eps1 != kw2_none) stack[sp++] = n.st[s].eps1; } }; std::uint16_t seed0[1] = {n.start}; subset start{}; close_seeds(seed0, 1, start); if (eps_accept_ways(n, seed0, 1) > 1) { fail(p, keyword2_error::overlap, 0); return; } if (intern(start) < 0) { fail(p, keyword2_error::states, 0); return; } for (int si = 0; si < ns; ++si) { for (unsigned b = 0; b < 256; ++b) { std::uint16_t hits[32]{}; int nh = 0; for (std::uint16_t s = 0; s < n.n; ++s) { if (!sub_test(states[si], s)) continue; if (n.st[s].dest == kw2_none || !bit_test(n.st[s].bits, b)) continue; if (nh < 32) hits[nh++] = s; } if (nh == 0) continue; if (nh >= 2) { for (int i = 0; i < nh; ++i) for (int j = i + 1; j < nh; ++j) if (nfa_intersect(n, n.st[hits[i]].dest, n.st[hits[j]].dest)) { fail(p, keyword2_error::overlap, 0); return; } } std::uint16_t dests[32]{}; for (int i = 0; i < nh; ++i) dests[i] = n.st[hits[i]].dest; if (eps_accept_ways(n, dests, nh) > 1) { fail(p, keyword2_error::overlap, 0); return; } subset nxt{}; close_seeds(dests, nh, nxt); if (intern(nxt) < 0) { fail(p, keyword2_error::states, 0); return; } } } } constexpr card block_before(card s, std::uint16_t lo, int k) { card sum{}; card p = card_one(); for (int j = 0; j < k; ++j) { if (j >= static_cast(lo)) sum = add_card(sum, p); p = mul_card(p, s); } return sum; } struct hit { bool ok = false; std::size_t pos = 0; u256 rank{}; }; constexpr int pad_digit(const node & nd, unsigned char ch) { for (std::uint16_t d = 0; d < nd.radix; ++d) if (nd.digs[d] == ch) return static_cast(d); return -1; } struct seq_tail { const std::uint16_t * ids = nullptr; std::uint16_t i = 0; std::uint16_t n = 0; u256 rank{}; }; struct ctx { const program * prog = nullptr; const char * s = nullptr; std::size_t len = 0; seq_tail wrap[24]{}; int sp = 0; int depth = 0; }; constexpr hit run_seq(ctx & c, const std::uint16_t * ids, std::uint16_t nids, std::uint16_t at, std::size_t pos, u256 rank); constexpr hit finish_seq(ctx & c, std::size_t pos, u256 rank) { if (c.sp > 0) { seq_tail w = c.wrap[--c.sp]; return run_seq(c, w.ids, w.n, w.i, pos, add_card(card{w.rank, false, false}, card{rank, false, false}).v); } hit h; h.ok = pos == c.len; h.pos = pos; h.rank = rank; return h; } constexpr hit run_id(ctx & c, std::uint16_t id, std::size_t pos, u256 rank_before, const std::uint16_t * rest, std::uint16_t ri, std::uint16_t rn) { if (++c.depth > 256) { --c.depth; return {}; } const node & nd = c.prog->tree.nodes[id]; hit result{}; switch (nd.k) { case node_kind::empty: result = run_seq(c, rest, rn, ri, pos, mul_add_u(rank_before, nd.lang, u256{})); break; case node_kind::lit: if (pos < c.len && static_cast(c.s[pos]) == nd.ch) result = run_seq(c, rest, rn, ri, pos + 1, mul_add_u(rank_before, nd.lang, u256{})); break; case node_kind::cls: case node_kind::dot: if (pos < c.len && bit_test(nd.bits, static_cast(c.s[pos]))) { u256 piece{}; unsigned char ch = static_cast(c.s[pos]); piece.w[0] = nd.k == node_kind::dot ? ch : static_cast(rank_below(nd.bits, ch)); result = run_seq(c, rest, rn, ri, pos + 1, mul_add_u(rank_before, nd.lang, piece)); } break; case node_kind::pad: { std::size_t end = pos; while (end < c.len && end - pos < nd.maxv && bit_test(nd.bits, static_cast(c.s[end]))) ++end; if (end - pos < nd.minv) break; u256 val{}; bool good = true; for (std::size_t k = pos; k < end; ++k) { int d = pad_digit(nd, static_cast(c.s[k])); if (d < 0) { good = false; break; } val = add_card(mul_card(card{val, false, false}, card_u64(nd.radix)), card_u64(static_cast(d))).v; } if (good) result = run_seq(c, rest, rn, ri, end, mul_add_u(rank_before, nd.lang, val)); break; } case node_kind::cat: { std::uint16_t buf[kw2_kids]{}; std::uint16_t m = 0; for (std::uint16_t i = 0; i < nd.n && m < kw2_kids; ++i) buf[m++] = c.prog->tree.kids[nd.a + i]; for (std::uint16_t j = ri; j < rn && m < kw2_kids; ++j) buf[m++] = rest[j]; result = run_seq(c, buf, m, 0, pos, rank_before); break; } case node_kind::alt: { card offset{}; for (std::uint16_t b = 0; b < nd.n; ++b) { std::uint16_t cid = c.prog->tree.kids[nd.a + b]; if (c.sp >= 24) break; int mark = c.sp; c.wrap[c.sp++] = seq_tail{rest, ri, rn, mul_add_u(rank_before, nd.lang, offset.v)}; hit h = run_id(c, cid, pos, u256{}, nullptr, 0, 0); if (c.sp > mark) --c.sp; if (h.ok) { result = h; break; } offset = add_card(offset, c.prog->tree.nodes[cid].lang); } break; } case node_kind::rep: { std::uint16_t unit = c.prog->tree.kids[nd.a]; for (int k = static_cast(nd.maxv); k >= static_cast(nd.minv); --k) { std::uint16_t copies[kw2_len_cap]{}; for (int i = 0; i < k; ++i) copies[i] = unit; card block = block_before(c.prog->tree.nodes[unit].lang, nd.minv, k); if (c.sp >= 24) break; int mark = c.sp; u256 base = mul_add_u(rank_before, nd.lang, block.v); c.wrap[c.sp++] = seq_tail{rest, ri, rn, base}; hit h = run_seq(c, copies, static_cast(k), 0, pos, u256{}); if (c.sp > mark) --c.sp; if (h.ok) { result = h; break; } } break; } } --c.depth; return result; } constexpr hit run_seq(ctx & c, const std::uint16_t * ids, std::uint16_t nids, std::uint16_t at, std::size_t pos, u256 rank) { if (at == nids) return finish_seq(c, pos, rank); return run_id(c, ids[at], pos, rank, ids, static_cast(at + 1), nids); } constexpr hit match_full(const program & p, const char * s, std::size_t n) { std::uint16_t root = p.tree.root; ctx c; c.prog = &p; c.s = s; c.len = n; return run_id(c, root, 0, u256{}, nullptr, 0, 0); } HEDLEY_NON_NULL(4) constexpr int unrank_node(const program & p, std::uint16_t id, u256 rank, char * out, int n); constexpr int unrank_node(const program & p, std::uint16_t id, u256 rank, char * out, int n) { const node & nd = p.tree.nodes[id]; switch (nd.k) { case node_kind::empty: return n; case node_kind::lit: out[n++] = static_cast(nd.ch); return n; case node_kind::cls: case node_kind::dot: out[n++] = static_cast(nd.k == node_kind::dot ? static_cast(rank.w[0]) : bit_at_index(nd.bits, static_cast(rank.w[0]))); return n; case node_kind::pad: { unsigned char digits[kw2_len_cap]{}; u256 cur = rank; for (int i = static_cast(nd.maxv) - 1; i >= 0; --i) { u256 q, r; divmod_u(cur, card_u64(nd.radix).v, q, r); digits[i] = static_cast(r.w[0]); cur = q; } int start = 0; while (start < static_cast(nd.maxv) - static_cast(nd.minv) && digits[start] == 0) ++start; for (int i = start; i < static_cast(nd.maxv); ++i) out[n++] = static_cast(nd.digs[digits[i]]); return n; } case node_kind::cat: { card suf = card_one(); card place[kw2_nodes]{}; for (int c = static_cast(nd.n) - 1; c >= 0; --c) { place[c] = suf; suf = mul_card(suf, p.tree.nodes[p.tree.kids[nd.a + static_cast(c)]].lang); } u256 rem = rank; for (std::uint16_t c = 0; c < nd.n; ++c) { u256 q, r; divmod_card(rem, place[c], q, r); n = unrank_node(p, p.tree.kids[nd.a + c], q, out, n); rem = r; } return n; } case node_kind::alt: { u256 rem = rank; for (std::uint16_t c = 0; c < nd.n; ++c) { const node & ch = p.tree.nodes[p.tree.kids[nd.a + c]]; if (cmp_card_u(rem, ch.lang) < 0) return unrank_node(p, p.tree.kids[nd.a + c], rem, out, n); rem = sub_u(rem, ch.lang.v); } return n; } case node_kind::rep: { std::uint16_t unit = p.tree.kids[nd.a]; card S = p.tree.nodes[unit].lang; u256 rem = rank; card pow = card_one(); for (std::uint16_t j = 0; j < nd.minv; ++j) pow = mul_card(pow, S); int k = static_cast(nd.minv); for (; k <= static_cast(nd.maxv); ++k) { if (cmp_card_u(rem, pow) < 0) break; rem = sub_u(rem, pow.v); if (k == static_cast(nd.maxv)) break; pow = mul_card(pow, S); } u256 rs[kw2_len_cap]{}; for (int i = k - 1; i >= 0; --i) { u256 q, r; divmod_card(rem, S, q, r); rs[i] = r; rem = q; } for (int i = 0; i < k; ++i) n = unrank_node(p, unit, rs[i], out, n); return n; } } return n; } HEDLEY_NON_NULL(3) constexpr int unrank_root(const program & p, u256 rank, char * out) { if (!p.lang.all && cmp_card_u(rank, p.lang) >= 0) return -1; return unrank_node(p, p.tree.root, rank, out, 0); } HEDLEY_NON_NULL(1) constexpr program compile_pattern(const char * pattern) { program p{}; parser ps{&p, pattern, 0, 0}; while (pattern[ps.n] != '\0' && ps.n < 1023) ++ps.n; if (pattern[ps.n] != '\0') { fail(p, keyword2_error::syntax, ps.n); return p; } p.tree.root = ps.parse_alt(); if (p.error == keyword2_error::ok && ps.i != ps.n) fail(p, keyword2_error::syntax, ps.i); if (p.error != keyword2_error::ok) return p; annotate(p, p.tree.root); if (p.error != keyword2_error::ok) return p; if (p.tree.nodes[p.tree.root].max_len > kw2_len_cap) { fail(p, keyword2_error::length, 0); return p; } p.lang = p.tree.nodes[p.tree.root].lang; if (p.lang.bad || p.lang.zero()) { fail(p, keyword2_error::width, 0); return p; } if (p.lang.all) p.bits = 256; else if (p.lang.one()) p.bits = 1; else p.bits = static_cast(bit_length_u(dec_u(p.lang.v))); if (p.bits < 1) p.bits = 1; if (p.bits > 256) { fail(p, keyword2_error::width, 0); return p; } std::uint64_t none[4]{}; check_delim(p, p.tree.root, none); if (p.error != keyword2_error::ok) return p; check_ambiguous(p); return p; } template struct keyword2_info { static constexpr program compiled = compile_pattern(Pattern); static constexpr std::size_t bits = compiled.bits < 1 ? 1 : compiled.bits; }; template constexpr auto pack_rank(u256 r) { using integral = utils::nonvoid_integral_type_from_bitlength_t; if constexpr (Bits <= 64) return static_cast(r.w[0]); else if constexpr (Bits <= 128) return static_cast( (static_cast(r.w[1]) << 64) | r.w[0]); else return uint256_t{uint128_t{r.w[3], r.w[2]}, uint128_t{r.w[1], r.w[0]}}; } template constexpr u256 unpack_rank(Integral v) { u256 r; if constexpr (Bits <= 64) r.w[0] = static_cast(v); else if constexpr (Bits <= 128) { auto x = static_cast(v); r.w[0] = static_cast(x); r.w[1] = static_cast(x >> 64); } else { uint128_t lo = static_cast(v); uint256_t hi = v >> 128; uint128_t ho = static_cast(hi); r.w[0] = static_cast(lo); r.w[1] = static_cast(static_cast(lo >> 64)); r.w[2] = static_cast(ho); r.w[3] = static_cast(static_cast(ho >> 64)); } return r; } } // namespace detail /// @brief Ranked keyword whose language is the pattern `Pattern`. /// @note A domain. It stores a `modint` rank. It is not a leaf type. /// @tparam Pattern null-terminated pattern with static storage. /// @see dpf::keyword /// @see dpf::modint /// `pad('0')[0-9]{0,4}` is a decimal of at most 4 digits. /// `[0-9a-f]{8}` is exactly 8 significant hex digits. template class keyword2 : public modint::bits> { using info = detail::keyword2_info; static constexpr detail::program const & prog = info::compiled; static_assert(prog.error != keyword2_error::syntax, "keyword2: syntax error in pattern"); static_assert(prog.error != keyword2_error::delim, "keyword2: pad field or variable repetition is not delimited"); static_assert(prog.error != keyword2_error::overlap, "keyword2: pattern is ambiguous"); static_assert(prog.error != keyword2_error::states, "keyword2: pattern exceeds the 64-state cap"); static_assert(prog.error != keyword2_error::length, "keyword2: pattern accepts a string longer than 64"); static_assert(prog.error != keyword2_error::width, "keyword2: language does not fit in 256 bits"); static_assert(prog.error == keyword2_error::ok, "keyword2: invalid pattern"); public: using parent = modint; using integral_type = typename parent::integral_type; static constexpr std::size_t bits = info::bits; constexpr keyword2() noexcept = default; constexpr keyword2(const keyword2 &) noexcept = default; constexpr keyword2(keyword2 &&) noexcept = default; constexpr keyword2(std::string_view str) : parent(encode_(str)) { } HEDLEY_NON_NULL(1) constexpr keyword2(const char * str) : keyword2(std::string_view(str)) { } constexpr keyword2 & operator=(std::string_view str) { parent::operator=(encode_(str)); return *this; } constexpr keyword2 & operator=(const keyword2 &) noexcept = default; constexpr keyword2 & operator=(keyword2 &&) noexcept = default; ~keyword2() noexcept = default; /// @brief Rank, including values outside the language that fill the bit width. /// @param rank the `rank` /// @return Rank, including values outside the language that fill the bit width static constexpr keyword2 from_rank(integral_type rank) noexcept { return keyword2{rank}; } constexpr integral_type rank() const noexcept { return parent::reduced_value(); } /// @brief Bitwise complement of the rank, in the `2^bits` domain. /// @return Bitwise complement of the rank, in the `2^bits` domain constexpr keyword2 operator~() const noexcept { return keyword2{parent::operator~()}; } operator std::string() const { char buf[detail::kw2_len_cap]; int n = detail::unrank_root(prog, detail::unpack_rank(parent::reduced_value()), buf); if (n < 0) throw std::domain_error("keyword2: rank is outside the language"); return std::string(buf, buf + n); } private: constexpr explicit keyword2(integral_type rank) noexcept : parent(rank) { } constexpr keyword2(parent val) noexcept : parent(val) { } static constexpr integral_type encode_(std::string_view str) { detail::hit h = detail::match_full(prog, str.data(), str.size()); utils::constexpr_maybe_throw(!h.ok, "keyword2: string is not in the pattern language"); return detail::pack_rank(h.rank); } friend std::ostream & operator<<(std::ostream & os, const keyword2 & k) { return os << static_cast(k); } friend std::istream & operator>>(std::istream & is, keyword2 & k) { std::string tmp; if (!(is >> tmp)) return is; try { k = keyword2(std::string_view(tmp)); } catch (const std::exception &) { is.setstate(std::ios::failbit); } return is; } friend struct utils::countl_zero_symmetric_difference; friend struct utils::msb_of; friend struct utils::mod_pow_2; friend struct utils::make_from_integral_value; }; /// @brief Compile diagnostic for a pattern that should not become a type. /// @tparam Pattern null-terminated pattern with static storage /// @return Compile diagnostic for a pattern that should not become a type template constexpr keyword2_error keyword2_status() noexcept { return detail::keyword2_info::compiled.error; } template std::string to_string(const keyword2 & str) { return static_cast(str); } namespace utils { template struct bitlength_of> : std::integral_constant::bits> { }; template struct msb_of> { using T = dpf::keyword2; using U = typename T::integral_type; static constexpr T value = T::from_rank(U{1} << bitlength_of_v - 1ul); }; template struct countl_zero_symmetric_difference> : countl_zero_symmetric_difference::parent> { }; template struct mod_pow_2> : mod_pow_2::parent> { }; template struct make_from_integral_value> { using T = dpf::keyword2; using integral_type = integral_type_from_bitlength_t>; constexpr T operator()(integral_type val) const noexcept { return T::from_rank(val); } }; } // namespace utils } // namespace dpf namespace std { template class numeric_limits> { public: using keyword_type = dpf::keyword2; static constexpr bool is_specialized = true; static constexpr bool is_signed = false; static constexpr bool is_integer = true; static constexpr bool is_exact = true; static constexpr bool has_infinity = false; static constexpr bool has_quiet_NaN = false; static constexpr bool has_signaling_NaN = false; static constexpr std::float_denorm_style has_denorm = std::denorm_absent; static constexpr bool has_denorm_loss = false; static constexpr std::float_round_style round_style = std::round_toward_zero; static constexpr bool is_iec559 = false; static constexpr bool is_bounded = true; static constexpr bool is_modulo = true; static constexpr int digits = keyword_type::bits; static constexpr int digits10 = static_cast( (static_cast(keyword_type::bits) * 30103ull) / 100000ull); static constexpr int max_digits10 = 0; static constexpr int radix = 2; static constexpr int min_exponent = 0; static constexpr int max_exponent = 0; static constexpr int min_exponent10 = 0; static constexpr int max_exponent10 = 0; static constexpr bool traps = std::numeric_limits::traps; static constexpr bool tinyness_before = false; static constexpr keyword_type min() noexcept { return keyword_type::from_rank(0); } static constexpr keyword_type lowest() noexcept { return min(); } static constexpr keyword_type max() noexcept { constexpr auto & prog = dpf::detail::keyword2_info::compiled; if (prog.lang.all) return keyword_type::from_rank(~typename keyword_type::integral_type{0}); return keyword_type::from_rank( dpf::detail::pack_rank(dpf::detail::dec_u(prog.lang.v))); } static constexpr keyword_type epsilon() noexcept { return keyword_type::from_rank(0); } static constexpr keyword_type round_error() noexcept { return keyword_type::from_rank(0); } static constexpr keyword_type infinity() noexcept { return keyword_type::from_rank(0); } static constexpr keyword_type quiet_NaN() noexcept { return keyword_type::from_rank(0); } static constexpr keyword_type signaling_NaN() noexcept { return keyword_type::from_rank(0); } static constexpr keyword_type denorm_min() noexcept { return keyword_type::from_rank(0); } }; template class numeric_limits const> : public numeric_limits> { }; template class numeric_limits volatile> : public numeric_limits> { }; template class numeric_limits const volatile> : public numeric_limits> { }; } // namespace std #endif // LIBDPF_INCLUDE_DPF_KEYWORD2_HPP__