1 /*
2  * migemo.c -
3  *
4  * Written By:  MURAOKA Taro <koron@tka.att.ne.jp>
5  */
6 module migemo_d.migemo;
7 
8 
9 private static import core.memory;
10 private static import core.stdc.ctype;
11 private static import core.stdc.stdio;
12 private static import core.stdc..string;
13 private static import migemo_d.charset;
14 private static import migemo_d.filename;
15 private static import migemo_d.mnode;
16 private static import migemo_d.romaji;
17 private static import migemo_d.rxgen;
18 private static import migemo_d.wordbuf;
19 private static import migemo_d.wordlist;
20 
21 //#if defined(_WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN32__)
22 //	#define MIGEMO_CALLTYPE __stdcall
23 //#else
24 //	#define MIGEMO_CALLTYPE
25 //#endif
26 
27 public enum MIGEMO_VERSION = "1.3";
28 
29 /* for migemo_load() */
30 public enum MIGEMO_DICTID_INVALID = 0;
31 public enum MIGEMO_DICTID_MIGEMO = 1;
32 public enum MIGEMO_DICTID_ROMA2HIRA = 2;
33 public enum MIGEMO_DICTID_HIRA2KATA = 3;
34 public enum MIGEMO_DICTID_HAN2ZEN = 4;
35 public enum MIGEMO_DICTID_ZEN2HAN = 5;
36 
37 /* for migemo_set_operator()/migemo_get_operator().  see: rxgen.h */
38 public enum MIGEMO_OPINDEX_OR = 0;
39 public enum MIGEMO_OPINDEX_NEST_IN = 1;
40 public enum MIGEMO_OPINDEX_NEST_OUT = 2;
41 public enum MIGEMO_OPINDEX_SELECT_IN = 3;
42 public enum MIGEMO_OPINDEX_SELECT_OUT = 4;
43 public enum MIGEMO_OPINDEX_NEWLINE = 5;
44 
45 /* see: rxgen.h */
46 public alias MIGEMO_PROC_CHAR2INT = extern (C) nothrow @nogc int function(const (char)*, uint*);
47 public alias MIGEMO_PROC_INT2CHAR = extern (C) nothrow @nogc int function(uint, char*);
48 
49 enum DICT_MIGEMO = "migemo-dict";
50 enum DICT_ROMA2HIRA = "roma2hira.dat";
51 enum DICT_HIRA2KATA = "hira2kata.dat";
52 enum DICT_HAN2ZEN = "han2zen.dat";
53 enum DICT_ZEN2HAN = "zen2han.dat";
54 enum BUFLEN_DETECT_CHARSET = 4096;
55 
56 alias MIGEMO_PROC_ADDWORD = extern (C) nothrow @nogc int function(void* data, char* word);
57 
58 /**
59  * migemoオブジェクト
60  */
61 struct _migemo
62 {
63 	int enable;
64 	migemo_d.mnode.mtree_p mtree;
65 	int charset;
66 	migemo_d.romaji.romaji* roma2hira;
67 	migemo_d.romaji.romaji* hira2kata;
68 	migemo_d.romaji.romaji* han2zen;
69 	migemo_d.romaji.romaji* zen2han;
70 	migemo_d.rxgen.rxgen* rx;
71 	.MIGEMO_PROC_ADDWORD addword;
72 	migemo_d.charset.CHARSET_PROC_CHAR2INT char2int;
73 }
74 
75 /**
76  * Migemoオブジェクト。migemo_open()で作成され、migemo_closeで破棄される。
77  */
78 public alias migemo = ._migemo;
79 
80 static immutable char[] VOWEL_CHARS = "aiueo\0";
81 
82 pure nothrow @nogc
83 private int my_strlen(const (char)* s)
84 
85 	in
86 	{
87 		assert(s != null);
88 	}
89 
90 	do
91 	{
92 		size_t len = core.stdc..string.strlen(s);
93 
94 		return (len <= int.max) ? (cast(int)(len)) : (int.max);
95 	}
96 
97 nothrow @nogc
98 package migemo_d.mnode.mtree_p load_mtree_dictionary(migemo_d.mnode.mtree_p mtree, const (char)* dict_file)
99 
100 	in
101 	{
102 		assert(mtree != null);
103 		assert(dict_file != null);
104 	}
105 
106 	do
107 	{
108 		core.stdc.stdio.FILE* fp = core.stdc.stdio.fopen(dict_file, "rt");
109 
110 		if (fp == null) {
111 			/* Can't find file */
112 			return null;
113 		}
114 
115 		scope (exit) {
116 			if (fp != null) {
117 				core.stdc.stdio.fclose(fp);
118 				fp = null;
119 			}
120 		}
121 
122 		mtree = migemo_d.mnode.mnode_load(mtree, fp);
123 
124 		return mtree;
125 	}
126 
127 nothrow @nogc
128 package migemo_d.mnode.mtree_p load_mtree_dictionary2(.migemo* obj, const (char)* dict_file)
129 
130 	in
131 	{
132 		assert(obj != null);
133 	}
134 
135 	do
136 	{
137 		if (obj.charset == migemo_d.charset.CHARSET_NONE) {
138 			/* 辞書の文字セットにあわせて正規表現生成時の関数を変更する */
139 			migemo_d.charset.CHARSET_PROC_CHAR2INT char2int = null;
140 			migemo_d.charset.CHARSET_PROC_INT2CHAR int2char = null;
141 			obj.charset = migemo_d.charset.charset_detect_file(dict_file);
142 			migemo_d.charset.charset_getproc(obj.charset, &char2int, &int2char);
143 
144 			if (char2int != null) {
145 				.migemo_setproc_char2int(obj, cast(.MIGEMO_PROC_CHAR2INT)(char2int));
146 				obj.char2int = char2int;
147 			}
148 
149 			if (int2char != null) {
150 				.migemo_setproc_int2char(obj, cast(.MIGEMO_PROC_INT2CHAR)(int2char));
151 			}
152 		}
153 
154 		return .load_mtree_dictionary(obj.mtree, dict_file);
155 	}
156 
157 pure nothrow @nogc
158 package void dircat(char* buf, const (char)* dir, const (char)* file)
159 
160 	in
161 	{
162 		assert(buf != null);
163 		assert(dir != null);
164 		assert(file != null);
165 	}
166 
167 	do
168 	{
169 		core.stdc..string.strcpy(buf, dir);
170 		core.stdc..string.strcat(buf, "/");
171 		core.stdc..string.strcat(buf, file);
172 	}
173 
174 /*
175  * migemo interfaces
176  */
177 
178 /**
179  * Migemoオブジェクトに辞書、またはデータファイルを追加読み込みする。
180  * dict_fileは読み込むファイル名を指定する。dict_idは読み込む辞書・データの
181  * 種類を指定するもので以下のうちどれか一つを指定する:
182  *
183  *  <dl>
184  *  <dt>MIGEMO_DICTID_MIGEMO</dt>
185  *	<dd>mikgemo-dict辞書</dd>
186  *  <dt>MIGEMO_DICTID_ROMA2HIRA</dt>
187  *	<dd>ローマ字→平仮名変換表</dd>
188  *  <dt>MIGEMO_DICTID_HIRA2KATA</dt>
189  *	<dd>平仮名→カタカナ変換表</dd>
190  *  <dt>MIGEMO_DICTID_HAN2ZEN</dt>
191  *	<dd>半角→全角変換表</dd>
192  *  <dt>MIGEMO_DICTID_ZEN2HAN</dt>
193  *	<dd>全角→半角変換表</dd>
194  *  </dl>
195  *
196  *  戻り値は実際に読み込んだ種類を示し、上記の他に読み込みに失敗したことを示す
197  *  次の価が返ることがある。
198  *
199  *  <dl><dt>MIGEMO_DICTID_INVALID</dt></dl>
200  *
201  * Params:
202  *      obj = Migemoオブジェクト
203  *      dict_id = 辞書ファイルの種類
204  *      dict_file = 辞書ファイルのパス
205  */
206 //MIGEMO_CALLTYPE
207 extern (C)
208 nothrow @nogc
209 export int migemo_load(.migemo* obj, int dict_id, const (char)* dict_file)
210 
211 	do
212 	{
213 		if ((obj == null) && (dict_file != null)) {
214 			return .MIGEMO_DICTID_INVALID;
215 		}
216 
217 		if (dict_id == .MIGEMO_DICTID_MIGEMO) {
218 			/* migemo辞書読み込み */
219 			migemo_d.mnode.mtree_p mtree = .load_mtree_dictionary2(obj, dict_file);
220 
221 			if (mtree == null) {
222 				return .MIGEMO_DICTID_INVALID;
223 			}
224 
225 			obj.mtree = mtree;
226 			obj.enable = 1;
227 
228 			/* Loaded successfully */
229 			return dict_id;
230 		} else {
231 			migemo_d.romaji.romaji* dict;
232 
233 			switch (dict_id) {
234 				/* ローマ字辞書読み込み */
235 				case .MIGEMO_DICTID_ROMA2HIRA:
236 					dict = obj.roma2hira;
237 
238 					break;
239 
240 				/* カタカナ辞書読み込み */
241 				case .MIGEMO_DICTID_HIRA2KATA:
242 					dict = obj.hira2kata;
243 
244 					break;
245 
246 				/* 半角→全角辞書読み込み */
247 				case .MIGEMO_DICTID_HAN2ZEN:
248 					dict = obj.han2zen;
249 
250 					break;
251 
252 				/* 半角→全角辞書読み込み */
253 				case .MIGEMO_DICTID_ZEN2HAN:
254 					dict = obj.zen2han;
255 
256 					break;
257 
258 				default:
259 					dict = null;
260 
261 					break;
262 			}
263 
264 			if ((dict != null) && (migemo_d.romaji.romaji_load(dict, dict_file) == 0)) {
265 				return dict_id;
266 			} else {
267 				return .MIGEMO_DICTID_INVALID;
268 			}
269 		}
270 	}
271 
272 /**
273  * Migemoオブジェクトを作成する。作成に成功するとオブジェクトが戻り値として
274  * 返り、失敗するとNULLが返る。dictで指定したファイルがmigemo-dict辞書として
275  * オブジェクト作成時に読み込まれる。辞書と同じディレクトリに:
276  *
277  *  <dl>
278  *  <dt>roma2hira.dat</dt>
279  *	<dd>ローマ字→平仮名変換表 </dd>
280  *  <dt>hira2kata.dat</dt>
281  *	<dd>平仮名→カタカナ変換表 </dd>
282  *  <dt>han2zen.dat</dt>
283  *	<dd>半角→全角変換表 </dd>
284  *  </dl>
285  *
286  * という名前のファイルが存在すれば、存在したものだけが読み込まれる。dictに
287  * NULLを指定した場合には、辞書を含めていかなるファイルも読み込まれない。
288  * ファイルはオブジェクト作成後にもmigemo_load()関数を使用することで追加読み
289  * 込みができる。
290  *
291  * Params:
292  *      dict = migemo-dict辞書のパス。NULLの時は辞書を読み込まない。
293  *
294  * Returns: 作成されたMigemoオブジェクト
295  */
296 //MIGEMO_CALLTYPE
297 extern (C)
298 nothrow @nogc
299 export .migemo* migemo_open(const (char)* dict)
300 
301 	do
302 	{
303 		/* migemoオブジェクトと各メンバを構築 */
304 		.migemo* obj = cast(.migemo*)(core.memory.pureCalloc(1, .migemo.sizeof));
305 
306 		if (obj == null) {
307 			return null;
308 		}
309 
310 		obj.enable = 0;
311 		obj.mtree = migemo_d.mnode.mnode_open(null);
312 		obj.charset = migemo_d.charset.CHARSET_NONE;
313 		obj.rx = migemo_d.rxgen.rxgen_open();
314 		obj.roma2hira = migemo_d.romaji.romaji_open();
315 		obj.hira2kata = migemo_d.romaji.romaji_open();
316 		obj.han2zen = migemo_d.romaji.romaji_open();
317 		obj.zen2han = migemo_d.romaji.romaji_open();
318 
319 		if ((obj.rx == null) || (obj.roma2hira == null) || (obj.hira2kata == null) || (obj.han2zen == null) || (obj.zen2han == null)) {
320 			.migemo_close(obj);
321 
322 			return obj = null;
323 		}
324 
325 		/* デフォルトmigemo辞書が指定されていたらローマ字とカタカナ辞書も探す */
326 		if (dict != null) {
327 			/**
328 			 *  いい加減な数値 
329 			 */
330 			enum _MAX_PATH = 1024;
331 
332 			char[_MAX_PATH] dir;
333 			char[_MAX_PATH] roma_dict;
334 			char[_MAX_PATH] kata_dict;
335 			char[_MAX_PATH] h2z_dict;
336 			char[_MAX_PATH] z2h_dict;
337 
338 			migemo_d.filename.filename_directory(&(dir[0]), dict);
339 			const (char)* tmp = (core.stdc..string.strlen(&(dir[0]))) ? (&(dir[0])) : (".");
340 			.dircat(&(roma_dict[0]), tmp, .DICT_ROMA2HIRA);
341 			.dircat(&(kata_dict[0]), tmp, .DICT_HIRA2KATA);
342 			.dircat(&(h2z_dict[0]), tmp, .DICT_HAN2ZEN);
343 			.dircat(&(z2h_dict[0]), tmp, .DICT_ZEN2HAN);
344 
345 			migemo_d.mnode.mtree_p mtree = .load_mtree_dictionary2(obj, dict);
346 
347 			if (mtree != null) {
348 				obj.mtree = mtree;
349 				obj.enable = 1;
350 				migemo_d.romaji.romaji_load(obj.roma2hira, &(roma_dict[0]));
351 				migemo_d.romaji.romaji_load(obj.hira2kata, &(kata_dict[0]));
352 				migemo_d.romaji.romaji_load(obj.han2zen, &(h2z_dict[0]));
353 				migemo_d.romaji.romaji_load(obj.zen2han, &(z2h_dict[0]));
354 			}
355 		}
356 
357 		return obj;
358 	}
359 
360 /**
361  * Migemoオブジェクトを破棄し、使用していたリソースを解放する。
362  *
363  * Params:
364  *      obj = 破棄するMigemoオブジェクト
365  */
366 //MIGEMO_CALLTYPE
367 extern (C)
368 nothrow @nogc
369 export void migemo_close(.migemo* obj)
370 
371 	do
372 	{
373 		if (obj != null) {
374 			if (obj.zen2han != null) {
375 				migemo_d.romaji.romaji_close(obj.zen2han);
376 				obj.zen2han = null;
377 			}
378 
379 			if (obj.han2zen != null) {
380 				migemo_d.romaji.romaji_close(obj.han2zen);
381 				obj.han2zen = null;
382 			}
383 
384 			if (obj.hira2kata != null) {
385 				migemo_d.romaji.romaji_close(obj.hira2kata);
386 				obj.hira2kata = null;
387 			}
388 
389 			if (obj.roma2hira != null) {
390 				migemo_d.romaji.romaji_close(obj.roma2hira);
391 				obj.roma2hira = null;
392 			}
393 
394 			if (obj.rx != null) {
395 				migemo_d.rxgen.rxgen_close(obj.rx);
396 				obj.rx = null;
397 			}
398 
399 			if (obj.mtree != null) {
400 				migemo_d.mnode.mnode_close(obj.mtree);
401 				obj.mtree = null;
402 			}
403 
404 			core.memory.pureFree(obj);
405 			obj = null;
406 		}
407 	}
408 
409 /*
410  * query version 2
411  */
412 
413 /**
414  * mnodeの持つ単語リストを正規表現生成エンジンに入力する。
415  */
416 extern (C)
417 nothrow @nogc
418 package void migemo_query_proc(migemo_d.mnode.mnode* p, void* data)
419 
420 	in
421 	{
422 		assert(p != null);
423 		assert(data != null);
424 	}
425 
426 	do
427 	{
428 		.migemo* object = cast(.migemo*)(data);
429 
430 		for (migemo_d.wordlist.wordlist_p list = p.list; list != null; list = list.next) {
431 			object.addword(object, list.ptr_);
432 		}
433 	}
434 
435 /**
436  * バッファを用意してmnodeに再帰で書き込ませる
437  */
438 nothrow @nogc
439 package void add_mnode_query(.migemo* object, char* query)
440 
441 	do
442 	{
443 		migemo_d.mnode.mnode* pnode = migemo_d.mnode.mnode_query(object.mtree, query);
444 
445 		if (pnode != null) {
446 			migemo_d.mnode.mnode_traverse(pnode, &.migemo_query_proc, object);
447 		}
448 	}
449 
450 /**
451  * 入力をローマから仮名に変換して検索キーに加える。
452  */
453 nothrow @nogc
454 package int add_roma(.migemo* object, char* query)
455 
456 	in
457 	{
458 		assert(object != null);
459 	}
460 
461 	do
462 	{
463 		char* stop;
464 		char* hira = migemo_d.romaji.romaji_convert(object.roma2hira, query, &stop);
465 
466 		scope (exit) {
467 			if (hira != null) {
468 				/* 平仮名解放 */
469 				migemo_d.romaji.romaji_release(object.roma2hira, hira);
470 				hira = null;
471 			}
472 		}
473 
474 		if (stop == null) {
475 			object.addword(object, hira);
476 			/* 平仮名による辞書引き */
477 			.add_mnode_query(object, hira);
478 
479 			/* 片仮名文字列を生成し候補に加える */
480 			char* kata = migemo_d.romaji.romaji_convert2(object.hira2kata, hira, null, 0);
481 
482 			scope (exit) {
483 				if (kata != null) {
484 					/* カタカナ解放 */
485 					migemo_d.romaji.romaji_release(object.hira2kata, kata);
486 					kata = null;
487 				}
488 			}
489 
490 			object.addword(object, kata);
491 
492 			/* TODO: 半角カナを生成し候補に加える */
493 			version (all) {
494 				char* han = migemo_d.romaji.romaji_convert2(object.zen2han, kata, null, 0);
495 
496 				scope (exit) {
497 					if (han != null) {
498 						migemo_d.romaji.romaji_release(object.zen2han, han);
499 						han = null;
500 					}
501 				}
502 
503 				object.addword(object, han);
504 				/*core.stdc.stdio.printf("kata=%s\nhan=%s\n", kata, han);*/
505 			}
506 
507 			/* カタカナによる辞書引き */
508 			.add_mnode_query(object, kata);
509 		}
510 
511 		return (stop) ? (1) : (0);
512 	}
513 
514 /**
515  * ローマ字の末尾に母音を付け加えて、各々を検索キーに加える。
516  */
517 nothrow @nogc
518 package void add_dubious_vowels(.migemo* object, char* buf, size_t index)
519 
520 	in
521 	{
522 		assert(buf != null);
523 	}
524 
525 	do
526 	{
527 		for (immutable (char)* ptr_ = &(.VOWEL_CHARS[0]); *ptr_ != '\0'; ++ptr_) {
528 			buf[index] = *ptr_;
529 			.add_roma(object, buf);
530 		}
531 	}
532 
533 /**
534  * ローマ字変換が不完全だった時に、[aiueo]および"xn"と"xtu"を補って変換して
535  * みる。
536  */
537 nothrow @nogc
538 package void add_dubious_roma(.migemo* object, migemo_d.rxgen.rxgen* rx, char* query)
539 
540 	in
541 	{
542 		assert(query != null);
543 	}
544 
545 	do
546 	{
547 		size_t len = core.stdc..string.strlen(query);
548 
549 		/*
550 		 * ローマ字の末尾のアレンジのためのバッファを確保する。
551 		 *	    内訳: オリジナルの長さ、NUL、吃音(xtu)、補足母音([aieuo])
552 		 */
553 		enum size_t end_buf_len = 1 + 3 + 1;
554 
555 		if (len == 0) {
556 			return;
557 		}
558 
559 		if (len > (size_t.max - end_buf_len)) {
560 			return;
561 		}
562 
563 		size_t max = len + end_buf_len;
564 		char* buf = cast(char*)(core.memory.pureMalloc(max));
565 
566 		if (buf == null) {
567 			return;
568 		}
569 
570 		scope (exit) {
571 			if (buf != null) {
572 				core.memory.pureFree(buf);
573 				buf = null;
574 			}
575 		}
576 
577 		buf[0 .. len] = query[0 .. len];
578 		core.stdc..string.memset(&buf[len], 0, max - len);
579 
580 		if (core.stdc..string.strchr(&(.VOWEL_CHARS[0]), buf[len - 1]) == null) {
581 			.add_dubious_vowels(object, buf, len);
582 
583 			/* 未確定単語の長さが2未満か、未確定文字の直前が母音ならば… */
584 			if ((len < 2) || (core.stdc..string.strchr(&(.VOWEL_CHARS[0]), buf[len - 2]) != null)) {
585 				if (buf[len - 1] == 'n') {
586 					/* 「ん」を補ってみる */
587 					buf[len - 1] = 'x';
588 					buf[len] = 'n';
589 					.add_roma(object, buf);
590 				} else {
591 					/* 「っ{元の子音}{母音}」を補ってみる */
592 					buf[len + 2] = buf[len - 1];
593 					buf[len - 1] = 'x';
594 					buf[len] = 't';
595 					buf[len + 1] = 'u';
596 					.add_dubious_vowels(object, buf, len + 3);
597 				}
598 			}
599 		}
600 	}
601 
602 /**
603  * queryを文節に分解する。文節の切れ目は通常アルファベットの大文字。文節が複
604  * 数文字の大文字で始まった文節は非大文字を区切りとする。
605  */
606 nothrow @nogc
607 package migemo_d.wordlist.wordlist_p parse_query(.migemo* object, const (char)* query)
608 
609 	in
610 	{
611 		assert(object != null);
612 		assert(query != null);
613 	}
614 
615 	do
616 	{
617 		const (char)* curr = query;
618 		migemo_d.wordlist.wordlist_p querylist = null;
619 		migemo_d.wordlist.wordlist_p* pp = &querylist;
620 
621 		int len;
622 
623 		while (true) {
624 			int sum = 0;
625 
626 			if ((object.char2int == null) || ((len = object.char2int(curr, null)) < 1)) {
627 				len = 1;
628 			}
629 
630 			const (char)* start = curr;
631 			int upper = ((len == 1) && (core.stdc.ctype.isupper(*curr)) && (core.stdc.ctype.isupper(curr[1])));
632 			curr += len;
633 			sum += len;
634 
635 			while (true) {
636 				if ((object.char2int == null) || ((len = object.char2int(curr, null)) < 1)) {
637 					len = 1;
638 				}
639 
640 				if ((*curr == '\0') || ((len == 1) && ((core.stdc.ctype.isupper(*curr) != 0) != upper))) {
641 					break;
642 				}
643 
644 				curr += len;
645 				sum += len;
646 			}
647 
648 			/* 文節を登録する */
649 			if ((start != null) && (start < curr)) {
650 				*pp = migemo_d.wordlist.wordlist_open_len(start, sum);
651 				pp = &(*pp).next;
652 			}
653 
654 			if (*curr == '\0') {
655 				break;
656 			}
657 		}
658 
659 		return querylist;
660 	}
661 
662 /**
663  * 1つの単語をmigemo変換。引数のチェックは行なわない。
664  */
665 nothrow @nogc
666 package int query_a_word(.migemo* object, char* query)
667 
668 	in
669 	{
670 		assert(object != null);
671 		assert(query != null);
672 	}
673 
674 	do
675 	{
676 		size_t len = core.stdc..string.strlen(query);
677 
678 		assert(size_t.max > len);
679 
680 		/* query自信はもちろん候補に加える */
681 		object.addword(object, query);
682 
683 		/* queryそのものでの辞書引き */
684 		char* lower = cast(char*)(core.memory.pureMalloc(len + 1));
685 
686 		scope (exit) {
687 			if (lower != null) {
688 				core.memory.pureFree(lower);
689 				lower = null;
690 			}
691 		}
692 
693 		if (lower == null) {
694 			.add_mnode_query(object, query);
695 		} else {
696 			int i = 0;
697 			int step;
698 
699 			// MBを考慮した大文字→小文字変換
700 			while (i <= len) {
701 				if ((object.char2int == null) || ((step = object.char2int(&query[i], null)) < 1)) {
702 					step = 1;
703 				}
704 
705 				if ((step == 1) && (core.stdc.ctype.isupper(query[i]))) {
706 					lower[i] = cast(char)(core.stdc.ctype.tolower(query[i]));
707 				} else {
708 					core.stdc..string.memcpy(&lower[i], &query[i], step);
709 				}
710 
711 				i += step;
712 			}
713 
714 			.add_mnode_query(object, lower);
715 		}
716 
717 		/* queryを全角にして候補に加える */
718 		char* zen = migemo_d.romaji.romaji_convert2(object.han2zen, query, null, 0);
719 
720 		scope (exit) {
721 			if (zen != null) {
722 				migemo_d.romaji.romaji_release(object.han2zen, zen);
723 				zen = null;
724 			}
725 		}
726 
727 		if (zen != null) {
728 			object.addword(object, zen);
729 		}
730 
731 		/* queryを半角にして候補に加える */
732 		char* han = migemo_d.romaji.romaji_convert2(object.zen2han, query, null, 0);
733 
734 		scope (exit) {
735 			if (han != null) {
736 				migemo_d.romaji.romaji_release(object.zen2han, han);
737 				han = null;
738 			}
739 		}
740 
741 		if (han != null) {
742 			object.addword(object, han);
743 		}
744 
745 		/* 平仮名、カタカナ、及びそれによる辞書引き追加 */
746 		if (.add_roma(object, query)) {
747 			.add_dubious_roma(object, object.rx, query);
748 		}
749 
750 		return 1;
751 	}
752 
753 extern (C)
754 nothrow @nogc
755 package int addword_rxgen(void* object, char* word)
756 
757 	in
758 	{
759 		assert(object != null);
760 	}
761 
762 	do
763 	{
764 		/* 正規表現生成エンジンに追加された単語を表示する */
765 		/*core.stdc.stdio.printf("addword_rxgen: %s\n", word);*/
766 		return migemo_d.rxgen.rxgen_add((cast(.migemo*)(object)).rx, word);
767 	}
768 
769 /**
770  * queryで与えられた文字列(ローマ字)を日本語検索のための正規表現へ変換する。
771  * 戻り値は変換された結果の文字列(正規表現)で、使用後は#migemo_release()関数
772  * へ渡すことで解放しなければならない。
773  *
774  * Params:
775  *      object = Migemoオブジェクト
776  *      query = 問い合わせ文字列
777  *
778  * Returns: 正規表現文字列。#migemo_release() で解放する必要有り。
779  */
780 //MIGEMO_CALLTYPE
781 extern (C)
782 nothrow @nogc
783 export char* migemo_query(.migemo* object, const (char)* query)
784 
785 	do
786 	{
787 		char* retval = null;
788 
789 		if ((object != null) && (object.rx != null) && (query != null)) {
790 			migemo_d.wordlist.wordlist_p querylist = .parse_query(object, query);
791 
792 			scope (exit) {
793 				if (querylist != null) {
794 					migemo_d.wordlist.wordlist_close(querylist);
795 					querylist = null;
796 				}
797 			}
798 
799 			if (querylist == null) {
800 				/* 空queryのためエラー */
801 				return retval;
802 			}
803 
804 			migemo_d.wordbuf.wordbuf_p outbuf = migemo_d.wordbuf.wordbuf_open();
805 
806 			scope (exit) {
807 				if (outbuf != null) {
808 					retval = outbuf.buf;
809 					outbuf.buf = null;
810 					migemo_d.wordbuf.wordbuf_close(outbuf);
811 					outbuf = null;
812 				}
813 			}
814 
815 			if (outbuf == null) {
816 				/* 出力用のメモリ領域不足のためエラー */
817 				return retval;
818 			}
819 
820 			/* 単語群をrxgenオブジェクトに入力し正規表現を得る */
821 			object.addword = &.addword_rxgen;
822 			migemo_d.rxgen.rxgen_reset(object.rx);
823 
824 			for (migemo_d.wordlist.wordlist_p p = querylist; p != null; p = p.next) {
825 				/*core.stdc.stdio.printf("query=%s\n", p.ptr_);*/
826 				.query_a_word(object, p.ptr_);
827 
828 				/* 検索パターン(正規表現)生成 */
829 				char* answer = migemo_d.rxgen.rxgen_generate(object.rx);
830 
831 				scope (exit) {
832 					if (answer != null) {
833 						migemo_d.rxgen.rxgen_release(object.rx, answer);
834 						answer = null;
835 					}
836 				}
837 
838 				migemo_d.rxgen.rxgen_reset(object.rx);
839 				migemo_d.wordbuf.wordbuf_cat(outbuf, answer);
840 			}
841 		}
842 
843 		return retval;
844 	}
845 
846 /**
847  * 使い終わったmigemo_query()関数で得られた正規表現を解放する。
848  *
849  * Params:
850  *      p = Migemoオブジェクト
851  *      string = 正規表現文字列
852  */
853 //MIGEMO_CALLTYPE
854 extern (C)
855 pure nothrow @trusted @nogc
856 export void migemo_release(.migemo* p, char* string_)
857 
858 	do
859 	{
860 		if (string_ != null) {
861 			core.memory.pureFree(string_);
862 		}
863 	}
864 
865 /**
866  * Migemoオブジェクトが生成する正規表現に使用するメタ文字(演算子)を指定す
867  * る。indexでどのメタ文字かを指定し、opで置き換える。indexには以下の値が指
868  * 定可能である:
869  *
870  *  <dl>
871  *  <dt>MIGEMO_OPINDEX_OR</dt>
872  *	<dd>論理和。デフォルトは "|" 。vimで利用する際は "\|" 。</dd>
873  *  <dt>MIGEMO_OPINDEX_NEST_IN</dt>
874  *	<dd>グルーピングに用いる開き括弧。デフォルトは "(" 。vimではレジスタ
875  *	\\1〜\\9に記憶させないようにするために "\%(" を用いる。Perlでも同様の
876  *	ことを目論むならば "(?:" が使用可能。</dd>
877  *  <dt>MIGEMO_OPINDEX_NEST_OUT</dt>
878  *	<dd>グルーピングの終了を表す閉じ括弧。デフォルトでは ")" 。vimでは
879  *	"\)" 。</dd>
880  *  <dt>MIGEMO_OPINDEX_SELECT_IN</dt>
881  *	<dd>選択の開始を表す開き角括弧。デフォルトでは "[" 。</dd>
882  *  <dt>MIGEMO_OPINDEX_SELECT_OUT</dt>
883  *	<dd>選択の終了を表す閉じ角括弧。デフォルトでは "]" 。</dd>
884  *  <dt>MIGEMO_OPINDEX_NEWLINE</dt>
885  *	<dd>各文字の間に挿入される「0個以上の空白もしくは改行にマッチする」
886  *	パターン。デフォルトでは "" であり設定されない。vimでは "\_s*" を指
887  *	定する。</dd>
888  *  </dl>
889  *
890  * デフォルトのメタ文字は特に断りがない限りPerlのそれと同じ意味である。設定
891  * に成功すると戻り値は1(0以外)となり、失敗すると0になる。
892  *
893  * Params:
894  *      object = Migemoオブジェクト
895  *      index = メタ文字識別子
896  *      op = メタ文字文字列
897  *
898  * Returns: 成功時0以外、失敗時0。
899  */
900 //MIGEMO_CALLTYPE
901 extern (C)
902 pure nothrow @nogc
903 export int migemo_set_operator(.migemo* object, int index, const (char)* op)
904 
905 	do
906 	{
907 		if (object != null) {
908 			int retval = migemo_d.rxgen.rxgen_set_operator(object.rx, index, op);
909 
910 			return (retval) ? (0) : (1);
911 		} else {
912 			return 0;
913 		}
914 	}
915 
916 /**
917  * Migemoオブジェクトが生成する正規表現に使用しているメタ文字(演算子)を取得
918  * する。indexについてはmigemo_set_operator()関数を参照。戻り値にはindexの指
919  * 定が正しければメタ文字を格納した文字列へのポインタが、不正であればNULLが
920  * 返る。
921  *
922  * Params:
923  *      object = Migemoオブジェクト
924  *      index = メタ文字識別子
925  *
926  * Returns: 現在のメタ文字文字列
927  */
928 //MIGEMO_CALLTYPE
929 extern (C)
930 pure nothrow @trusted @nogc @live
931 export const (char)* migemo_get_operator(.migemo* object, int index)
932 
933 	do
934 	{
935 		return (object != null) ? (migemo_d.rxgen.rxgen_get_operator(object.rx, index)) : (null);
936 	}
937 
938 /**
939  * Migemoオブジェクトにコード変換用のプロシージャを設定する。プロシージャに
940  * ついての詳細は「型リファレンス」セクションのMIGEMO_PROC_CHAR2INTを参照。
941  *
942  * Params:
943  *      object = Migemoオブジェクト
944  *      proc = コード変換用プロシージャ
945  */
946 //MIGEMO_CALLTYPE
947 extern (C)
948 nothrow @nogc
949 export void migemo_setproc_char2int(.migemo* object, .MIGEMO_PROC_CHAR2INT proc)
950 
951 	do
952 	{
953 		if (object != null) {
954 			migemo_d.rxgen.rxgen_setproc_char2int(object.rx, proc);
955 		}
956 	}
957 
958 /**
959  * Migemoオブジェクトにコード変換用のプロシージャを設定する。プロシージャに
960  * ついての詳細は「型リファレンス」セクションのMIGEMO_PROC_INT2CHARを参照。
961  *
962  * Params:
963  *      object = Migemoオブジェクト
964  *      proc = コード変換用プロシージャ
965  */
966 //MIGEMO_CALLTYPE
967 extern (C)
968 nothrow @nogc
969 export void migemo_setproc_int2char(.migemo* object, .MIGEMO_PROC_INT2CHAR proc)
970 
971 	do
972 	{
973 		if (object != null) {
974 			migemo_d.rxgen.rxgen_setproc_int2char(object.rx, proc);
975 		}
976 	}
977 
978 /**
979  * Migemoオブジェクトにmigemo_dictが読み込めているかをチェックする。有効な
980  * migemo_dictを読み込めて内部に変換テーブルが構築できていれば0以外(TRUE)
981  * を、構築できていないときには0(FALSE)を返す。
982  *
983  * Params:
984  *      obj = Migemoオブジェクト
985  *
986  * Returns: 成功時0以外、失敗時0。
987  */
988 //MIGEMO_CALLTYPE
989 extern (C)
990 pure nothrow @trusted @nogc @live
991 export int migemo_is_enable(.migemo* obj)
992 
993 	do
994 	{
995 		return (obj != null) ? (obj.enable) : (0);
996 	}
997 
998 debug {
999 	/*
1000 	 * 主にデバッグ用の隠し関数
1001 	 */
1002 	//MIGEMO_CALLTYPE
1003 	extern (C)
1004 	nothrow @nogc
1005 	export void migemo_print(.migemo* object)
1006 
1007 		do
1008 		{
1009 			if (object != null) {
1010 				migemo_d.mnode.mnode_print(object.mtree, null);
1011 			}
1012 		}
1013 }