dict: lookups take a trie; a prefix search is its own function; no self-key rule

dictlookup took a Lang for two reasons that were not its business: to
know whether to walk below the key (the emoji dictionary) and to drop a
candidate equal to the key except there.  No dictionary lists its key
among its candidates — the rule was left from a first version that put
the reading first itself — so it goes, and the walk is dictprefix(),
called by the emoji search alone.
This commit is contained in:
2026-08-17 01:31:51 +09:00
parent 3d862bdc0d
commit 5faefd9a87
6 changed files with 59 additions and 51 deletions

46
dict.c
View File

@@ -1,13 +1,9 @@
#include "dat.h"
#include "fn.h"
/*
* Appends the space-separated words of a node's entry to out[], up to
* max: its candidates, minus the key itself, except in the emoji
* dictionary, where a query may name its own answer.
*/
/* Appends the space-separated words of a node's entry to out[], up to max. */
static int
words(Lang *l, Str *key, Tnode *nd, Str *out, int n, int max)
words(Tnode *nd, Str *out, int n, int max)
{
char *p, *e, *sp;
Str tmp;
@@ -22,8 +18,7 @@ words(Lang *l, Str *key, Tnode *nd, Str *out, int n, int max)
sp = p;
while(p < e && *p != ' ')
p++;
if(sinit(&tmp, sp, p - sp) && tmp.n > 0 &&
(l->lang == LangEMOJI || scmp(&tmp, key) != 0))
if(sinit(&tmp, sp, p - sp) && tmp.n > 0)
out[n++] = tmp;
}
return n;
@@ -31,33 +26,40 @@ words(Lang *l, Str *key, Tnode *nd, Str *out, int n, int max)
/* The entries at and below a node, in the dictionary's order. */
static int
below(Lang *l, Str *key, int ni, Str *out, int n, int max)
below(Trie *t, int ni, Str *out, int n, int max)
{
Tnode *nd;
int ci;
nd = &l->dict->nodes[ni];
n = words(l, key, nd, out, n, max);
for(ci = nd->child; ci >= 0 && n < max; ci = l->dict->nodes[ci].sibling)
n = below(l, key, ci, out, n, max);
nd = &t->nodes[ni];
n = words(nd, out, n, max);
for(ci = nd->child; ci >= 0 && n < max; ci = t->nodes[ci].sibling)
n = below(t, ci, out, n, max);
return n;
}
/*
* Fills out[] with up to max candidates for key. The emoji dictionary is
* searched by prefix, the key's own entry first; the others exactly.
*/
/* Fills out[] with up to max candidates for key: the words of its entry. */
int
dictlookup(Lang *l, Str *key, Str *out, int max)
dictlookup(Trie *t, Str *key, Str *out, int max)
{
int ni;
ni = key->n == 0 ? -1 : trienode(l->dict, key);
ni = key->n == 0 ? -1 : trienode(t, key);
if(ni < 0)
return 0;
if(l->lang == LangEMOJI)
return below(l, key, ni, out, 0, max);
return words(l, key, &l->dict->nodes[ni], out, 0, max);
return words(&t->nodes[ni], out, 0, max);
}
/* As dictlookup, for every entry key is a prefix of, key's own first. */
int
dictprefix(Trie *t, Str *key, Str *out, int max)
{
int ni;
ni = key->n == 0 ? -1 : trienode(t, key);
if(ni < 0)
return 0;
return below(t, ni, out, 0, max);
}
static Trie*

3
fn.h
View File

@@ -21,7 +21,8 @@ int trielookup(Trie*, Str*, char**, int*);
Lang* getlang(int);
void langinit(char*);
int dictlookup(Lang*, Str*, Str*, int);
int dictlookup(Trie*, Str*, Str*, int);
int dictprefix(Trie*, Str*, Str*, int);
void drawthread(void*);
void popuparea(Area*, int, Area*, int, int, Area*);

View File

@@ -289,7 +289,7 @@ dictqokuri(Str *reading)
key = *reading;
key.n = i;
sputr(&key, c);
n = dictlookup(im.l, &key, kouho, Maxkouho);
n = dictlookup(im.l->dict, &key, kouho, Maxkouho);
sclear(&okuri);
for(j = i - (reading->r[i-1] == L''); j < reading->n; j++)
sputr(&okuri, reading->r[j]);
@@ -310,7 +310,7 @@ dictqjp(void)
if(!isjp(&im) || !jpreading(im.l->map, &im.pre, &im.raw, &reading) ||
reading.n == 0)
return;
im.nkouho = dictlookup(im.l, &reading, im.kouho, Maxkouho);
im.nkouho = dictlookup(im.l->dict, &reading, im.kouho, Maxkouho);
dictqokuri(&reading);
}
@@ -572,13 +572,13 @@ emojiquery(void)
transstr(im.l, nil, &search.raw, &local);
foldascii(&local);
clearkouho();
n = dictlookup(getlang(LangEMOJI), &raw, kouho, Maxkouho);
n = dictprefix(getlang(LangEMOJI)->dict, &raw, kouho, Maxkouho);
rawhit = n != 0;
for(i = 0; i < n; i++)
addkouho(&kouho[i]);
localhit = 0;
if(scmp(&raw, &local) != 0){
n = dictlookup(getlang(LangEMOJI), &local, kouho, Maxkouho);
n = dictprefix(getlang(LangEMOJI)->dict, &local, kouho, Maxkouho);
localhit = n != 0;
for(i = 0; i < n; i++)
addkouho(&kouho[i]);
@@ -593,8 +593,8 @@ hanjaquery(void)
{
transstr(im.l, &search.seed, &search.raw, &search.text);
clearkouho();
im.nkouho = dictlookup(getlang(LangHANJA), &search.text, im.kouho,
Maxkouho);
im.nkouho = dictlookup(getlang(LangHANJA)->dict, &search.text,
im.kouho, Maxkouho);
selectfirst();
}

View File

@@ -15,10 +15,10 @@ dictionary_candidates(struct ct *t)
lang = getlang(LangJP);
saved = lang->dict;
lang->dict = trienew();
trieput(lang->dict, "かな", strlen("かな"), " 候補1 かな 候補2 ",
strlen(" 候補1 かな 候補2 "));
trieput(lang->dict, "かな", strlen("かな"), " 候補1 候補2 ",
strlen(" 候補1 候補2 "));
key = mkstr("かな");
n = dictlookup(lang, &key, kouho, Maxkouho);
n = dictlookup(lang->dict, &key, kouho, Maxkouho);
if(CT_EQ_INT(t, 2, n)){
checkstr(t, "candidate 1", "候補1", &kouho[0]);
checkstr(t, "candidate 2", "候補2", &kouho[1]);
@@ -34,13 +34,13 @@ dictionary_candidates(struct ct *t)
*p = '\0';
trieput(lang->dict, "key", 3, many, strlen(many));
key = mkstr("key");
n = dictlookup(lang, &key, kouho, Maxkouho);
n = dictlookup(lang->dict, &key, kouho, Maxkouho);
if(CT_EQ_INT(t, Maxkouho, n)){
checkstr(t, "first capped candidate", "c00", &kouho[0]);
snprint(item, sizeof item, "c%02d", Maxkouho-1);
checkstr(t, "last capped candidate", item, &kouho[Maxkouho-1]);
}
CT_EQ_INT(t, 3, dictlookup(lang, &key, kouho, 3));
CT_EQ_INT(t, 3, dictlookup(lang->dict, &key, kouho, 3));
trieclose(lang->dict);
lang->dict = saved;
}
@@ -56,28 +56,33 @@ dictionary_misses(struct ct *t)
saved = lang->dict;
lang->dict = trienew();
key = mkstr("");
CT_EQ_INT(t, 0, dictlookup(lang, &key, kouho, Maxkouho));
CT_EQ_INT(t, 0, dictlookup(lang->dict, &key, kouho, Maxkouho));
key = mkstr("missing");
CT_EQ_INT(t, 0, dictlookup(lang, &key, kouho, Maxkouho));
CT_EQ_INT(t, 0, dictlookup(getlang(LangKO), &key, kouho, Maxkouho));
CT_EQ_INT(t, 0, dictlookup(lang->dict, &key, kouho, Maxkouho));
CT_EQ_INT(t, 0, dictlookup(getlang(LangKO)->dict, &key, kouho, Maxkouho));
trieclose(lang->dict);
lang->dict = saved;
}
void
dictionary_emoji_identity(struct ct *t)
dictionary_prefix(struct ct *t)
{
Str kouho[Maxkouho], key;
Trie *saved;
Lang *lang;
Trie *dict;
lang = getlang(LangEMOJI);
saved = lang->dict;
lang->dict = trienew();
trieput(lang->dict, "é", strlen("é"), "é", strlen("é"));
key = mkstr("é");
if(CT_EQ_INT(t, 1, dictlookup(lang, &key, kouho, Maxkouho)))
checkstr(t, "emoji identity candidate", "é", &kouho[0]);
trieclose(lang->dict);
lang->dict = saved;
dict = trienew();
trieput(dict, "smile", 5, "A B", 3);
trieput(dict, "smiley", 6, "B C", 3);
trieput(dict, "sad", 3, "D", 1);
key = mkstr("smile");
if(CT_EQ_INT(t, 4, dictprefix(dict, &key, kouho, Maxkouho))){
checkstr(t, "own entry first", "A", &kouho[0]);
checkstr(t, "entry below", "C", &kouho[3]);
}
key = mkstr("s");
CT_EQ_INT(t, 5, dictprefix(dict, &key, kouho, Maxkouho));
CT_EQ_INT(t, 2, dictprefix(dict, &key, kouho, 2));
key = mkstr("x");
CT_EQ_INT(t, 0, dictprefix(dict, &key, kouho, Maxkouho));
trieclose(dict);
}

View File

@@ -95,7 +95,7 @@ void engine_randomized_stress(struct ct*);
void engine_full_boundary_passthrough(struct ct*);
void dictionary_candidates(struct ct*);
void dictionary_misses(struct ct*);
void dictionary_emoji_identity(struct ct*);
void dictionary_prefix(struct ct*);
void ipc_masks_modifiers(struct ct*);
void ipc_control_and_caret_frames(struct ct*);
void ipc_runtime_path(struct ct*);

View File

@@ -118,7 +118,7 @@ static const struct ct_test tests[] = {
{ "engine/full-boundary-passthrough", engine_full_boundary_passthrough },
{ "dict/candidates", dictionary_candidates },
{ "dict/misses", dictionary_misses },
{ "dict/emoji-identity", dictionary_emoji_identity },
{ "dict/prefix", dictionary_prefix },
{ "ipc/masks-modifiers", ipc_masks_modifiers },
{ "ipc/control-caret-frames", ipc_control_and_caret_frames },
{ "ipc/runtime-path", ipc_runtime_path },