1 /*
2 * Word Dictionary
3 * ファイルの辞書のインターフェース、存在するデータは
4 * キャッシュされるのでここでは存在しない単語の
5 * サーチを高速にする必要がある。
6 *
7 * anthy_gang_fill_seq_ent()が中心となる関数である
8 * 指定したword_dicから指定した文字列をインデックスとしてもつエントリに
9 * 語尾を付加してseq_entに追加する
10 *
11 * a)辞書の形式とb)辞書アクセスの高速化c)辞書ファイルのエンコーディング
12 * このソース中で扱ってるのでかなり複雑化してます.
13 *
14 * Copyright (C) 2000-2007 TABATA Yusuke
15 * Copyright (C) 2005-2006 YOSHIDA Yuichi
16 * Copyright (C) 2001-2002 TAKAI Kosuke
17 *
18 */
19 /*
20 This library is free software; you can redistribute it and/or
21 modify it under the terms of the GNU Lesser General Public
22 License as published by the Free Software Foundation; either
23 version 2 of the License, or (at your option) any later version.
24
25 This library is distributed in the hope that it will be useful,
26 but WITHOUT ANY WARRANTY; without even the implied warranty of
27 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
28 Lesser General Public License for more details.
29
30 You should have received a copy of the GNU Lesser General Public
31 License along with this library; if not, write to the Free Software
32 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
33 */
34
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <ctype.h>
39
40 #include "config.h"
41 #include <anthy/anthy.h>
42 #include <anthy/alloc.h>
43 #include <anthy/dic.h>
44 #include <anthy/word_dic.h>
45 #include <anthy/logger.h>
46 #include <anthy/xstr.h>
47 #include <anthy/diclib.h>
48
49 #include "dic_main.h"
50 #include "dic_ent.h"
51
52 #define NO_WORD -1
53
54 static allocator word_dic_ator;
55
56 struct lookup_context {
57 struct gang_elm **array;
58 int nr;
59 int nth;
60 int is_reverse;
61 };
62
63 /* 1バイト目を見て、文字が何バイトあるかを返す */
64 static int
mb_fragment_len(const char * str)65 mb_fragment_len(const char *str)
66 {
67 unsigned char c = *((const unsigned char *)str);
68 if (c < 0x80) {
69 return 1;
70 }
71 if (c < 0xe0) {
72 return 2;
73 }
74 if (c < 0xf0) {
75 return 3;
76 }
77 if (c < 0xf8) {
78 return 4;
79 }
80 if (c < 0xfc) {
81 return 5;
82 }
83 return 6;
84 }
85
86 static int
is_printable(char * str)87 is_printable(char *str)
88 {
89 unsigned char *tmp = (unsigned char *)str;
90 if (*tmp > 31 && *tmp < 127) {
91 return 1;
92 }
93 if (mb_fragment_len(str) > 1) {
94 return 1;
95 }
96 return 0;
97 }
98
99 /* 辞書のエンコーディングからxcharを作る */
100 static xchar
form_mb_char(const char * str)101 form_mb_char(const char *str)
102 {
103 xchar xc;
104 anthy_utf8_to_ucs4_xchar(str, &xc);
105 return xc;
106 }
107
108 static int
hash(xstr * x)109 hash(xstr *x)
110 {
111 return anthy_xstr_hash(x)&
112 (YOMI_HASH_ARRAY_SIZE*YOMI_HASH_ARRAY_BITS-1);
113 }
114
115 static int
check_hash_ent(struct word_dic * wdic,xstr * xs)116 check_hash_ent(struct word_dic *wdic, xstr *xs)
117 {
118 int val = hash(xs);
119 int idx = (val>>YOMI_HASH_ARRAY_SHIFT)&(YOMI_HASH_ARRAY_SIZE-1);
120 int bit = val & ((1<<YOMI_HASH_ARRAY_SHIFT)-1);
121 return wdic->hash_ent[idx] & (1<<bit);
122 }
123
124 static int
wtype_str_len(const char * str)125 wtype_str_len(const char *str)
126 {
127 int i;
128 for (i = 0; str[i] && str[i]!= ' '; i++);
129 return i;
130 }
131
132 /* 辞書の行中をスキャンするための状態保持 */
133 struct wt_stat {
134 wtype_t wt;
135 const char *wt_name;
136 int feature;
137 int freq;
138 int order_bonus;/* 辞書中の順序による頻度のボーナス */
139 int offset;/* 文字列中のオフセット */
140 const char *line;
141 int encoding;
142 };
143 /*
144 * #XX*123 というCannadicの形式をパーズする
145 * #XX
146 * #XX*123
147 * #XX,x*123
148 */
149 static const char *
parse_wtype_str(struct wt_stat * ws)150 parse_wtype_str(struct wt_stat *ws)
151 {
152 int len;
153 char *buf;
154 char *freq_part;
155 char *feature_part;
156 const char *wt_name;
157 /* バッファへコピーする */
158 len = wtype_str_len(&ws->line[ws->offset]);
159 buf = alloca(len + 1);
160 strncpy(buf, &ws->line[ws->offset], len);
161 buf[len] = 0;
162
163 /* 素性(未使用) */
164 feature_part = strchr(buf, ',');
165 if (feature_part) {
166 ws->feature = 1;
167 } else {
168 ws->feature = 0;
169 }
170
171 /* 頻度をparseする */
172 freq_part = strchr(buf, '*');
173 if (freq_part) {
174 *freq_part = 0;
175 freq_part ++;
176 ws->freq = atoi(freq_part) * FREQ_RATIO;
177 } else {
178 ws->freq = FREQ_RATIO - 2;
179 }
180
181 /**/
182 wt_name = anthy_type_to_wtype(buf, &ws->wt);
183 if (!wt_name) {
184 ws->wt = anthy_wt_none;
185 }
186 ws->offset += len;
187 return wt_name;
188 }
189
190
191 static int
normalize_freq(struct wt_stat * ws)192 normalize_freq(struct wt_stat* ws)
193 {
194 if (ws->freq < 0) {
195 ws->freq *= -1;
196 }
197 return ws->freq + ws->order_bonus;
198 }
199
200 /* '\\'によるエスケープに対応したコピー */
201 static void
copy_to_buf(char * buf,const char * src,int char_count)202 copy_to_buf(char *buf, const char *src, int char_count)
203 {
204 int pos;
205 int i;
206 pos = 0;
207 for (i = 0; i < char_count; i++){
208 if (src[i] == '\\') {
209 if (src[i + 1] == ' ') {
210 i ++;
211 } else if (src[i + 1] == '\\') {
212 i ++;
213 }
214 }
215 buf[pos] = src[i];
216 pos ++;
217 }
218 buf[pos] = 0;
219 }
220
221 /** seq_entにdic_entを追加する */
222 static int
add_dic_ent(struct seq_ent * seq,struct wt_stat * ws,xstr * yomi,int is_reverse)223 add_dic_ent(struct seq_ent *seq, struct wt_stat *ws,
224 xstr* yomi, int is_reverse)
225 {
226 int i;
227 /* 辞書ファイル中のバイト数 */
228 int char_count;
229 char *buf;
230 xstr *xs;
231 int freq;
232 wtype_t w = ws->wt;
233 const char *s = &ws->line[ws->offset];
234
235 /* 単語の文字数を計算 */
236 for (i = 0, char_count = 0;
237 s[i] && (s[i] != ' ') && (s[i] != '#'); i++) {
238 char_count ++;
239 if (s[i] == '\\') {
240 char_count++;
241 i++;
242 }
243 }
244
245 /* 品詞が定義されていないので無視 */
246 if (!ws->wt_name) {
247 return char_count;
248 }
249
250 /* freqが負なのは逆変換用 */
251 if (!is_reverse && ws->freq < 0) {
252 return char_count;
253 }
254
255 /* bufに単語をコピー */
256 buf = alloca(char_count+1);
257 copy_to_buf(buf, s, char_count);
258
259 xs = anthy_cstr_to_xstr(buf, ws->encoding);
260
261 /* freqが正なのは順変換用 */
262 if (is_reverse && ws->freq > 0) {
263 /* 再変換の際に、変換済みの部分と未変換の部分が混じっていた場合に対応する為に、
264 平仮名のみからなる部分は順辞書にその読みを持つ単語があればdic_entを生成する。
265 */
266 if (anthy_get_xstr_type(yomi) & XCT_HIRA) {
267 freq = normalize_freq(ws);
268 anthy_mem_dic_push_back_dic_ent(seq, 0, yomi, w,
269 ws->wt_name, freq, 0);
270 }
271 anthy_free_xstr(xs);
272 return char_count;
273 }
274
275 freq = normalize_freq(ws);
276
277 anthy_mem_dic_push_back_dic_ent(seq, 0, xs, w, ws->wt_name, freq, 0);
278 anthy_free_xstr(xs);
279 return char_count;
280 }
281
282 static int
add_compound_ent(struct seq_ent * seq,struct wt_stat * ws,xstr * yomi,int is_reverse)283 add_compound_ent(struct seq_ent *seq, struct wt_stat *ws,
284 xstr* yomi,
285 int is_reverse)
286 {
287 int len = wtype_str_len(&ws->line[ws->offset]);
288 char *buf = alloca(len);
289 xstr *xs;
290 int freq;
291
292 (void)yomi;
293
294 /* freqが負なのは逆変換用 */
295 if (!is_reverse && ws->freq < 0) {
296 /* 普段の変換では要らない */
297 return len;
298 }
299
300 /* freqが正なのは順変換用 */
301 if (is_reverse && ws->freq > 0) {
302
303 /* 再変換の際に、変換済みの部分と未変換の部分が混じっていた場合に対応する為に、
304 平仮名のみからなる部分は順辞書にその読みを持つ単語があればdic_entを生成する。
305 */
306 /*
307 yomiに#_等を付加した文字列を作る必要がある
308 if (anthy_get_xstr_type(yomi) & (XCT_HIRA | XCT_KATA)) {
309 freq = normalize_freq(ws);
310 anthy_mem_dic_push_back_compound_ent(seq, xs, ws->wt, freq);
311 }
312 */
313 return len;
314 }
315
316 strncpy(buf, &ws->line[ws->offset + 1], len - 1);
317 buf[len - 1] = 0;
318 xs = anthy_cstr_to_xstr(buf, ws->encoding);
319
320 freq = normalize_freq(ws);
321 anthy_mem_dic_push_back_dic_ent(seq, 1, xs, ws->wt,
322 ws->wt_name, freq, 0);
323 anthy_free_xstr(xs);
324
325 return len;
326 }
327
328 static void
init_wt_stat(struct wt_stat * ws,char * line)329 init_wt_stat(struct wt_stat *ws, char *line)
330 {
331 ws->wt_name = NULL;
332 ws->freq = 0;
333 ws->feature = 0;
334 ws->order_bonus = 0;
335 ws->offset = 0;
336 ws->line = line;
337 ws->encoding = ANTHY_EUC_JP_ENCODING;
338 if (*(ws->line) == 'u') {
339 ws->encoding = ANTHY_UTF8_ENCODING;
340 ws->line ++;
341 }
342 }
343
344 /** 辞書のエントリの情報を元にseq_entをうめる */
345 static void
fill_dic_ent(char * line,struct seq_ent * seq,xstr * yomi,int is_reverse)346 fill_dic_ent(char *line, struct seq_ent *seq,
347 xstr* yomi, int is_reverse)
348 {
349 struct wt_stat ws;
350 init_wt_stat(&ws, line);
351
352 while (ws.line[ws.offset]) {
353 if (ws.line[ws.offset] == '#') {
354 if (isalpha(ws.line[ws.offset + 1])) {
355 /* 品詞*頻度 */
356 ws.wt_name = parse_wtype_str(&ws);
357 /**/
358 ws.order_bonus = FREQ_RATIO - 1;
359 } else {
360 /* 複合語候補 */
361 ws.offset += add_compound_ent(seq, &ws,
362 yomi,
363 is_reverse);
364 }
365 } else {
366 /* 単語 */
367 ws.offset += add_dic_ent(seq, &ws, yomi,
368 is_reverse);
369 if (ws.order_bonus > 0) {
370 ws.order_bonus --;
371 }
372 }
373 if (ws.line[ws.offset] == ' ') {
374 ws.offset++;
375 }
376 }
377 }
378
379 /*
380 * sに書かれた文字列によってxを変更する
381 * 返り値は読み進めたバイト数
382 */
383 static int
mkxstr(char * s,xstr * x)384 mkxstr(char *s, xstr *x)
385 {
386 int i, len;
387 /* s[0]には巻き戻しの文字数 */
388 x->len -= (s[0] - 1);
389 for (i = 1; is_printable(&s[i]); i ++) {
390 len = mb_fragment_len(&s[i]);
391 if (len > 1) {
392 /* マルチバイト */
393 x->str[x->len] = form_mb_char(&s[i]);
394 x->len ++;
395 i += (len - 1);
396 } else {
397 /* 1バイト文字 */
398 x->str[x->len] = s[i];
399 x->len ++;
400 }
401 }
402 return i;
403 }
404
405 static int
set_next_idx(struct lookup_context * lc)406 set_next_idx(struct lookup_context *lc)
407 {
408 lc->nth ++;
409 while (lc->nth < lc->nr) {
410 if (lc->array[lc->nth]->tmp.idx != NO_WORD) {
411 return 1;
412 }
413 lc->nth ++;
414 }
415 return 0;
416 }
417
418 /** ページ中の単語の場所を調べる */
419 static void
search_words_in_page(struct lookup_context * lc,int page,char * s)420 search_words_in_page(struct lookup_context *lc, int page, char *s)
421 {
422 int o = 0;
423 xchar *buf;
424 xstr xs;
425 int nr = 0;
426 /* このページ中にあるもっとも長い単語を格納しうる長さ */
427 buf = alloca(sizeof(xchar)*strlen(s)/2);
428 xs.str = buf;
429 xs.len = 0;
430
431 while (*s) {
432 int r;
433 s += mkxstr(s, &xs);
434 r = anthy_xstrcmp(&xs, &lc->array[lc->nth]->xs);
435 if (!r) {
436 lc->array[lc->nth]->tmp.idx = o + page * WORDS_PER_PAGE;
437 nr ++;
438 if (!set_next_idx(lc)) {
439 return ;
440 }
441 /* 同じページ内で次の単語を探す */
442 }
443 o ++;
444 }
445 if (nr == 0) {
446 /* このページで1語も見つからなかったら、この単語は無い */
447 lc->array[lc->nth]->tmp.idx = NO_WORD;
448 set_next_idx(lc);
449 }
450 /* 現在の単語は次の呼び出しで探す */
451 }
452
453 /**/
454 static int
compare_page_index(struct word_dic * wdic,const char * key,int page)455 compare_page_index(struct word_dic *wdic, const char *key, int page)
456 {
457 char buf[100];
458 char *s = &wdic->page[anthy_dic_ntohl(wdic->page_index[page])];
459 int i;
460 s++;
461 for (i = 0; is_printable(&s[i]);) {
462 int j, l = mb_fragment_len(&s[i]);
463 for (j = 0; j < l; j++) {
464 buf[i+j] = s[i+j];
465 }
466 i += l;
467 }
468 buf[i] = 0;
469 return strcmp(key ,buf);
470 }
471
472 /* 再帰的にバイナリサーチをする */
473 static int
get_page_index_search(struct word_dic * wdic,const char * key,int f,int t)474 get_page_index_search(struct word_dic *wdic, const char *key, int f, int t)
475 {
476 /* anthy_xstrcmpが-1で無くなったところを探す */
477 int c,p;
478 c = (f+t)/2;
479 if (f+1==t) {
480 return c;
481 } else {
482 p = compare_page_index(wdic, key, c);
483 if (p < 0) {
484 return get_page_index_search(wdic, key, f, c);
485 } else {
486 /* c<= <t */
487 return get_page_index_search(wdic, key, c, t);
488 }
489 }
490 }
491
492 /** keyを含む可能性のあるページの番号を得る、
493 * 範囲チェックをしてバイナリサーチを行うget_page_index_searchを呼ぶ
494 */
495 static int
get_page_index(struct word_dic * wdic,struct lookup_context * lc)496 get_page_index(struct word_dic *wdic, struct lookup_context *lc)
497 {
498 int page;
499 const char *key = lc->array[lc->nth]->key;
500 /* 最初のページの読みよりも小さい */
501 if (compare_page_index(wdic, key, 0) < 0) {
502 return -1;
503 }
504 /* 最後のページの読みよりも大きいので、最後のページに含まれる可能性がある */
505 if (compare_page_index(wdic, key, wdic->nr_pages-1) >= 0) {
506 return wdic->nr_pages-1;
507 }
508 /* 検索する */
509 page = get_page_index_search(wdic, key, 0, wdic->nr_pages);
510 return page;
511 }
512
513 static int
get_nr_page(struct word_dic * h)514 get_nr_page(struct word_dic *h)
515 {
516 int i;
517 for (i = 1; anthy_dic_ntohl(h->page_index[i]); i++);
518 return i;
519 }
520
521 static char *
get_section(struct word_dic * wdic,int section)522 get_section(struct word_dic *wdic, int section)
523 {
524 int *p = (int *)wdic->dic_file;
525 int offset = anthy_dic_ntohl(p[section]);
526 return &wdic->dic_file[offset];
527 }
528
529 /** 辞書ファイルをmmapして、word_dic中の各セクションのポインタを取得する */
530 static int
get_word_dic_sections(struct word_dic * wdic)531 get_word_dic_sections(struct word_dic *wdic)
532 {
533 wdic->entry_index = (int *)get_section(wdic, 2);
534 wdic->entry = (char *)get_section(wdic, 3);
535 wdic->page = (char *)get_section(wdic, 4);
536 wdic->page_index = (int *)get_section(wdic, 5);
537 wdic->uc_section = (char *)get_section(wdic, 6);
538 wdic->hash_ent = (unsigned char *)get_section(wdic, 7);
539
540 return 0;
541 }
542
543 /** 指定された単語の辞書中のインデックスを調べる */
544 static void
search_yomi_index(struct word_dic * wdic,struct lookup_context * lc)545 search_yomi_index(struct word_dic *wdic, struct lookup_context *lc)
546 {
547 int p;
548 int page_number;
549
550 /* すでに無いことが分かっている */
551 if (lc->array[lc->nth]->tmp.idx == NO_WORD) {
552 set_next_idx(lc);
553 return ;
554 }
555
556 p = get_page_index(wdic, lc);
557 if (p == -1) {
558 lc->array[lc->nth]->tmp.idx = NO_WORD;
559 set_next_idx(lc);
560 return ;
561 }
562
563 page_number = anthy_dic_ntohl(wdic->page_index[p]);
564 search_words_in_page(lc, p, &wdic->page[page_number]);
565 }
566
567 static void
find_words(struct word_dic * wdic,struct lookup_context * lc)568 find_words(struct word_dic *wdic, struct lookup_context *lc)
569 {
570 int i;
571 /* 検索前に除去 */
572 for (i = 0; i < lc->nr; i++) {
573 lc->array[i]->tmp.idx = NO_WORD;
574 if (lc->array[i]->xs.len > 31) {
575 /* 32文字以上単語には未対応 */
576 continue;
577 }
578 /* hashにないなら除去 */
579 if (!check_hash_ent(wdic, &lc->array[i]->xs)) {
580 continue;
581 }
582 /* NO_WORDでない値を設定することで検索対象とする */
583 lc->array[i]->tmp.idx = 0;
584 }
585 /* 検索する */
586 lc->nth = 0;
587 while (lc->nth < lc->nr) {
588 search_yomi_index(wdic, lc);
589 }
590 }
591
592 static void
load_words(struct word_dic * wdic,struct lookup_context * lc)593 load_words(struct word_dic *wdic, struct lookup_context *lc)
594 {
595 int i;
596 for (i = 0; i < lc->nr; i++) {
597 int yomi_index;
598 yomi_index = lc->array[i]->tmp.idx;
599 if (yomi_index != NO_WORD) {
600 int entry_index;
601 struct seq_ent *seq;
602 seq = anthy_cache_get_seq_ent(&lc->array[i]->xs,
603 lc->is_reverse);
604 entry_index = anthy_dic_ntohl(wdic->entry_index[yomi_index]);
605 fill_dic_ent(&wdic->entry[entry_index],
606 seq,
607 &lc->array[i]->xs,
608 lc->is_reverse);
609 anthy_validate_seq_ent(seq, &lc->array[i]->xs, lc->is_reverse);
610 }
611 }
612 }
613
614 /** word_dicから単語を検索する
615 * 辞書キャッシュから呼ばれる
616 * (gang lookupにすることを検討する)
617 */
618 void
anthy_gang_fill_seq_ent(struct word_dic * wdic,struct gang_elm ** array,int nr,int is_reverse)619 anthy_gang_fill_seq_ent(struct word_dic *wdic,
620 struct gang_elm **array, int nr,
621 int is_reverse)
622 {
623 struct lookup_context lc;
624 lc.array = array;
625 lc.nr = nr;
626 lc.is_reverse = is_reverse;
627
628 /* 各単語の場所を探す */
629 find_words(wdic, &lc);
630 /* 単語の情報を読み込む */
631 load_words(wdic, &lc);
632 }
633
634 struct word_dic *
anthy_create_word_dic(void)635 anthy_create_word_dic(void)
636 {
637 struct word_dic *wdic;
638
639 wdic = anthy_smalloc(word_dic_ator);
640 memset(wdic, 0, sizeof(*wdic));
641
642 /* 辞書ファイルをマップする */
643 wdic->dic_file = anthy_file_dic_get_section("word_dic");
644
645 /* 各セクションのポインタを取得する */
646 if (get_word_dic_sections(wdic) == -1) {
647 anthy_sfree(word_dic_ator, wdic);
648 return 0;
649 }
650 wdic->nr_pages = get_nr_page(wdic);
651
652 /* 用例辞書をマップする */
653 return wdic;
654 }
655
656 void
anthy_release_word_dic(struct word_dic * wdic)657 anthy_release_word_dic(struct word_dic *wdic)
658 {
659 anthy_sfree(word_dic_ator, wdic);
660 }
661
662 void
anthy_init_word_dic(void)663 anthy_init_word_dic(void)
664 {
665 word_dic_ator = anthy_create_allocator(sizeof(struct word_dic), NULL);
666 }
667