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;
};
/*
* 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;
struct Emit
{
int eat;
Str s;
Str next;
Str raw;
};
typedef struct Tnode Tnode;

14
ko.c
View File

@@ -189,6 +189,10 @@ issyl(Rune r)
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
transko(Im *im, Rune c)
{
@@ -199,10 +203,7 @@ transko(Im *im, Rune c)
memset(&e, 0, sizeof e);
jm = keytojamo(c);
if(jm == 0){
if(im->pre.n > 0){
e.s = im->pre;
sclear(&im->pre);
}
return e;
}
@@ -217,14 +218,12 @@ transko(Im *im, Rune c)
ci = Choidx(last);
ji = Jungidx(jm);
if(ci >= 0 && ji >= 0){
spopr(&im->pre);
sputr(&e.next, compose(ci, ji, 0));
return e;
}
if(Jungidx(last) >= 0 && ji >= 0){
comb = combine(cvow, nelem(cvow), last, jm);
if(comb){
spopr(&im->pre);
sputr(&e.next, comb);
return e;
}
@@ -239,7 +238,6 @@ transko(Im *im, Rune c)
if(joi == 0){
ni = Jongidx(jm);
if(ni > 0){
spopr(&im->pre);
sputr(&e.next, compose(ci, ji, ni));
return e;
}
@@ -248,7 +246,6 @@ transko(Im *im, Rune c)
comb = combine(cvow, nelem(cvow), jung[ji], jm);
if(comb){
ni = Jungidx(comb);
spopr(&im->pre);
sputr(&e.next, compose(ci, ni, 0));
return e;
}
@@ -260,7 +257,6 @@ transko(Im *im, Rune c)
if(comb){
ni = Jongidx(comb);
if(ni > 0){
spopr(&im->pre);
sputr(&e.next, compose(ci, ji, ni));
return e;
}
@@ -271,14 +267,12 @@ transko(Im *im, Rune c)
if(splitpair(cjong, nelem(cjong), jc, &stay, &next)){
si = Jongidx(stay);
ni = Choidx(next);
spopr(&im->pre);
sputr(&e.s, compose(ci, ji, si));
sputr(&e.next, compose(ni, vi, 0));
return e;
}
ni = Choidx(jc);
if(ni >= 0){
spopr(&im->pre);
sputr(&e.s, compose(ci, ji, 0));
sputr(&e.next, compose(ni, vi, 0));
return e;

136
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);
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);
}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->raw);
}
@@ -266,78 +266,64 @@ 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)
key = e.raw;
sputr(&key, c);
sclear(&mapped);
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)
e = im.l->trans(&im, c);
sappend(com, &e.s);
im.pre = e.next;
im.raw = e.raw;
if(e.eat && isjp(&im))
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);
return e.eat;
}
Lang*
@@ -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

View File

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

69
vi.c
View File

@@ -31,12 +31,6 @@ static struct {
{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
toneidx(Rune c)
{
@@ -114,20 +108,44 @@ onsetglide(Str *m)
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;
int i, tidx, vi, last, penult, glide;
Rune v, b1, b2;
if(!istone(c) && c != 'z')
return transmap(im, c);
mappedkey = transmap(im, c);
if(mappedkey.eat)
return mappedkey;
e = transmap(im, c);
if(e.eat)
return e;
memset(&e, 0, sizeof e);
if(im->pre.n == 0)
return e;
@@ -175,17 +193,24 @@ transvi(Im *im, Rune c)
return e;
}
if(c == 'z')
mapped.r[vi] = removetone(mapped.r[vi]);
else{
tidx = toneidx(c);
if(tidx < 0)
mapped.r[vi] = removetone(mapped.r[vi]);
else
mapped.r[vi] = applytone(mapped.r[vi], tidx);
}
e.eat = 1;
e.next = mapped;
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
backvi(Im *im)
{
@@ -200,10 +225,10 @@ backvi(Im *im)
raw = im->raw;
spopr(&raw);
sclear(&im->pre);
sclear(&im->raw);
for(i = 0; i < raw.n; i++){
e = transvi(im, raw.r[i]);
sclear(&im->pre);
sappend(&im->pre, &e.next);
im->pre = e.next;
im->raw = e.raw;
}
im->raw = raw;
}