From ab0366b7f7853a9315eaf4e97fbd2eb46c386a89 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Mon, 17 Aug 2026 01:18:09 +0900 Subject: [PATCH] engine: verbs and adjectives through SKK's okuri-ari entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A sixth of kanji.dict is SKK's okuri-ari entries, keyed by a stem and the letter of the okurigana that follows it — かk: 書 描 掛 —, and no reading ever ends in a letter, so かく offered 確 and 各 but never 書く, and no verb or adjective could be converted at all. Now every split of a complete reading is tried, longest stem first, with the okurigana put back: かく adds 書く and 描く after the exact candidates, よむ gives 読む, あかい 赤い, おおきい 大きい, いった 言った, かいた 書いた. A stem that ends in っ writes it in kana too, so いt and いっt agree. --- strans.c | 75 +++++++++++++++++++++++++++++++++++++-------- tests/engine_test.c | 26 ++++++++++++++++ 2 files changed, 89 insertions(+), 12 deletions(-) diff --git a/strans.c b/strans.c index 8234c57..257f2d7 100644 --- a/strans.c +++ b/strans.c @@ -110,6 +110,18 @@ cyclekouho(int delta) im.sel = (im.sel + delta + im.nkouho) % im.nkouho; } +static void +addkouho(Str *s) +{ + int i; + + for(i = 0; i < im.nkouho; i++) + if(scmp(&im.kouho[i], s) == 0) + return; + if(im.nkouho < Maxkouho) + im.kouho[im.nkouho++] = *s; +} + static int numberedkouho(u32int ks, u32int mod) { @@ -238,6 +250,56 @@ redraw(void) channbsend(drawc, &dc); } +/* SKK's letter for the okurigana a kana begins: its row, or its vowel. */ +static Rune +okuriletter(Rune r) +{ + static char *rows[] = { + "aあ", "iい", "uう", "eえ", "oお", + "kかきくけこ", "gがぎぐげご", "sさしすせそ", "zざじずぜぞ", + "tたちつてとっ", "dだぢづでど", "nなにぬねのん", "hはひふへほ", + "bばびぶべぼ", "pぱぴぷぺぽ", "mまみむめも", "yやゆよ", + "rらりるれろ", "wわ", + }; + int i; + + for(i = 0; i < nelem(rows); i++) + if(utfrune(rows[i]+1, r) != nil) + return rows[i][0]; + return 0; +} + +/* + * SKK keys a verb or adjective by its stem and the letter of the + * okurigana that follows (かk: 書, 描, ...). Every split of the reading + * is tried, longest stem first, and the okurigana is put back; a っ + * ending the stem (いっt: 言, 行) is written in kana too. + */ +static void +dictqokuri(Str *reading) +{ + Str key, okuri, kouho[Maxkouho]; + Rune c; + int i, j, n; + + for(i = reading->n - 1; i > 0; i--){ + c = okuriletter(reading->r[i]); + if(c == 0) + continue; + key = *reading; + key.n = i; + sputr(&key, c); + n = dictlookup(im.l, &key, kouho, Maxkouho); + sclear(&okuri); + for(j = i - (reading->r[i-1] == L'っ'); j < reading->n; j++) + sputr(&okuri, reading->r[j]); + for(j = 0; j < n; j++){ + sappend(&kouho[j], &okuri); + addkouho(&kouho[j]); + } + } +} + /* Kanji candidates for a complete reading; none is chosen yet. */ static void dictqjp(void) @@ -248,6 +310,7 @@ dictqjp(void) if(!jpreading(im.l->map, &im.pre, &im.raw, &reading) || reading.n == 0) return; im.nkouho = dictlookup(im.l, &reading, im.kouho, Maxkouho); + dictqokuri(&reading); } static int @@ -472,18 +535,6 @@ transstr(Lang *l, Str *raw, Str *out) commitim(&q, out); } -static void -addkouho(Str *s) -{ - int i; - - for(i = 0; i < im.nkouho; i++) - if(scmp(&im.kouho[i], s) == 0) - return; - if(im.nkouho < Maxkouho) - im.kouho[im.nkouho++] = *s; -} - /* The typed keys and their transliteration are both emoji queries. */ static void emojiquery(void) diff --git a/tests/engine_test.c b/tests/engine_test.c index ee54571..4df825d 100644 --- a/tests/engine_test.c +++ b/tests/engine_test.c @@ -1200,6 +1200,32 @@ engine_japanese_candidates(struct ct *t) checkstr(t, "typo mended", "かんじ", &shown); CT_EQ_INT(t, 2, im.nkouho); CT_CHECK(t, keystroke(Kesc, 0, &com)); + /* SKK's okuri-ari entries convert verbs: stem plus okurigana. */ + setdict(jp->dict, "かk", "書 描"); + setdict(jp->dict, "かく", "確"); + if(!typekeys(t, "kaku", &com)) + goto cleanup; + if(CT_EQ_INT(t, 3, im.nkouho)){ + checkstr(t, "exact candidate first", "確", &im.kouho[0]); + checkstr(t, "okurigana put back", "書く", &im.kouho[1]); + checkstr(t, "second okuri candidate", "描く", &im.kouho[2]); + } + CT_CHECK(t, keystroke(Kesc, 0, &com)); + setdict(jp->dict, "おおk", "大"); + if(!typekeys(t, "ookii", &com)) + goto cleanup; + if(CT_EQ_INT(t, 1, im.nkouho)) + checkstr(t, "two-kana okurigana", "大きい", &im.kouho[0]); + CT_CHECK(t, keystroke(Kesc, 0, &com)); + setdict(jp->dict, "いt", "言 行"); + setdict(jp->dict, "いっt", "言 行"); + if(!typekeys(t, "itta", &com)) + goto cleanup; + if(CT_EQ_INT(t, 2, im.nkouho)){ + checkstr(t, "sokuon written in kana", "言った", &im.kouho[0]); + checkstr(t, "sokuon written in kana", "行った", &im.kouho[1]); + } + CT_CHECK(t, keystroke(Kesc, 0, &com)); /* Backspace looks a complete shorter reading up again. */ setdict(jp->dict, "かん", "缶"); if(!typekeys(t, "kanji", &com))