grammar 0.5

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,692 @@
1
+ #!/bin/env ruby
2
+ # = grammar.rb - specify BNF-like grammar directly in Ruby
3
+ # $Id: grammar.rb,v 1.1 2005/10/13 23:58:45 eric_mahurin Exp $
4
+ # Author:: Eric Mahurin (Eric under Mahurin at yahoo dot com)
5
+ # License:: Ruby license
6
+ # Home:: http://rubyforge.org/projects/grammar
7
+
8
+ # This base class defines common operators to the derived Grammar classes to
9
+ # make specifying the Grammar look similar to BNF. This base class also serves
10
+ # the purpose of handling recursion in the Grammar.
11
+ class Grammar
12
+ class << self
13
+ alias_method(:[],:new)
14
+ # With several interlocking recursive grammars, this can be used.
15
+ # For each argument that the block needs, an empty Grammar is
16
+ # given. The result of the block should be an Array of the final
17
+ # grammars for those arguments.
18
+ def multiple(&block) # :yield: *recursive_grammars
19
+ grammars = (1..block.arity).map { self.new }
20
+ grammars.zip(yield(*grammars)) { |g,g1| g << g1 }
21
+ grammars
22
+ end
23
+ end
24
+ # Creates a Grammar from another +grammar+. If +grammar+ is not given
25
+ # and a block is instead, the block is passed +self+ (to handle recursion)
26
+ # and the resulting grammar from this block will be used.
27
+ def initialize(grammar=nil,&block) # :yield: +self+
28
+ @grammar = grammar || block && yield(self)
29
+ end
30
+ # Reinitialize with another Grammar. This will be needed for recursion
31
+ # unless the block form of new is used.
32
+ def << (*args)
33
+ initialize(*args)
34
+ end
35
+ # Match to elements at a Cursor while advancing. When matched, a parse
36
+ # buffer is returned. Instead of an empty Array, the seed to this parse buffer
37
+ # can be given by +buffer+ which should respond to #concat and #<< like Array.
38
+ # When a mismatch occurs several possibilities exist. If +lookahead+ and
39
+ # the Grammar is within its lookahead (defaults one element/token - can be
40
+ # controlled by #lookahead), the cursor is moved back to where it started and
41
+ # +false+ is returned. Otherwise an exception describing the mismatch is
42
+ # raised.
43
+ def scan(cursor,buffer=[],lookahead=false)
44
+ @grammar.scan(cursor,buffer,lookahead)
45
+ end
46
+ # Same as #scan except the +cursor+ is held in place
47
+ def check(cursor,buffer=[],lookahead=false)
48
+ cursor.pos { (@grammar||self).scan(cursor,buffer,lookahead) }
49
+ end
50
+ def scanner(me,cursor,buffer,lookahead,hold) # :nodoc:
51
+ hold ?
52
+ "#{me}.check(#{cursor},#{buffer},#{lookahead})" :
53
+ "#{me}.scan(#{cursor},#{buffer},#{lookahead})"
54
+ end
55
+ def leaves # :nodoc:
56
+ [@grammar||self]
57
+ end
58
+ # Creates a new Grammar that matches +self+ or +other+ if that fails.
59
+ def |(other)
60
+ Inline.new(self,other) { |us,them,cursor,buffer,lookahead,hold|
61
+ "(#{us[cursor,buffer,true,hold]} ||
62
+ #{them[cursor,buffer,lookahead,hold]})"
63
+ }
64
+ end
65
+ # Creates a new Grammar that matches +self+ followed by +other+.
66
+ # The resulting match list is a concatenation from the match lists
67
+ # from +self+ and +other+.
68
+ def +(other)
69
+ Inline.new(self,other) { |us,them,cursor,buffer,lookahead|
70
+ "(#{us[cursor,buffer,lookahead,false]} &&
71
+ #{them[cursor,buffer,false,false]})"
72
+ }
73
+ end
74
+ # Generates a Grammar that matches when +self+ (in-place) and +other+.
75
+ def &(other)
76
+ Inline.new(self,other) { |us,them,cursor,buffer,lookahead,hold|
77
+ "(#{us[cursor,buffer,lookahead,true]} &&
78
+ #{them[cursor,buffer,lookahead,hold]})"
79
+ }
80
+ end
81
+ # Creates a new Grammar that matches +self+ replicated +multiplier+ times.
82
+ # +multiplier+ can be a Range to specify a variable multiplier. The
83
+ # +multiplier+ just needs to responds to #=== to determine the min and
84
+ # max iterations.
85
+ def *(multiplier)
86
+ Inline.new(self,nil,multiplier) { |us,multiplier,cursor,buffer,lookahead|
87
+ Inline.var { |n,ret,look| "(
88
+ #{n} = -1
89
+ #{ret} = false
90
+ #{look} = #{lookahead}
91
+ while true
92
+ if #{multiplier}===(#{n}+=1)
93
+ if !#{ret}
94
+ #{ret} = #{buffer}
95
+ #{look} = true
96
+ end
97
+ else
98
+ break(#{ret}) if #{ret}
99
+ end
100
+ #{us[cursor,buffer,look,false]} or break(#{ret})
101
+ #{look} = false if !#{ret}
102
+ end
103
+ )" }
104
+ }
105
+ end
106
+ # Creates a new zero-width Grammar that matches +self+.
107
+ def +@
108
+ Inline.new(self) { |us,cursor,buffer,lookahead,hold|
109
+ "(#{us[cursor,'DISCARD',lookahead,true]} && #{buffer})"
110
+ }
111
+ end
112
+ # Creates a new zero-width Grammar that matches anything but +self+.
113
+ def -@
114
+ Inline.new(self) { |us,cursor,buffer,lookahead,hold|
115
+ "(!#{us[cursor,'DISCARD',true,true]} ? #{buffer} :
116
+ !#{lookahead}&&raise(Error.new(cursor,'a negative syntatic predicate')))"
117
+ }
118
+ end
119
+ # Returns a Grammar that as long as what follows doesn't match +self+, it
120
+ # matches to the next element. Most useful for a single element Grammar.
121
+ def ~
122
+ (-self)&ANY
123
+ end
124
+ # Creates a new Grammar that optionally matches +self+.
125
+ def optional
126
+ self|NULL
127
+ end
128
+ # Matches a list of +self+ (plus possibly other stuff) one or more times.
129
+ # The arguments are an alternating list of optional terminators and
130
+ # separators. Along with #list0 you should be able to describe any
131
+ # tail recursive grammar. This is equivalent to this recursive Grammar:
132
+ #
133
+ # Grammar.new { |g| a+(z|b+(y|...g)) }
134
+ #
135
+ # where a, b, ... are +self+ and the separators and z, y, ... are the
136
+ # terminators.
137
+ #
138
+ # When a terminator is +nil+, the next item is treated
139
+ # as optional (i.e. instead of a+(nil|g), a+(g|) is used).
140
+ #
141
+ # When there is a missing terminator at the end of +term_sep+ (and it is
142
+ # non-empty), the list is not allowed to stop at that point.
143
+ def list1(*term_sep)
144
+ term_sep.push(nil) if term_sep.empty?
145
+ term_sep.unshift(self)
146
+ Inline.new(*term_sep.compact) { |*args|
147
+ cursor,buffer,lookahead = args.slice!(-3,3)
148
+ Inline.var { |look,ret|
149
+ terminated = (term_sep.size&1).nonzero? || term_sep[-1]
150
+ code = "(
151
+ #{look} = #{lookahead}
152
+ #{terminated ? (ret=false;'') : "#{ret} = false"}
153
+ while true
154
+ #{args[j=0][cursor,buffer,look,false]} or break(#{ret})
155
+ #{look} = #{terminated ? false : true}
156
+ #{terminated ? '' : "#{ret} = #{buffer}"}"
157
+ 1.step(term_sep.size-1,2) { |i|
158
+ if term_sep[i]
159
+ code << "
160
+ #{args[j+=1][cursor,buffer,true,false]} and break(#{buffer})"
161
+ if i+1<term_sep.size
162
+ code << "
163
+ #{args[j+=1][cursor,buffer,false,false]} or break(false)"
164
+ end
165
+ elsif i+1<term_sep.size
166
+ code << "
167
+ #{args[j+=1][cursor,buffer,true,false]} or break(#{buffer})"
168
+ end
169
+ }
170
+ code << "
171
+ end
172
+ )"
173
+ }
174
+ }
175
+ end
176
+ # Matches a list of +self+ (plus possibly other stuff) zero or more times.
177
+ # The arguments are an alternating list of optional terminators and
178
+ # separators. Along with #list1 you should be able to describe any
179
+ # tail recursive grammar. This is equivalent to this recursive Grammar:
180
+ #
181
+ # Grammar.new { |g| x|(a+(z|b+(y|...g))) }
182
+ #
183
+ # where a, b, ... are +self+ and the separators and z, y, ..., x are the
184
+ # terminators.
185
+ #
186
+ # When a terminator is +nil+/missing, the next item is treated
187
+ # as optional.
188
+ def list0(*term_sep)
189
+ term_sep.push(nil) if (term_sep.size&1).zero?
190
+ term_sep.unshift(self)
191
+ Inline.new(*term_sep.compact) { |*args|
192
+ cursor,buffer,lookahead = args.slice!(-3,3)
193
+ Inline.var { |look,ret|
194
+ code = "("
195
+ code << "
196
+ #{look} = #{lookahead}" if term_sep[-1]
197
+ code << "
198
+ while true"
199
+ j = -2
200
+ -1.step(term_sep.size-3,2) { |i|
201
+ if term_sep[i]
202
+ code << "
203
+ #{args[j+=1][cursor,buffer,true,false]} and break(#{buffer})"
204
+ if j.zero?
205
+ code << "
206
+ #{args[j+=1][cursor,buffer,look,false]} or break(false)
207
+ #{look} = false"
208
+ else
209
+ code << "
210
+ #{args[j+=1][cursor,buffer,false,false]} or break(false)"
211
+ end
212
+ else
213
+ j += 1 if j==2
214
+ code << "
215
+ #{args[j+=1][cursor,buffer,true,false]} or break(#{buffer})"
216
+ end
217
+ }
218
+ code << "
219
+ end)"
220
+ }
221
+ }
222
+ end
223
+ # Creates a new Grammar where the entire grammar is considered a
224
+ # part of the lookahead (instead of just the first element).
225
+ def lookahead
226
+ Inline.new(self) { |us,cursor,buffer,lookahead|
227
+ Inline.var { |branch| "(
228
+ #{branch} = #{buffer}.class.new
229
+ #{cursor}.pos? { begin
230
+ #{us[cursor,branch,false]}
231
+ rescue Error => err
232
+ raise(err) if !#{lookahead}
233
+ end } && #{buffer}.concat(#{branch})
234
+ )" }
235
+ }
236
+ end
237
+ # Creates a new Grammar where the match list of +self+ is filtered by
238
+ # some code.
239
+ # When a +klass+ is given, +klass+.new is used as the buffer to hold what
240
+ # will be passed to the code. Otherwise this temporary buffer will come
241
+ # from buffer.class.new.
242
+ # If the block needs 1 argument, this temporary buffer will be passed
243
+ # and the block should return something that will be given to buffer.concat.
244
+ # If the block needs 2 arguments, the second argument will be the buffer
245
+ # and the block should do the concatenation.
246
+ # If there is no block, the temporary buffer is passed to buffer.concat
247
+ # directly. Use this to get some isolation.
248
+ def filter(klass=nil,&code) # :yield: branch[, buffer]
249
+ if !code
250
+ if klass
251
+ Inline.new(self,nil,klass) { |us,klass,cursor,buffer,lookahead,hold|
252
+ Inline.var { |branch| "(
253
+ #{branch}=#{klass}.new
254
+ #{us[cursor,branch,lookahead,hold]} &&
255
+ #{buffer}.concat(#{branch})
256
+ )"}
257
+ }
258
+ else
259
+ Inline.new(self) { |us,cursor,buffer,lookahead,hold|
260
+ Inline.var { |branch| "(
261
+ #{branch}=#{buffer}.class.new
262
+ #{us[cursor,branch,lookahead,hold]} &&
263
+ #{buffer}.concat(#{branch})
264
+ )"}
265
+ }
266
+ end
267
+ elsif code.arity>=2
268
+ if klass
269
+ Inline.new(self,nil,klass,code) { |us,klass,code,cursor,buffer,lookahead,hold|
270
+ Inline.var { |branch| "(
271
+ #{branch}=#{klass}.new
272
+ #{us[cursor,branch,lookahead,hold]} &&
273
+ (#{code}[#{branch},#{buffer}]||
274
+ raise(Error.new(cursor,'a filtered '+#{branch}.inspect)))
275
+ )"}
276
+ }
277
+ else
278
+ Inline.new(self,nil,code) { |us,code,cursor,buffer,lookahead,hold|
279
+ Inline.var { |branch| "(
280
+ #{branch}=#{buffer}.class.new
281
+ #{us[cursor,branch,lookahead,hold]} &&
282
+ (#{code}[#{branch},#{buffer}]||
283
+ raise(Error.new(cursor,'a filtered '+#{branch}.inspect)))
284
+ )"}
285
+ }
286
+ end
287
+ else
288
+ if klass
289
+ Inline.new(self,nil,klass,code) { |us,klass,code,cursor,buffer,lookahead,hold|
290
+ Inline.var { |branch| "(
291
+ #{branch}=#{klass}.new
292
+ #{us[cursor,branch,lookahead,hold]} &&
293
+ #{buffer}.concat(#{code}[#{branch}]||
294
+ raise(Error.new(cursor,'a filtered '+#{branch}.inspect)))
295
+ )"}
296
+ }
297
+ else
298
+ Inline.new(self,nil,code) { |us,code,cursor,buffer,lookahead,hold|
299
+ Inline.var { |branch| "(
300
+ #{branch}=#{buffer}.class.new
301
+ #{us[cursor,branch,lookahead,hold]} &&
302
+ #{buffer}.concat(#{code}[#{branch}]||
303
+ raise(Error.new(cursor,'a filtered '+#{branch}.inspect)))
304
+ )"}
305
+ }
306
+ end
307
+ end
308
+ end
309
+ # Returns a Grammar that discards the match list from +self+
310
+ def discard
311
+ Inline.new(self) { |us,cursor,buffer,lookahead,hold|
312
+ "(#{us[cursor,'DISCARD',lookahead,hold]}&&#{buffer})"
313
+ }
314
+ end
315
+ # Returns a Grammar that groups the match list from +self+. A temporary
316
+ # buffer is formed just list #filter, but buffer.<< is used instead of
317
+ # buffer.concat.
318
+ def group(klass=nil)
319
+ if klass
320
+ Inline.new(self,nil,klass) { |us,klass,cursor,buffer,lookahead,hold|
321
+ Inline.var { |branch| "(
322
+ #{branch}=#{klass}.new
323
+ #{us[cursor,branch,lookahead,hold]} &&
324
+ #{buffer}<<#{branch}
325
+ )"}
326
+ }
327
+ else
328
+ Inline.new(self) { |us,cursor,buffer,lookahead,hold|
329
+ Inline.var { |branch| "(
330
+ #{branch}=#{buffer}.class.new
331
+ #{us[cursor,branch,lookahead,hold]} &&
332
+ #{buffer}<<#{branch}
333
+ )"}
334
+ }
335
+ end
336
+ end
337
+
338
+
339
+ # A Grammar that can flatten itself (with code strings) to reduce the
340
+ # amount of method calls needed while parsing. This is tricky stuff.
341
+ # Will explain later.
342
+ class Inline < Grammar
343
+ def initialize(*objects,&block) # :yield: cursor,buffer,lookahead[,hold]
344
+ @objects = objects
345
+ @block = block
346
+ end
347
+ Arg_names = %w(cursor buffer lookahead)
348
+ def scan(cursor,buffer=[],lookahead=false) # :nodoc:
349
+ (class << self;self;end).class_eval(
350
+ "def scan(cursor,buffer=[],lookahead=false)\n"+
351
+ scanner(*(_leaf_names+Arg_names+[false]))+
352
+ "\nend"
353
+ )
354
+ scan(cursor,buffer,lookahead)
355
+ end
356
+ def check(cursor,buffer=[],lookahead=false) # :nodoc:
357
+ (class << self;self;end).class_eval(
358
+ "def check(cursor,buffer=[],lookahead=false)\n"+
359
+ scanner(*(_leaf_names+Arg_names+[true]))+
360
+ "\nend"
361
+ )
362
+ check(cursor,buffer,lookahead)
363
+ end
364
+ def scanner(*leaves_args) # :nodoc:
365
+ objects = _extractors.map { |e| e[leaves_args] }
366
+ args = objects+leaves_args
367
+ if @block.arity<args.size and args.slice!(-1)
368
+ "#{leaves_args[0]}.pos{#{@block.call(*args)}}"
369
+ else
370
+ @block.call(*args)
371
+ end
372
+ end
373
+ def leaves # :nodoc:
374
+ @_ or begin
375
+ @_ = []
376
+ @extractors = []
377
+ @objects.inject(false) { |leaf,object|
378
+ if leaf
379
+ @_ << object
380
+ @extractors << lambda { |leaves_args|
381
+ leaves_args.slice!(0)
382
+ }
383
+ true
384
+ elsif !object
385
+ true
386
+ elsif false
387
+ # enable this code to disable code flattening
388
+ @_ << object
389
+ @extractors << lambda { |leaves_args|
390
+ g = leaves_args.slice!(0)
391
+ lambda { |*args|
392
+ "#{g}.#{args.slice!(-1) ? 'check' : 'scan'}(#{args.join(',')})"
393
+ }
394
+ }
395
+ false
396
+ else
397
+ leaves = object.leaves
398
+ @_.concat(leaves)
399
+ n = leaves.size
400
+ @extractors << lambda { |leaves_args|
401
+ leaf_names = leaves_args.slice!(0,n)
402
+ lambda { |*args| object.scanner(*(leaf_names+args)) }
403
+ }
404
+ false
405
+ end
406
+ }
407
+ remove_instance_variable(:@objects)
408
+ @_
409
+ end
410
+ end
411
+ def _extractors # :nodoc:
412
+ @extractors or (leaves;@extractors)
413
+ end
414
+ def _leaf_names # :nodoc:
415
+ (0...leaves.size).map { |i| "@_[#{i}]" }
416
+ end
417
+ def inspect # :nodoc:
418
+ to_s[0..-2].concat(" #{scanner(*(leaves+Arg_names+[false]))}>")
419
+ end
420
+ @@symbol = "_0".to_sym
421
+ # used for generating "local" variable names
422
+ def self.var(&block)
423
+ critical0 = Thread.critical
424
+ Thread.critical = true
425
+ if block
426
+ begin
427
+ symbol = @@symbol
428
+ symbols = []
429
+ block.arity.times {
430
+ symbols << @@symbol
431
+ @@symbol = @@symbol.to_s.succ.to_sym
432
+ }
433
+ # this better not need other threads - critical section
434
+ yield(*symbols)
435
+ ensure
436
+ @@symbol = symbol
437
+ end
438
+ else
439
+ begin
440
+ @@symbol
441
+ ensure
442
+ @@symbol = @@symbol.to_s.succ.to_sym
443
+ end
444
+ end
445
+ ensure
446
+ Thread.critical = critical0
447
+ end
448
+ end
449
+
450
+ # A Grammar that matches using arbitrary code
451
+ class Code < Inline
452
+ def initialize(&code) # :yield: cursor,buffer,lookahead
453
+ if code.arity<4
454
+ super(nil,code) { |code,cursor,buffer,lookahead|
455
+ "#{code}[#{cursor},#{buffer},#{lookahead}]"
456
+ }
457
+ else
458
+ super(nil,code) { |code,cursor,buffer,lookahead,hold|
459
+ "#{code}[#{cursor},#{buffer},#{lookahead},#{hold}]"
460
+ }
461
+ end
462
+ end
463
+ end
464
+
465
+ # Lookup grammar from next token. Need to doc.
466
+ class Lookup < Grammar
467
+ def initialize(lookup)
468
+ @lookup = lookup
469
+ end
470
+ def scan(cursor,buffer=[],lookahead=false) # :nodoc:
471
+ v = cursor.read1next
472
+ if grammar = @lookup[v]
473
+ buffer << v
474
+ grammar.scan(cursor,buffer,false)
475
+ else
476
+ raise(Error.new(cursor,"no grammar for #{v} found in #{@lookup}"))
477
+ end
478
+ end
479
+ end
480
+ class LookupAhead < Grammar
481
+ def initialize(lookup)
482
+ @lookup = lookup
483
+ end
484
+ def scan(cursor,buffer=[],lookahead=false) # :nodoc:
485
+ v = cursor.read1after
486
+ if grammar = @lookup[v]
487
+ grammar.scan(cursor,buffer,false)
488
+ elsif lookahead
489
+ false
490
+ else
491
+ raise(Error.new(cursor,"no grammar for #{v} found in #{@lookup}"))
492
+ end
493
+ end
494
+ end
495
+
496
+
497
+ # Grammar that matches to a sequence. An object responding to #[index]
498
+ # (i.e. String/Array) is used to represent this sequence. Each element
499
+ # returned by #[] should respond to #== to compare each element in the
500
+ # sequence.
501
+ class Sequence < Grammar
502
+ def initialize(value,partial=false)
503
+ @value = value
504
+ @partial = partial
505
+ end
506
+ def scan(cursor,buffer=[],lookahead=false) # :nodoc:
507
+ i = cursor.scan(@value,false,false,buffer)
508
+ if !i
509
+ if lookahead
510
+ false
511
+ else
512
+ raise(Error.new(cursor,@value[0]))
513
+ end
514
+ elsif !@partial and i<0
515
+ raise(Error.new(cursor,@value[-i]))
516
+ else
517
+ buffer
518
+ end
519
+ end
520
+ def inspect
521
+ "#{self.class}.new(#{@value.inspect},#{@partial.inspect})"
522
+ end
523
+ def to_s
524
+ inspect
525
+ end
526
+ end
527
+ # Grammar that matches elements until it finds a specific sequence.
528
+ # Compare to IO#gets.
529
+ class SequenceUntil < Grammar
530
+ def initialize(value,allow_eof=false)
531
+ @value = value
532
+ @allow_eof = allow_eof
533
+ end
534
+ def scan(cursor,buffer=[],lookahead=false) # :nodoc:
535
+ len,i = cursor.scan_until(@value,false,false,buffer)
536
+ if !len
537
+ if lookahead
538
+ false
539
+ else
540
+ raise(Error.new(cursor,@value[0]))
541
+ end
542
+ elsif !@allow_eof and len.nonzero? and i<=0
543
+ raise(Error.new(cursor,@value[-i]))
544
+ else
545
+ buffer
546
+ end
547
+ end
548
+ end
549
+ # Grammar that matches to a single element. An object responding to #==
550
+ # is used to do the matching.
551
+ class Element < Inline
552
+ def initialize(value)
553
+ super(nil,value) { |value,cursor,buffer,lookahead,hold|
554
+ condition = hold ?
555
+ "#{value}==(v=#{cursor}.read1after)" :
556
+ "(v=#{cursor}.scan1next(#{value}))"
557
+ "(#{condition} ? " +
558
+ "#{buffer} << v : " +
559
+ "!#{lookahead}&&raise(Error.new(#{cursor},#{value})))"
560
+ }
561
+ end
562
+ end
563
+ # Grammar that always fails (with a +message+)
564
+ class Fail < Inline
565
+ def initialize(message)
566
+ super { |cursor,buffer,lookahead|
567
+ "!#{lookahead}&&raise(Error.new(cursor,#{message.inspect}))"
568
+ }
569
+ end
570
+ end
571
+
572
+ # Grammar that matches any single element
573
+ ANY = Inline.new { |cursor,buffer,lookahead,hold|
574
+ "((v=#{cursor}.read1#{hold ? 'after' : 'next'}) ? " +
575
+ "#{buffer} << v : " +
576
+ "!#{lookahead}&&raise(Error.new(#{cursor},'any element')))"
577
+ }
578
+ # Grammar that always passes and matches nothing
579
+ NULL = Inline.new { |_,buffer,_,_| "#{buffer}" }
580
+ # Grammar that matches the end-of-file (or end-of-cursor)
581
+ EOF = Inline.new { |cursor,buffer,_,_|
582
+ "(!#{cursor}.skip1after&&#{buffer})"
583
+ }
584
+
585
+ # Exception class for handling Grammar errors
586
+ class Error < RuntimeError
587
+ attr_accessor(:cursor,:expected,:found)
588
+ def initialize(cursor=nil,expected=nil,found=nil)
589
+ @cursor = cursor
590
+ @expected = expected
591
+ @found = found
592
+ end
593
+ def to_s
594
+ err = [super]
595
+ err << "expected #{@expected.inspect}" if @expected
596
+ err << "found #{@found.inspect}" if @found
597
+ begin
598
+ #err << @cursor.to_s if @cursor
599
+ rescue
600
+ end
601
+ err * ", "
602
+ end
603
+ end
604
+
605
+ # :stopdoc:
606
+ # Parse buffer that throws out everything
607
+ DISCARD = Class.new {
608
+ def concat(v);self;end
609
+ def << (v);self;end
610
+ define_method(:class) do;self;end # using "def class" messed up rdoc
611
+ def new;self;end
612
+ }.new
613
+ # :startdoc:
614
+
615
+ end
616
+
617
+
618
+ class Cursor
619
+ # A Cursor that gets its data from a producer Thread. This Thread is
620
+ # generated from the block given (passed +self+). The code in this
621
+ # block is expected to apply the << and concat methods to the argument
622
+ # given. The current Thread is the consumer.
623
+ #
624
+ # Unfortunately, this Cursor isn't full-featured (yet). It is not
625
+ # reversable. This will one day be reversable #pos*.
626
+ class Producer < Cursor
627
+ def initialize(max_size=16,&producer)
628
+ @buffer = []
629
+ @size = 0
630
+ @max_size = max_size
631
+ @consumer = Thread.current
632
+ @producer = Thread.new { producer[self] }
633
+ end
634
+ def new_data
635
+ []
636
+ end
637
+ def read1next
638
+ while (Thread.critical=true;@buffer.empty?&&@producer.alive?)
639
+ Thread.critical = false
640
+ @producer.run
641
+ end
642
+ v = @buffer.shift
643
+ @size -= 1
644
+ v
645
+ ensure
646
+ Thread.critical = false
647
+ end
648
+ def read1after
649
+ v = read1next
650
+ unless v.nil?;begin
651
+ Thread.critical = true
652
+ @buffer.unshift(v)
653
+ ensure
654
+ Thread.critical = false
655
+ end;end
656
+ v
657
+ end
658
+ def skip1after
659
+ read1after.nil? ? nil : true
660
+ end
661
+ def scan1next(v)
662
+ v0 = read1next
663
+ (v0.nil? || v==v0) ? v0 : begin
664
+ Thread.critical = true
665
+ @buffer.unshift(v0)
666
+ nil
667
+ ensure
668
+ Thread.critical = false
669
+ end
670
+ end
671
+ def << (v)
672
+ while (Thread.critical=true;@size>=@max_size&&@consumer.alive?)
673
+ Thread.critical = false
674
+ @consumer.run
675
+ end
676
+ @buffer << v
677
+ @size += 1
678
+ self
679
+ ensure
680
+ Thread.critical = false
681
+ end
682
+ def concat(value)
683
+ i = 0
684
+ until (v = value[i]).nil?
685
+ self << v
686
+ end
687
+ self
688
+ end
689
+ end
690
+ end
691
+
692
+
@@ -0,0 +1,12 @@
1
+ proc factorial a {
2
+ set ret 1
3
+ # factorial(0) == 1
4
+ if $a {
5
+ set ret [product $a [factorial \
6
+ [subtract $a 1]]]
7
+ }
8
+ sum $ret
9
+ }
10
+ set x::y(1) 6
11
+ concat "factorial(\"$x::y(1)\") = " [factorial $x::y(1)] "\n"
12
+
@@ -0,0 +1,114 @@
1
+ #!/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'cursor/io'
5
+ require 'cursor/indexed'
6
+ require 'grammar'
7
+ require 'duck'
8
+ require 'set'
9
+
10
+ class Expression
11
+
12
+ def self.lexer
13
+ space = Grammar::Element[Set[?\ ,?\t,?\n].
14
+ duck!(:==,:include?,:to_s,:inspect)]
15
+ spacing = space.discard.list1
16
+
17
+ alpha = Grammar::Element[(Set[?_]+(?a..?z)+(?A..?Z)).
18
+ duck!(:==,:include?,:to_s,:inspect)]
19
+ alphanum = Grammar::Element[(Set[?_]+(?0..?9)+(?a..?z)+(?A..?Z)).
20
+ duck!(:==,:include?,:to_s,:inspect)]
21
+ identifier = (alpha+alphanum.list0).
22
+ filter(String) { |iden,buf| buf << iden.to_sym }
23
+
24
+ int = Grammar::Element[(?0..?9).duck!(:==,:===)].list1
25
+ number =
26
+ (int.group(String) +
27
+ ((Grammar::Element[?.]+int).optional +
28
+ ((Grammar::Element[?e]|Grammar::Element[?E])+
29
+ (Grammar::Element[?+]|Grammar::Element[?-]).optional+int).optional).
30
+ group(String)).
31
+ filter(Array) { |num,buf|
32
+ buf << (num[1].empty? ? num.to_s.to_i : num.to_s.to_f)
33
+ }
34
+
35
+ hex = Grammar::Element[(
36
+ Set[]+(?0..?9)+(?a..?f)+(?A..?F)
37
+ ).duck!(:==,:include?,:to_s,:inspect)]
38
+ octal = Grammar::Element[(?0..?7).duck!(:==,:===)]
39
+ backslashed = Grammar::Element[?\\].discard+(
40
+ Grammar::Element[?a].filter { "\a" } |
41
+ Grammar::Element[?b].filter { "\b" } |
42
+ Grammar::Element[?f].filter { "\f" } |
43
+ Grammar::Element[?n].filter { "\n" } |
44
+ Grammar::Element[?r].filter { "\r" } |
45
+ Grammar::Element[?t].filter { "\t" } |
46
+ Grammar::Element[?v].filter { "\v" } |
47
+ Grammar::Element[?x].discard+hex.list1.filter { |n|
48
+ eval(%Q("\\x#{n}"))
49
+ } |
50
+ octal.list1.filter { |n|
51
+ eval(%Q("\\#{n}"))
52
+ } |
53
+ Grammar::ANY
54
+ )
55
+ character =
56
+ backslashed |
57
+ Grammar::ANY
58
+ string = Grammar::Element[?\"].discard +
59
+ character.list1(Grammar::Element[?\"].discard).group(String)
60
+ other = Grammar::ANY.filter(String) { |op,buf| buf << op.to_sym }
61
+
62
+ (
63
+ spacing |
64
+ identifier |
65
+ number |
66
+ string |
67
+ other
68
+ ).list0(Grammar::EOF)
69
+ end
70
+
71
+ def self.parser
72
+ integer = Grammar::Element[Integer.duck!(:==,:===)]
73
+ float = Grammar::Element[Float.duck!(:==,:===)]
74
+ string = Grammar::Element[String.duck!(:==,:===)]
75
+ identifier = Grammar::Element[lambda { |v|
76
+ Symbol===v && ( /\A[_a-zA-Z]/=~v.to_s )
77
+ }.duck!(:==)]
78
+ expression = Grammar.new { |expression|
79
+ primary =
80
+ integer |
81
+ float |
82
+ string |
83
+ identifier |
84
+ Grammar::Element[:"("].discard+expression+Grammar::Element[:")"].discard
85
+ product_op = Grammar::Element[:"*"]|Grammar::Element[:"/"]
86
+ product = primary.filter(Array) { |x,buf|
87
+ op = buf.pop
88
+ buf.concat(x)
89
+ buf << op if op
90
+ buf
91
+ }.list1(nil,product_op)
92
+ sum_op = Grammar::Element[:"+"]|Grammar::Element[:"-"]
93
+ sum = product.filter(Array) { |x,buf|
94
+ op = buf.pop
95
+ buf.concat(x)
96
+ buf << op if op
97
+ buf
98
+ }.list1(nil,sum_op)
99
+ }
100
+ end
101
+
102
+ end
103
+
104
+ if __FILE__==$0
105
+ lexer = Expression.lexer
106
+ token_buffer = Cursor::Producer.new { |buffer|
107
+ lexer.scan($stdin.to_cursor,buffer)
108
+ }
109
+ parser = Expression.parser
110
+ result = parser.scan(token_buffer)
111
+ p result
112
+ result
113
+ end
114
+
@@ -0,0 +1,163 @@
1
+ #!/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'cursor/io'
5
+ require 'cursor/indexed'
6
+ require 'grammar'
7
+ require 'duck'
8
+ require 'set'
9
+
10
+ class Tcl
11
+
12
+ def initialize
13
+
14
+ @variables = {}
15
+ @procs = {}
16
+
17
+ space = Grammar::Element[Set[?\ ,?\t].duck!(:==,:include?)].discard
18
+ newline = Grammar::Element[?\n]
19
+ command_separator = newline|Grammar::Element[?;]
20
+ hex = Grammar::Element[(
21
+ Set[]+(?0..?9)+(?a..?f)+(?A..?F)
22
+ ).duck!(:==,:include?)]
23
+ octal = Grammar::Element[(?0..?7).duck!(:==,:===)]
24
+ alphanum = Grammar::Element[(
25
+ Set[?_]+(?0..?9)+(?a..?z)+(?A..?Z)
26
+ ).duck!(:==,:include?)]
27
+
28
+ backslashed = Grammar::Element[?\\].discard+(
29
+ Grammar::Element[?a].filter { "\a" } |
30
+ Grammar::Element[?b].filter { "\b" } |
31
+ Grammar::Element[?f].filter { "\f" } |
32
+ Grammar::Element[?n].filter { "\n" } |
33
+ Grammar::Element[?r].filter { "\r" } |
34
+ Grammar::Element[?t].filter { "\t" } |
35
+ Grammar::Element[?v].filter { "\v" } |
36
+ Grammar::Element[?x].discard+hex.list1.filter { |n|
37
+ eval(%Q("\\x#{n}"))
38
+ } |
39
+ Grammar::Element[?u].discard+hex.list1 { |n| # don't know what to do with unicode
40
+ eval(%Q("\\x#{n}"))
41
+ } |
42
+ octal.list1.filter { |n|
43
+ eval(%Q("\\#{n}"))
44
+ } |
45
+ (newline.discard+space.list0).filter { " " } |
46
+ Grammar::ANY
47
+ )
48
+
49
+ braced_element = Grammar.new { |braced_element|
50
+ Grammar::Element[?\{]+braced_element.list0(Grammar::Element[?\}]) |
51
+ Grammar::Element[?\\].discard+(
52
+ (newline.discard+space.list0).filter { " " } |
53
+ Grammar::NULL.filter { "\\" }
54
+ ) |
55
+ newline |
56
+ Grammar::ANY
57
+ }
58
+ braced = Grammar::Element[?\{].discard+braced_element.list0(Grammar::Element[?\}].discard)
59
+
60
+ element = Grammar.new
61
+
62
+ varname = (
63
+ alphanum |
64
+ Grammar::Element[?\:]*(2..+1.0/0)
65
+ ).list1
66
+ index = Grammar::Element[?\(]+element.list1(Grammar::Element[?\)])
67
+ variable = (Grammar::Element[?\$].discard + (
68
+ varname.group(String) + index.group(String).optional |
69
+ braced.group(String)
70
+ )).filter { |var| @variables[var.to_s.to_sym].to_s }
71
+
72
+ quoted = Grammar::Element[?\"].discard+element.list1(Grammar::Element[?\"].discard)
73
+ comment = (Grammar::Element[?\#]+Grammar::ANY.list0(newline)).discard
74
+
75
+ commander = lambda { |terminator|
76
+ word_terminator = space.list1|+command_separator|+terminator
77
+ word = ((braced|quoted)+word_terminator|element.list1(word_terminator)).
78
+ group(String).filter {|x,t| t.concat(x)}
79
+ command = space.list0 + (
80
+ comment |
81
+ word.list0(command_separator.discard|+terminator).
82
+ filter(Array) { |com,ret|
83
+ com.empty? ? ret : ret.replace(send(*com).to_s)
84
+ }
85
+ )
86
+ command.list0(terminator.discard)
87
+ }
88
+
89
+ bracketed = Grammar::Element[?[].discard+commander[Grammar::Element[?]]]
90
+
91
+ @interpreter = commander[Grammar::EOF]
92
+
93
+ element << (backslashed | bracketed | variable | newline | Grammar::ANY)
94
+
95
+ end
96
+
97
+ def interpret(cursor)
98
+ @interpreter.scan(cursor,"",false)
99
+ end
100
+
101
+ def method_missing(name,*args)
102
+ vars,body = *@procs[name]
103
+ return "<#{name}#{args.inspect}>" if !body
104
+ variables = @variables
105
+ @variables = {}
106
+ vars.zip(args).each { |var,val| @variables[var.to_sym] = val }
107
+ ret = interpret(body.to_cursor)
108
+ @variables = variables
109
+ ret
110
+ end
111
+
112
+ def proc(name,args,body)
113
+ @procs[name.to_sym] = [args,body]
114
+ ""
115
+ end
116
+
117
+ def set(name,value)
118
+ @variables[name.to_sym] = value
119
+ end
120
+
121
+ def if(condition,body)
122
+ # should really use expr to get condition
123
+ unless %w(0 false no off).include?(condition.to_s)
124
+ interpret(body.to_cursor)
125
+ end
126
+ # need to handle elsif and else
127
+ ""
128
+ end
129
+
130
+ def sum(*values)
131
+ values.inject(0) { |sum, v| sum + eval(v) }
132
+ end
133
+
134
+ def product(*values)
135
+ values.inject(1) { |sum, v| sum * eval(v) }
136
+ end
137
+
138
+ def subtract(a,b)
139
+ eval(a)-eval(b)
140
+ end
141
+
142
+ def divide(a,b)
143
+ eval(a)/eval(b)
144
+ end
145
+
146
+ def puts(str)
147
+ $stdout.print(str,"\n")
148
+ ""
149
+ end
150
+
151
+ def concat(*args)
152
+ args.inject("") { |concatenated, arg| concatenated.concat(arg) }
153
+ end
154
+
155
+ end
156
+
157
+ if $0==__FILE__
158
+ result = Tcl.new.interpret($stdin.to_cursor)
159
+ p result
160
+ result
161
+ end
162
+
163
+
@@ -0,0 +1,4 @@
1
+ "one\n" *
2
+ (2+3.0 /4e0*(5E+0+6.0e-0)-07)+
3
+ (" eight\t"*nine)
4
+
@@ -0,0 +1,274 @@
1
+ #!/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'test/unit'
5
+ require 'test/unit/collector'
6
+ require 'cursor/indexed'
7
+ require 'grammar'
8
+
9
+ module Test
10
+ module Unit
11
+ class AutoRunner
12
+ alias_method(:_options_,:options)
13
+ def options
14
+ @options = _options_
15
+ @options.on('-i', '--iterations=NUMBER', Float,
16
+ "Randomly run tests for a number of iterations.") do |iterations|
17
+ $random_iterations = iterations
18
+ end
19
+ @options.on('-s', '--seed=NUMBER', Integer,
20
+ "Random seed.") do |seed|
21
+ $random_seed = seed.nonzero?
22
+ end
23
+ @options
24
+ end
25
+ alias_method(:_run_,:run)
26
+ def run
27
+ $output_level = @output_level
28
+ $random_seed ||= (srand;srand)
29
+ srand($random_seed)
30
+ _run_
31
+ end
32
+ end
33
+ module Collector
34
+ alias_method(:_add_suite_,:add_suite)
35
+ def add_suite(destination, suite)
36
+ _add_suite_(destination, suite)
37
+ if $random_iterations
38
+ (class << suite.tests;self;end).class_eval {
39
+ def each
40
+ n = size
41
+ ($random_iterations*n).to_i.times {
42
+ yield(slice(rand(n)))
43
+ }
44
+ end
45
+ }
46
+ end
47
+ destination
48
+ end
49
+ end
50
+ class TestSuite
51
+ def run(result, &progress_block)
52
+ yield(STARTED, name)
53
+ catch(:stop_suite) {
54
+ @tests.each { |test|
55
+ catch(:invalid_test) {
56
+ test.run(result, &progress_block)
57
+ }
58
+ }
59
+ }
60
+ yield(FINISHED, name)
61
+ end
62
+ end
63
+ class RandomTestCase < TestCase
64
+ def self.suite
65
+ suite = super
66
+ puts("random_seed: #{$random_seed}") if !suite.tests.empty? and $output_level>=UI::NORMAL
67
+ suite
68
+ end
69
+ undef_method(:default_test) # so that RandomTestCase is empty
70
+ def teardown
71
+ if not passed?
72
+ puts("\nrandom_seed: #{$random_seed}")
73
+ throw(:stop_suite)
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+
81
+ class Grammar
82
+
83
+ class Test < ::Test::Unit::RandomTestCase
84
+
85
+ class Grammars
86
+ include ::Test::Unit::Assertions
87
+ def initialize
88
+ @grammar = []
89
+ @match = []
90
+ @parsed = []
91
+ @mismatch = []
92
+ @partial_match = []
93
+ end
94
+ def get
95
+ i = rand(@grammar.size.nonzero? || throw(:invalid_test))
96
+ return @grammar[i],@match[i],@parsed[i],@mismatch[i],@partial_match[i]
97
+ end
98
+ def add(grammar,match,parsed,mismatch=nil,partial_match=nil)
99
+ puts("#{grammar.inspect} #{match.inspect} #{parsed.inspect} #{mismatch.inspect} #{partial_match.inspect}") if
100
+ $output_level>=::Test::Unit::UI::VERBOSE
101
+ match.size.times { |i|
102
+ assert_equal(parsed[i],grammar.scan(match[i].to_cursor))
103
+ }
104
+ if mismatch
105
+ assert_raise(Grammar::Error){p grammar.scan(mismatch.to_cursor)}
106
+ assert_equal(false,grammar.scan(mismatch.to_cursor,[],true))
107
+ end
108
+ if partial_match
109
+ assert_raise(Grammar::Error){grammar.scan(partial_match.to_cursor)}
110
+ end
111
+ @grammar << grammar
112
+ @match << match
113
+ @parsed << parsed
114
+ @mismatch << mismatch
115
+ @partial_match << partial_match
116
+ nil
117
+ end
118
+ end
119
+
120
+ def self.suite
121
+ suite = super
122
+ self.plant
123
+ suite
124
+ end
125
+
126
+ def self.plant
127
+ @@grammars = Grammars.new
128
+ end
129
+
130
+ def test_Sequence
131
+ partial = rand(2)==1
132
+ value = ["a","bc"][rand(2)]
133
+ match = value.dup
134
+ if partial
135
+ match = [match,match[0,1+rand(value.size)]]
136
+ else
137
+ match = [match]
138
+ end
139
+ parsed = match.map{|s|s.unpack("C*")}
140
+ @@grammars.add(Grammar::Sequence.new(value,partial),match,parsed,"")
141
+ end
142
+
143
+ def test_Element
144
+ value = [?a,?b][rand(2)]
145
+ match = ["" << value]
146
+ parsed = [[value]]
147
+ @@grammars.add(Grammar::Element.new(value),match,parsed,"")
148
+ end
149
+
150
+ def test_NULL
151
+ @@grammars.add(Grammar::NULL,[""],[[]])
152
+ end
153
+
154
+ def test_or
155
+ grammar1,match1,parsed1,mismatch1,partial1 = @@grammars.get
156
+ grammar2,match2,parsed2,mismatch2,partial2 = @@grammars.get
157
+ i = rand(match1.size)
158
+ match1 = match1[i]
159
+ parsed1 = parsed1[i]
160
+ i = rand(match2.size)
161
+ match2 = match2[i]
162
+ parsed2 = parsed2[i]
163
+ begin
164
+ # match2 shouldn't match grammar1
165
+ grammar1.scan(match2.to_cursor,[],true) and throw(:invalid_test)
166
+ rescue Grammar::Error
167
+ throw(:invalid_test) # partial match
168
+ end
169
+ @@grammars.add(
170
+ grammar1|grammar2,
171
+ [match1,match2],
172
+ [parsed1,parsed2],
173
+ mismatch1==mismatch2 ? mismatch1 : nil,
174
+ partial1==partial2 ? partial1 : nil
175
+ )
176
+ end
177
+
178
+ def test_plus
179
+ grammar1,match1,parsed1,mismatch1,partial1 = @@grammars.get
180
+ grammar2,match2,parsed2,mismatch2,partial2 = @@grammars.get
181
+ i = rand(match1.size)
182
+ match1 = match1[i]
183
+ parsed1 = parsed1[i]
184
+ i = rand(match2.size)
185
+ match2 = match2[i]
186
+ parsed2 = parsed2[i]
187
+ # grammar1 shouldn't eat into match2
188
+ begin
189
+ grammar1.scan((match1+match2).to_cursor)==parsed1 or
190
+ throw(:invalid_test)
191
+ rescue Grammar::Error
192
+ throw(:invalid_test)
193
+ end
194
+ @@grammars.add(
195
+ grammar1+grammar2,
196
+ [match1+match2],
197
+ [parsed1+parsed2],
198
+ mismatch1,
199
+ partial1 || (mismatch2 && match1+mismatch2)
200
+ )
201
+ end
202
+
203
+ def test_Grammar
204
+ grammar1,match1,parsed1,mismatch1,partial1 = @@grammars.get
205
+ grammar2,match2,parsed2,mismatch2,partial2 = @@grammars.get
206
+ i = rand(match1.size)
207
+ match1 = match1[i]
208
+ parsed1 = parsed1[i]
209
+ i = rand(match2.size)
210
+ match2 = match2[i]
211
+ parsed2 = parsed2[i]
212
+ !match1.empty? or throw(:invalid_test)
213
+ begin
214
+ (grammar1.scan((match1+match1).to_cursor)==parsed1 and
215
+ grammar1.scan((match1+match2).to_cursor)==parsed1 and
216
+ grammar2.scan((match2+match2).to_cursor)==parsed2) or
217
+ throw(:invalid_test)
218
+ grammar1.scan(match2.to_cursor,[],true) and throw(:invalid_test)
219
+ rescue Grammar::Error
220
+ throw(:invalid_test)
221
+ end
222
+ grammar = Grammar.new
223
+ grammar << grammar1+(grammar|Grammar::NULL)+grammar2
224
+ @@grammars.add(
225
+ grammar,
226
+ [match1+match2,match1+match1+match1+match2+match2+match2],
227
+ [parsed1+parsed2,parsed1+parsed1+parsed1+parsed2+parsed2+parsed2],
228
+ mismatch1,
229
+ partial1
230
+ )
231
+ end
232
+
233
+ def test_times
234
+ grammar1,match1,parsed1,mismatch1,partial1 = @@grammars.get
235
+ begin
236
+ grammar1.scan("".to_cursor,[],true) and throw(:invalid_test)
237
+ rescue Grammar::Error
238
+ throw(:invalid_test)
239
+ end
240
+ min = rand(2)
241
+ diff = rand(4)
242
+ match = ""
243
+ parsed = []
244
+ match0 = nil
245
+ parsed0 = nil
246
+ (min+rand(diff+1)).times {
247
+ i = rand(match1.size)
248
+ match.concat(match1[i])
249
+ parsed.concat(parsed1[i])
250
+ if match0
251
+ begin
252
+ grammar1.scan((match0+match1[i]).to_cursor)==parsed0 or
253
+ throw(:invalid_test)
254
+ rescue Grammar::Error
255
+ throw(:invalid_test)
256
+ end
257
+ end
258
+ match0 = match1[i]
259
+ parsed0 = parsed1[i]
260
+ }
261
+ diff = 1.0/0.0 if diff==3
262
+ multiplier = (diff.zero? && rand(2).zero?) ? min : (min..(min+diff))
263
+ @@grammars.add(
264
+ grammar1*multiplier,
265
+ [match],
266
+ [parsed],
267
+ min.zero? ? nil : mismatch1,
268
+ min.zero? ? nil : (partial1 || (min>1 && match1[rand(match1.size)]))
269
+ )
270
+ end
271
+
272
+ end
273
+ end
274
+
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.10
3
+ specification_version: 1
4
+ name: grammar
5
+ version: !ruby/object:Gem::Version
6
+ version: "0.5"
7
+ date: 2005-10-13
8
+ summary: BNF-like grammar specified directly in ruby
9
+ require_paths:
10
+ - lib
11
+ email: Eric under Mahurin at yahoo dot com
12
+ homepage: http://rubyforge.org/projects/grammar/
13
+ rubyforge_project: grammar
14
+ description:
15
+ autorequire:
16
+ default_executable:
17
+ bindir:
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ authors:
28
+ - Eric Mahurin
29
+ files:
30
+ - lib/grammar.rb
31
+ - test/test_grammar.rb
32
+ - samples/fact.tcl
33
+ - samples/infix2postfix.rb
34
+ - samples/tcl.rb
35
+ - samples/test.infix
36
+ - samples/CVS
37
+ test_files:
38
+ - test/test_grammar.rb
39
+ rdoc_options: []
40
+ extra_rdoc_files: []
41
+ executables: []
42
+ extensions: []
43
+ requirements: []
44
+ dependencies:
45
+ - !ruby/object:Gem::Dependency
46
+ name: cursor
47
+ version_requirement:
48
+ version_requirements: !ruby/object:Gem::Version::Requirement
49
+ requirements:
50
+ -
51
+ - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0.9"
54
+ version: