ferret 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README +1 -1
- data/TODO +4 -1
- data/TUTORIAL +9 -1
- data/ext/ferret.c +28 -3
- data/ext/ferret.h +24 -24
- data/ext/index_io.c +13 -28
- data/ext/ram_directory.c +11 -11
- data/ext/segment_merge_queue.c +2 -2
- data/ext/string_helper.c +1 -1
- data/ext/term.c +19 -13
- data/ext/term_buffer.c +3 -3
- data/lib/ferret.rb +1 -1
- data/lib/ferret/analysis/standard_tokenizer.rb +1 -1
- data/lib/ferret/analysis/token.rb +8 -0
- data/lib/ferret/analysis/tokenizers.rb +6 -6
- data/lib/ferret/index/index.rb +120 -2
- data/lib/ferret/index/index_writer.rb +7 -4
- data/lib/ferret/index/multi_reader.rb +1 -1
- data/lib/ferret/index/segment_reader.rb +1 -1
- data/lib/ferret/query_parser.rb +25 -17
- data/lib/ferret/query_parser/query_parser.tab.rb +292 -208
- data/lib/ferret/search/range_query.rb +2 -2
- data/test/test_helper.rb +7 -0
- data/test/unit/index/tc_index.rb +10 -5
- data/test/unit/index/tc_multiple_term_doc_pos_enum.rb +4 -1
- data/test/unit/query_parser/tc_query_parser.rb +43 -15
- data/test/unit/store/tm_store.rb +2 -2
- data/test/unit/ts_analysis.rb +1 -15
- data/test/unit/ts_document.rb +2 -4
- data/test/unit/ts_index.rb +2 -18
- data/test/unit/ts_query_parser.rb +2 -3
- data/test/unit/ts_search.rb +2 -10
- data/test/unit/ts_store.rb +1 -5
- data/test/unit/ts_utils.rb +1 -9
- metadata +2 -2
data/README
CHANGED
@@ -107,7 +107,7 @@ be true about Ferret. Apart from the bits about it being in Java.
|
|
107
107
|
|
108
108
|
[<b>David Balmain</b>] Port to Ruby
|
109
109
|
|
110
|
-
[
|
110
|
+
[The Apache Software Foundation (Doug Cutting and friends)] Original Apache Lucene
|
111
111
|
|
112
112
|
== License
|
113
113
|
|
data/TODO
CHANGED
data/TUTORIAL
CHANGED
@@ -28,7 +28,9 @@ For more options when creating an Index refer to Ferret::Index::Index.
|
|
28
28
|
|
29
29
|
=== Adding Documents
|
30
30
|
|
31
|
-
To add a document you can simply add a string or an array of strings.
|
31
|
+
To add a document you can simply add a string or an array of strings. This will
|
32
|
+
store all the strings in the "" (ie empty string) field (unless you specify the
|
33
|
+
default field when you create the index).
|
32
34
|
|
33
35
|
index << "This is a new document to be indexed"
|
34
36
|
index << ["And here", "is another", "new document", "to be indexed"]
|
@@ -108,6 +110,12 @@ document type;
|
|
108
110
|
|
109
111
|
NOTE: documents are indexed from 0.
|
110
112
|
|
113
|
+
The default field is an empty string when you use the simple string document so
|
114
|
+
to access those strings you'll have type;
|
115
|
+
|
116
|
+
index << "This is a document"
|
117
|
+
index[0][""]
|
118
|
+
|
111
119
|
Let's go back to the database example above. If we store all of our documents
|
112
120
|
with an id then we can access that field using the id. As long as we called
|
113
121
|
our id field "id" we can do this
|
data/ext/ferret.c
CHANGED
@@ -1,18 +1,43 @@
|
|
1
1
|
#include "ferret.h"
|
2
2
|
|
3
|
+
/* IDs */
|
4
|
+
ID frt_newobj;
|
5
|
+
|
6
|
+
/* Modules */
|
7
|
+
VALUE mFerret;
|
8
|
+
VALUE mStore;
|
9
|
+
VALUE mIndex;
|
10
|
+
VALUE mUtils;
|
11
|
+
VALUE mStringHelper;
|
12
|
+
|
13
|
+
/* Classes */
|
14
|
+
VALUE cRAMDirectory;
|
15
|
+
VALUE cIndexIn;
|
16
|
+
VALUE cBufferedIndexIn;
|
17
|
+
VALUE cFSIndexIn;
|
18
|
+
VALUE cIndexOut;
|
19
|
+
VALUE cBufferedIndexOut;
|
20
|
+
VALUE cFSIndexOut;
|
21
|
+
VALUE cRAMIndexOut;
|
22
|
+
VALUE cRAMIndexIn;
|
23
|
+
VALUE cTerm;
|
24
|
+
VALUE cTermBuffer;
|
25
|
+
VALUE cPriorityQueue;
|
26
|
+
VALUE cSegmentMergeQueue;
|
27
|
+
|
3
28
|
void
|
4
29
|
Init_ferret_ext(void)
|
5
30
|
{
|
6
|
-
|
31
|
+
/* IDs */
|
7
32
|
frt_newobj = rb_intern("new");
|
8
33
|
|
9
|
-
|
34
|
+
/* Modules */
|
10
35
|
mFerret = rb_define_module("Ferret");
|
11
36
|
mStore = rb_define_module_under(mFerret, "Store");
|
12
37
|
mIndex = rb_define_module_under(mFerret, "Index");
|
13
38
|
mUtils = rb_define_module_under(mFerret, "Utils");
|
14
39
|
|
15
|
-
|
40
|
+
/* Inits */
|
16
41
|
Init_indexio();
|
17
42
|
Init_term();
|
18
43
|
Init_term_buffer();
|
data/ext/ferret.h
CHANGED
@@ -42,32 +42,32 @@ typedef struct RAMFile {
|
|
42
42
|
int length;
|
43
43
|
} RAMFile;
|
44
44
|
|
45
|
-
|
46
|
-
ID frt_newobj;
|
45
|
+
/* IDs */
|
46
|
+
extern ID frt_newobj;
|
47
47
|
|
48
|
-
|
49
|
-
VALUE mFerret;
|
50
|
-
VALUE mStore;
|
51
|
-
VALUE mIndex;
|
52
|
-
VALUE mUtils;
|
53
|
-
VALUE mStringHelper;
|
48
|
+
/* Modules */
|
49
|
+
extern VALUE mFerret;
|
50
|
+
extern VALUE mStore;
|
51
|
+
extern VALUE mIndex;
|
52
|
+
extern VALUE mUtils;
|
53
|
+
extern VALUE mStringHelper;
|
54
54
|
|
55
|
-
|
56
|
-
VALUE cRAMDirectory;
|
57
|
-
VALUE cIndexIn;
|
58
|
-
VALUE cBufferedIndexIn;
|
59
|
-
VALUE cFSIndexIn;
|
60
|
-
VALUE cIndexOut;
|
61
|
-
VALUE cBufferedIndexOut;
|
62
|
-
VALUE cFSIndexOut;
|
63
|
-
VALUE cRAMIndexOut;
|
64
|
-
VALUE cRAMIndexIn;
|
65
|
-
VALUE cTerm;
|
66
|
-
VALUE cTermBuffer;
|
67
|
-
VALUE cPriorityQueue;
|
68
|
-
VALUE cSegmentMergeQueue;
|
55
|
+
/* Classes */
|
56
|
+
extern VALUE cRAMDirectory;
|
57
|
+
extern VALUE cIndexIn;
|
58
|
+
extern VALUE cBufferedIndexIn;
|
59
|
+
extern VALUE cFSIndexIn;
|
60
|
+
extern VALUE cIndexOut;
|
61
|
+
extern VALUE cBufferedIndexOut;
|
62
|
+
extern VALUE cFSIndexOut;
|
63
|
+
extern VALUE cRAMIndexOut;
|
64
|
+
extern VALUE cRAMIndexIn;
|
65
|
+
extern VALUE cTerm;
|
66
|
+
extern VALUE cTermBuffer;
|
67
|
+
extern VALUE cPriorityQueue;
|
68
|
+
extern VALUE cSegmentMergeQueue;
|
69
69
|
|
70
|
-
|
70
|
+
/* Ferret Inits */
|
71
71
|
extern void Init_indexio();
|
72
72
|
extern void Init_term();
|
73
73
|
extern void Init_priority_queue();
|
@@ -76,7 +76,7 @@ extern void Init_segment_merge_queue();
|
|
76
76
|
extern void Init_ram_directory();
|
77
77
|
extern void Init_string_helper();
|
78
78
|
|
79
|
-
|
79
|
+
/* External functions */
|
80
80
|
extern int frt_hash(register char *p, register int len);
|
81
81
|
extern unsigned long long frt_read_vint(VALUE self);
|
82
82
|
extern void frt_read_chars(VALUE self, char *buf, int offset, int len);
|
data/ext/index_io.c
CHANGED
@@ -82,7 +82,7 @@ frt_indexin_refill(VALUE self)
|
|
82
82
|
rStr, INT2FIX(0), INT2FIX(len_to_read));
|
83
83
|
|
84
84
|
memcpy(my_buf->buffer, RSTRING(rStr)->ptr, BUFFER_SIZE);
|
85
|
-
|
85
|
+
/* my_buf->buffer = StringValuePtr(rStr); */
|
86
86
|
|
87
87
|
my_buf->len = len_to_read;
|
88
88
|
my_buf->start = start;
|
@@ -143,7 +143,7 @@ frt_read_bytes(VALUE self, VALUE rbuffer, int offset, int len)
|
|
143
143
|
|
144
144
|
my_buf->start = my_buf->start + len;
|
145
145
|
my_buf->pos = 0;
|
146
|
-
my_buf->len = 0;
|
146
|
+
my_buf->len = 0; /* trigger refill() on read() */
|
147
147
|
}
|
148
148
|
|
149
149
|
return rbuf;
|
@@ -168,11 +168,11 @@ frt_indexin_seek(VALUE self, VALUE rpos)
|
|
168
168
|
Data_Get_Struct(self, IndexBuffer, my_buf);
|
169
169
|
|
170
170
|
if ((pos >= my_buf->start) && (pos < (my_buf->start + my_buf->len))) {
|
171
|
-
my_buf->pos = pos - my_buf->start;
|
171
|
+
my_buf->pos = pos - my_buf->start; /* seek within buffer */
|
172
172
|
} else {
|
173
173
|
my_buf->start = pos;
|
174
174
|
my_buf->pos = 0;
|
175
|
-
my_buf->len = 0;
|
175
|
+
my_buf->len = 0; /* trigger refill() on read() */
|
176
176
|
rb_funcall(self, frt_seek_internal, 1, rpos);
|
177
177
|
}
|
178
178
|
return Qnil;
|
@@ -229,9 +229,9 @@ frt_read_vint(VALUE self)
|
|
229
229
|
register int shift = 7;
|
230
230
|
|
231
231
|
b = frt_read_byte(self);
|
232
|
-
i = b & 0x7F;
|
232
|
+
i = b & 0x7F; /* 0x7F = 0b01111111 */
|
233
233
|
|
234
|
-
while ((b & 0x80) != 0) {
|
234
|
+
while ((b & 0x80) != 0) {/* 0x80 = 0b10000000 */
|
235
235
|
b = frt_read_byte(self);
|
236
236
|
i |= (b & 0x7F) << shift;
|
237
237
|
shift += 7;
|
@@ -249,7 +249,7 @@ frt_indexin_read_vint(VALUE self)
|
|
249
249
|
void
|
250
250
|
frt_read_chars(VALUE self, char* buffer, int off, int len)
|
251
251
|
{
|
252
|
-
|
252
|
+
/* byte_t b, b1, b2; */
|
253
253
|
int end, i;
|
254
254
|
|
255
255
|
end = off + len;
|
@@ -257,21 +257,6 @@ frt_read_chars(VALUE self, char* buffer, int off, int len)
|
|
257
257
|
for(i = off; i < end; i++) {
|
258
258
|
buffer[i] = frt_read_byte(self);
|
259
259
|
}
|
260
|
-
// for(i = off; i < end; i++){
|
261
|
-
// b = frt_read_byte(self);
|
262
|
-
// if((b & 0x80) == 0){
|
263
|
-
// buffer[i] = (char)(b & 0x7F);
|
264
|
-
// } else {
|
265
|
-
// if((b & 0xE0) != 0xE0){
|
266
|
-
// b1 = frt_read_byte(self);
|
267
|
-
// buffer[i] = (char)(((b & 0x1F) << 6) | (b1 & 0x3F));
|
268
|
-
// } else{
|
269
|
-
// b1 = frt_read_byte(self);
|
270
|
-
// b2 = frt_read_byte(self);
|
271
|
-
// buffer[i] = (char)(((b & 0x0F) << 12) | ((b1 & 0x3F) << 6) | (b2 & 0x3F));
|
272
|
-
// }
|
273
|
-
// }
|
274
|
-
// }
|
275
260
|
}
|
276
261
|
|
277
262
|
static VALUE
|
@@ -412,7 +397,7 @@ static VALUE
|
|
412
397
|
frt_indexout_write_ulong(VALUE self, VALUE rulong)
|
413
398
|
{
|
414
399
|
unsigned long long l;
|
415
|
-
l = rb_num2ull(rulong);
|
400
|
+
l = rb_num2ull(rulong); /* ruby 1.8 doesn't have NUM2ULL. Added in 1.9 */
|
416
401
|
frt_write_byte(self, (l >> 56) & 0xFF);
|
417
402
|
frt_write_byte(self, (l >> 48) & 0xFF);
|
418
403
|
frt_write_byte(self, (l >> 40) & 0xFF);
|
@@ -492,13 +477,13 @@ frt_indexout_write_string(VALUE self, VALUE rstr)
|
|
492
477
|
void
|
493
478
|
Init_indexio(void)
|
494
479
|
{
|
495
|
-
|
480
|
+
/* IDs */
|
496
481
|
frt_length = rb_intern("length");
|
497
482
|
frt_flush_buffer = rb_intern("flush_buffer");
|
498
483
|
frt_read_internal = rb_intern("read_internal");
|
499
484
|
frt_seek_internal = rb_intern("seek_internal");
|
500
485
|
|
501
|
-
|
486
|
+
/* IndexInput */
|
502
487
|
cIndexIn = rb_define_class_under(mStore, "IndexInput", rb_cObject);
|
503
488
|
cBufferedIndexIn = rb_define_class_under(mStore, "BufferedIndexInput", cIndexIn);
|
504
489
|
rb_define_alloc_func(cBufferedIndexIn, frt_indexbuffer_alloc);
|
@@ -518,7 +503,7 @@ Init_indexio(void)
|
|
518
503
|
rb_define_method(cBufferedIndexIn, "read_string", frt_indexin_read_string, 0);
|
519
504
|
rb_define_method(cBufferedIndexIn, "read_chars", frt_indexin_read_bytes, 3);
|
520
505
|
|
521
|
-
|
506
|
+
/* IndexOutput */
|
522
507
|
cIndexOut = rb_define_class_under(mStore, "IndexOutput", rb_cObject);
|
523
508
|
cBufferedIndexOut = rb_define_class_under(mStore, "BufferedIndexOutput", cIndexOut);
|
524
509
|
rb_define_alloc_func(cBufferedIndexOut, frt_indexbuffer_alloc);
|
@@ -538,6 +523,6 @@ Init_indexio(void)
|
|
538
523
|
rb_define_method(cBufferedIndexOut, "write_chars", frt_indexout_write_chars, 3);
|
539
524
|
rb_define_method(cBufferedIndexOut, "write_string", frt_indexout_write_string, 1);
|
540
525
|
|
541
|
-
|
542
|
-
|
526
|
+
/* FSIndexInput */
|
527
|
+
/*cFSIndexIn = rb_define_class_under(mStore, "FSIndexInput", cBufferedIndexIn); */
|
543
528
|
}
|
data/ext/ram_directory.c
CHANGED
@@ -101,7 +101,7 @@ frt_rio_flush_buffer(VALUE self, VALUE rsrc, VALUE rlen)
|
|
101
101
|
int buffer_number, buffer_offset, bytes_in_buffer, bytes_to_copy;
|
102
102
|
int src_offset;
|
103
103
|
int len = FIX2INT(rlen);
|
104
|
-
|
104
|
+
/* char *src = StringValuePtr(rsrc); */
|
105
105
|
int pointer = FIX2INT(rb_iv_get(self, "pointer"));
|
106
106
|
|
107
107
|
VALUE file = rb_iv_get(self, "file");
|
@@ -275,26 +275,26 @@ frt_rii_close(VALUE self)
|
|
275
275
|
void
|
276
276
|
Init_ram_directory(void)
|
277
277
|
{
|
278
|
-
|
278
|
+
/* IDs */
|
279
279
|
flush = rb_intern("flush");
|
280
280
|
seek = rb_intern("seek");
|
281
281
|
|
282
|
-
|
282
|
+
/* RAMDirectory */
|
283
283
|
VALUE cDirectory = rb_define_class_under(mStore, "Directory", rb_cObject);
|
284
284
|
cRAMDirectory = rb_define_class_under(mStore, "RAMDirectory", cDirectory);
|
285
285
|
|
286
|
-
|
286
|
+
/* RAMFile */
|
287
287
|
VALUE cRAMFile = rb_define_class_under(cRAMDirectory, "RAMFile", rb_cObject);
|
288
288
|
rb_define_alloc_func(cRAMFile, frt_rf_alloc);
|
289
289
|
|
290
|
-
|
290
|
+
/* Methods */
|
291
291
|
rb_define_method(cRAMFile, "length", frt_rf_length, 0);
|
292
292
|
|
293
|
-
|
293
|
+
/* RAMIndexOutput */
|
294
294
|
cRAMIndexOut = rb_define_class_under(cRAMDirectory, "RAMIndexOutput", cBufferedIndexOut);
|
295
|
-
|
295
|
+
/*rb_define_alloc_func(cRAMIndexOut, frt_ramio_alloc); */
|
296
296
|
|
297
|
-
|
297
|
+
/* Methods */
|
298
298
|
rb_define_method(cRAMIndexOut, "initialize", frt_rio_init, 1);
|
299
299
|
rb_define_method(cRAMIndexOut, "length", frt_rio_length, 0);
|
300
300
|
rb_define_method(cRAMIndexOut, "flush_buffer", frt_rio_flush_buffer, 2);
|
@@ -303,11 +303,11 @@ Init_ram_directory(void)
|
|
303
303
|
rb_define_method(cRAMIndexOut, "close", frt_rio_close, 0);
|
304
304
|
rb_define_method(cRAMIndexOut, "write_to", frt_rio_write_to, 1);
|
305
305
|
|
306
|
-
|
306
|
+
/* RAMIndexInput */
|
307
307
|
cRAMIndexIn = rb_define_class_under(cRAMDirectory, "RAMIndexInput", cBufferedIndexIn);
|
308
|
-
|
308
|
+
/*rb_define_alloc_func(cRAMIndexIn, frt_ramio_alloc); */
|
309
309
|
|
310
|
-
|
310
|
+
/* Methods */
|
311
311
|
rb_define_method(cRAMIndexIn, "initialize", frt_rii_init, 1);
|
312
312
|
rb_define_method(cRAMIndexIn, "length", frt_rii_length, 0);
|
313
313
|
rb_define_method(cRAMIndexIn, "read_internal", frt_rii_read_internal, 3);
|
data/ext/segment_merge_queue.c
CHANGED
@@ -30,11 +30,11 @@ frt_smq_less_than(VALUE self, VALUE rsti1, VALUE rsti2)
|
|
30
30
|
void
|
31
31
|
Init_segment_merge_queue(void)
|
32
32
|
{
|
33
|
-
|
33
|
+
/* IDs */
|
34
34
|
eq = rb_intern("==");
|
35
35
|
lt = rb_intern("<");
|
36
36
|
|
37
|
-
|
37
|
+
/* SegmentMergeQueue */
|
38
38
|
cSegmentMergeQueue = rb_define_class_under(mIndex, "SegmentMergeQueue", cPriorityQueue);
|
39
39
|
|
40
40
|
rb_define_method(cSegmentMergeQueue, "less_than", frt_smq_less_than, 2);
|
data/ext/string_helper.c
CHANGED
@@ -35,7 +35,7 @@ frt_sh_string_difference(VALUE self, VALUE rstr1, VALUE rstr2)
|
|
35
35
|
void
|
36
36
|
Init_string_helper(void)
|
37
37
|
{
|
38
|
-
|
38
|
+
/* StringHelper */
|
39
39
|
mStringHelper = rb_define_module_under(mUtils, "StringHelper");
|
40
40
|
|
41
41
|
rb_define_method(mStringHelper, "string_difference", frt_sh_string_difference, 2);
|
data/ext/term.c
CHANGED
@@ -145,13 +145,15 @@ frt_term_compare_to(VALUE self, VALUE rother)
|
|
145
145
|
} else
|
146
146
|
comp = mylen > olen ? 1 : -1;
|
147
147
|
}
|
148
|
-
|
149
|
-
|
150
|
-
|
148
|
+
/*
|
149
|
+
comp = strcmp(term->field, other->field);
|
150
|
+
if(comp == 0)
|
151
|
+
comp = strcmp(term->text, other->text);
|
152
|
+
*/
|
151
153
|
return INT2FIX(comp);
|
152
154
|
}
|
153
155
|
|
154
|
-
|
156
|
+
/* keep in synch with fuction above */
|
155
157
|
int
|
156
158
|
frt_term_compare_to_int(VALUE self, VALUE rother)
|
157
159
|
{
|
@@ -175,9 +177,11 @@ frt_term_compare_to_int(VALUE self, VALUE rother)
|
|
175
177
|
} else
|
176
178
|
comp = mylen > olen ? 1 : -1;
|
177
179
|
}
|
178
|
-
|
179
|
-
|
180
|
-
|
180
|
+
/*
|
181
|
+
comp = strcmp(term->field, other->field);
|
182
|
+
if(comp == 0)
|
183
|
+
comp = strcmp(term->text, other->text);
|
184
|
+
*/
|
181
185
|
return comp;
|
182
186
|
}
|
183
187
|
|
@@ -214,11 +218,13 @@ frt_term_eq(VALUE self, VALUE rother)
|
|
214
218
|
}
|
215
219
|
|
216
220
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
221
|
+
/*
|
222
|
+
static VALUE
|
223
|
+
frt_term_compare_to(VALUE self, VALUE other)
|
224
|
+
{
|
225
|
+
return INT2FIX(frt_term_compare_to_int(self, other));
|
226
|
+
}
|
227
|
+
*/
|
222
228
|
|
223
229
|
static VALUE
|
224
230
|
frt_term_hash(VALUE self)
|
@@ -238,7 +244,7 @@ frt_term_hash(VALUE self)
|
|
238
244
|
void
|
239
245
|
Init_term(void)
|
240
246
|
{
|
241
|
-
|
247
|
+
/* Term */
|
242
248
|
cTerm = rb_define_class_under(mIndex, "Term", rb_cObject);
|
243
249
|
rb_define_alloc_func(cTerm, frt_term_alloc);
|
244
250
|
rb_include_module(cTerm, rb_mComparable);
|
data/ext/term_buffer.c
CHANGED
@@ -267,15 +267,15 @@ frt_termbuffer_hash(VALUE self)
|
|
267
267
|
|
268
268
|
void
|
269
269
|
Init_term_buffer(void) {
|
270
|
-
|
270
|
+
/* IDs */
|
271
271
|
field_name = rb_intern("name");
|
272
272
|
|
273
|
-
|
273
|
+
/* TermBuffer */
|
274
274
|
cTermBuffer = rb_define_class_under(mIndex, "TermBuffer", rb_cObject);
|
275
275
|
rb_define_alloc_func(cTermBuffer, frt_termbuffer_alloc);
|
276
276
|
rb_include_module(cTermBuffer, rb_mComparable);
|
277
277
|
|
278
|
-
|
278
|
+
/* Methods */
|
279
279
|
rb_define_method(cTermBuffer, "initialize", frt_termbuffer_init, 0);
|
280
280
|
rb_define_method(cTermBuffer, "initialize_copy", frt_termbuffer_init_copy, 1);
|
281
281
|
rb_define_method(cTermBuffer, "text", frt_termbuffer_get_text, 0);
|
data/lib/ferret.rb
CHANGED
@@ -12,7 +12,7 @@ module Ferret::Analysis
|
|
12
12
|
# words correctly as well as tokenizing things like email addresses, web
|
13
13
|
# addresses, phone numbers, etc.
|
14
14
|
|
15
|
-
class StandardTokenizer <
|
15
|
+
class StandardTokenizer < RegExpTokenizer
|
16
16
|
ALPHA = /[[:alpha:]]+/
|
17
17
|
APOSTROPHE = /#{ALPHA}('#{ALPHA})+/
|
18
18
|
ACRONYM = /#{ALPHA}\.(#{ALPHA}\.)+/
|