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

6
dat.h
View File

@@ -48,12 +48,18 @@ struct Str
int n; int n;
}; };
/*
* What one key does to a composition: text to commit, the new pending
* text, and the new raw state (Telex key history, or pending Japanese
* romaji), given the old ones in Im.
*/
typedef struct Emit Emit; typedef struct Emit Emit;
struct Emit struct Emit
{ {
int eat; int eat;
Str s; Str s;
Str next; Str next;
Str raw;
}; };
typedef struct Tnode Tnode; typedef struct Tnode Tnode;

14
ko.c
View File

@@ -189,6 +189,10 @@ issyl(Rune r)
return r >= Sbase && r < Sbase + Ncho*Njung*Njong; return r >= Sbase && r < Sbase + Ncho*Njung*Njong;
} }
/*
* The pending text is at most one syllable; a key either extends it,
* commits it and starts another, or commits it and passes through.
*/
Emit Emit
transko(Im *im, Rune c) transko(Im *im, Rune c)
{ {
@@ -199,10 +203,7 @@ transko(Im *im, Rune c)
memset(&e, 0, sizeof e); memset(&e, 0, sizeof e);
jm = keytojamo(c); jm = keytojamo(c);
if(jm == 0){ if(jm == 0){
if(im->pre.n > 0){
e.s = im->pre; e.s = im->pre;
sclear(&im->pre);
}
return e; return e;
} }
@@ -217,14 +218,12 @@ transko(Im *im, Rune c)
ci = Choidx(last); ci = Choidx(last);
ji = Jungidx(jm); ji = Jungidx(jm);
if(ci >= 0 && ji >= 0){ if(ci >= 0 && ji >= 0){
spopr(&im->pre);
sputr(&e.next, compose(ci, ji, 0)); sputr(&e.next, compose(ci, ji, 0));
return e; return e;
} }
if(Jungidx(last) >= 0 && ji >= 0){ if(Jungidx(last) >= 0 && ji >= 0){
comb = combine(cvow, nelem(cvow), last, jm); comb = combine(cvow, nelem(cvow), last, jm);
if(comb){ if(comb){
spopr(&im->pre);
sputr(&e.next, comb); sputr(&e.next, comb);
return e; return e;
} }
@@ -239,7 +238,6 @@ transko(Im *im, Rune c)
if(joi == 0){ if(joi == 0){
ni = Jongidx(jm); ni = Jongidx(jm);
if(ni > 0){ if(ni > 0){
spopr(&im->pre);
sputr(&e.next, compose(ci, ji, ni)); sputr(&e.next, compose(ci, ji, ni));
return e; return e;
} }
@@ -248,7 +246,6 @@ transko(Im *im, Rune c)
comb = combine(cvow, nelem(cvow), jung[ji], jm); comb = combine(cvow, nelem(cvow), jung[ji], jm);
if(comb){ if(comb){
ni = Jungidx(comb); ni = Jungidx(comb);
spopr(&im->pre);
sputr(&e.next, compose(ci, ni, 0)); sputr(&e.next, compose(ci, ni, 0));
return e; return e;
} }
@@ -260,7 +257,6 @@ transko(Im *im, Rune c)
if(comb){ if(comb){
ni = Jongidx(comb); ni = Jongidx(comb);
if(ni > 0){ if(ni > 0){
spopr(&im->pre);
sputr(&e.next, compose(ci, ji, ni)); sputr(&e.next, compose(ci, ji, ni));
return e; return e;
} }
@@ -271,14 +267,12 @@ transko(Im *im, Rune c)
if(splitpair(cjong, nelem(cjong), jc, &stay, &next)){ if(splitpair(cjong, nelem(cjong), jc, &stay, &next)){
si = Jongidx(stay); si = Jongidx(stay);
ni = Choidx(next); ni = Choidx(next);
spopr(&im->pre);
sputr(&e.s, compose(ci, ji, si)); sputr(&e.s, compose(ci, ji, si));
sputr(&e.next, compose(ni, vi, 0)); sputr(&e.next, compose(ni, vi, 0));
return e; return e;
} }
ni = Choidx(jc); ni = Choidx(jc);
if(ni >= 0){ if(ni >= 0){
spopr(&im->pre);
sputr(&e.s, compose(ci, ji, 0)); sputr(&e.s, compose(ci, ji, 0));
sputr(&e.next, compose(ni, vi, 0)); sputr(&e.next, compose(ni, vi, 0));
return e; return e;

136
strans.c
View File

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

View File

@@ -15,6 +15,7 @@ typevi(struct ct *t, char *keys, char *want)
e = transvi(&state, (uchar)*p); e = transvi(&state, (uchar)*p);
sappend(&out, &e.s); sappend(&out, &e.s);
state.pre = e.next; state.pre = e.next;
state.raw = e.raw;
if(!e.eat) if(!e.eat)
sputr(&out, (uchar)*p); sputr(&out, (uchar)*p);
} }

69
vi.c
View File

@@ -31,12 +31,6 @@ static struct {
{L'Y', {L'Ý', L'', L'', L'', L''}}, {L'Y', {L'Ý', L'', L'', L'', L''}},
}; };
static int
istone(Rune c)
{
return c == 's' || c == 'f' || c == 'r' || c == 'x' || c == 'j';
}
static int static int
toneidx(Rune c) toneidx(Rune c)
{ {
@@ -114,20 +108,44 @@ onsetglide(Str *m)
return -1; return -1;
} }
Emit /*
transvi(Im *im, Rune c) * The pending text is the key sequence itself, read through the map for
* display; raw keeps every key of it so Backspace can replay one fewer.
*/
static Emit
history(Im *im, Rune c, Emit e)
{ {
Emit e, mappedkey; Str mapped;
e.raw = im->raw;
if(e.s.n > 0 || !e.eat)
sclear(&e.raw);
if(!e.eat || e.next.n == 0)
return e;
if(e.raw.n < Maxrunes){
sputr(&e.raw, c);
return e;
}
/* The history is full: flush the composed text as it stands. */
if(!mapget(im->l->map, &e.next, &mapped))
mapped = e.next;
sappend(&e.s, &mapped);
sclear(&e.next);
sclear(&e.raw);
return e;
}
static Emit
tone(Im *im, Rune c)
{
Emit e;
Str mapped, pre; Str mapped, pre;
int i, tidx, vi, last, penult, glide; int i, tidx, vi, last, penult, glide;
Rune v, b1, b2; Rune v, b1, b2;
if(!istone(c) && c != 'z') e = transmap(im, c);
return transmap(im, c); if(e.eat)
mappedkey = transmap(im, c); return e;
if(mappedkey.eat)
return mappedkey;
memset(&e, 0, sizeof e); memset(&e, 0, sizeof e);
if(im->pre.n == 0) if(im->pre.n == 0)
return e; return e;
@@ -175,17 +193,24 @@ transvi(Im *im, Rune c)
return e; return e;
} }
if(c == 'z')
mapped.r[vi] = removetone(mapped.r[vi]);
else{
tidx = toneidx(c); tidx = toneidx(c);
if(tidx < 0)
mapped.r[vi] = removetone(mapped.r[vi]);
else
mapped.r[vi] = applytone(mapped.r[vi], tidx); mapped.r[vi] = applytone(mapped.r[vi], tidx);
}
e.eat = 1; e.eat = 1;
e.next = mapped; e.next = mapped;
return e; return e;
} }
Emit
transvi(Im *im, Rune c)
{
if(toneidx(c) < 0 && c != 'z')
return history(im, c, transmap(im, c));
return history(im, c, tone(im, c));
}
void void
backvi(Im *im) backvi(Im *im)
{ {
@@ -200,10 +225,10 @@ backvi(Im *im)
raw = im->raw; raw = im->raw;
spopr(&raw); spopr(&raw);
sclear(&im->pre); sclear(&im->pre);
sclear(&im->raw);
for(i = 0; i < raw.n; i++){ for(i = 0; i < raw.n; i++){
e = transvi(im, raw.r[i]); e = transvi(im, raw.r[i]);
sclear(&im->pre); im->pre = e.next;
sappend(&im->pre, &e.next); im->raw = e.raw;
} }
im->raw = raw;
} }