grammar 0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: