Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116"""Symbol mode: its alphabet is safe to post, and it round-trips and checks.
python3 python/test_symbols.py"""import hashlibimport mathimport randomimport sysimport unicodedatafrom pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))import sigilcode as Bimport sigilsymbols as SS
def check(name, S): def error(text, length): try: S.decode(text, length) return None except B.DecodeError as e: return e.kind
a = S.ALPHABET assert len(set(a)) == S.N == len(a) and S.N > 48, S.N for c in a: assert unicodedata.category(c)[0] in "SP", hex(ord(c)) # a symbol, not a letter or mark assert unicodedata.normalize("NFKC", c) == c, hex(ord(c)) # a service may normalize a post assert unicodedata.normalize("NFC", c) == c, hex(ord(c)) assert not B.ignored(c), hex(ord(c)) # decode must not skip it assert c not in B.ALPHABET, hex(ord(c)) # never mistaken for the main alphabet assert ord(c) not in range(0xFE00, 0xFE10) and ord(c) != 0x200D print(f"{name}: {S.N} symbols, each one code point, stable under NFC and NFKC, none ignorable")
rng = random.Random(384) for length in (1, 2, 16, 20, 32, 48, 64, 100): n, k = S.layout(length) assert k > S.N and B.is_prime(k) for _ in range(50): data = rng.randbytes(length) text = S.encode(data) assert len(text) == n and S.decode(text, length) == data # a service may append text or emoji selectors, or break lines decorated = "\ufe0f".join(text) + "\n" assert S.decode(decorated, length) == data h = hashlib.sha256(b"sigil").digest() text = S.encode(h) print(f"{name}: sha256(sigil) as symbols:", text, f"({len(text)} characters)")
# every changed character and every swap of neighbours fails the check for _ in range(200): data = rng.randbytes(32) text = list(S.encode(data)) i = rng.randrange(len(text)) other = rng.choice([c for c in S.ALPHABET if c != text[i]]) bad = text[:] bad[i] = other assert error("".join(bad), 32) in ("check", "range") j = rng.randrange(len(text) - 1) if text[j] != text[j + 1]: sw = text[:] sw[j], sw[j + 1] = sw[j + 1], sw[j] assert error("".join(sw), 32) in ("check", "range") assert error(S.encode(b"\x01" * 32)[:-1], 32) == "length" assert error("a" * len(S.encode(b"\x01" * 32)), 32) == "character" data = rng.randbytes(32) text = S.encode(data) readings = [[c] for c in text] readings[5] = list(S.ALPHABET) # one character unread: all are tried, one decodes assert S.decode_readings(readings, 32) == [data] print(f"{name}: changed characters, swaps, truncation, foreign characters and unread characters behave")
def check_seeds(S): base = S.ALPHABET assert S.with_seed(0).ALPHABET == base n = len(base) # distinct seeds give distinct orders, every order is a permutation, N! wraps, and a small seed moves most symbols assert len({S.with_seed(s).ALPHABET for s in range(500)}) == 500 assert all(sorted(S.with_seed(s).ALPHABET) == sorted(base) for s in (1, 7, 10**30)) assert S.with_seed(math.factorial(n) + 5).ALPHABET == S.with_seed(5).ALPHABET for seed in (1, 2, 1234): moved = sum(x != y for x, y in zip(base, S.with_seed(seed).ALPHABET)) assert moved > 0.9 * n, (seed, moved, n) rng = random.Random(7) for seed in (1, 12345, 10**40): c = S.with_seed(seed) for _ in range(100): d = rng.randbytes(32) assert c.decode(c.encode(d), 32) == d # a sigil written under one seed is refused under another, but for a one-in-K chance c1, c2 = S.with_seed(1234), S.with_seed(98765) k = c1.layout(32)[1] hits = 0 for _ in range(3000): try: c2.decode(c1.encode(rng.randbytes(32)), 32) hits += 1 except B.DecodeError: pass assert hits <= 6 * 3000 // k + 3, (hits, k) print(f"seeds: seed 0 is the stored order, {n}! orders, cross-seed decoding accepted {hits} of 3000 (about 1 in {k} expected)")
def main(): check("wingdings", SS.WINGDINGS) check("webdings", SS.WEBDINGS) assert not set(SS.WINGDINGS.ALPHABET) & set(SS.WEBDINGS.ALPHABET) # a sigil's mode is plain from its characters check_seeds(SS.WINGDINGS) check_seeds(SS.WEBDINGS) assert SS.encode(b"x") == SS.WINGDINGS.encode(b"x")
main()