#include #include #include #include #include #include #include #include #include #include #include "dpf.hpp" namespace { static constexpr char dec4[] = "pad('0')[0-9]{0,4}"; static constexpr char dec12[] = "pad('0')[0-9]{1,2}"; static constexpr char letters[] = "pad('\\0')[a-z]{0,3}"; static constexpr char hex3[] = "pad('0')[0-9a-f]{0,3}"; static constexpr char hexfixed[] = "[0-9a-f]{3}"; static constexpr char digits4[] = "[0-9]{4}"; static constexpr char fields[] = "pad('0')[0-9]{0,2}-pad('0')[0-9]{0,3}"; static constexpr char words[] = "[ab]{1,2}"; static constexpr char either[] = "cat|car"; static constexpr char holes[] = "pad('0')[013]{0,2}"; static constexpr char overlap[] = "a|a"; static constexpr char ambiguous[] = "(a|ab)(b|)"; static constexpr char adjacent[] = "pad('0')[0-9]{0,2}pad('0')[0-9]{0,2}"; static constexpr char star[] = "a*"; static constexpr char too_long[] = "[a-z]{65}"; static constexpr char undelimited[] = "[a-z]{1,3}[a-z]"; static constexpr char empty_pat[] = ""; static constexpr char group_pat[] = "()"; static constexpr char a_empty[] = "a()"; static constexpr char empty_a[] = "()a"; static constexpr char a_empty_b[] = "a()b"; static constexpr char padding[] = "padding"; static constexpr char lit_star[] = "[*]"; static constexpr char lit_plus[] = "[+]"; static constexpr char esc_star[] = "\\*"; static constexpr char esc_plus[] = "\\+"; static constexpr char lit_dot[] = "\\."; static constexpr char lit_q[] = "\\?"; static constexpr char lit_lp[] = "\\("; static constexpr char lit_rp[] = "\\)"; static constexpr char lit_bar[] = "\\|"; static constexpr char lit_lb[] = "\\["; static constexpr char lit_rb[] = "\\]"; static constexpr char lit_lc[] = "\\{"; static constexpr char lit_rc[] = "\\}"; static constexpr char lit_bs[] = "\\\\"; static constexpr char lit_n[] = "\\n"; static constexpr char lit_t[] = "\\t"; static constexpr char lit_r[] = "\\r"; static constexpr char lit_A[] = "\\x41"; static constexpr char lit_nul[] = "\\x00"; static constexpr char lit_sq[] = "\\'"; static constexpr char lit_dq[] = "\\\""; static constexpr char lit_dash[] = "\\-"; static constexpr char esc_cat[] = "\\n\\t\\x41"; static constexpr char cba[] = "[cba]"; static constexpr char dupcls[] = "[aab]"; static constexpr char dashcls[] = "[a\\-c]"; static constexpr char endash[] = "[a-]"; static constexpr char bracketcls[] = "[\\]a]"; static constexpr char range_esc[] = "[\\x01-\\x03]"; static constexpr char dashrange[] = "[--a]"; static constexpr char highcls[] = "[\\x7f-\\x81]"; static constexpr char negab[] = "[^ab]"; static constexpr char dot1[] = "."; static constexpr char dot2[] = ".{2}"; static constexpr char dotq[] = ".?"; static constexpr char adot[] = "a."; static constexpr char src_alt[] = "c|a|b"; static constexpr char a_ab[] = "a|ab"; static constexpr char ab_a[] = "ab|a"; static constexpr char mid_empty[] = "a||b"; static constexpr char lead_empty[] = "|a"; static constexpr char trail_empty[] = "a|"; static constexpr char nested_alt[] = "(a|bb)(c|dd)"; static constexpr char blocks[] = "(a|b)(c|d)"; static constexpr char nest_paren[] = "((((a|b))))"; static constexpr char opt2[] = "a?b?"; static constexpr char opt7[] = "a?b?c?d?e?f?g?"; static constexpr char alt_rep[] = "(a|b){2}"; static constexpr char alt_opt[] = "(a|b)?"; static constexpr char alt_pow[] = "(c|a|b){2}"; static constexpr char grp_rep[] = "(ab){0,2}"; static constexpr char grp_opt[] = "(ab)?"; static constexpr char a_rep[] = "a{2,4}"; static constexpr char a01[] = "a{01}"; static constexpr char a00[] = "a{0,0}"; static constexpr char pad0[] = "pad('0')[0-9]{0}"; static constexpr char a63[] = "a{63}"; static constexpr char a0_63[] = "a{0,63}"; static constexpr char cls_pow8[] = "[ab]{8}"; static constexpr char cls_pow9[] = "[ab]{9}"; static constexpr char cls_cat[] = "[ab][cd]"; static constexpr char xcls[] = "x[ab]"; static constexpr char three[] = "[ab]c[de]"; static constexpr char cls2[] = "[ab]{2}[ab]"; static constexpr char repx[] = "[ab]{0,2}x"; static constexpr char varlen[] = "[ab]{1,3}"; static constexpr char grp2[] = "(a[bc]){1,2}"; static constexpr char abx[] = "(ab|cd){1,2}x"; static constexpr char pad_order[] = "pad('0')[a-f0-9]{0,1}"; static constexpr char pad_x[] = "pad('x')[ab]{0,2}"; static constexpr char pad_sp[] = "pad(' ')[ab]{0,2}"; static constexpr char pad_fix[] = "pad('0')[0-9]{2}"; static constexpr char pad_fix_x[] = "pad('0')[0-9]{2}x"; static constexpr char two_fields[] = "pad('0')[0-9]{0,1}xpad('0')[0-9]{0,1}"; static constexpr char pad_quote[] = "pad('\\'')[ab]{0,2}"; static constexpr char pad_nl[] = "pad('\\n')[ab]{0,2}"; static constexpr char pad_hex[] = "pad('\\x2a')[ab]{0,2}"; static constexpr char space_pat[] = "a b"; static constexpr char alts53[] = "!|#|$|%|&|,|-|/|0|1|2|3|4|5|6|7|8|9|:|;|<|=|>|@|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|^|_|`"; static constexpr char dots32[] = ".{32}"; static constexpr char a64[] = "a{64}"; static constexpr char a0_64[] = "a{0,64}"; static constexpr char a64b[] = "a{64}b"; static constexpr char ab32[] = "(ab){32}"; static constexpr char ab33[] = "(ab){33}"; static constexpr char alts54[] = "!|#|$|%|&|,|-|/|0|1|2|3|4|5|6|7|8|9|:|;|<|=|>|@|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|^|_|`|a"; static constexpr char dots32ab[] = ".{32}[ab]"; static constexpr char dots33[] = ".{33}"; static constexpr char dot65[] = ".{65}"; static constexpr char qempty[] = "()?"; static constexpr char deepq[] = "(a?)?"; static constexpr char both_empty[] = "(|)"; static constexpr char justbar[] = "|"; static constexpr char dotora[] = ".|a"; static constexpr char classora[] = "[ab]|b"; static constexpr char aq[] = "a?|a"; static constexpr char padfixd[] = "pad('0')[0-9]{2}[0-9]"; static constexpr char varfollow[] = "[ab]{0,1}a"; static constexpr char repnull[] = "(a?){1,2}"; static constexpr char altnull[] = "(a|){1,2}"; static constexpr char quantpad[] = "pad('0')[0-9]{1}{1}"; static constexpr char padneg[] = "pad('a')[^b]{0,1}"; static constexpr char lohi[] = "a{2,1}"; static constexpr char unbounded[] = "a{1,}"; static constexpr char fullneg[] = "[^\\x00-\\xff]"; static constexpr char plus[] = "a+"; static constexpr char emptycls[] = "[]"; static constexpr char negempty[] = "[^]"; static constexpr char inv[] = "[z-a]"; static constexpr char inv_esc[] = "[\\x03-\\x01]"; static constexpr char badesc[] = "\\q"; static constexpr char badhex[] = "\\xGG"; static constexpr char shorthex[] = "\\x1"; static constexpr char openp[] = "(a"; static constexpr char closep[] = "a)"; static constexpr char openb[] = "[a-z"; static constexpr char openq[] = "a{1"; static constexpr char spaceq[] = "a{1, 2}"; static constexpr char padopen[] = "pad("; static constexpr char padempty[] = "pad('')[a]{1}"; static constexpr char padnoq[] = "pad('a')[a]"; static constexpr char padnoc[] = "pad('a')"; static constexpr char aqq[] = "a??"; #define KW2_A8 "aaaaaaaa" #define KW2_A64 KW2_A8 KW2_A8 KW2_A8 KW2_A8 KW2_A8 KW2_A8 KW2_A8 KW2_A8 #define KW2_A512 KW2_A64 KW2_A64 KW2_A64 KW2_A64 KW2_A64 KW2_A64 KW2_A64 KW2_A64 static constexpr char lits63[] = KW2_A8 KW2_A8 KW2_A8 KW2_A8 KW2_A8 KW2_A8 KW2_A8 "aaaaaaa"; static constexpr char lits64[] = KW2_A64; static constexpr char long_pat[] = KW2_A512 KW2_A512; #undef KW2_A512 #undef KW2_A64 #undef KW2_A8 std::size_t bits_for_cardinality(std::uint64_t n) { if (n <= 1) return 1; std::size_t bits = 0; for (auto v = n - 1; v != 0; v >>= 1) ++bits; return bits; } std::vector class_bytes(std::initializer_list chars) { std::vector ordered(chars); std::sort(ordered.begin(), ordered.end()); ordered.erase(std::unique(ordered.begin(), ordered.end()), ordered.end()); std::vector out; for (unsigned char c : ordered) out.emplace_back(1, static_cast(c)); return out; } std::vector byte_span(unsigned lo, unsigned hi) { std::vector out; for (unsigned c = lo; c <= hi; ++c) out.emplace_back(1, static_cast(c)); return out; } std::vector negated_bytes(std::initializer_list excluded) { bool drop[256] = {}; for (unsigned char c : excluded) drop[c] = true; std::vector out; for (int c = 0; c < 256; ++c) if (!drop[c]) out.emplace_back(1, static_cast(c)); return out; } std::vector concat_lang(const std::vector & left, const std::vector & right) { std::vector out; out.reserve(left.size() * right.size()); for (const auto & l : left) for (const auto & r : right) out.push_back(l + r); return out; } std::vector block_lang(std::vector> parts) { std::vector out; for (auto & part : parts) out.insert(out.end(), part.begin(), part.end()); return out; } std::vector repeat_lang(const std::vector & unit, int lo, int hi) { std::vector acc; std::vector level{""}; for (int k = 0; k <= hi; ++k) { if (k >= lo) acc.insert(acc.end(), level.begin(), level.end()); if (k == hi) break; std::vector next; next.reserve(level.size() * unit.size()); for (const auto & prefix : level) for (const auto & piece : unit) next.push_back(prefix + piece); level.swap(next); } return acc; } std::vector pad_language(std::string digits, int lo, int hi) { const int radix = static_cast(digits.size()); int count = 1; for (int i = 0; i < hi; ++i) count *= radix; std::vector out; out.reserve(static_cast(count)); for (int value = 0; value < count; ++value) { std::string raw(static_cast(hi), digits[0]); int rest = value; for (int i = hi - 1; i >= 0; --i) { raw[static_cast(i)] = digits[static_cast(rest % radix)]; rest /= radix; } int start = 0; while (start < hi - lo && raw[static_cast(start)] == digits[0]) ++start; out.push_back(raw.substr(static_cast(start))); } return out; } std::vector optional_chain(std::string letters) { const int n = static_cast(letters.size()); std::vector out; const unsigned total = 1u << n; out.reserve(total); for (unsigned mask = 0; mask < total; ++mask) { std::string spelling; for (int i = 0; i < n; ++i) if ((mask & (1u << (n - 1 - i))) != 0) spelling.push_back(letters[static_cast(i)]); out.push_back(std::move(spelling)); } return out; } template void expect_language(const std::vector & canonical) { using kw = dpf::keyword2; ASSERT_EQ(dpf::keyword2_status(), dpf::keyword2_error::ok); const auto n = static_cast(std::numeric_limits::max().rank()) + 1; ASSERT_EQ(n, canonical.size()); ASSERT_EQ(kw::bits, bits_for_cardinality(n)); ASSERT_EQ(dpf::utils::bitlength_of_v, kw::bits); for (std::uint64_t r = 0; r < n; ++r) { const std::string & spelling = canonical[static_cast(r)]; kw spelled{std::string_view(spelling)}; kw ranked = kw::from_rank(static_cast(r)); EXPECT_EQ(spelled.rank(), r) << spelling; EXPECT_EQ(std::string(ranked), spelling) << r; EXPECT_EQ(spelled, ranked); EXPECT_EQ(dpf::to_string(ranked), spelling); if (r > 0) EXPECT_LT(kw::from_rank(static_cast(r - 1)), ranked); } if (!canonical.empty()) { EXPECT_EQ(std::string(std::numeric_limits::min()), canonical.front()); EXPECT_EQ(std::string(std::numeric_limits::max()), canonical.back()); EXPECT_EQ(std::numeric_limits::lowest(), std::numeric_limits::min()); } if (kw::bits < 63 && n < (std::uint64_t{1} << kw::bits)) { kw ghost = kw::from_rank(static_cast(n)); EXPECT_EQ(ghost.rank(), n); EXPECT_GT(ghost, std::numeric_limits::max()); EXPECT_THROW(static_cast(ghost), std::domain_error); } } template void expect_spellings(std::initializer_list spellings) { expect_language(std::vector(spellings.begin(), spellings.end())); } template void expect_only(std::string text) { expect_language(std::vector{std::move(text)}); } template void expect_status(dpf::keyword2_error error) { EXPECT_EQ(dpf::keyword2_status(), error); } template void expect_rejected(std::initializer_list bad) { using kw = dpf::keyword2; for (std::string_view spelling : bad) EXPECT_THROW(kw{spelling}, std::domain_error) << std::string(spelling); } template void roundtrip_language() { using kw = dpf::keyword2; const auto last = std::numeric_limits::max().rank(); const auto count = static_cast(last) + 1; ASSERT_LE(count, 200000u); for (std::uint64_t r = 0; r < count; ++r) { kw key = kw::from_rank(static_cast(r)); kw back{std::string(key)}; ASSERT_EQ(back.rank(), r) << r; } } } // namespace TEST(keyword2, decimal_pad_collapses_leading_zeros) { using kw = dpf::keyword2; EXPECT_EQ(kw::bits, 14u); EXPECT_EQ(kw{"7"}.rank(), 7u); EXPECT_EQ(kw{"007"}.rank(), 7u); EXPECT_EQ(kw{""}.rank(), 0u); EXPECT_EQ(kw{"0"}.rank(), 0u); EXPECT_EQ(std::string(kw{"007"}), "7"); EXPECT_EQ(std::string(std::numeric_limits::max()), "9999"); EXPECT_THROW(kw{"10000"}, std::domain_error); EXPECT_THROW(kw{"12a"}, std::domain_error); } TEST(keyword2, decimal_minimum_width_keeps_a_zero) { using kw = dpf::keyword2; EXPECT_EQ(std::string(kw{"0"}), "0"); EXPECT_EQ(kw{"00"}.rank(), 0u); EXPECT_EQ(std::string(kw{"00"}), "0"); EXPECT_THROW(kw{""}, std::domain_error); } TEST(keyword2, nul_pad_matches_legacy_letter_ranks) { using kw = dpf::keyword2; EXPECT_EQ(kw{"b"}.rank(), 2u); EXPECT_EQ(kw{"aa"}.rank(), 28u); EXPECT_EQ(kw{"cat"}.rank(), 2234u); EXPECT_LT(kw{"b"}.rank(), kw{"aa"}.rank()); EXPECT_EQ(std::string(kw{"cat"}), "cat"); EXPECT_EQ(kw{std::string_view("\0a", 2)}.rank(), kw{"a"}.rank()); } TEST(keyword2, hex_pad_versus_fixed_width) { using padded = dpf::keyword2; EXPECT_EQ(padded{"a"}.rank(), 10u); EXPECT_EQ(padded{"00a"}.rank(), 10u); EXPECT_EQ(std::string(padded{"00a"}), "a"); using fixed = dpf::keyword2; EXPECT_EQ(std::string(fixed{"00a"}), "00a"); EXPECT_THROW(fixed{"a"}, std::domain_error); } TEST(keyword2, fixed_digits_keep_leading_zeros) { using kw = dpf::keyword2; EXPECT_EQ(kw{"0007"}.rank(), 7u); EXPECT_EQ(std::string(kw{"0007"}), "0007"); EXPECT_THROW(kw{"7"}, std::domain_error); } TEST(keyword2, delimited_pad_fields_share_a_rank) { using kw = dpf::keyword2; EXPECT_EQ(kw{"7-8"}.rank(), 7008u); EXPECT_EQ(kw{"07-008"}.rank(), 7008u); EXPECT_EQ(std::string(kw{"07-008"}), "7-8"); } TEST(keyword2, variable_repeat_is_shortlex) { using kw = dpf::keyword2; EXPECT_EQ(std::string(kw::from_rank(0)), "a"); EXPECT_EQ(std::string(kw::from_rank(1)), "b"); EXPECT_EQ(std::string(kw::from_rank(2)), "aa"); EXPECT_LT(kw{"b"}.rank(), kw{"aa"}.rank()); } TEST(keyword2, alternatives_are_source_order_blocks) { using kw = dpf::keyword2; EXPECT_EQ(std::string(kw{"cat"}), "cat"); EXPECT_EQ(std::string(kw{"car"}), "car"); EXPECT_LT(kw{"cat"}.rank(), kw{"car"}.rank()); } TEST(keyword2, pad_digit_order_follows_the_class_text) { using kw = dpf::keyword2; EXPECT_EQ(kw{"3"}.rank(), 2u); EXPECT_EQ(std::string(kw{"3"}), "3"); EXPECT_EQ(kw{"13"}.rank(), 5u); } TEST(keyword2, rejected_patterns) { EXPECT_EQ(dpf::keyword2_status(), dpf::keyword2_error::overlap); EXPECT_EQ(dpf::keyword2_status(), dpf::keyword2_error::overlap); EXPECT_EQ(dpf::keyword2_status(), dpf::keyword2_error::delim); EXPECT_EQ(dpf::keyword2_status(), dpf::keyword2_error::delim); EXPECT_EQ(dpf::keyword2_status(), dpf::keyword2_error::syntax); EXPECT_EQ(dpf::keyword2_status(), dpf::keyword2_error::length); EXPECT_EQ(dpf::keyword2_status(), dpf::keyword2_error::ok); } TEST(keyword2, roundtrip_covers_every_rank) { roundtrip_language(); roundtrip_language(); roundtrip_language(); roundtrip_language(); roundtrip_language(); roundtrip_language(); roundtrip_language(); roundtrip_language(); roundtrip_language(); roundtrip_language(); } TEST(keyword2, point_query_treats_pad_spellings_as_one_key) { using kw = dpf::keyword2; auto [k0, k1] = dpf::make_dpf(kw{"7"}, dpf::bit{1}); auto hit = [&](kw point) { return dpf::reconstruct(*dpf::eval_point(k0, point), *dpf::eval_point(k1, point)) == dpf::bit::one; }; EXPECT_TRUE(hit(kw{"7"})); EXPECT_TRUE(hit(kw{"007"})); EXPECT_FALSE(hit(kw{"42"})); } TEST(keyword2, stream_extractor_reports_a_bad_token) { using kw = dpf::keyword2; std::stringstream in("007 12a"); kw good; ASSERT_TRUE(static_cast(in >> good)); EXPECT_EQ(std::string(good), "7"); kw bad; EXPECT_FALSE(static_cast(in >> bad)); } TEST(keyword2, literals_and_escapes_are_single_strings) { expect_only(""); expect_only(""); expect_only("a"); expect_only("a"); expect_only("ab"); expect_only("padding"); expect_only("*"); expect_only("+"); expect_only("."); expect_only("?"); expect_only("("); expect_only(")"); expect_only("|"); expect_only("["); expect_only("]"); expect_only("{"); expect_only("}"); expect_only("\\"); expect_only("\n"); expect_only("\t"); expect_only("\r"); expect_only("A"); expect_only(std::string(1, '\0')); expect_only("'"); expect_only("\""); expect_only("-"); expect_only("\n\tA"); expect_only("a"); expect_only(""); expect_only(""); expect_only("a b"); expect_rejected({"0", "00"}); expect_rejected({"a"}); expect_rejected({"a", "ab", "a b"}); } TEST(keyword2, classes_rank_by_byte_value) { using kw = dpf::keyword2; EXPECT_EQ(kw{"a"}.rank(), 0u); EXPECT_EQ(kw{"c"}.rank(), 2u); expect_language(class_bytes({'c', 'b', 'a'})); expect_language(class_bytes({'a', 'a', 'b'})); expect_language(class_bytes({'a', '-', 'c'})); expect_language(class_bytes({'a', '-'})); expect_language(class_bytes({']', 'a'})); expect_language(byte_span(1, 3)); expect_language(byte_span(static_cast('-'), static_cast('a'))); expect_language(byte_span(0x7f, 0x81)); expect_rejected({"", "ab", "d", "A"}); } TEST(keyword2, dot_and_negated_class_cover_bytes) { expect_language(byte_span(0, 255)); expect_language(negated_bytes({'a', 'b'})); expect_rejected({"a", "b", "", "ab"}); using dot = dpf::keyword2; char pair[] = {1, 2}; EXPECT_EQ(dot{std::string_view(pair, 2)}.rank(), 258u); char hi[] = {'\xff', '\xff'}; EXPECT_EQ(std::string(dot{std::string_view(hi, 2)}), std::string(hi, hi + 2)); EXPECT_EQ(dot::bits, 16u); expect_rejected({"", "a", std::string_view(pair, 1)}); std::vector optional{""}; for (int b = 0; b < 256; ++b) optional.emplace_back(1, static_cast(b)); expect_language(optional); using opt = dpf::keyword2; EXPECT_EQ(opt{std::string_view("\0", 1)}.rank(), 1u); EXPECT_EQ(std::string(opt::from_rank(256)).size(), 1u); EXPECT_EQ(static_cast(std::string(opt::from_rank(256))[0]), 255u); auto prefixed = concat_lang({"a"}, byte_span(0, 255)); expect_language(prefixed); } TEST(keyword2, concatenation_is_mixed_radix_and_alternation_is_source_order) { expect_spellings({"c", "a", "b"}); expect_spellings({"a", "ab"}); expect_spellings({"ab", "a"}); expect_spellings({"a", "", "b"}); expect_spellings({"", "a"}); expect_spellings({"a", ""}); expect_spellings({"ac", "add", "bbc", "bbdd"}); expect_language(concat_lang({"a", "b"}, {"c", "d"})); expect_spellings({"a", "b"}); expect_language(concat_lang({"a", "b"}, {"c", "d"})); expect_spellings({"xa", "xb"}); expect_spellings({"acd", "ace", "bcd", "bce"}); expect_language([&] { std::vector out; std::string token; for (char c : std::string(alts53)) { if (c == '|') { out.push_back(token); token.clear(); } else token.push_back(c); } out.push_back(token); return out; }()); using alt = dpf::keyword2; EXPECT_EQ(alt{"!"}.rank(), 0u); EXPECT_EQ(alt{"0"}.rank(), 8u); EXPECT_EQ(alt{"A"}.rank(), 24u); EXPECT_EQ(std::string(std::numeric_limits::max()), "`"); } TEST(keyword2, quantifiers_are_shortlex_and_left_to_right) { expect_language(optional_chain("ab")); expect_language(optional_chain("abcdefg")); using chain = dpf::keyword2; EXPECT_EQ(chain{""}.rank(), 0u); EXPECT_EQ(chain{"g"}.rank(), 1u); EXPECT_EQ(chain{"a"}.rank(), 64u); EXPECT_EQ(chain{"abcdefg"}.rank(), 127u); expect_language(repeat_lang({"a", "b"}, 2, 2)); expect_language(block_lang({{""}, {"a", "b"}})); expect_language(repeat_lang({"c", "a", "b"}, 2, 2)); expect_language(repeat_lang({"ab"}, 0, 2)); expect_spellings({"", "ab"}); expect_spellings({"aa", "aaa", "aaaa"}); expect_language(repeat_lang({"a", "b"}, 3, 3)); expect_language(concat_lang(repeat_lang({"a", "b"}, 0, 2), {"x"})); expect_language(repeat_lang({"a", "b"}, 1, 3)); expect_language(repeat_lang({"ab", "ac"}, 1, 2)); expect_language(concat_lang(repeat_lang({"ab", "cd"}, 1, 2), {"x"})); expect_language(repeat_lang({"a", "b"}, 8, 8)); expect_language(repeat_lang({"a", "b"}, 9, 9)); std::vector up_to_63; for (int n = 0; n <= 63; ++n) up_to_63.emplace_back(static_cast(n), 'a'); expect_language(up_to_63); expect_only(std::string(63, 'a')); expect_only(std::string(63, 'a')); expect_rejected({"", "a", "aaaaa"}); expect_rejected({"", "a", "aa", "xx"}); } TEST(keyword2, pad_fields_rank_as_integers_in_written_digit_order) { expect_language(pad_language("0abcdef123456789", 0, 1)); expect_language(pad_language("xab", 0, 2)); expect_language(pad_language(" ab", 0, 2)); expect_language(pad_language("0123456789", 2, 2)); expect_language(concat_lang(pad_language("0123456789", 2, 2), {"x"})); expect_language(concat_lang( concat_lang(pad_language("0123456789", 0, 1), {"x"}), pad_language("0123456789", 0, 1))); expect_language(pad_language("'ab", 0, 2)); expect_language(pad_language("\nab", 0, 2)); expect_language(pad_language("*ab", 0, 2)); using px = dpf::keyword2; EXPECT_EQ(px{"x"}, px{""}); EXPECT_EQ(px{"xx"}, px{""}); EXPECT_EQ(std::string(px{"xa"}), "a"); EXPECT_EQ(std::string(px{"ax"}), "ax"); EXPECT_EQ(px{"xa"}.rank(), 1u); using pq = dpf::keyword2; EXPECT_EQ(pq{"'"}, pq{""}); EXPECT_EQ(std::string(pq{"'a"}), "a"); using pn = dpf::keyword2; EXPECT_EQ(pn{std::string_view("\n", 1)}, pn{""}); EXPECT_EQ(std::string(pn{std::string_view("\na", 2)}), "a"); using ps = dpf::keyword2; EXPECT_EQ(ps{" "}, ps{""}); EXPECT_EQ(std::string(ps{" a"}), "a"); using pf = dpf::keyword2; EXPECT_EQ(std::string(pf{"07"}), "07"); EXPECT_EQ(pf{"07"}.rank(), 7u); expect_rejected({"7", "007", "a0"}); using dec = dpf::keyword2; for (int value = 0; value < 10000; ++value) { std::string body = std::to_string(value); std::string padded = std::string(4 - body.size(), '0') + body; std::string canonical = value == 0 ? std::string() : body; EXPECT_EQ(dec{padded}.rank(), static_cast(value)); EXPECT_EQ(std::string(dec{padded}), canonical); } expect_language(pad_language("013", 0, 2)); } TEST(keyword2, syntax_errors_name_the_broken_token) { static_assert(dpf::keyword2_error::ok == dpf::keyword2_error{0}); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); expect_status(dpf::keyword2_error::syntax); } TEST(keyword2, delimiter_and_overlap_errors) { expect_status(dpf::keyword2_error::delim); expect_status(dpf::keyword2_error::delim); expect_status(dpf::keyword2_error::delim); expect_status(dpf::keyword2_error::delim); expect_status(dpf::keyword2_error::delim); expect_status(dpf::keyword2_error::delim); expect_status(dpf::keyword2_error::delim); expect_status(dpf::keyword2_error::delim); expect_status(dpf::keyword2_error::overlap); expect_status(dpf::keyword2_error::overlap); expect_status(dpf::keyword2_error::overlap); expect_status(dpf::keyword2_error::overlap); expect_status(dpf::keyword2_error::overlap); expect_status(dpf::keyword2_error::overlap); expect_status(dpf::keyword2_error::overlap); } TEST(keyword2, length_state_and_width_caps) { expect_status(dpf::keyword2_error::states); expect_status(dpf::keyword2_error::states); expect_status(dpf::keyword2_error::states); expect_status(dpf::keyword2_error::states); expect_status(dpf::keyword2_error::states); expect_status(dpf::keyword2_error::length); expect_status(dpf::keyword2_error::length); expect_status(dpf::keyword2_error::length); expect_status(dpf::keyword2_error::length); expect_status(dpf::keyword2_error::width); expect_status(dpf::keyword2_error::width); using full = dpf::keyword2; EXPECT_EQ(dpf::keyword2_status(), dpf::keyword2_error::ok); EXPECT_EQ(full::bits, 256u); EXPECT_EQ(std::string(full::from_rank(0)), std::string(32, '\0')); char low[32] = {}; low[31] = 5; EXPECT_EQ(full{std::string_view(low, 32)}, full::from_rank(full::integral_type{5})); char high[32] = {}; high[0] = 1; EXPECT_GT(full{std::string_view(high, 32)}, full{std::string_view(low, 32)}); EXPECT_EQ(std::string(std::numeric_limits::max()), std::string(32, '\xff')); EXPECT_EQ(std::numeric_limits::digits, 256); expect_rejected({std::string(31, '\0'), std::string(33, 'a')}); } TEST(keyword2, values_compare_assign_and_print_as_spellings) { using kw = dpf::keyword2; kw original{"42"}; kw copy = original; kw moved = std::move(copy); EXPECT_EQ(std::string(moved), "42"); EXPECT_EQ(moved, original); kw assigned; EXPECT_EQ(assigned.rank(), 0u); EXPECT_FALSE(static_cast(assigned)); assigned = std::string_view{"007"}; EXPECT_EQ(std::string(assigned), "7"); EXPECT_TRUE(static_cast(assigned)); assigned = original; EXPECT_EQ(assigned, kw{"42"}); EXPECT_EQ(kw{"007"}, kw{"7"}); EXPECT_FALSE(kw{"007"} < kw{"7"}); EXPECT_LT(kw{"7"}, kw{"42"}); EXPECT_EQ(static_cast(kw{"42"}), 42u); kw held{"42"}; EXPECT_THROW(held = std::string_view{"12a"}, std::domain_error); EXPECT_EQ(std::string(held), "42"); std::ostringstream printed; printed << dpf::keyword2{"00a"}; EXPECT_EQ(printed.str(), "a"); std::ostringstream empty; empty << kw{""}; EXPECT_TRUE(empty.str().empty()); std::stringstream spaced(" 00a b zz"); dpf::keyword2 hex; ASSERT_TRUE(static_cast(spaced >> hex)); EXPECT_EQ(std::string(hex), "a"); ASSERT_TRUE(static_cast(spaced >> hex)); EXPECT_EQ(hex.rank(), 11u); dpf::keyword2 kept = hex; EXPECT_FALSE(static_cast(spaced >> kept)); EXPECT_EQ(kept, hex); std::stringstream retry("zz a"); dpf::keyword2 again{"b"}; EXPECT_FALSE(static_cast(retry >> again)); EXPECT_EQ(std::string(again), "b"); retry.clear(); ASSERT_TRUE(static_cast(retry >> again)); EXPECT_EQ(std::string(again), "a"); std::stringstream blank(" "); EXPECT_FALSE(static_cast(blank >> again)); EXPECT_EQ(std::string(again), "a"); using spaced_kw = dpf::keyword2; std::stringstream words_in; words_in << spaced_kw{"a b"}; EXPECT_EQ(words_in.str(), "a b"); spaced_kw unread{"a b"}; EXPECT_FALSE(static_cast(words_in >> unread)); EXPECT_EQ(std::string(unread), "a b"); using dot = dpf::keyword2; char raw[] = {0, 1}; EXPECT_EQ(dot{std::string_view(raw, 2)}.rank(), 1u); EXPECT_THROW(dot{"\0\1"}, std::domain_error); try { kw{"nope"}; FAIL() << "expected domain_error"; } catch (const std::domain_error & err) { EXPECT_NE(std::string(err.what()).find("keyword2"), std::string::npos); } try { static_cast(kw::from_rank(10000)); FAIL() << "expected domain_error"; } catch (const std::domain_error & err) { EXPECT_NE(std::string(err.what()).find("outside"), std::string::npos); } } TEST(keyword2, numeric_limits_and_bit_utilities_follow_the_rank) { using kw = dpf::keyword2; EXPECT_EQ(kw::bits, 3u); EXPECT_EQ(std::numeric_limits::digits, 3); EXPECT_EQ(std::numeric_limits::digits10, 0); EXPECT_EQ(std::numeric_limits::radix, 2); EXPECT_FALSE(std::numeric_limits::is_signed); EXPECT_TRUE(std::numeric_limits::is_integer); EXPECT_TRUE(std::numeric_limits::is_exact); EXPECT_TRUE(std::numeric_limits::is_bounded); EXPECT_TRUE(std::numeric_limits::is_modulo); EXPECT_FALSE(std::numeric_limits::has_infinity); EXPECT_EQ(std::numeric_limits::epsilon().rank(), 0u); EXPECT_EQ(std::numeric_limits::min(), kw{"a"}); EXPECT_EQ(std::string(std::numeric_limits::max()), "bb"); EXPECT_EQ(std::numeric_limits::digits, 3); EXPECT_EQ(std::numeric_limits::max(), std::numeric_limits::max()); EXPECT_EQ(std::numeric_limits::min(), kw{"a"}); using dec = dpf::keyword2; EXPECT_EQ(std::numeric_limits::digits, 14); EXPECT_EQ(std::numeric_limits::digits10, 4); EXPECT_EQ(std::string(dpf::utils::msb_of_v), "ba"); EXPECT_EQ(dpf::utils::make_default_v, kw{"b"}); EXPECT_EQ(dpf::utils::make_from_integral_value{}(4), kw{"ba"}); EXPECT_EQ(dpf::utils::to_integral_type{}(kw{"ba"}), 4u); EXPECT_EQ(dpf::utils::mod_pow_2{}(kw{"bb"}, 2), 1u); EXPECT_EQ(dpf::utils::countl_zero_symmetric_difference{}(kw{"a"}, kw{"b"}), 2u); EXPECT_EQ(dpf::utils::countl_zero_symmetric_difference{}(kw{"aa"}, kw{"aa"}), kw::bits); EXPECT_EQ((~kw{"a"}).rank(), 7u); EXPECT_EQ((~kw::from_rank(7)).rank(), 0u); kw cursor = std::numeric_limits::max(); ++cursor; EXPECT_EQ(cursor.rank(), 6u); EXPECT_THROW(static_cast(cursor), std::domain_error); cursor = kw::from_rank(7); cursor++; EXPECT_EQ(cursor.rank(), 0u); EXPECT_EQ(std::string(cursor), "a"); } TEST(keyword2, dpf_queries_follow_the_rank) { using kw = dpf::keyword2; kw target{"abababab"}; ASSERT_EQ(target.rank(), 0b01010101u); auto [k0, k1] = dpf::make_dpf(target, dpf::bit{1}); auto memo0 = dpf::make_basic_path_memoizer>(); auto memo1 = dpf::make_basic_path_memoizer>(); auto hit = [&](kw point, auto & m0, auto & m1) { return dpf::reconstruct(*dpf::eval_point(k0, point, m0), *dpf::eval_point(k1, point, m1)) == dpf::bit::one; }; auto plain0 = dpf::make_nonmemoizing_path_memoizer>(); auto plain1 = dpf::make_nonmemoizing_path_memoizer>(); for (unsigned rank = 0; rank < (1u << kw::bits); ++rank) { kw point = kw::from_rank(rank); EXPECT_EQ(hit(point, plain0, plain1), point == target) << rank; EXPECT_EQ(hit(point, memo0, memo1), point == target) << rank; } kw from = kw::from_rank(83); kw to = kw::from_rank(87); auto [buf0, iter0] = dpf::eval_interval(k0, from, to); auto [buf1, iter1] = dpf::eval_interval(k1, from, to); auto it0 = iter0.begin(); auto it1 = iter1.begin(); for (unsigned rank = 83; it0 != iter0.end(); ++rank, ++it0, ++it1) { bool on = static_cast(*it0) ^ static_cast(*it1); EXPECT_EQ(on, rank == target.rank()) << rank; } auto [full0, full0_iter] = dpf::eval_full(k0); auto [full1, full1_iter] = dpf::eval_full(k1); auto fit0 = full0_iter.begin(); auto fit1 = full1_iter.begin(); unsigned seen = 0; for (; fit0 != full0_iter.end(); ++seen, ++fit0, ++fit1) { bool on = static_cast(*fit0) ^ static_cast(*fit1); EXPECT_EQ(on, seen == target.rank()) << seen; } EXPECT_EQ(seen, 1u << kw::bits); } static_assert(dpf::keyword2{"c"}.rank() == 0); static_assert(dpf::keyword2{"a"}.rank() == 1); static_assert(dpf::keyword2{"c"} < dpf::keyword2{"b"}); static_assert(dpf::keyword2{"007"}.rank() == 7); static_assert(dpf::keyword2{""}.rank() == 0); static_assert(dpf::keyword2{"ab"}.rank() == 3); static_assert(dpf::keyword2_status() == dpf::keyword2_error::syntax); static_assert(dpf::keyword2_status() == dpf::keyword2_error::overlap); static_assert(dpf::keyword2_status() == dpf::keyword2_error::delim); static_assert(dpf::keyword2_status() == dpf::keyword2_error::states); static_assert(dpf::keyword2_status() == dpf::keyword2_error::length); static_assert(dpf::keyword2_status() == dpf::keyword2_error::width); static_assert(std::numeric_limits>::max().rank() == 5); static_assert(std::numeric_limits>::min().rank() == 0);