From a557fb114af6fcccff2eaa7762153525d59fbbfb Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sun, 16 Aug 2026 16:04:57 +0900 Subject: [PATCH] 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. --- dat.h | 6 ++ ko.c | 16 ++---- strans.c | 144 ++++++++++++++++++++---------------------------- tests/vi_test.c | 1 + vi.c | 69 +++++++++++++++-------- 5 files changed, 118 insertions(+), 118 deletions(-) diff --git a/dat.h b/dat.h index 0d285ea..1bd5039 100644 --- a/dat.h +++ b/dat.h @@ -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; diff --git a/ko.c b/ko.c index 03fa4eb..e2e29a4 100644 --- a/ko.c +++ b/ko.c @@ -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); - } + e.s = 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; diff --git a/strans.c b/strans.c index c07af48..fba78c3 100644 --- a/strans.c +++ b/strans.c @@ -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 diff --git a/tests/vi_test.c b/tests/vi_test.c index 752c7ff..c91282c 100644 --- a/tests/vi_test.c +++ b/tests/vi_test.c @@ -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); } diff --git a/vi.c b/vi.c index c5ccbd3..a855d91 100644 --- a/vi.c +++ b/vi.c @@ -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') + tidx = toneidx(c); + if(tidx < 0) mapped.r[vi] = removetone(mapped.r[vi]); - else{ - tidx = toneidx(c); + 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; }