engine: one trans contract for every language

Japanese was never dispatched through the Lang table: dotrans and
transstr both special-cased it into transjp, which edited Im in place
and wrote commits through a side argument, while transko popped and
cleared a pre that every caller overwrote anyway, and Vietnamese kept
its key history in dotrans. Emit now carries the new raw state next to
the new pre; transjp, transko and transvi are pure functions of (Im,
key), and dotrans and transstr have a single path. Vietnamese history
bookkeeping lives in vi.c, where the full-history flush no longer
resets the caret as a side effect; istone was toneidx() >= 0.
This commit is contained in:
2026-08-16 16:04:57 +09:00
parent abaea77248
commit a557fb114a
5 changed files with 118 additions and 118 deletions

144
strans.c
View File

@@ -7,6 +7,7 @@ static void *activeowner;
static int activecap;
static Caret caret;
static int candidatechosen;
static Emit transjp(Im*, Rune);
static void backjp(Im*);
static int maplookup(Trie*, Str*, Str*);
@@ -21,8 +22,8 @@ static Search search;
Lang langs[] = {
{LangEN, nil, nil, nil, nil, nil, nil},
{LangJP, "hira", "kanji", transmap, backjp, nil, nil},
{LangJPK, "kata", nil, transmap, backjp, nil, nil},
{LangJP, "hira", "kanji", transjp, backjp, nil, nil},
{LangJPK, "kata", nil, transjp, backjp, nil, nil},
{LangKO, nil, nil, transko, backko, nil, nil},
{LangHANJA, nil, "hanja", nil, nil, nil, nil},
{LangEMOJI, nil, "emoji", nil, nil, nil, nil},
@@ -106,28 +107,29 @@ isjp(Im *p)
/*
* Japanese keeps completed kana in pre and an ambiguous romaji syllable in
* raw. A mapped raw suffix is part of the reading even while it remains
* pending (notably n, which may still grow into nya).
* pending (notably n, which may still grow into nya). Returns whether
* the reading is complete.
*/
static int
jpreading(Im *p, Str *s)
jpreading(Trie *map, Str *pre, Str *raw, Str *s)
{
Str mapped;
*s = p->pre;
if(p->raw.n == 0)
*s = *pre;
if(raw->n == 0)
return 1;
if(mapget(p->l->map, &p->raw, &mapped)){
if(mapget(map, raw, &mapped)){
sappend(s, &mapped);
return 1;
}
sappend(s, &p->raw);
sappend(s, raw);
return 0;
}
static int
haspre(Im *p)
{
return p->pre.n != 0 || (isjp(p) && p->raw.n != 0);
return p->pre.n != 0 || p->raw.n != 0;
}
/* The preedit as clients see it: Telex keys read back through the map. */
@@ -135,7 +137,7 @@ static void
impre(Str *s)
{
if(isjp(&im))
jpreading(&im, s);
jpreading(im.l->map, &im.pre, &im.raw, s);
else if(!mapget(im.l->map, &im.pre, s))
*s = im.pre;
}
@@ -200,7 +202,7 @@ dictqjp(void)
Str reading;
clearkouho();
if(!jpreading(&im, &reading) || reading.n == 0)
if(!jpreading(im.l->map, &im.pre, &im.raw, &reading) || reading.n == 0)
return;
im.nkouho = dictlookup(im.l, &reading, im.kouho, Maxkouho);
selectfirst();
@@ -223,13 +225,11 @@ commitim(Im *p, Str *com)
{
Str val;
if(isjp(p)){
jpreading(p, &val);
sappend(com, &val);
}else if(p->l->map != nil && mapget(p->l->map, &p->pre, &val))
sappend(com, &val);
else
sappend(com, &p->pre);
if(isjp(p))
jpreading(p->l->map, &p->pre, &p->raw, &val);
else if(!mapget(p->l->map, &p->pre, &val))
val = p->pre;
sappend(com, &val);
sclear(&p->pre);
sclear(&p->raw);
}
@@ -266,77 +266,63 @@ keeptail(Str *s)
s->n--;
}
static int
transjp(Im *p, Rune c, Str *com)
static Emit
transjp(Im *p, Rune c)
{
Emit e;
Str key, mapped;
if(c == '\'' && p->raw.n == 1 && p->raw.r[0] == 'n'){
if(mapget(p->l->map, &p->raw, &mapped))
sappend(&p->pre, &mapped);
sclear(&p->raw);
return 1;
memset(&e, 0, sizeof e);
e.eat = 1;
e.next = p->pre;
e.raw = p->raw;
if(c == '\'' && e.raw.n == 1 && e.raw.r[0] == 'n'){
if(mapget(p->l->map, &e.raw, &mapped))
sappend(&e.next, &mapped);
sclear(&e.raw);
return e;
}
key = p->raw;
if(key.n < Maxrunes)
sputr(&key, c);
sclear(&mapped);
key = e.raw;
sputr(&key, c);
if(maplookup(p->l->map, &key, &mapped)){
p->raw = key;
e.raw = key;
if(issmalltsu(&mapped)){
sappend(&p->pre, &mapped);
keeptail(&p->raw);
sappend(&e.next, &mapped);
keeptail(&e.raw);
}
return 1;
return e;
}
if(p->raw.n != 0 && mapget(p->l->map, &p->raw, &mapped)){
sappend(&p->pre, &mapped);
sclear(&p->raw);
if(e.raw.n != 0 && mapget(p->l->map, &e.raw, &mapped)){
sappend(&e.next, &mapped);
sclear(&e.raw);
sclear(&key);
sputr(&key, c);
if(maplookup(p->l->map, &key, &mapped)){
p->raw = key;
return 1;
e.raw = key;
return e;
}
}
commitim(p, com);
return 0;
/* The key starts no syllable: flush the reading and pass it on. */
jpreading(p->l->map, &e.next, &e.raw, &e.s);
sclear(&e.next);
sclear(&e.raw);
e.eat = 0;
return e;
}
static int
dotrans(Rune c, Str *com)
{
Emit e;
Str mapped;
if(isjp(&im)){
e.eat = transjp(&im, c, com);
if(e.eat)
dictqjp();
else
clearkouho();
return e.eat;
}
e = im.l->trans(&im, c);
if(im.l->lang == LangVI){
if(e.s.n > 0 || !e.eat)
sclear(&im.raw);
if(e.eat && e.next.n > 0){
if(im.raw.n >= Maxrunes){
if(!mapget(im.l->map, &e.next, &mapped))
mapped = e.next;
sappend(com, &mapped);
reset();
return e.eat;
}
sputr(&im.raw, c);
}
}
clearkouho();
if(e.s.n > 0)
sappend(com, &e.s);
sclear(&im.pre);
sappend(&im.pre, &e.next);
sappend(com, &e.s);
im.pre = e.next;
im.raw = e.raw;
if(e.eat && isjp(&im))
dictqjp();
else
clearkouho();
return e.eat;
}
@@ -430,42 +416,30 @@ foldascii(Str *s)
s->r[i] += 'a' - 'A';
}
/* What typing raw in language l would produce, composition included. */
static void
transstr(Lang *l, Str *raw, Str *out)
{
Im q;
Emit e;
Str mapped;
int i;
sclear(out);
if(l == nil || l->trans == nil || (l->trans == transmap && l->map == nil)){
if(l->trans == nil){
*out = *raw;
return;
}
memset(&q, 0, sizeof q);
q.l = l;
if(isjp(&q)){
for(i = 0; i < raw->n; i++)
if(!transjp(&q, raw->r[i], out) && out->n < Maxrunes)
sputr(out, raw->r[i]);
if(haspre(&q))
commitim(&q, out);
return;
}
for(i = 0; i < raw->n; i++){
e = l->trans(&q, raw->r[i]);
sappend(out, &e.s);
q.pre = e.next;
if(!e.eat && out->n < Maxrunes)
q.raw = e.raw;
if(!e.eat)
sputr(out, raw->r[i]);
}
if(q.pre.n == 0)
return;
if(l->map != nil && mapget(l->map, &q.pre, &mapped))
sappend(out, &mapped);
else
sappend(out, &q.pre);
commitim(&q, out);
}
static void