fabulator-grammar 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ module Fabulator::Grammar::Expr
2
+ class Any
3
+ def to_regex
4
+ %r{.}
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,45 @@
1
+ module Fabulator::Grammar::Expr
2
+ class CharSet
3
+ def initialize(cs)
4
+ chars = ""
5
+ ranges = ""
6
+ if cs[0..0] == '-'
7
+ chars = '-'
8
+ cs = cs[1..cs.length-1]
9
+ end
10
+ bits = cs.split(/-/) # to pull out ranges
11
+ if bits.size == 1
12
+ chars = bits[0]
13
+ else
14
+ if bits[0].size > 1
15
+ chars += b[0..0]
16
+ end
17
+ while(bits.size > 1)
18
+ b = bits.shift
19
+ if b.size > 2
20
+ chars += b[1..b.size-2]
21
+ end
22
+ ranges += Regexp.quote(b[b.size-1 .. b.size-1]) + '-' + Regexp.quote(bits[0][0..0])
23
+ end
24
+ if bits[0].size > 1
25
+ chars += bits[0][1..bits[0].size-1]
26
+ end
27
+ end
28
+ chars = chars.collect{ |cc| Regexp.quote(cc) }.join('')
29
+ @set = chars + ranges
30
+ @inverted = false
31
+ end
32
+
33
+ def inverted
34
+ @inverted = true
35
+ end
36
+
37
+ def to_regex
38
+ if @set != ''
39
+ Regexp.compile("[" + (@inverted ? '^' : '') + @set + "]")
40
+ else
41
+ %r{}
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,36 @@
1
+ module Fabulator::Grammar::Expr
2
+ class Rule
3
+ def initialize
4
+ @sequences = [ ]
5
+ @anchor_start = false
6
+ @anchor_end = false
7
+ end
8
+
9
+ def anchor_start
10
+ @anchor_start = true
11
+ end
12
+
13
+ def anchor_end
14
+ @anchor_end = true
15
+ end
16
+
17
+ def add_sequence(s)
18
+ @sequences << s
19
+ end
20
+
21
+ def to_regex
22
+ r = %r{#{@sequences.collect{ |s| s.to_regex }}}
23
+ if @anchor_start
24
+ if @anchor_end
25
+ %r{^#{r}$}
26
+ else
27
+ %r{^#{r}}
28
+ end
29
+ elsif @anchor_end
30
+ %r{#{r}$}
31
+ else
32
+ r
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,13 @@
1
+ module Fabulator::Grammar::Expr
2
+ class RuleRef
3
+ def initialize(qname)
4
+ bits = qname.split(/:/,2)
5
+ @ns_prefix = bits[0]
6
+ @name = bits[1]
7
+ end
8
+
9
+ def to_regex
10
+ %r{}
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module Fabulator::Grammar::Expr
2
+ class Rules
3
+ def initialize
4
+ @alternatives = [ ]
5
+ end
6
+
7
+ def add_alternative(a)
8
+ @alternatives << a
9
+ end
10
+
11
+ def to_regex
12
+ Regexp.union(@alternatives.collect{|a| a.to_regex })
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,26 @@
1
+ module Fabulator::Grammar::Expr
2
+ class Sequence
3
+ def initialize(sub_seq, count = [ :one ])
4
+ @sub_sequence = sub_seq
5
+ @modifiers = ''
6
+ case (count - [:min]).first
7
+ when :zero_or_one: @modifiers = '?'
8
+ when :one_or_more: @modifiers = '+'
9
+ when :zero_or_more: @modifiers = '*'
10
+ when :exact:
11
+ amt = count.select{ |c| !c.is_a?(Symbol) }
12
+ @modifiers = '{' + amt.first.to_s + '}'
13
+ when :range:
14
+ ends = count.select{ |c| !c.is_a?(Symbol) }
15
+ @modifiers = '{'+ends.join(",")+'}'
16
+ end
17
+ if count.include?(:min) && @modifiers != ''
18
+ @modifiers += "?"
19
+ end
20
+ end
21
+
22
+ def to_regex
23
+ s = %r{#{@sub_sequence.to_regex}#{@modifiers}}
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ module Fabulator::Grammar::Expr
2
+ class Text
3
+ def initialize(t)
4
+ @text = t
5
+ end
6
+
7
+ def to_regex
8
+ Regexp.escape(@text)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,548 @@
1
+ #
2
+ # DO NOT MODIFY!!!!
3
+ # This file is automatically generated by Racc 1.4.6
4
+ # from Racc grammer file "".
5
+ #
6
+
7
+ require 'racc/parser.rb'
8
+ module Fabulator
9
+ module Grammar
10
+ class Parser < Racc::Parser
11
+
12
+ module_eval(<<'...end regex.racc/module_eval...', 'regex.racc', 48)
13
+ require 'fabulator/grammar'
14
+
15
+ def parse(t, ctx)
16
+ @source = t
17
+ @curpos = 0
18
+ @context = ctx
19
+ @line = 0
20
+
21
+ @yydebug = true
22
+
23
+ @last_token = nil
24
+
25
+ do_parse
26
+ end
27
+
28
+ def on_error(*args)
29
+ raise Fabulator::Grammar::ParserError.new("unable to parse '#{args[1]}' near line #{@line + 1}, column #{@col}")
30
+ end
31
+
32
+ @@regex = {
33
+ :ncname => %r{(?:[a-zA-Z_][-a-zA-Z0-9_.]*)}
34
+ }
35
+
36
+ @@regex[:qname] = %r{((?:#{@@regex[:ncname]}:)?#{@@regex[:ncname]})}
37
+
38
+ def next_token
39
+ @token = nil
40
+ white_space = 0
41
+ new_line = 0
42
+ @col = 0
43
+ while @curpos < @source.length && @source[@curpos..@curpos] =~ /\s/ do
44
+ if @source[@curpos..@curpos] =~ /\n/
45
+ new_line = new_line + 1
46
+ @line = @line + 1
47
+ @col = 0
48
+ else
49
+ @col = @col + 1
50
+ end
51
+ @curpos = @curpos + 1
52
+ white_space = white_space + 1
53
+ end
54
+
55
+ # skip comments delimited by (: :)
56
+ # comments can be nested
57
+ # these are XPath 2.0 comments
58
+ #
59
+ if @curpos < @source.length && @source[@curpos..@curpos+1] == '(:'
60
+ comment_depth = 1
61
+ @curpos = @curpos + 2
62
+ @col = @col + 2
63
+ while comment_depth > 0 && @curpos < @source.length
64
+ if @source[@curpos..@curpos+1] == '(:'
65
+ comment_depth = comment_depth + 1
66
+ @curpos = @curpos + 1
67
+ @col = @col + 1
68
+ end
69
+ if @source[@curpos..@curpos+1] == ':)'
70
+ comment_depth = comment_depth - 1
71
+ @curpos = @curpos + 1
72
+ @col = @col + 1
73
+ end
74
+ @curpos = @curpos + 1
75
+ @col = @col + 1
76
+ end
77
+ white_space = white_space + 1
78
+ end
79
+
80
+ while @curpos < @source.length && @source[@curpos..@curpos] =~ /\s/ do
81
+ if @source[@curpos..@curpos] =~ /\n/
82
+ new_line = new_line + 1
83
+ @line = @line + 1
84
+ @col = 0
85
+ else
86
+ @col = @col + 1
87
+ end
88
+ @curpos = @curpos + 1
89
+ white_space = white_space + 1
90
+ end
91
+
92
+ if @curpos >= @source.length
93
+ @last_token = nil
94
+ return [ false, false ]
95
+ end
96
+
97
+ case @source[@curpos..@curpos]
98
+ when '<': @token = [ :LT, '<' ]
99
+ when '>': @token = [ :GT, '>' ]
100
+ when '[': @token = [ :LB, '[' ]
101
+ when ']': @token = [ :RB, ']' ]
102
+ when '(': @token = [ :LP, '(' ]
103
+ when ')': @token = [ :RP, ')' ]
104
+ when '{': @token = [ :LC, '{' ]
105
+ when '}': @token = [ :RC, '}' ]
106
+ when ':': @token = [ :COLON, ':' ]
107
+ when ',': @token = [ :COMMA, ',' ]
108
+ when '|': @token = [ :PIPE, '|' ]
109
+ when '*': @token = [ :STAR, '*' ]
110
+ when '+': @token = [ :PLUS, '+' ]
111
+ when '.': @token = [ :DOT, '.' ]
112
+ when '?': @token = [ :QUESTION, '?' ]
113
+ when '$': @token = [ :DOLLAR, '$' ]
114
+ when '^': @token = [ :CARET, '^' ]
115
+ end
116
+
117
+ if @token.nil?
118
+ # get longest sequence of non-special characters
119
+ # if it's all digits, report INTEGER
120
+ # if it's a qname, report QNAME
121
+ # otherwise, report TEXT
122
+ @source[@curpos..@source.length-1] =~ /^(((\\.)|[^ \$\^\[\]<>\{\}\(\):,|*+.?])+)*/
123
+ text = $1
124
+ bits = text.split(/\\/)
125
+ text = bits.join('')
126
+ @curpos += bits.size - 1
127
+ if text.length > 0
128
+ if @source[@curpos+text.length .. @curpos+text.length] =~ /[*?+\{]/
129
+ text = text[0..text.length-2]
130
+ @token = [ :TEXT, text ]
131
+ else
132
+ case text
133
+ when /^\d+$/: @token = [ :INTEGER, text ]
134
+ when /^#{@@regex[:ncname]}$/: @token = [ :NCNAME, text ]
135
+ else @token = [ :TEXT, text ]
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ if @token.nil?
142
+ puts "Uh oh... we don't know what to do: #{@source[@curpos .. @source.length-1]}"
143
+ else
144
+ @curpos += @token[1].length
145
+ end
146
+
147
+ return @token
148
+ end
149
+ ...end regex.racc/module_eval...
150
+ ##### State transition tables begin ###
151
+
152
+ racc_action_table = [
153
+ 31, 45, 25, 26, 28, 29, 35, 6, 46, 8,
154
+ 9, 10, 12, 13, 39, 16, 17, 5, 19, 6,
155
+ 8, 9, 10, 22, 13, 10, 16, 17, 33, 19,
156
+ 34, 8, 9, 10, 8, 9, 10, 42, 43, 3,
157
+ 36, 37, 38, 23, 3, 41, 20, 44, 3, 47,
158
+ 48, 49 ]
159
+
160
+ racc_action_check = [
161
+ 19, 43, 14, 14, 14, 14, 25, 30, 43, 19,
162
+ 19, 19, 4, 4, 30, 4, 4, 1, 4, 1,
163
+ 4, 4, 4, 7, 7, 13, 7, 7, 23, 7,
164
+ 24, 7, 7, 7, 31, 31, 31, 38, 38, 17,
165
+ 26, 28, 29, 10, 6, 32, 5, 40, 0, 45,
166
+ 46, 47 ]
167
+
168
+ racc_action_pointer = [
169
+ 45, 17, nil, nil, 8, 46, 41, 19, nil, nil,
170
+ 28, nil, nil, 11, -14, nil, nil, 36, nil, -3,
171
+ nil, nil, nil, 14, 24, -11, 23, nil, 24, 29,
172
+ 5, 22, 34, nil, nil, nil, nil, nil, 17, nil,
173
+ 36, nil, nil, -12, nil, 29, 33, 34, nil, nil ]
174
+
175
+ racc_action_default = [
176
+ -7, -33, -1, -7, -3, -33, -7, -4, -18, -19,
177
+ -20, -8, -5, -33, -10, -17, -13, -7, -12, -33,
178
+ 50, -2, -6, -33, -33, -22, -26, -9, -24, -33,
179
+ -33, -33, -33, -21, -11, -23, -27, -25, -33, -14,
180
+ -33, -15, -28, -33, -16, -33, -30, -29, -31, -32 ]
181
+
182
+ racc_goto_table = [
183
+ 1, 32, 7, 21, 24, 27, nil, nil, nil, nil,
184
+ nil, nil, nil, 40, nil, nil, nil, 30 ]
185
+
186
+ racc_goto_check = [
187
+ 1, 8, 3, 2, 7, 6, nil, nil, nil, nil,
188
+ nil, nil, nil, 8, nil, nil, nil, 1 ]
189
+
190
+ racc_goto_pointer = [
191
+ nil, 0, -3, -1, nil, nil, -9, -9, -18 ]
192
+
193
+ racc_goto_default = [
194
+ nil, nil, 2, 4, 11, 14, nil, 15, 18 ]
195
+
196
+ racc_reduce_table = [
197
+ 0, 0, :racc_error,
198
+ 1, 23, :_reduce_1,
199
+ 3, 23, :_reduce_2,
200
+ 1, 24, :_reduce_3,
201
+ 2, 24, :_reduce_4,
202
+ 2, 24, :_reduce_5,
203
+ 3, 24, :_reduce_6,
204
+ 0, 25, :_reduce_7,
205
+ 2, 25, :_reduce_8,
206
+ 2, 26, :_reduce_9,
207
+ 1, 26, :_reduce_10,
208
+ 3, 27, :_reduce_11,
209
+ 1, 27, :_reduce_12,
210
+ 1, 27, :_reduce_13,
211
+ 3, 27, :_reduce_14,
212
+ 3, 27, :_reduce_15,
213
+ 4, 27, :_reduce_16,
214
+ 1, 30, :_reduce_17,
215
+ 1, 30, :_reduce_18,
216
+ 1, 30, :_reduce_19,
217
+ 1, 29, :_reduce_20,
218
+ 3, 29, :_reduce_21,
219
+ 1, 28, :_reduce_22,
220
+ 2, 28, :_reduce_23,
221
+ 1, 28, :_reduce_24,
222
+ 2, 28, :_reduce_25,
223
+ 1, 28, :_reduce_26,
224
+ 2, 28, :_reduce_27,
225
+ 3, 28, :_reduce_28,
226
+ 5, 28, :_reduce_29,
227
+ 4, 28, :_reduce_30,
228
+ 5, 28, :_reduce_31,
229
+ 6, 28, :_reduce_32 ]
230
+
231
+ racc_reduce_n = 33
232
+
233
+ racc_shift_n = 50
234
+
235
+ racc_token_table = {
236
+ false => 0,
237
+ :error => 1,
238
+ :PIPE => 2,
239
+ :CARET => 3,
240
+ :DOLLAR => 4,
241
+ :LT => 5,
242
+ :GT => 6,
243
+ :DOT => 7,
244
+ :LP => 8,
245
+ :RP => 9,
246
+ :LB => 10,
247
+ :RB => 11,
248
+ :TEXT => 12,
249
+ :INTEGER => 13,
250
+ :NCNAME => 14,
251
+ :COLON => 15,
252
+ :STAR => 16,
253
+ :QUESTION => 17,
254
+ :PLUS => 18,
255
+ :LC => 19,
256
+ :RC => 20,
257
+ :COMMA => 21 }
258
+
259
+ racc_nt_base = 22
260
+
261
+ racc_use_result_var = true
262
+
263
+ Racc_arg = [
264
+ racc_action_table,
265
+ racc_action_check,
266
+ racc_action_default,
267
+ racc_action_pointer,
268
+ racc_goto_table,
269
+ racc_goto_check,
270
+ racc_goto_default,
271
+ racc_goto_pointer,
272
+ racc_nt_base,
273
+ racc_reduce_table,
274
+ racc_token_table,
275
+ racc_shift_n,
276
+ racc_reduce_n,
277
+ racc_use_result_var ]
278
+
279
+ Racc_token_to_s_table = [
280
+ "$end",
281
+ "error",
282
+ "PIPE",
283
+ "CARET",
284
+ "DOLLAR",
285
+ "LT",
286
+ "GT",
287
+ "DOT",
288
+ "LP",
289
+ "RP",
290
+ "LB",
291
+ "RB",
292
+ "TEXT",
293
+ "INTEGER",
294
+ "NCNAME",
295
+ "COLON",
296
+ "STAR",
297
+ "QUESTION",
298
+ "PLUS",
299
+ "LC",
300
+ "RC",
301
+ "COMMA",
302
+ "$start",
303
+ "rules",
304
+ "anchored_rule",
305
+ "rule",
306
+ "sequence",
307
+ "sub_sequence",
308
+ "sequence_qualifiers",
309
+ "qname",
310
+ "text" ]
311
+
312
+ Racc_debug_parser = false
313
+
314
+ ##### State transition tables end #####
315
+
316
+ # reduce 0 omitted
317
+
318
+ module_eval(<<'.,.,', 'regex.racc', 5)
319
+ def _reduce_1(val, _values, result)
320
+ result = Fabulator::Grammar::Expr::Rules.new; result.add_alternative(val[0])
321
+ result
322
+ end
323
+ .,.,
324
+
325
+ module_eval(<<'.,.,', 'regex.racc', 6)
326
+ def _reduce_2(val, _values, result)
327
+ result = val[0]; result.add_alternative(val[2])
328
+ result
329
+ end
330
+ .,.,
331
+
332
+ module_eval(<<'.,.,', 'regex.racc', 8)
333
+ def _reduce_3(val, _values, result)
334
+ result = val[0]
335
+ result
336
+ end
337
+ .,.,
338
+
339
+ module_eval(<<'.,.,', 'regex.racc', 9)
340
+ def _reduce_4(val, _values, result)
341
+ result = val[1]; result.anchor_start
342
+ result
343
+ end
344
+ .,.,
345
+
346
+ module_eval(<<'.,.,', 'regex.racc', 10)
347
+ def _reduce_5(val, _values, result)
348
+ result = val[0]; result.anchor_end
349
+ result
350
+ end
351
+ .,.,
352
+
353
+ module_eval(<<'.,.,', 'regex.racc', 11)
354
+ def _reduce_6(val, _values, result)
355
+ result = val[1]; result.anchor_start; result.anchor_end
356
+ result
357
+ end
358
+ .,.,
359
+
360
+ module_eval(<<'.,.,', 'regex.racc', 13)
361
+ def _reduce_7(val, _values, result)
362
+ result = Fabulator::Grammar::Expr::Rule.new;
363
+ result
364
+ end
365
+ .,.,
366
+
367
+ module_eval(<<'.,.,', 'regex.racc', 14)
368
+ def _reduce_8(val, _values, result)
369
+ result = val[0]; result.add_sequence(val[1]);
370
+ result
371
+ end
372
+ .,.,
373
+
374
+ module_eval(<<'.,.,', 'regex.racc', 16)
375
+ def _reduce_9(val, _values, result)
376
+ result = Fabulator::Grammar::Expr::Sequence.new(val[0], val[1])
377
+ result
378
+ end
379
+ .,.,
380
+
381
+ module_eval(<<'.,.,', 'regex.racc', 17)
382
+ def _reduce_10(val, _values, result)
383
+ result = Fabulator::Grammar::Expr::Sequence.new(val[0])
384
+ result
385
+ end
386
+ .,.,
387
+
388
+ module_eval(<<'.,.,', 'regex.racc', 19)
389
+ def _reduce_11(val, _values, result)
390
+ result = Fabulator::Grammar::Expr::RuleRef.new(val[1])
391
+ result
392
+ end
393
+ .,.,
394
+
395
+ module_eval(<<'.,.,', 'regex.racc', 20)
396
+ def _reduce_12(val, _values, result)
397
+ result = Fabulator::Grammar::Expr::Text.new(val[0])
398
+ result
399
+ end
400
+ .,.,
401
+
402
+ module_eval(<<'.,.,', 'regex.racc', 21)
403
+ def _reduce_13(val, _values, result)
404
+ result = Fabulator::Grammar::Expr::Any.new
405
+ result
406
+ end
407
+ .,.,
408
+
409
+ module_eval(<<'.,.,', 'regex.racc', 22)
410
+ def _reduce_14(val, _values, result)
411
+ result = val[1]
412
+ result
413
+ end
414
+ .,.,
415
+
416
+ module_eval(<<'.,.,', 'regex.racc', 23)
417
+ def _reduce_15(val, _values, result)
418
+ result = Fabulator::Grammar::Expr::CharSet.new(val[1])
419
+ result
420
+ end
421
+ .,.,
422
+
423
+ module_eval(<<'.,.,', 'regex.racc', 24)
424
+ def _reduce_16(val, _values, result)
425
+ result = Fabulator::Grammar::Expr::CharSet.new(val[2]); result.inverted
426
+ result
427
+ end
428
+ .,.,
429
+
430
+ module_eval(<<'.,.,', 'regex.racc', 26)
431
+ def _reduce_17(val, _values, result)
432
+ result = val[0]
433
+ result
434
+ end
435
+ .,.,
436
+
437
+ module_eval(<<'.,.,', 'regex.racc', 27)
438
+ def _reduce_18(val, _values, result)
439
+ result = val[0]
440
+ result
441
+ end
442
+ .,.,
443
+
444
+ module_eval(<<'.,.,', 'regex.racc', 28)
445
+ def _reduce_19(val, _values, result)
446
+ result = val[0]
447
+ result
448
+ end
449
+ .,.,
450
+
451
+ module_eval(<<'.,.,', 'regex.racc', 30)
452
+ def _reduce_20(val, _values, result)
453
+ result = val[0]
454
+ result
455
+ end
456
+ .,.,
457
+
458
+ module_eval(<<'.,.,', 'regex.racc', 31)
459
+ def _reduce_21(val, _values, result)
460
+ result = val[0] + ':' + val[2]
461
+ result
462
+ end
463
+ .,.,
464
+
465
+ module_eval(<<'.,.,', 'regex.racc', 33)
466
+ def _reduce_22(val, _values, result)
467
+ result = [ :zero_or_more ]
468
+ result
469
+ end
470
+ .,.,
471
+
472
+ module_eval(<<'.,.,', 'regex.racc', 34)
473
+ def _reduce_23(val, _values, result)
474
+ result = [ :zero_or_more, :min ]
475
+ result
476
+ end
477
+ .,.,
478
+
479
+ module_eval(<<'.,.,', 'regex.racc', 35)
480
+ def _reduce_24(val, _values, result)
481
+ result = [ :one_or_more ]
482
+ result
483
+ end
484
+ .,.,
485
+
486
+ module_eval(<<'.,.,', 'regex.racc', 36)
487
+ def _reduce_25(val, _values, result)
488
+ result = [ :one_or_more, :min ]
489
+ result
490
+ end
491
+ .,.,
492
+
493
+ module_eval(<<'.,.,', 'regex.racc', 37)
494
+ def _reduce_26(val, _values, result)
495
+ result = [ :zero_or_one ]
496
+ result
497
+ end
498
+ .,.,
499
+
500
+ module_eval(<<'.,.,', 'regex.racc', 38)
501
+ def _reduce_27(val, _values, result)
502
+ result = [ :zero_or_one, :min ]
503
+ result
504
+ end
505
+ .,.,
506
+
507
+ module_eval(<<'.,.,', 'regex.racc', 39)
508
+ def _reduce_28(val, _values, result)
509
+ result = [ :exact, val[1].to_i ]
510
+ result
511
+ end
512
+ .,.,
513
+
514
+ module_eval(<<'.,.,', 'regex.racc', 40)
515
+ def _reduce_29(val, _values, result)
516
+ result = [ :range, val[1].to_i, val[3].to_i ]
517
+ result
518
+ end
519
+ .,.,
520
+
521
+ module_eval(<<'.,.,', 'regex.racc', 41)
522
+ def _reduce_30(val, _values, result)
523
+ result = [ :range, val[1], '' ]
524
+ result
525
+ end
526
+ .,.,
527
+
528
+ module_eval(<<'.,.,', 'regex.racc', 42)
529
+ def _reduce_31(val, _values, result)
530
+ result = [ :min, :range, val[1], '' ]
531
+ result
532
+ end
533
+ .,.,
534
+
535
+ module_eval(<<'.,.,', 'regex.racc', 43)
536
+ def _reduce_32(val, _values, result)
537
+ result = [ :min, :range, val[1].to_i, val[3].to_i ]
538
+ result
539
+ end
540
+ .,.,
541
+
542
+ def _reduce_none(val, _values, result)
543
+ val[0]
544
+ end
545
+
546
+ end # class Parser
547
+ end # module Grammar
548
+ end # module Fabulator