/* * meow_pinyin.c — 中文拼音Ⅰ级候选引擎(静态表驱动,纯 C,零堆)。 * * 词库在 meow_pinyin_lex.h(gen_pinyin_lex.py 离线生成,.rodata)。 * 供 meow_ui_ime.c(MEOW_IME_PINYIN 模式)调用;SDK 集成时本 TU 进 meow_ui.a。 * * 查询 = 当前组合拼音串(去声调小写,如 "nihao")→ 候选列表(UTF-8)。 * 排序规则(与设计稿 §4.4 / /tmp/pyim engine.c 一致): * 1) 短语精确命中(sp==输入,如 nihao→你好)最优先; * 2) 音节切分组合(ni+hao→你好/你号/呢好…)按「每音节候选字序号之和」升序; * 3) 前缀整词提示(sp 以输入为前缀,如 ni→你好/你好吗)排最后(边打边出整词)。 * * 设计约束:无 stdio(拉体积),无动态分配(MIPS32/14MB 意识)。切分 DFS + 组合 * 上限常量封死(MAXSPLIT/MAXCOMB),防长组合串爆炸。词表按 sp 前缀匹配(线性回溯)。 */ #include "meow_pinyin_lex.h" #include #include #define MAXCAND 32 /* 每轮候选上限(与 IME 候选数组对齐) */ #define MAXSPLIT 64 /* 切分枚举上限 */ #define MAXSYL 8 /* 一次组合的音节数上限 */ #define TK 4 /* 每音节取前 K 个字符再组合(限组合数) */ #define MAXCOMB 120 /* 笛卡尔组合 guard(防爆炸) */ typedef struct { char hz[64]; int score; int src; int seg; } cand; /* src: 0=短语 1=切分组合 2=前缀词提示; seg=切分音节数 */ /* 定长拷贝一个 NUL 结尾字符串到 d[64];用 memcpy 规避 -Wstringop-truncation(来源可能超 63)。 */ static void set_hz(char *d, const char *s) { size_t n = strlen(s); if (n > 63) n = 63; memcpy(d, s, n); d[n] = 0; } /* 收集某音节候选字(UTF-8 单字),填入 chars[][4](UTF-8 ≤3B + NUL)。返回字数。 */ static int syl_chars(int s, char chars[][4], int maxk) { const char *h = SYL[s].hz; int n = 0, i = 0; while (h[i] && n < maxk) { int l = 1; if ((unsigned char)h[i] >= 0xF0) l = 4; else if ((unsigned char)h[i] >= 0xE0) l = 3; else if ((unsigned char)h[i] >= 0xC0) l = 2; if (n < maxk) { memcpy(chars[n], h + i, (size_t)l); chars[n][l] = 0; n++; } i += l; } return n; } /* 枚举 py 全部合法音节切分(DFS)。g_split[i]=切分路径(音节下标数组)。 */ static int g_split[MAXSPLIT][MAXSYL]; static int g_splitlen[MAXSPLIT]; static int g_ns; static void dfs_split(const char *py, int i, int path[], int plen) { if (g_ns >= MAXSPLIT) return; if (py[i] == 0) { if (plen == 0) return; memcpy(g_split[g_ns], path, (size_t)plen * sizeof(int)); g_splitlen[g_ns] = plen; g_ns++; return; } for (int s = 0; s < SYL_N && g_ns < MAXSPLIT; s++) { int l = (int)strlen(SYL[s].py); if (strncmp(py + i, SYL[s].py, (size_t)l) == 0) { path[plen] = s; dfs_split(py, i + l, path, plen + 1); } } } static int cmp_cand(const void *a, const void *b) { const cand *x = (const cand *)a, *y = (const cand *)b; if (x->score != y->score) return x->score - y->score; /* 升序:score 越小越常用 */ if (x->seg != y->seg) return x->seg - y->seg; /* score 相同:音节数越少越优先(完整音节读法) */ return x->src - y->src; } static void dedupe(cand *c, int *n) { for (int i = 0; i < *n; i++) for (int j = i + 1; j < *n; j++) if (strcmp(c[i].hz, c[j].hz) == 0) { c[j].score = 99999; } int w = 0; for (int i = 0; i < *n; i++) if (c[i].score != 99999) c[w++] = c[i]; *n = w; } /* 主入口:输入当前拼音组合串(去声调),输出候选列表(UTF-8),返回个数。 * out 至少 maxout 项,每项定长 64B(拷贝,非持久)。 */ int py_predict(const char *py, char out[][64], int maxout) { cand cs[MAXCAND]; int nc = 0; const int len = (int)strlen(py); if (len == 0 || maxout <= 0) return 0; if (maxout > MAXCAND) maxout = MAXCAND; /* 1) 短语精确命中(band 0:最优先)。词表可能多条同拼(如 'shi'/'dui' 单字词),按序给 rank。 */ int exact_rank = 0; for (int i = 0; i < WORD_N && nc < MAXCAND; i++) { if (strcmp(WORDS[i].sp, py) == 0) { set_hz(cs[nc].hz, WORDS[i].hz); cs[nc].score = exact_rank++; cs[nc].src = 0; cs[nc].seg = 1; nc++; } } /* 2) 前缀词提示(band 2:边打边出整词,排在单字组合之后) */ int pre_rank = 0; for (int i = 0; i < WORD_N && nc < MAXCAND; i++) { int wl = (int)strlen(WORDS[i].sp); if (wl > len && strncmp(WORDS[i].sp, py, (size_t)len) == 0) { set_hz(cs[nc].hz, WORDS[i].hz); cs[nc].score = 200 + pre_rank++; cs[nc].src = 2; cs[nc].seg = 1; nc++; } } /* 3) 音节切分组合 */ g_ns = 0; { int path[MAXSYL]; dfs_split(py, 0, path, 0); } for (int s = 0; s < g_ns && nc < MAXCAND; s++) { const int *seg = g_split[s]; int nseg = g_splitlen[s]; char cset[MAXSYL][TK][4]; int cnt[MAXSYL]; for (int k = 0; k < nseg; k++) cnt[k] = syl_chars(seg[k], cset[k], TK); int iter[MAXSYL]; for (int k = 0; k < nseg; k++) iter[k] = 0; int guard = 0; while (guard++ < MAXCOMB && nc < MAXCAND) { char buf[64]; buf[0] = 0; int score = 0; for (int k = 0; k < nseg; k++) { strcat(buf, cset[k][iter[k]]); score += iter[k]; } if (buf[0]) { set_hz(cs[nc].hz, buf); cs[nc].score = 100 + score; /* band 1:切分组合(sum of char index 越小越常用) */ cs[nc].src = 1; cs[nc].seg = nseg; nc++; } int k = nseg - 1; while (k >= 0) { iter[k]++; if (iter[k] < cnt[k]) break; iter[k] = 0; k--; } if (k < 0) break; } } dedupe(cs, &nc); qsort(cs, (size_t)nc, sizeof(cand), cmp_cand); if (nc > maxout) nc = maxout; for (int i = 0; i < nc; i++) { memcpy(out[i], cs[i].hz, 64); } return nc; }