202 lines
6.0 KiB
Python
202 lines
6.0 KiB
Python
|
#!/usr/bin/env python3
|
||
|
|
||
|
# This script is based on
|
||
|
# https://github.com/rust-lang/rust/blob/master/library/core/src/unicode/printable.py
|
||
|
# distributed under https://github.com/rust-lang/rust/blob/master/LICENSE-MIT.
|
||
|
|
||
|
# This script uses the following Unicode tables:
|
||
|
# - UnicodeData.txt
|
||
|
|
||
|
|
||
|
from collections import namedtuple
|
||
|
import csv
|
||
|
import os
|
||
|
import subprocess
|
||
|
|
||
|
NUM_CODEPOINTS=0x110000
|
||
|
|
||
|
def to_ranges(iter):
|
||
|
current = None
|
||
|
for i in iter:
|
||
|
if current is None or i != current[1] or i in (0x10000, 0x20000):
|
||
|
if current is not None:
|
||
|
yield tuple(current)
|
||
|
current = [i, i + 1]
|
||
|
else:
|
||
|
current[1] += 1
|
||
|
if current is not None:
|
||
|
yield tuple(current)
|
||
|
|
||
|
def get_escaped(codepoints):
|
||
|
for c in codepoints:
|
||
|
if (c.class_ or "Cn") in "Cc Cf Cs Co Cn Zl Zp Zs".split() and c.value != ord(' '):
|
||
|
yield c.value
|
||
|
|
||
|
def get_file(f):
|
||
|
try:
|
||
|
return open(os.path.basename(f))
|
||
|
except FileNotFoundError:
|
||
|
subprocess.run(["curl", "-O", f], check=True)
|
||
|
return open(os.path.basename(f))
|
||
|
|
||
|
Codepoint = namedtuple('Codepoint', 'value class_')
|
||
|
|
||
|
def get_codepoints(f):
|
||
|
r = csv.reader(f, delimiter=";")
|
||
|
prev_codepoint = 0
|
||
|
class_first = None
|
||
|
for row in r:
|
||
|
codepoint = int(row[0], 16)
|
||
|
name = row[1]
|
||
|
class_ = row[2]
|
||
|
|
||
|
if class_first is not None:
|
||
|
if not name.endswith("Last>"):
|
||
|
raise ValueError("Missing Last after First")
|
||
|
|
||
|
for c in range(prev_codepoint + 1, codepoint):
|
||
|
yield Codepoint(c, class_first)
|
||
|
|
||
|
class_first = None
|
||
|
if name.endswith("First>"):
|
||
|
class_first = class_
|
||
|
|
||
|
yield Codepoint(codepoint, class_)
|
||
|
prev_codepoint = codepoint
|
||
|
|
||
|
if class_first is not None:
|
||
|
raise ValueError("Missing Last after First")
|
||
|
|
||
|
for c in range(prev_codepoint + 1, NUM_CODEPOINTS):
|
||
|
yield Codepoint(c, None)
|
||
|
|
||
|
def compress_singletons(singletons):
|
||
|
uppers = [] # (upper, # items in lowers)
|
||
|
lowers = []
|
||
|
|
||
|
for i in singletons:
|
||
|
upper = i >> 8
|
||
|
lower = i & 0xff
|
||
|
if len(uppers) == 0 or uppers[-1][0] != upper:
|
||
|
uppers.append((upper, 1))
|
||
|
else:
|
||
|
upper, count = uppers[-1]
|
||
|
uppers[-1] = upper, count + 1
|
||
|
lowers.append(lower)
|
||
|
|
||
|
return uppers, lowers
|
||
|
|
||
|
def compress_normal(normal):
|
||
|
# lengths 0x00..0x7f are encoded as 00, 01, ..., 7e, 7f
|
||
|
# lengths 0x80..0x7fff are encoded as 80 80, 80 81, ..., ff fe, ff ff
|
||
|
compressed = [] # [truelen, (truelenaux), falselen, (falselenaux)]
|
||
|
|
||
|
prev_start = 0
|
||
|
for start, count in normal:
|
||
|
truelen = start - prev_start
|
||
|
falselen = count
|
||
|
prev_start = start + count
|
||
|
|
||
|
assert truelen < 0x8000 and falselen < 0x8000
|
||
|
entry = []
|
||
|
if truelen > 0x7f:
|
||
|
entry.append(0x80 | (truelen >> 8))
|
||
|
entry.append(truelen & 0xff)
|
||
|
else:
|
||
|
entry.append(truelen & 0x7f)
|
||
|
if falselen > 0x7f:
|
||
|
entry.append(0x80 | (falselen >> 8))
|
||
|
entry.append(falselen & 0xff)
|
||
|
else:
|
||
|
entry.append(falselen & 0x7f)
|
||
|
|
||
|
compressed.append(entry)
|
||
|
|
||
|
return compressed
|
||
|
|
||
|
def print_singletons(uppers, lowers, uppersname, lowersname):
|
||
|
print(" static constexpr singleton {}[] = {{".format(uppersname))
|
||
|
for u, c in uppers:
|
||
|
print(" {{{:#04x}, {}}},".format(u, c))
|
||
|
print(" };")
|
||
|
print(" static constexpr unsigned char {}[] = {{".format(lowersname))
|
||
|
for i in range(0, len(lowers), 8):
|
||
|
print(" {}".format(" ".join("{:#04x},".format(l) for l in lowers[i:i+8])))
|
||
|
print(" };")
|
||
|
|
||
|
def print_normal(normal, normalname):
|
||
|
print(" static constexpr unsigned char {}[] = {{".format(normalname))
|
||
|
for v in normal:
|
||
|
print(" {}".format(" ".join("{:#04x},".format(i) for i in v)))
|
||
|
print(" };")
|
||
|
|
||
|
def main():
|
||
|
file = get_file("https://www.unicode.org/Public/UNIDATA/UnicodeData.txt")
|
||
|
|
||
|
codepoints = get_codepoints(file)
|
||
|
|
||
|
CUTOFF=0x10000
|
||
|
singletons0 = []
|
||
|
singletons1 = []
|
||
|
normal0 = []
|
||
|
normal1 = []
|
||
|
extra = []
|
||
|
|
||
|
for a, b in to_ranges(get_escaped(codepoints)):
|
||
|
if a > 2 * CUTOFF:
|
||
|
extra.append((a, b - a))
|
||
|
elif a == b - 1:
|
||
|
if a & CUTOFF:
|
||
|
singletons1.append(a & ~CUTOFF)
|
||
|
else:
|
||
|
singletons0.append(a)
|
||
|
elif a == b - 2:
|
||
|
if a & CUTOFF:
|
||
|
singletons1.append(a & ~CUTOFF)
|
||
|
singletons1.append((a + 1) & ~CUTOFF)
|
||
|
else:
|
||
|
singletons0.append(a)
|
||
|
singletons0.append(a + 1)
|
||
|
else:
|
||
|
if a >= 2 * CUTOFF:
|
||
|
extra.append((a, b - a))
|
||
|
elif a & CUTOFF:
|
||
|
normal1.append((a & ~CUTOFF, b - a))
|
||
|
else:
|
||
|
normal0.append((a, b - a))
|
||
|
|
||
|
singletons0u, singletons0l = compress_singletons(singletons0)
|
||
|
singletons1u, singletons1l = compress_singletons(singletons1)
|
||
|
normal0 = compress_normal(normal0)
|
||
|
normal1 = compress_normal(normal1)
|
||
|
|
||
|
print("""\
|
||
|
FMT_FUNC auto is_printable(uint32_t cp) -> bool {\
|
||
|
""")
|
||
|
print_singletons(singletons0u, singletons0l, 'singletons0', 'singletons0_lower')
|
||
|
print_singletons(singletons1u, singletons1l, 'singletons1', 'singletons1_lower')
|
||
|
print_normal(normal0, 'normal0')
|
||
|
print_normal(normal1, 'normal1')
|
||
|
print("""\
|
||
|
auto lower = static_cast<uint16_t>(cp);
|
||
|
if (cp < 0x10000) {
|
||
|
return is_printable(lower, singletons0,
|
||
|
sizeof(singletons0) / sizeof(*singletons0),
|
||
|
singletons0_lower, normal0, sizeof(normal0));
|
||
|
}
|
||
|
if (cp < 0x20000) {
|
||
|
return is_printable(lower, singletons1,
|
||
|
sizeof(singletons1) / sizeof(*singletons1),
|
||
|
singletons1_lower, normal1, sizeof(normal1));
|
||
|
}\
|
||
|
""")
|
||
|
for a, b in extra:
|
||
|
print(" if (0x{:x} <= cp && cp < 0x{:x}) return false;".format(a, a + b))
|
||
|
print("""\
|
||
|
return cp < 0x{:x};
|
||
|
}}\
|
||
|
""".format(NUM_CODEPOINTS))
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
main()
|