mkemoji wrote a row for every prefix of every alias, so that a query matched as it was typed; the trie is a prefix index already, and with a real emoji list those rows would be four times the aliases themselves. Now emoji.dict has one row per alias, trienode() names a key's node, and dictlookup walks the entries at and below it, the key's own first, up to Maxkouho. Trie children are appended rather than pushed, so the walk keeps the file's order and a bare digit still picks the superscript or subscript it always did. The hand-written symbol rows move to symbol.src; emoji.src is left to the emoji. mkemoji reads both by default, or the files it is given.
194 lines
3.6 KiB
C
194 lines
3.6 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;
|
|
}
|
|
|
|
/* Appends, so that a walk of the children keeps the file's order. */
|
|
static int
|
|
add(Trie *t, int ni, char c)
|
|
{
|
|
int last, pi;
|
|
|
|
last = -1;
|
|
for(pi = t->nodes[ni].child; pi >= 0; pi = t->nodes[pi].sibling)
|
|
last = pi;
|
|
pi = newnode(t);
|
|
t->nodes[pi].c = c;
|
|
if(last < 0)
|
|
t->nodes[ni].child = pi;
|
|
else
|
|
t->nodes[last].sibling = 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);
|
|
}
|
|
|
|
/* The node key leads to, or -1. A nil trie is an unloaded map: every key misses. */
|
|
int
|
|
trienode(Trie *t, Str *key)
|
|
{
|
|
char buf[Maxutf];
|
|
int i, klen, ni;
|
|
|
|
if(t == nil)
|
|
return -1;
|
|
klen = stoutf(key, buf, sizeof buf);
|
|
ni = 0;
|
|
for(i = 0; i < klen && ni >= 0; i++)
|
|
ni = find(t, ni, buf[i]);
|
|
return ni;
|
|
}
|
|
|
|
int
|
|
trielookup(Trie *t, Str *key, char **val, int *vlen)
|
|
{
|
|
int ni;
|
|
|
|
*val = nil;
|
|
*vlen = 0;
|
|
ni = trienode(t, key);
|
|
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;
|
|
}
|