1914 lines
58 KiB
C++
1914 lines
58 KiB
C++
/// @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 <cstddef>
|
|
#include <cstdint>
|
|
#include <istream>
|
|
#include <limits>
|
|
#include <ostream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <type_traits>
|
|
|
|
#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<unsigned __int128>(b.w[i]) + borrow;
|
|
if (aa >= bb)
|
|
{
|
|
r.w[i] = static_cast<std::uint64_t>(aa - bb);
|
|
borrow = 0;
|
|
}
|
|
else
|
|
{
|
|
r.w[i] = static_cast<std::uint64_t>(
|
|
(aa + (static_cast<unsigned __int128>(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<unsigned __int128>(a.v.w[i])
|
|
+ b.v.w[i] + carry;
|
|
r.v.w[i] = static_cast<std::uint64_t>(s);
|
|
carry = static_cast<std::uint64_t>(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<unsigned __int128>(a.v.w[i]) * b.v.w[j] + carry;
|
|
out[i + j] = static_cast<std::uint64_t>(cur);
|
|
carry = cur >> 64;
|
|
}
|
|
out[i + 4] = static_cast<std::uint64_t>(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<unsigned __int128>(d.w[i]) + borrow;
|
|
if (aa >= bb)
|
|
{
|
|
rw[i] = static_cast<std::uint64_t>(aa - bb);
|
|
borrow = 0;
|
|
}
|
|
else
|
|
{
|
|
rw[i] = static_cast<std::uint64_t>(
|
|
(aa + (static_cast<unsigned __int128>(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<int>(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<unsigned char>(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<unsigned char>((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<unsigned char>(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<unsigned>(peek() - '0');
|
|
++i;
|
|
if (v > kw2_len_cap)
|
|
{
|
|
fail(*p, keyword2_error::length, i);
|
|
return 0;
|
|
}
|
|
}
|
|
return static_cast<std::uint16_t>(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<unsigned char>(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<unsigned char>(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<unsigned char>(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<std::uint16_t>(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<unsigned char>(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<std::uint16_t>(kw2_len_cap + 1)
|
|
: static_cast<std::uint16_t>(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<std::uint64_t>(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<std::uint16_t>(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<std::uint32_t>(nd.minv) * u.min_len);
|
|
saturate_len(nd.max_len, static_cast<std::uint32_t>(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<int>(nd.n) - 1; c >= 0; --c)
|
|
{
|
|
std::uint16_t cid = p.tree.kids[nd.a + static_cast<std::uint16_t>(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<std::uint32_t>(a) * cap + b;
|
|
seen[bit / 8] = static_cast<unsigned char>(seen[bit / 8] | (1u << (bit % 8)));
|
|
};
|
|
auto test = [&](std::uint16_t a, std::uint16_t b) {
|
|
std::uint32_t bit = static_cast<std::uint32_t>(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<int>(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<int>(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<unsigned char>(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<unsigned char>(c.s[pos])))
|
|
{
|
|
u256 piece{};
|
|
unsigned char ch = static_cast<unsigned char>(c.s[pos]);
|
|
piece.w[0] = nd.k == node_kind::dot
|
|
? ch : static_cast<std::uint64_t>(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<unsigned char>(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<unsigned char>(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<std::uint64_t>(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<int>(nd.maxv); k >= static_cast<int>(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<std::uint16_t>(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<std::uint16_t>(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);
|
|
}
|
|
|
|
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<char>(nd.ch);
|
|
return n;
|
|
case node_kind::cls:
|
|
case node_kind::dot:
|
|
out[n++] = static_cast<char>(nd.k == node_kind::dot
|
|
? static_cast<unsigned char>(rank.w[0])
|
|
: bit_at_index(nd.bits, static_cast<int>(rank.w[0])));
|
|
return n;
|
|
case node_kind::pad:
|
|
{
|
|
unsigned char digits[kw2_len_cap]{};
|
|
u256 cur = rank;
|
|
for (int i = static_cast<int>(nd.maxv) - 1; i >= 0; --i)
|
|
{
|
|
u256 q, r;
|
|
divmod_u(cur, card_u64(nd.radix).v, q, r);
|
|
digits[i] = static_cast<unsigned char>(r.w[0]);
|
|
cur = q;
|
|
}
|
|
int start = 0;
|
|
while (start < static_cast<int>(nd.maxv) - static_cast<int>(nd.minv)
|
|
&& digits[start] == 0) ++start;
|
|
for (int i = start; i < static_cast<int>(nd.maxv); ++i)
|
|
out[n++] = static_cast<char>(nd.digs[digits[i]]);
|
|
return n;
|
|
}
|
|
case node_kind::cat:
|
|
{
|
|
card suf = card_one();
|
|
card place[kw2_nodes]{};
|
|
for (int c = static_cast<int>(nd.n) - 1; c >= 0; --c)
|
|
{
|
|
place[c] = suf;
|
|
suf = mul_card(suf, p.tree.nodes[p.tree.kids[nd.a + static_cast<std::uint16_t>(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<int>(nd.minv);
|
|
for (; k <= static_cast<int>(nd.maxv); ++k)
|
|
{
|
|
if (cmp_card_u(rem, pow) < 0) break;
|
|
rem = sub_u(rem, pow.v);
|
|
if (k == static_cast<int>(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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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<std::uint16_t>(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 <const char * Pattern>
|
|
struct keyword2_info
|
|
{
|
|
static constexpr program compiled = compile_pattern(Pattern);
|
|
static constexpr std::size_t bits = compiled.bits < 1 ? 1 : compiled.bits;
|
|
};
|
|
|
|
template <std::size_t Bits>
|
|
constexpr auto pack_rank(u256 r)
|
|
{
|
|
using integral = utils::nonvoid_integral_type_from_bitlength_t<Bits>;
|
|
if constexpr (Bits <= 64) return static_cast<integral>(r.w[0]);
|
|
else if constexpr (Bits <= 128)
|
|
return static_cast<integral>(
|
|
(static_cast<unsigned __int128>(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 <std::size_t Bits, typename Integral>
|
|
constexpr u256 unpack_rank(Integral v)
|
|
{
|
|
u256 r;
|
|
if constexpr (Bits <= 64) r.w[0] = static_cast<std::uint64_t>(v);
|
|
else if constexpr (Bits <= 128)
|
|
{
|
|
auto x = static_cast<unsigned __int128>(v);
|
|
r.w[0] = static_cast<std::uint64_t>(x);
|
|
r.w[1] = static_cast<std::uint64_t>(x >> 64);
|
|
}
|
|
else
|
|
{
|
|
uint128_t lo = static_cast<uint128_t>(v);
|
|
uint256_t hi = v >> 128;
|
|
uint128_t ho = static_cast<uint128_t>(hi);
|
|
r.w[0] = static_cast<std::uint64_t>(lo);
|
|
r.w[1] = static_cast<std::uint64_t>(static_cast<uint128_t>(lo >> 64));
|
|
r.w[2] = static_cast<std::uint64_t>(ho);
|
|
r.w[3] = static_cast<std::uint64_t>(static_cast<uint128_t>(ho >> 64));
|
|
}
|
|
return r;
|
|
}
|
|
|
|
} // namespace detail
|
|
|
|
/// @brief Ranked keyword whose language is the pattern `Pattern`.
|
|
/// @tparam Pattern null-terminated pattern with static storage.
|
|
/// `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 <const char * Pattern>
|
|
class keyword2
|
|
: public modint<detail::keyword2_info<Pattern>::bits>
|
|
{
|
|
using info = detail::keyword2_info<Pattern>;
|
|
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<info::bits>;
|
|
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)) { }
|
|
|
|
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() = default;
|
|
|
|
/// @brief 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.
|
|
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<bits>(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<std::domain_error>(!h.ok,
|
|
"keyword2: string is not in the pattern language");
|
|
return detail::pack_rank<bits>(h.rank);
|
|
}
|
|
|
|
friend std::ostream & operator<<(std::ostream & os, const keyword2 & k)
|
|
{
|
|
return os << static_cast<std::string>(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<keyword2>;
|
|
friend struct utils::msb_of<keyword2>;
|
|
friend struct utils::mod_pow_2<keyword2>;
|
|
friend struct utils::make_from_integral_value<keyword2>;
|
|
};
|
|
|
|
/// @brief Compile diagnostic for a pattern that should not become a type.
|
|
template <const char * Pattern>
|
|
constexpr keyword2_error keyword2_status() noexcept
|
|
{
|
|
return detail::keyword2_info<Pattern>::compiled.error;
|
|
}
|
|
|
|
template <const char * Pattern>
|
|
std::string to_string(const keyword2<Pattern> & str)
|
|
{
|
|
return static_cast<std::string>(str);
|
|
}
|
|
|
|
namespace utils
|
|
{
|
|
|
|
template <const char * Pattern>
|
|
struct bitlength_of<dpf::keyword2<Pattern>>
|
|
: std::integral_constant<std::size_t, dpf::keyword2<Pattern>::bits> { };
|
|
|
|
template <const char * Pattern>
|
|
struct msb_of<dpf::keyword2<Pattern>>
|
|
{
|
|
using T = dpf::keyword2<Pattern>;
|
|
using U = typename T::integral_type;
|
|
static constexpr T value = T::from_rank(U{1} << bitlength_of_v<T> - 1ul);
|
|
};
|
|
|
|
template <const char * Pattern>
|
|
struct countl_zero_symmetric_difference<dpf::keyword2<Pattern>>
|
|
: countl_zero_symmetric_difference<typename dpf::keyword2<Pattern>::parent> { };
|
|
|
|
template <const char * Pattern>
|
|
struct mod_pow_2<dpf::keyword2<Pattern>>
|
|
: mod_pow_2<typename dpf::keyword2<Pattern>::parent> { };
|
|
|
|
template <const char * Pattern>
|
|
struct make_from_integral_value<dpf::keyword2<Pattern>>
|
|
{
|
|
using T = dpf::keyword2<Pattern>;
|
|
using integral_type = integral_type_from_bitlength_t<bitlength_of_v<T>>;
|
|
constexpr T operator()(integral_type val) const noexcept
|
|
{
|
|
return T::from_rank(val);
|
|
}
|
|
};
|
|
|
|
} // namespace utils
|
|
|
|
} // namespace dpf
|
|
|
|
namespace std
|
|
{
|
|
|
|
template <const char * Pattern>
|
|
class numeric_limits<dpf::keyword2<Pattern>>
|
|
{
|
|
public:
|
|
using keyword_type = dpf::keyword2<Pattern>;
|
|
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<int>(
|
|
(static_cast<unsigned long long>(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<typename keyword_type::integral_type>::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<Pattern>::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<keyword_type::bits>(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 <const char * Pattern>
|
|
class numeric_limits<dpf::keyword2<Pattern> const>
|
|
: public numeric_limits<dpf::keyword2<Pattern>> { };
|
|
|
|
template <const char * Pattern>
|
|
class numeric_limits<dpf::keyword2<Pattern> volatile>
|
|
: public numeric_limits<dpf::keyword2<Pattern>> { };
|
|
|
|
template <const char * Pattern>
|
|
class numeric_limits<dpf::keyword2<Pattern> const volatile>
|
|
: public numeric_limits<dpf::keyword2<Pattern>> { };
|
|
|
|
} // namespace std
|
|
|
|
#endif // LIBDPF_INCLUDE_DPF_KEYWORD2_HPP__
|