Files
strans/vi.c
Hojun-Cho a557fb114a 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.
2026-08-16 16:04:57 +09:00

235 lines
4.8 KiB
C

#include "dat.h"
#include "fn.h"
static struct {
Rune base;
Rune tone[5]; /* s f r x j */
} vitone[] = {
{L'a', {L'á', L'à', L'', L'ã', L''}},
{L'â', {L'', L'', L'', L'', L''}},
{L'ă', {L'', L'', L'', L'', L''}},
{L'e', {L'é', L'è', L'', L'', L''}},
{L'ê', {L'ế', L'', L'', L'', L''}},
{L'i', {L'í', L'ì', L'', L'ĩ', L''}},
{L'o', {L'ó', L'ò', L'', L'õ', L''}},
{L'ô', {L'', L'', L'', L'', L''}},
{L'ơ', {L'', L'', L'', L'', L''}},
{L'u', {L'ú', L'ù', L'', L'ũ', L''}},
{L'ư', {L'', L'', L'', L'', L''}},
{L'y', {L'ý', L'', L'', L'', L''}},
{L'A', {L'Á', L'À', L'', L'Ã', L''}},
{L'Â', {L'', L'', L'', L'', L''}},
{L'Ă', {L'', L'', L'', L'', L''}},
{L'E', {L'É', L'È', L'', L'', L''}},
{L'Ê', {L'', L'', L'', L'', L''}},
{L'I', {L'Í', L'Ì', L'', L'Ĩ', L''}},
{L'O', {L'Ó', L'Ò', L'', L'Õ', L''}},
{L'Ô', {L'', L'', L'', L'', L''}},
{L'Ơ', {L'', L'', L'', L'', L''}},
{L'U', {L'Ú', L'Ù', L'', L'Ũ', L''}},
{L'Ư', {L'', L'', L'', L'', L''}},
{L'Y', {L'Ý', L'', L'', L'', L''}},
};
static int
toneidx(Rune c)
{
switch(c){
case 's': return 0;
case 'f': return 1;
case 'r': return 2;
case 'x': return 3;
case 'j': return 4;
}
return -1;
}
static int
isvowel(Rune c)
{
int i;
for(i = 0; i < nelem(vitone); i++)
if(vitone[i].base == c)
return 1;
return 0;
}
static Rune
removetone(Rune c)
{
int i, j;
for(i = 0; i < nelem(vitone); i++){
if(vitone[i].base == c)
return c;
for(j = 0; j < 5; j++)
if(vitone[i].tone[j] == c)
return vitone[i].base;
}
return c;
}
static Rune
applytone(Rune c, int tidx)
{
int i;
Rune base;
base = removetone(c);
for(i = 0; i < nelem(vitone); i++)
if(vitone[i].base == base)
return vitone[i].tone[tidx];
return c;
}
/*
* In qu- and gi- onsets the u/i is a glide that belongs to the onset,
* not the rime, so it must not bear the tone (qua -> quá, gia -> giá).
* The i of gi- is only a glide when another vowel follows; otherwise it
* is the nucleus itself (gì, gìn). Returns the rune index to skip, or -1.
*/
static int
onsetglide(Str *m)
{
Rune a, b;
int i;
if(m->n < 2)
return -1;
a = m->r[0];
b = removetone(m->r[1]);
if((a == 'q' || a == 'Q') && (b == 'u' || b == 'U'))
return 1;
if((a == 'g' || a == 'G') && (b == 'i' || b == 'I'))
for(i = 2; i < m->n; i++)
if(isvowel(removetone(m->r[i])))
return 1;
return -1;
}
/*
* 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)
{
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;
e = transmap(im, c);
if(e.eat)
return e;
memset(&e, 0, sizeof e);
if(im->pre.n == 0)
return e;
if(im->pre.r[im->pre.n - 1] == '\\'){
pre = im->pre;
pre.n--;
if(!mapget(im->l->map, &pre, &mapped))
mapped = pre;
sputr(&mapped, c);
e.eat = 1;
e.s = mapped;
return e;
}
if(!mapget(im->l->map, &im->pre, &mapped))
mapped = im->pre;
glide = onsetglide(&mapped);
last = -1;
penult = -1;
for(i = 0; i < mapped.n; i++){
if(i == glide)
continue;
v = removetone(mapped.r[i]);
if(isvowel(v)){
penult = last;
last = i;
}
}
vi = -1;
if(last >= 0){
if(last < mapped.n - 1 || penult < 0)
vi = last;
else{
b1 = removetone(mapped.r[penult]);
b2 = removetone(mapped.r[last]);
if((b1 == 'o' || b1 == 'O') && (b2 == 'a' || b2 == 'A' || b2 == 'e' || b2 == 'E'))
vi = last;
else if((b1 == 'u' || b1 == 'U') && (b2 == 'y' || b2 == 'Y'))
vi = last;
else
vi = penult;
}
}
if(vi < 0){
e.s = mapped;
return e;
}
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)
{
Emit e;
Str raw;
int i;
if(im->raw.n == 0){
spopr(&im->pre);
return;
}
raw = im->raw;
spopr(&raw);
sclear(&im->pre);
sclear(&im->raw);
for(i = 0; i < raw.n; i++){
e = transvi(im, raw.r[i]);
im->pre = e.next;
im->raw = e.raw;
}
}