1 /*
2  * 文節の最小単位であるwordlistを構成する
3  *
4  * anthy_make_word_list_all()
5  * 文節の形式を満たす部分文字列を列挙する
6  *  いくかの経路で列挙されたword_listは
7  *  anthy_commit_word_listでsplitter_contextに追加される
8  *
9  * Funded by IPA未踏ソフトウェア創造事業 2002 2/27
10  * Copyright (C) 2000-2006 TABATA Yusuke
11  * Copyright (C) 2004-2006 YOSHIDA Yuichi
12  * Copyright (C) 2000-2003 UGAWA Tomoharu
13  *
14  */
15 
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <string.h>
19 #include <arpa/inet.h>
20 
21 #include <anthy/alloc.h>
22 #include <anthy/record.h>
23 #include <anthy/xstr.h>
24 #include <anthy/diclib.h>
25 #include <anthy/wtype.h>
26 #include <anthy/ruleparser.h>
27 #include <anthy/dic.h>
28 #include <anthy/splitter.h>
29 #include <anthy/feature_set.h>
30 #include "wordborder.h"
31 
32 #define HF_THRESH 784
33 
34 static void *weak_word_array;
35 
36 static wtype_t anthy_wtype_name_noun;
37 #if 0
38 static wtype_t anthy_wtype_prefix;
39 #endif
40 static wtype_t anthy_wtype_num_prefix;
41 static wtype_t anthy_wtype_num_postfix;
42 static wtype_t anthy_wtype_name_postfix;
43 static wtype_t anthy_wtype_sv_postfix;
44 
45 /* デバッグ用 */
46 void
anthy_print_word_list(struct splitter_context * sc,struct word_list * wl)47 anthy_print_word_list(struct splitter_context *sc,
48 		      struct word_list *wl)
49 {
50   xstr xs;
51   if (!wl) {
52     printf("--\n");
53     return ;
54   }
55   /* 接頭辞 */
56   xs.len = wl->part[PART_CORE].from - wl->from;
57   xs.str = sc->ce[wl->from].c;
58   anthy_putxstr(&xs);
59   printf(".");
60   /* 自立語 */
61   xs.len = wl->part[PART_CORE].len;
62   xs.str = sc->ce[wl->part[PART_CORE].from].c;
63   anthy_putxstr(&xs);
64   printf(".");
65   /* 接尾辞 */
66   xs.len = wl->part[PART_POSTFIX].len;
67   xs.str = sc->ce[wl->part[PART_CORE].from + wl->part[PART_CORE].len].c;
68   anthy_putxstr(&xs);
69   printf("-");
70   /* 付属語 */
71   xs.len = wl->part[PART_DEPWORD].len;
72   xs.str = sc->ce[wl->part[PART_CORE].from +
73 		  wl->part[PART_CORE].len +
74 		  wl->part[PART_POSTFIX].len].c;
75   anthy_putxstr(&xs);
76   anthy_print_wtype(wl->part[PART_CORE].wt);
77   printf(" %s%s\n", anthy_seg_class_sym(wl->seg_class),
78 	 (wl->is_compound ? ",compound" : ""));
79 }
80 
81 int
anthy_dep_word_hash(xstr * xs)82 anthy_dep_word_hash(xstr *xs)
83 {
84   return anthy_xstr_hash(xs) % WORD_HASH_MAX;
85 }
86 
87 /** word_listを比較する、枝刈りのためなので、
88     厳密な比較である必要は無い */
89 static int
word_list_same(struct word_list * wl1,struct word_list * wl2)90 word_list_same(struct word_list *wl1, struct word_list *wl2)
91 {
92   if (wl1->node_id != wl2->node_id ||
93       wl1->from != wl2->from ||
94       wl1->len != wl2->len ||
95       wl1->mw_features != wl2->mw_features ||
96       wl1->tail_ct != wl2->tail_ct ||
97       wl1->part[PART_CORE].len != wl2->part[PART_CORE].len ||
98       wl1->is_compound != wl2->is_compound ||
99       !anthy_wtype_equal(wl1->part[PART_CORE].wt, wl2->part[PART_CORE].wt) ||
100       wl1->head_pos != wl2->head_pos) {
101     return 0;
102   }
103   if (wl1->part[PART_DEPWORD].dc != wl2->part[PART_DEPWORD].dc) {
104     return 0;
105   }
106   /* 同じと判断 */
107   return 1;
108 }
109 
110 static void
set_features(struct word_list * wl)111 set_features(struct word_list *wl)
112 {
113   if (anthy_wtype_get_pos(wl->part[PART_CORE].wt) == POS_NOUN &&
114       anthy_wtype_get_sv(wl->part[PART_CORE].wt)) {
115     wl->mw_features |= MW_FEATURE_SV;
116   }
117   if (wl->part[PART_POSTFIX].len || wl->part[PART_PREFIX].len) {
118     wl->mw_features |= MW_FEATURE_SUFFIX;
119   }
120   if (anthy_wtype_get_pos(wl->part[PART_CORE].wt) == POS_NUMBER) {
121     wl->mw_features |= MW_FEATURE_NUM;
122   }
123   if (wl->part[PART_CORE].len == 1) {
124     wl->mw_features |= MW_FEATURE_CORE1;
125   }
126   if (wl->part[PART_CORE].len == 0) {
127     wl->mw_features |= MW_FEATURE_DEP_ONLY;
128   }
129   if (wl->part[PART_CORE].freq > HF_THRESH) {
130     wl->mw_features |= MW_FEATURE_HIGH_FREQ;
131   }
132 }
133 
134 /** 作ったword_listのスコアを計算してからコミットする */
135 void
anthy_commit_word_list(struct splitter_context * sc,struct word_list * wl)136 anthy_commit_word_list(struct splitter_context *sc,
137 		       struct word_list *wl)
138 {
139   struct word_list *tmp;
140   xstr xs;
141 
142   /* 付属語だけのword_listで、長さ0のもやってくるので */
143   if (wl->len == 0) return;
144   /**/
145   wl->last_part = PART_DEPWORD;
146 
147   /**/
148   set_features(wl);
149   /* 文節境界の検索で使用するクラスの設定 */
150   anthy_set_seg_class(wl);
151   /**/
152   xs.len = wl->part[PART_DEPWORD].len;
153   xs.str = sc->ce[wl->part[PART_POSTFIX].from + wl->part[PART_POSTFIX].len].c;
154   wl->dep_word_hash = anthy_dep_word_hash(&xs);
155   if (wl->part[PART_POSTFIX].len) {
156     xs.len = wl->part[PART_POSTFIX].len;
157     xs.str = sc->ce[wl->part[PART_POSTFIX].from].c;
158   }
159 
160   /* 同じ内容のword_listがないかを調べる */
161   for (tmp = sc->word_split_info->cnode[wl->from].wl; tmp; tmp = tmp->next) {
162     if (word_list_same(tmp, wl)) {
163       return ;
164     }
165   }
166   /* wordlistのリストに追加 */
167   wl->next = sc->word_split_info->cnode[wl->from].wl;
168   sc->word_split_info->cnode[wl->from].wl = wl;
169 
170   /* デバッグプリント */
171   if (anthy_splitter_debug_flags() & SPLITTER_DEBUG_WL) {
172     anthy_print_word_list(sc, wl);
173   }
174 }
175 
176 struct word_list *
anthy_alloc_word_list(struct splitter_context * sc)177 anthy_alloc_word_list(struct splitter_context *sc)
178 {
179   return anthy_smalloc(sc->word_split_info->WlAllocator);
180 }
181 
182 /* 後続の活用語尾、助詞、助動詞を付ける */
183 static void
make_following_word_list(struct splitter_context * sc,struct word_list * tmpl)184 make_following_word_list(struct splitter_context *sc,
185 			 struct word_list *tmpl)
186 {
187   /* このxsは自立語部の後続の文字列 */
188   xstr xs;
189   xs.str = sc->ce[tmpl->from+tmpl->len].c;
190   xs.len = sc->char_count - tmpl->from - tmpl->len;
191   tmpl->part[PART_DEPWORD].from =
192     tmpl->part[PART_POSTFIX].from + tmpl->part[PART_POSTFIX].len;
193 
194   if (tmpl->node_id >= 0) {
195     /* 普通のword_list */
196     anthy_scan_node(sc, tmpl, &xs, tmpl->node_id);
197   } else {
198     /* 自立語がないword_list */
199     struct wordseq_rule rule;
200     struct word_list new_tmpl;
201     int i;
202     int nr_rule = anthy_get_nr_dep_rule();
203     new_tmpl = *tmpl;
204     /* 名詞35の後に続くルールに対して */
205     for (i = 0; i < nr_rule; ++i) {
206       anthy_get_nth_dep_rule(i, &rule);
207       if (anthy_wtype_get_pos(rule.wt) == POS_NOUN
208 	  && anthy_wtype_get_scos(rule.wt) == SCOS_T35) {
209 	new_tmpl.part[PART_CORE].wt = rule.wt;
210 	new_tmpl.node_id = rule.node_id;
211 	new_tmpl.head_pos = anthy_wtype_get_pos(new_tmpl.part[PART_CORE].wt);
212 	anthy_scan_node(sc, &new_tmpl, &xs, new_tmpl.node_id);
213       }
214     }
215   }
216 }
217 
218 static void
push_part_back(struct word_list * tmpl,int len,seq_ent_t se,wtype_t wt)219 push_part_back(struct word_list *tmpl, int len,
220 	       seq_ent_t se, wtype_t wt)
221 {
222   tmpl->len += len;
223   tmpl->part[PART_POSTFIX].len += len;
224   tmpl->part[PART_POSTFIX].wt = wt;
225   tmpl->part[PART_POSTFIX].seq = se;
226   tmpl->last_part = PART_POSTFIX;
227 }
228 
229 /* 接尾辞をくっつける */
230 static void
make_suc_words(struct splitter_context * sc,struct word_list * tmpl)231 make_suc_words(struct splitter_context *sc,
232 	       struct word_list *tmpl)
233 {
234   int i, right;
235 
236   wtype_t core_wt = tmpl->part[PART_CORE].wt;
237   /* 数詞、名前、サ変名詞のいずれかに付属語は付く */
238   int core_is_num = 0;
239   int core_is_name = 0;
240   int core_is_sv_noun = 0;
241 
242   /* まず、接尾辞が付く自立語かチェックする */
243   if (anthy_wtype_get_pos (core_wt) == POS_NUMBER) {
244     core_is_num = 1;
245   }
246   if (anthy_wtype_get_pos (core_wt) == POS_NOUN &&
247       anthy_wtype_get_cos (core_wt) == COS_JN) {
248     core_is_name = 1;
249   }
250   if (anthy_wtype_get_sv(core_wt)) {
251     core_is_sv_noun = 1;
252   }
253   if (!core_is_num && !core_is_name && !core_is_sv_noun) {
254     return ;
255   }
256 
257   right = tmpl->part[PART_CORE].from + tmpl->part[PART_CORE].len;
258   /* 自立語の右側の文字列に対して */
259   for (i = 1;
260        i <= sc->word_split_info->seq_len[right];
261        i++){
262     xstr xs;
263     seq_ent_t suc;
264     xs.str = sc->ce[right].c;
265     xs.len = i;
266     suc = anthy_get_seq_ent_from_xstr(&xs, sc->is_reverse);
267     if (anthy_get_seq_ent_pos(suc, POS_SUC)) {
268       /* 右側の文字列は付属語なので、自立語の品詞にあわせてチェック */
269       struct word_list new_tmpl;
270       if (core_is_num &&
271 	  anthy_get_seq_ent_wtype_freq (suc, anthy_wtype_num_postfix)) {
272 	new_tmpl = *tmpl;
273 	push_part_back(&new_tmpl, i, suc, anthy_wtype_num_postfix);
274 	make_following_word_list(sc, &new_tmpl);
275       }
276       if (core_is_name &&
277 	  anthy_get_seq_ent_wtype_freq (suc, anthy_wtype_name_postfix)) {
278 	new_tmpl = *tmpl;
279 	push_part_back(&new_tmpl, i, suc, anthy_wtype_name_postfix);
280 	make_following_word_list(sc, &new_tmpl);
281       }
282       if (core_is_sv_noun &&
283 	  anthy_get_seq_ent_wtype_freq (suc, anthy_wtype_sv_postfix)) {
284 	new_tmpl = *tmpl;
285 	push_part_back(&new_tmpl, i, suc, anthy_wtype_sv_postfix);
286 	make_following_word_list(sc, &new_tmpl);
287       }
288     }
289   }
290 }
291 
292 static void
push_part_front(struct word_list * tmpl,int len,seq_ent_t se,wtype_t wt)293 push_part_front(struct word_list *tmpl, int len,
294 		seq_ent_t se, wtype_t wt)
295 {
296   tmpl->from = tmpl->from - len;
297   tmpl->len = tmpl->len + len;
298   tmpl->part[PART_PREFIX].from = tmpl->from;
299   tmpl->part[PART_PREFIX].len += len;
300   tmpl->part[PART_PREFIX].wt = wt;
301   tmpl->part[PART_PREFIX].seq = se;
302 }
303 
304 /* 接頭辞をくっつけてから接尾辞をくっつける */
305 static void
make_pre_words(struct splitter_context * sc,struct word_list * tmpl)306 make_pre_words(struct splitter_context *sc,
307 	       struct word_list *tmpl)
308 {
309   int i;
310   wtype_t core_wt = tmpl->part[PART_CORE].wt;
311   int core_is_num = 0;
312   /* 自立語は数詞か? */
313   if (anthy_wtype_get_pos (core_wt) == POS_NUMBER) {
314     core_is_num = 1;
315   }
316   /* 接頭辞を列挙する */
317   for (i = 1;
318        i <= sc->word_split_info->rev_seq_len[tmpl->part[PART_CORE].from];
319        i++) {
320     seq_ent_t pre;
321     /* このxsは自立語部の前の文字列 */
322     xstr xs;
323     xs.str = sc->ce[tmpl->part[PART_CORE].from - i].c;
324     xs.len = i;
325     pre = anthy_get_seq_ent_from_xstr(&xs, sc->is_reverse);
326     if (anthy_get_seq_ent_pos(pre, POS_PRE)) {
327       struct word_list new_tmpl;
328       if (core_is_num &&
329 	  anthy_get_seq_ent_wtype_freq (pre, anthy_wtype_num_prefix)) {
330 	new_tmpl = *tmpl;
331 	push_part_front(&new_tmpl, i, pre, anthy_wtype_num_prefix);
332 	make_following_word_list(sc, &new_tmpl);
333 	/* 数の場合は接尾辞もくっつける */
334 	make_suc_words(sc, &new_tmpl);
335       }
336 #if 0
337       else if (anthy_get_seq_ent_wtype_freq (pre, anthy_wtype_prefix)) {
338 	new_tmpl = *tmpl;
339 	push_part_front(&new_tmpl, i, pre, anthy_wtype_prefix);
340 	make_following_word_list(sc, &new_tmpl);
341       }
342 #endif
343     }
344   }
345 }
346 
347 /* wordlistを初期化する */
348 static void
setup_word_list(struct word_list * wl,int from,int len,int is_compound,int is_weak)349 setup_word_list(struct word_list *wl, int from, int len,
350 		int is_compound, int is_weak)
351 {
352   int i;
353   wl->from = from;
354   wl->len = len;
355   wl->is_compound = is_compound;
356   /* partの配列を初期化する */
357   for (i = 0; i < NR_PARTS; i++) {
358     wl->part[i].from = 0;
359     wl->part[i].len = 0;
360     wl->part[i].wt = anthy_wt_none;
361     wl->part[i].seq = 0;
362     wl->part[i].freq = 1;/* 頻度の低い単語としておく */
363     wl->part[i].dc = DEP_NONE;
364   }
365   /* 自立語のパートを設定 */
366   wl->part[PART_CORE].from = from;
367   wl->part[PART_CORE].len = len;
368   /**/
369   wl->mw_features = MW_FEATURE_NONE;
370   wl->node_id = -1;
371   wl->last_part = PART_CORE;
372   wl->head_pos = POS_NONE;
373   wl->tail_ct = CT_NONE;
374   if (is_weak) {
375     wl->mw_features |= MW_FEATURE_WEAK_SEQ;
376   }
377 }
378 
379 /*
380  * ある独立語に対して、接頭辞、接尾辞、付属語を付けたものを
381  * 文節の候補(=word_list)としてcacheに追加する
382  */
383 static void
make_word_list(struct splitter_context * sc,seq_ent_t se,int from,int len,int is_compound,int is_weak)384 make_word_list(struct splitter_context *sc,
385 	       seq_ent_t se,
386 	       int from, int len,
387 	       int is_compound,
388 	       int is_weak)
389 {
390   struct word_list tmpl;
391   struct wordseq_rule rule;
392   int nr_rule = anthy_get_nr_dep_rule();
393   int i;
394 
395   /* テンプレートの初期化 */
396   setup_word_list(&tmpl, from, len, is_compound, is_weak);
397   tmpl.part[PART_CORE].seq = se;
398 
399   /* 各ルールにマッチするか比較 */
400   for (i = 0; i < nr_rule; ++i) {
401     int freq;
402     anthy_get_nth_dep_rule(i, &rule);
403     if (!is_compound) {
404       freq = anthy_get_seq_ent_wtype_freq (se, rule.wt);
405     } else {
406       freq = anthy_get_seq_ent_wtype_compound_freq (se, rule.wt);
407     }
408 
409     if (freq) {
410       /* 自立語の品詞はそのルールにあっている */
411       if (anthy_splitter_debug_flags() & SPLITTER_DEBUG_ID) {
412 	/* 品詞表のデバッグ用*/
413 	xstr xs;
414 	xs.str = sc->ce[tmpl.part[PART_CORE].from].c;
415 	xs.len = tmpl.part[PART_CORE].len;
416 	anthy_putxstr(&xs);
417 	printf(" freq=%d rule_id=%d node_id=%d\n",
418 	       freq, i, rule.node_id);
419       }
420       /* 遷移したルールの情報を転記する */
421       tmpl.part[PART_CORE].wt = rule.wt;
422       tmpl.part[PART_CORE].freq = freq;
423       tmpl.node_id = rule.node_id;
424       tmpl.head_pos = anthy_wtype_get_pos(tmpl.part[PART_CORE].wt);
425 
426       /**/
427       tmpl.part[PART_POSTFIX].from =
428 	tmpl.part[PART_CORE].from +
429 	tmpl.part[PART_CORE].len;
430       /**/
431       if (anthy_wtype_get_pos(rule.wt) == POS_NOUN ||
432 	  anthy_wtype_get_pos(rule.wt) == POS_NUMBER) {
433 	/* 接頭辞、接尾辞は名詞、数詞にしか付かないことにしている */
434 	make_pre_words(sc, &tmpl);
435 	make_suc_words(sc, &tmpl);
436       }
437       /* 接頭辞、接尾辞無しで助詞助動詞をつける */
438       make_following_word_list(sc, &tmpl);
439     }
440   }
441 }
442 
443 static void
make_dummy_head(struct splitter_context * sc)444 make_dummy_head(struct splitter_context *sc)
445 {
446   struct word_list tmpl;
447   setup_word_list(&tmpl, 0, 0, 0, 0);
448   tmpl.part[PART_CORE].seq = 0;
449   tmpl.part[PART_CORE].wt = anthy_wtype_noun;
450 
451   tmpl.head_pos = anthy_wtype_get_pos(tmpl.part[PART_CORE].wt);
452   make_suc_words(sc, &tmpl);
453 }
454 
455 static int
compare_hash(const void * kp,const void * cp)456 compare_hash(const void *kp, const void *cp)
457 {
458   const int *h = kp;
459   const int *c = cp;
460   return (*h) - ntohl(*c);
461 }
462 
463 static int
check_weak(xstr * xs)464 check_weak(xstr *xs)
465 {
466   const int *array = (int *)weak_word_array;
467   int nr;
468   int h;
469   if (!array) {
470     return 0;
471   }
472   nr = ntohl(array[1]);
473   h = anthy_xstr_hash(xs);
474   if (bsearch(&h, &array[16], nr,
475 	      sizeof(int), compare_hash)) {
476     return 1;
477   }
478   return 0;
479 }
480 
481 /* コンテキストに設定された文字列の部分文字列から全てのword_listを列挙する */
482 void
anthy_make_word_list_all(struct splitter_context * sc)483 anthy_make_word_list_all(struct splitter_context *sc)
484 {
485   int i, j;
486   xstr xs;
487   seq_ent_t se;
488   struct depword_ent {
489     struct depword_ent *next;
490     int from, len;
491     int is_compound;
492     int is_weak;
493     seq_ent_t se;
494   } *head, *de;
495   struct word_split_info_cache *info;
496   allocator de_ator;
497 
498   weak_word_array = anthy_file_dic_get_section("weak_words");
499 
500   info = sc->word_split_info;
501   head = NULL;
502   de_ator = anthy_create_allocator(sizeof(struct depword_ent), 0);
503 
504   xs.str = sc->ce[0].c;
505   xs.len = sc->char_count;
506   anthy_gang_load_dic(&xs, sc->is_reverse);
507 
508   /* 全ての自立語を列挙 */
509   /* 開始地点のループ */
510   for (i = 0; i < sc->char_count ; i++) {
511     int search_len = sc->char_count - i;
512     int search_from = 0;
513     if (search_len > 30) {
514       search_len = 30;
515     }
516 
517     /* 文字列長のループ(長い方から) */
518     for (j = search_len; j > search_from; j--) {
519       /* seq_entを取得する */
520       xs.len = j;
521       xs.str = sc->ce[i].c;
522       se = anthy_get_seq_ent_from_xstr(&xs, sc->is_reverse);
523 
524       /* 単語として認識できない */
525       if (!se) {
526 	continue;
527       }
528 
529       /* 各、部分文字列が単語ならば接頭辞、接尾辞の
530 	 最大長を調べてマークする */
531       if (j > info->seq_len[i] &&
532 	  anthy_get_seq_ent_pos(se, POS_SUC)) {
533 	info->seq_len[i] = j;
534       }
535       if (j > info->rev_seq_len[i + j] &&
536 	  anthy_get_seq_ent_pos(se, POS_PRE)) {
537 	info->rev_seq_len[i + j] = j;
538       }
539 
540       /* 発見した自立語をリストに追加 */
541       if (anthy_get_seq_ent_indep(se) &&
542 	  /* 複合語で無い候補があることを確認 */
543 	  anthy_has_non_compound_ents(se)) {
544 	de = (struct depword_ent *)anthy_smalloc(de_ator);
545 	de->from = i;
546 	de->len = j;
547 	de->se = se;
548 	de->is_compound = 0;
549 	de->is_weak = check_weak(&xs);
550 
551 	de->next = head;
552 	head = de;
553       }
554       /* 発見した複合語をリストに追加 */
555       if (anthy_has_compound_ents(se)) {
556 	de = (struct depword_ent *)anthy_smalloc(de_ator);
557 	de->from = i;
558 	de->len = j;
559 	de->se = se;
560 	de->is_compound = 1;
561 	de->is_weak = 0;
562 
563 	de->next = head;
564 	head = de;
565       }
566     }
567   }
568 
569   /* 発見した自立語全てに対して付属語パターンの検索 */
570   for (de = head; de; de = de->next) {
571     make_word_list(sc, de->se, de->from, de->len,
572 		   de->is_compound, de->is_weak);
573   }
574 
575     /* 自立語の無いword_list */
576   for (i = 0; i < sc->char_count; i++) {
577     struct word_list tmpl;
578     setup_word_list(&tmpl, i, 0, 0, 0);
579     if (i == 0) {
580       make_following_word_list(sc, &tmpl);
581     } else {
582       int type = anthy_get_xchar_type(*sc->ce[i - 1].c);
583       if ((type & (XCT_CLOSE | XCT_SYMBOL)) &&
584 	  !(type & XCT_PUNCTUATION)) {
585 	/* 句読点以外の記号 */
586 	make_following_word_list(sc, &tmpl);
587       }
588     }
589   }
590 
591   /* 先頭に0文字の自立語を付ける */
592   make_dummy_head(sc);
593 
594   anthy_free_allocator(de_ator);
595 }
596 
597 int
anthy_init_wordlist(void)598 anthy_init_wordlist (void)
599 {
600   /* {"人名",POS_NOUN,COS_JN,SCOS_NONE,CC_NONE,CT_NONE,WF_INDEP} */
601   anthy_type_to_wtype ("#JN", &anthy_wtype_name_noun);
602 #if 0
603   /* {"名詞接頭辞",POS_PRE,COS_NONE,SCOS_NONE,CC_NONE,CT_NONE,WF_INDEP} */
604   anthy_type_to_wtype ("#PRE", &anthy_wtype_prefix);
605 #endif
606   /* {"数接頭辞",POS_PRE,COS_NN,SCOS_NONE,CC_NONE,CT_NONE,WF_NONE} */
607   anthy_type_to_wtype ("#NNPRE", &anthy_wtype_num_prefix);
608 
609   /* {"数接尾辞",POS_SUC,COS_NN,SCOS_NONE,CC_NONE,CT_NONE,WF_NONE} */
610   /* {"#JS",POS_SUC,COS_NN,SCOS_NONE,CC_NONE,CT_NONE,WF_INDEP}    # "助数詞" */
611   anthy_type_to_wtype ("#JS", &anthy_wtype_num_postfix);
612 
613   /* {"人名接尾辞",POS_SUC,COS_JN,SCOS_NONE,CC_NONE,CT_NONE,WF_INDEP} */
614   anthy_type_to_wtype ("#JNSUC", &anthy_wtype_name_postfix);
615 
616   /* {"サ変接尾辞",POS_SUC,COS_SVSUFFIX,SCOS_NONE,CC_NONE,CT_NONE,WF_INDEP} */
617   anthy_type_to_wtype ("#SVSUC", &anthy_wtype_sv_postfix);
618 
619   return 0;
620 }
621