data(symbol): a prefix owns its slots only through its own row

map/symbol.src:12 says "Bare 1-9 choose from a prefix search; keep digit
aliases in matching slots", map/README:55 repeats it, and both
tests/engine_test.c:1001 and tests/mkemoji_test.py were written to it.
None of it happened.  dictprefix ends in below(), which returns a node's
own words first and then its children in rune order (dict.c:29-39), and
under `^` the punctuation sorts ahead of the digits:

	^ gave  ⁽ ⁾ ⁺ ⁻ ⁰ ¹ ² ³ ⁴ ⁵ ...
	_ gave  ₍ ₎ ₊ ₋ ₀ ₁ ₂ ₃ ₄ ₅ ...
	< gave  ← ♥ 🫰 🫶 ≤ ≠

so the file's own aliases picked the wrong character every time:

	          before   after
	^ then 1  ⁽        ¹
	^ then 2  ⁾        ²
	_ then 1  ₍        ₁
	< then 3  🫰        ♥

mkemoji has no bare `^` row to emit, because no source row claims `^` as
an alias -- the prefix exists in the trie only as the parent of `^1`..`^9`
and `^(`..`^n`, and a parent has no words of its own.  Giving the nine
superscripts `^` as a second alias, the nine subscripts `_`, and ←≤♥≠ `<`,
makes build() group them in source order and emit three rows:

	<	← ≤ ♥ ≠
	^	¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹
	_	₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉

which is what engine_test.c:1001's fixture has said all along, and the
first thing below() now returns.  Nothing is lost: `^0`, `^(`, `^i` and
their kind still answer their own key, `0` is not a selection key so it
still extends the query, and addkouho drops the duplicate when a child
repeats what the parent already offered.  Three rows on 18953.

tests/mkemoji_test.py was asking the wrong question.  It checked that the
first nine `^` keys in the file are `^1`..`^9` -- true before and after,
and decided nothing, because the file's order is not the trie's.  It now
checks the row the engine actually reads, and fails without this change
with a bare KeyError on `^`.

What this does not cover: `+1` and `-1`.  They are not slot aliases, they
are words -- plus one, minus one -- and  and  are the right first
answers to `+` and `-`.  👍 is slot 3 of `+` and reachable there.
This commit is contained in:
2026-08-18 09:26:46 +09:00
parent a351f50308
commit 5abd885e11
3 changed files with 30 additions and 28 deletions

View File

@@ -8,6 +8,7 @@
:( ☹
:) ☺
<- ←
< ← ≤ ♥ ≠
<= ≤
<3 ♥ 🫰 🫶
<> ≠
@@ -16,6 +17,7 @@
=> ⇒
>= ≥
^1 ¹
^ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹
^2 ²
^3 ³
^4 ⁴
@@ -25,6 +27,7 @@
^8 ⁸
^9 ⁹
_1 ₁
_ ₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉
_2 ₂
_3 ₃
_4 ₄

View File

@@ -10,31 +10,31 @@
☺ :)
# Bare 1-9 choose from a prefix search; keep digit aliases in matching slots.
← <-
≤ <=
♥ <3
≠ <> !=
← <- <
≤ <= <
♥ <3 <
≠ <> != <
≡ ==
⇒ =>
≥ >=
¹ ^1
² ^2
³ ^3
⁴ ^4
⁵ ^5
⁶ ^6
⁷ ^7
⁸ ^8
⁹ ^9
₁ _1
₂ _2
₃ _3
₄ _4
₅ _5
₆ _6
₇ _7
₈ _8
₉ _9
¹ ^1 ^
² ^2 ^
³ ^3 ^
⁴ ^4 ^
⁵ ^5 ^
⁶ ^6 ^
⁷ ^7 ^
⁸ ^8 ^
⁹ ^9 ^
₁ _1 _
₂ _2 _
₃ _3 _
₄ _4 _
₅ _5 _
₆ _6 _
₇ _7 _
₈ _8 _
₉ _9 _
⁽ ^(
⁾ ^)

View File

@@ -35,12 +35,11 @@ class MkemojiTest(unittest.TestCase):
result = generate()
self.assertEqual(result.returncode, 0, result.stderr)
data = table(result.stdout)
keys = list(data)
self.assertEqual([k for k in keys if k[:1] == "^"][:9],
[f"^{d}" for d in range(1, 10)])
self.assertEqual([k for k in keys if k[:1] == "_"][:9],
[f"_{d}" for d in range(1, 10)])
self.assertEqual([k for k in keys if k[:1] == "<"][2], "<3")
# The engine walks a prefix's children in rune order, so only the
# prefix's own row decides which candidate a digit picks.
self.assertEqual(data["^"].split(), list("¹²³⁴⁵⁶⁷⁸⁹"))
self.assertEqual(data["_"].split(), list("₁₂₃₄₅₆₇₈₉"))
self.assertEqual(data["<"].split()[2], "")
self.assertEqual(data["^1"], "¹")
self.assertEqual(data["_2"], "")
self.assertEqual(data["<3"].split()[0], "")