Files

157 lines
6.1 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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;
}