Files
strans/trie.c
Hojun-Cho c7718ece52 data: one trie for maps and dictionaries, one loader
The hash map served a single exact-match lookup that the trie already
answers, at the price of a second container, a second file loader with
its own drift, and a Str-to-UTF-8 conversion on every chain probe. The
files are small (kanji.dict is 7.5k lines), so the trie holds both.
trieopen validates keys and each space-separated value word against Str
and reports path:line; trielookup takes the Str every caller holds and
treats a nil trie as an unloaded map; trienew/trieput exist for tests.
The overflow guards on growth, Trie.root (always 0) and the per-language
init loop written twice are gone.
2026-08-16 15:56:32 +09:00

180 lines
3.3 KiB
C

#include <errno.h>
#include "dat.h"
#include "fn.h"
/*
* A byte trie over UTF-8 keys. Maps need prefix matches while composing;
* dictionaries need exact matches; both are small enough for one structure.
* Node 0 is the root.
*/
static int
newnode(Trie *t)
{
int i;
if(t->n == t->cap){
t->cap *= 2;
t->nodes = erealloc(t->nodes, t->cap * sizeof(Tnode));
}
i = t->n++;
memset(&t->nodes[i], 0, sizeof(Tnode));
t->nodes[i].child = -1;
t->nodes[i].sibling = -1;
return i;
}
static int
find(Trie *t, int ni, char c)
{
int pi;
for(pi = t->nodes[ni].child; pi >= 0; pi = t->nodes[pi].sibling)
if(t->nodes[pi].c == c)
return pi;
return -1;
}
static int
add(Trie *t, int ni, char c)
{
int pi;
pi = newnode(t);
t->nodes[pi].c = c;
t->nodes[pi].sibling = t->nodes[ni].child;
t->nodes[ni].child = pi;
return pi;
}
Trie*
trienew(void)
{
Trie *t;
t = emalloc(sizeof(*t));
t->cap = 1024;
t->nodes = emalloc(t->cap * sizeof(Tnode));
t->n = 0;
newnode(t);
return t;
}
void
trieput(Trie *t, char *key, int klen, char *val, int vlen)
{
int ni, ci, i;
ni = 0;
for(i = 0; i < klen; i++){
ci = find(t, ni, key[i]);
if(ci < 0)
ci = add(t, ni, key[i]);
ni = ci;
}
free(t->nodes[ni].val);
t->nodes[ni].val = emalloc(vlen + 1);
memmove(t->nodes[ni].val, val, vlen);
t->nodes[ni].val[vlen] = '\0';
t->nodes[ni].vlen = vlen;
}
static char*
readline(Biobuf *b, char *path)
{
char *line;
errno = 0;
line = Brdstr(b, '\n', 1);
if(errno != 0)
die("can't read %s: %r", path);
return line;
}
/*
* A file holds "key<tab>value" lines; ';' starts a comment. Keys and each
* space-separated word of a value must fit a Str, which is what lookups
* hand back.
*/
Trie*
trieopen(char *path)
{
Trie *t;
Biobuf *b;
Str s;
char *e, *line, *p, *tab;
int len, lineno;
b = Bopen(path, OREAD);
if(b == nil)
die("can't open %s: %r", path);
t = trienew();
for(lineno = 1; (line = readline(b, path)) != nil; lineno++){
len = Blinelen(b);
if(memchr(line, '\0', len) != nil)
die("%s:%d: NUL byte", path, lineno);
if(len > 0 && line[len-1] == '\r')
line[--len] = '\0';
if(len == 0 || line[0] == ';'){
free(line);
continue;
}
tab = memchr(line, '\t', len);
if(tab == nil || tab == line || tab == line+len-1 ||
memchr(tab+1, '\t', line+len-(tab+1)) != nil)
die("%s:%d: expected key, tab, value", path, lineno);
if(!sinit(&s, line, tab-line))
die("%s:%d: invalid or oversized key", path, lineno);
for(p = tab+1; p < line+len; p = e+1){
e = memchr(p, ' ', line+len-p);
if(e == nil)
e = line+len;
if(!sinit(&s, p, e-p))
die("%s:%d: invalid or oversized value", path, lineno);
}
trieput(t, line, tab-line, tab+1, line+len-(tab+1));
free(line);
}
if(Bterm(b) < 0)
die("can't close %s: %r", path);
return t;
}
void
trieclose(Trie *t)
{
int i;
if(t == nil)
return;
for(i = 0; i < t->n; i++)
free(t->nodes[i].val);
free(t->nodes);
free(t);
}
/* A nil trie is an unloaded map: every key misses. */
int
trielookup(Trie *t, Str *key, char **val, int *vlen)
{
char buf[Maxutf];
int i, klen, ni;
*val = nil;
*vlen = 0;
if(t == nil)
return TrieMiss;
klen = stoutf(key, buf, sizeof buf);
ni = 0;
for(i = 0; i < klen; i++){
ni = find(t, ni, buf[i]);
if(ni < 0)
return TrieMiss;
}
if(t->nodes[ni].val == nil)
return TriePrefix;
*val = t->nodes[ni].val;
*vlen = t->nodes[ni].vlen;
return TrieExact;
}