git-subtree-dir: sdk git-subtree-mainline:17a972d46fgit-subtree-split:2e7561d74f
157 lines
6.1 KiB
C
157 lines
6.1 KiB
C
/*
|
||
* 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 <string.h>
|
||
#include <stdlib.h>
|
||
|
||
#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;
|
||
}
|