"""The client's reading of Klammertext's character codes (character_codes.js) against the tables `kdesc -c` prints and the rules of the live Klammertext tree, K/mac (character.cpp, katom_process.cpp; K/src is deprecated), as `ktext -s` showed them on 2026-10-13. Needs node (the type checker's). """ import json import os import subprocess import unittest CLIENT = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "client", "js") HARNESS = """ import { codeAtEnd } from %s; const texts = JSON.parse(await new Promise((resolve) => { let data = ''; process.stdin.on('data', (chunk) => { data += chunk; }); process.stdin.on('end', () => resolve(data)); })); console.log(JSON.stringify(texts.map((text) => codeAtEnd(text)))); """ # `kdesc -c`, 2026-10-13: Displayed, Written. DIACRITICS = [("é", "^e'"), ("à", "^a`"), ("ô", "^oh"), ("ñ", "^n~"), ("ü", '^u"'), ("ç", "^cc"), ("ō", "^o-"), ("ğ", "^gb"), ("å", "^ar"), ("ė", "^ed"), ("š", "^sw"), ("ő", "^oa")] EXTENDED_LATIN = [("ß", "^s^"), ("ı", "^i^"), ("İ", "^I^"), ("þ", "^t^"), ("Þ", "^T^"), ("ð", "^e^"), ("Ð", "^E^"), ("ø", "^o^"), ("Ø", "^O^"), ("đ", "^d^"), ("Đ", "^D^"), ("æ", "^ae^"), ("Æ", "^AE^"), ("œ", "^oe^"), ("Œ", "^OE^")] PINYIN = [("ā", "^a1"), ("á", "^a2"), ("ǎ", "^a3"), ("à", "^a4")] def codes_at_end(texts): harness = HARNESS % json.dumps(os.path.join(CLIENT, "character_codes.js")) result = subprocess.run(["node", "--input-type=module", "-e", harness], input=json.dumps(texts), capture_output=True, text=True, check=True) return json.loads(result.stdout) class CharacterCodesTest(unittest.TestCase): def check(self, cases): """[(text, expected)]: expected is the character, or None.""" results = codes_at_end([text for text, _ in cases]) for (text, expected), result in zip(cases, results): with self.subTest(text=text): if expected is None: self.assertIsNone(result) else: self.assertEqual(result, {"length": len(text) - len("Vor "), "character": expected}) def test_the_tables(self): cases = [("Vor " + written, shown) for shown, written in DIACRITICS + EXTENDED_LATIN + PINYIN] self.check(cases) # Precomposed, one code point each, as the tables display them. for shown, _ in DIACRITICS + EXTENDED_LATIN + PINYIN: self.assertEqual(len(shown), 1) def test_other_bases_and_capitals(self): """The base of a diacritic is a letter of any alphabet, not a digit, a space or ASCII punctuation.""" self.check([("Vor ^A'", "Á"), ("Vor ^U\"", "Ü"), ("Vor ^Ss", None), ("Vor ^Sw", "Š"), ("Vor ^c'", "ć"), ("Vor ^Ccedilla", None), ("Vor ^e1", "ē"), ("Vor ^u3", "ǔ"), ("Vor ^x1", None), ("Vor ^q~", "q̃"), ("Vor ^1~", None), ("Vor ^.~", None), ("Vor ^ö-", "\u022B"), ("Vor ^α'", "ά")]) def test_code_points_and_the_caret(self): self.check([("Vor ^00E9^", "é"), ("Vor ^00e9^", "é"), ("Vor ^20AC^", "€"), ("Vor ^^", "^"), ("Vor ^00AE^", "®"), # not ^E^ at its end ("Vor ^130ED^", "𓃭"), ("Vor ^1F600^", "😀"), ("Vor ^10FFFD^", "\U0010FFFD"), # 1 to 3 digits are not a code point: a control # character, or a collision with the mnemonics. ("Vor ^a^", None), ("Vor ^7^", None), ("Vor ^ae^", "æ"), ("Vor ^0E9^", None), # Not a character: beyond the range, a surrogate, a # noncharacter. Stays as typed (an error in a document). ("Vor ^110000^", None), ("Vor ^D800^", None), ("Vor ^FFFE^", None), ("Vor ^FDD0^", None)]) def test_sequences(self): """Code points separated by spaces between one pair of carets.""" self.check([("Vor ^9053 53EF 9053 975E 5A38 9053^", "道可道非娸道"), ("Vor ^9053 53EF^", "道可"), ("Vor ^0041 1F600^", "A😀"), ("Vor ^ 9053^", None), ("Vor ^9053 ^", None), # no space inside the carets ("Vor ^9053 D800^", None), # one bad member spoils it # A single character stands for itself, as in # Klammertext, once a code point is among the members # (Andy, 2026-10-13); without one, x^2 + y^2 stays. ("Vor ^9053 a 53EF^", "道a可"), ("Vor ^a 1F600^", "a😀"), ("Vor ^9053 @^", "道@"), ("Vor ^2 + y^", None), ("Vor ^a b^", None), ("Vor ^ab 9053^", None), ("Vor ^a^", None)]) def test_an_unfinished_or_no_code(self): """A keystroke at a time: nothing until the code is complete, and a caret with something else after it stays.""" self.check([("Vor ^", None), ("Vor ^o", None), ("Vor ^ae", None), ("Vor ^00E", None), ("Vor ^00E9", None), ("Vor ^ a", None), ("Vor ^o\"a", None), ("Vor 2^3", None), ("Vor ^@'", None), ("Vor ", None), ("", None)]) if __name__ == "__main__": unittest.main()