auto-ast.rb revision 324c4644fee44b9898524c09511bd33c3f12e2df
1#!/usr/bin/ruby
2# encoding: utf-8
3
4require 'antlr3/test/functional'
5
6class TestAutoAST < ANTLR3::Test::Functional
7  
8  def parse( grammar, rule, input, expect_errors = false )
9    @grammar = inline_grammar( grammar )
10    compile_and_load @grammar
11    grammar_module = self.class.const_get( @grammar.name )
12    
13    grammar_module::Lexer.send( :include, ANTLR3::Test::CollectErrors )
14    grammar_module::Lexer.send( :include, ANTLR3::Test::CaptureOutput )
15    grammar_module::Parser.send( :include, ANTLR3::Test::CollectErrors )
16    grammar_module::Parser.send( :include, ANTLR3::Test::CaptureOutput )
17    
18    lexer  = grammar_module::Lexer.new( input )
19    parser = grammar_module::Parser.new( lexer )
20    
21    r = parser.send( rule )
22    parser.reported_errors.should be_empty unless expect_errors
23    result = ''
24    
25    unless r.nil?
26      result += r.result if r.respond_to?( :result )
27      result += r.tree.inspect if r.tree
28    end
29    return( expect_errors ? [ result, parser.reported_errors ] : result )
30  end
31  
32  def tree_parse( grammar, tree_grammar, rule, tree_rule, input )
33    @grammar = inline_grammar( grammar )
34    @tree_grammar = inline_grammar( tree_grammar )
35    compile_and_load @grammar
36    compile_and_load @tree_grammar
37    
38    grammar_module = self.class.const_get( @grammar.name )
39    tree_grammar_module = self.class.const_get( @tree_grammar.name )
40    
41    grammar_module::Lexer.send( :include, ANTLR3::Test::CollectErrors )
42    grammar_module::Lexer.send( :include, ANTLR3::Test::CaptureOutput )
43    grammar_module::Parser.send( :include, ANTLR3::Test::CollectErrors )
44    grammar_module::Parser.send( :include, ANTLR3::Test::CaptureOutput )
45    tree_grammar_module::TreeParser.send( :include, ANTLR3::Test::CollectErrors )
46    tree_grammar_module::TreeParser.send( :include, ANTLR3::Test::CaptureOutput )
47    
48    lexer  = grammar_module::Lexer.new( input )
49    parser = grammar.module::Parser.new( lexer )
50    r = parser.send( rule )
51    nodes = ANTLR3::CommonTreeNodeStream( r.tree )
52    nodes.token_stream = parser.input
53    walker = tree_grammar_module::TreeParser.new( nodes )
54    r = walker.send( tree_rule )
55    
56    return( r ? r.tree.inspect : '' )
57  end
58  
59  
60  example 'flat token list' do
61    result = parse( <<-'END', :a, 'abc 34' )
62      grammar TokenList;
63      options {language=Ruby;output=AST;}
64      a : ID INT ;
65      ID : 'a'..'z'+ ;
66      INT : '0'..'9'+;
67      WS : (' '|'\n') {$channel=HIDDEN;};
68    END
69    result.should == 'abc 34'
70  end
71  
72  example 'token list in a single-alternative subrule' do
73    result = parse( <<-'END', :a, 'abc 34' )
74      grammar TokenListInSingleAltBlock;
75      options {language=Ruby;output=AST;}
76      a : (ID INT) ;
77      ID : 'a'..'z'+ ;
78      INT : '0'..'9'+;
79      WS : (' '|'\n') {$channel=HIDDEN;} ;
80    END
81    result.should == 'abc 34'
82  end
83  
84  example "simple root at the outer level via the `^' operator" do
85    result = parse( <<-'END', :a, 'abc 34' )
86      grammar SimpleRootAtOuterLevel;
87      options {language=Ruby;output=AST;}
88      a : ID^ INT ;
89      ID : 'a'..'z'+ ;
90      INT : '0'..'9'+;
91      WS : (' '|'\n') {$channel=HIDDEN;} ;
92    END
93    result.should == '(abc 34)'
94  end
95  
96  example "outer-level root changing token order from the `^' operator" do
97    result = parse( <<-'END', :a, '34 abc' )
98      grammar SimpleRootAtOuterLevelReverse;
99      options {language=Ruby;output=AST;}
100      a : INT ID^ ;
101      ID : 'a'..'z'+ ;
102      INT : '0'..'9'+;
103      WS : (' '|'\n') {$channel=HIDDEN;} ;
104    END
105    result.should == '(abc 34)'
106  end
107  
108  example "leaving out tokens using the `!' operator" do
109    result = parse( <<-'END', :a, 'abc 34 dag 4532' )
110      grammar Bang;
111      options {language=Ruby;output=AST;}
112      a : ID INT! ID! INT ;
113      ID : 'a'..'z'+ ;
114      INT : '0'..'9'+;
115      WS : (' '|'\n') {$channel=HIDDEN;} ;
116    END
117    
118    result.should == 'abc 4532'
119  end
120  
121  example "tokens in `(...)?' optional subrule" do
122    result = parse( <<-'END', :a, 'a 1 b' )
123      grammar OptionalThenRoot;
124      options {language=Ruby;output=AST;}
125      a : ( ID INT )? ID^ ;
126      ID : 'a'..'z'+ ;
127      INT : '0'..'9'+;
128      WS : (' '|'\n') {$channel=HIDDEN;} ;
129    END
130    result.should == '(b a 1)'
131  end
132  
133  example "labeled literal-string root token" do
134    result = parse( <<-'END', :a, 'void foo;' )
135      grammar LabeledStringRoot;
136      options {language=Ruby;output=AST;}
137      a : v='void'^ ID ';' ;
138      ID : 'a'..'z'+ ;
139      INT : '0'..'9'+;
140      WS : (' '|'\n') {$channel=HIDDEN;} ;
141    END
142    result.should == '(void foo ;)'
143  end
144  
145  example 'rule with token wildcard' do
146    result = parse( <<-'END', :a, 'void foo;' )
147      grammar Wildcard;
148      options {language=Ruby;output=AST;}
149      a : v='void'^ . ';' ;
150      ID : 'a'..'z'+ ;
151      INT : '0'..'9'+;
152      WS : (' '|'\n') {$channel=HIDDEN;} ;
153    END
154    result.should == '(void foo ;)'
155  end
156  
157  example "token wildcard as root via the `^' operator" do
158    result = parse( <<-'END', :a, 'void foo;' )
159      grammar WildcardRoot;
160      options {language=Ruby;output=AST;}
161      a : v='void' .^ ';' ;
162      ID : 'a'..'z'+ ;
163      INT : '0'..'9'+;
164      WS : (' '|'\n') {$channel=HIDDEN;} ;
165    END
166    result.should == '(foo void ;)'
167  end
168  
169  example "labeled token wildcard as root via the `^' operator" do
170    result = parse( <<-'END', :a, 'void foo;' )
171      grammar WildcardRootWithLabel;
172      options {language=Ruby;output=AST;}
173      a : v='void' x=.^ ';' ;
174      ID : 'a'..'z'+ ;
175      INT : '0'..'9'+;
176      WS : (' '|'\n') {$channel=HIDDEN;} ;
177    END
178    result.should == '(foo void ;)'
179  end
180  
181  
182  example "token wildcard as root (with list label)" do
183    result = parse( <<-'END', :a, 'void foo;' )
184      grammar WildcardRootWithListLabel;
185      options {language=Ruby;output=AST;}
186      a : v='void' x=.^ ';' ;
187      ID : 'a'..'z'+ ;
188      INT : '0'..'9'+;
189      WS : (' '|'\n') {$channel=HIDDEN;} ;
190  
191    END
192    result.should == '(foo void ;)'
193  end
194  
195  example "trashed token wildcard" do
196    result = parse( <<-'END', :a, 'void foo;' )
197      grammar WildcardBangWithListLabel;
198      options {language=Ruby;output=AST;}
199      a : v='void' x=.! ';' ;
200      ID : 'a'..'z'+ ;
201      INT : '0'..'9'+;
202      WS : (' '|'\n') {$channel=HIDDEN;} ;
203  
204    END
205    result.should == 'void ;'
206  end
207  
208  example "multiple occurences of the `^' operator in a list of tokens" do
209    result = parse( <<-'END', :a, 'a 34 c' )
210      grammar RootRoot;
211      options {language=Ruby;output=AST;}
212      a : ID^ INT^ ID ;
213      ID : 'a'..'z'+ ;
214      INT : '0'..'9'+;
215      WS : (' '|'\n') {$channel=HIDDEN;} ;
216  
217    END
218    result.should == '(34 a c)'
219  end
220  
221  example "another case of multiple occurences of the `^' operator" do
222    result = parse( <<-'END', :a, 'a 34 c' )
223      grammar RootRoot2;
224      options {language=Ruby;output=AST;}
225      a : ID INT^ ID^ ;
226      ID : 'a'..'z'+ ;
227      INT : '0'..'9'+;
228      WS : (' '|'\n') {$channel=HIDDEN;} ;
229  
230    END
231    result.should == '(c (34 a))'
232  end
233  
234  example "root-hoist using `^' from within a (...)+ block" do
235    result = parse( <<-'END', :a, 'a 34 * b 9 * c' )
236      grammar RootThenRootInLoop;
237      options {language=Ruby;output=AST;}
238      a : ID^ (INT '*'^ ID)+ ;
239      ID  : 'a'..'z'+ ;
240      INT : '0'..'9'+;
241      WS : (' '|'\n') {$channel=HIDDEN;} ;
242  
243    END
244    result.should == '(* (* (a 34) b 9) c)'
245  end
246  
247  example "nested subrules without any AST ops resulting in a flat list" do
248    result = parse( <<-'END', :a, 'void a b;' )
249      grammar NestedSubrule;
250      options {language=Ruby;output=AST;}
251      a : 'void' (({
252      #do nothing
253      } ID|INT) ID | 'null' ) ';' ;
254      ID : 'a'..'z'+ ;
255      INT : '0'..'9'+;
256      WS : (' '|'\n') {$channel=HIDDEN;} ;
257  
258    END
259    result.should == 'void a b ;'
260  end
261  
262  example "invoking another rule without any AST ops, resulting in a flat list" do
263    result = parse( <<-'END', :a, 'int a' )
264      grammar InvokeRule;
265      options {language=Ruby;output=AST;}
266      a  : type ID ;
267      type : {
268        # do nothing
269      }'int' | 'float' ;
270      ID : 'a'..'z'+ ;
271      INT : '0'..'9'+;
272      WS : (' '|'\n') {$channel=HIDDEN;} ;
273  
274    END
275    result.should == 'int a'
276  end
277  
278  example "hoisting the results of another rule as root using the `^' operator" do
279    result = parse( <<-'END', :a, 'int a' )
280      grammar InvokeRuleAsRoot;
281      options {language=Ruby;output=AST;}
282      a  : type^ ID ;
283      type : {
284        # do nothing
285      }'int' | 'float' ;
286      ID : 'a'..'z'+ ;
287      INT : '0'..'9'+;
288      WS : (' '|'\n') {$channel=HIDDEN;} ;
289  
290    END
291    result.should == '(int a)'
292  end
293  
294  example "hoisting another rule's true as root using the `^' operator (with a label)" do
295    result = parse( <<-'END', :a, 'int a' )
296      grammar InvokeRuleAsRootWithLabel;
297      options {language=Ruby;output=AST;}
298      a  : x=type^ ID ;
299      type : {
300        # do nothing
301      }'int' | 'float' ;
302      ID : 'a'..'z'+ ;
303      INT : '0'..'9'+;
304      WS : (' '|'\n') {$channel=HIDDEN;} ;
305  
306    END
307    result.should == '(int a)'
308  end
309  
310  example "hoisting another rule's result tree as root using the `^' operator (with a list += label)" do
311    result = parse( <<-'END', :a, 'int a' )
312      grammar InvokeRuleAsRootWithListLabel;
313      options {language=Ruby;output=AST;}
314      a  : x+=type^ ID ;
315      type : {
316        # do nothing
317      }'int' | 'float' ;
318      ID : 'a'..'z'+ ;
319      INT : '0'..'9'+;
320      WS : (' '|'\n') {$channel=HIDDEN;} ;
321  
322    END
323    result.should == '(int a)'
324  end
325  
326  example "root-hoist via `^' within a (...)* loop resulting in a deeply-nested tree" do
327    result = parse( <<-'END', :a, 'a+b+c+d' )
328      grammar RuleRootInLoop;
329      options {language=Ruby;output=AST;}
330      a : ID ('+'^ ID)* ;
331      ID : 'a'..'z'+ ;
332      INT : '0'..'9'+;
333      WS : (' '|'\n') {$channel=HIDDEN;} ;
334  
335    END
336    result.should == '(+ (+ (+ a b) c) d)'
337  end
338  
339  example "hoisting another rule's result tree as root from within a (...)* loop resulting in a deeply nested tree" do
340    result = parse( <<-'END', :a, 'a+b+c-d' )
341      grammar RuleInvocationRuleRootInLoop;
342      options {language=Ruby;output=AST;}
343      a : ID (op^ ID)* ;
344      op : {
345        # do nothing
346      }'+' | '-' ;
347      ID : 'a'..'z'+ ;
348      INT : '0'..'9'+;
349      WS : (' '|'\n') {$channel=HIDDEN;} ;
350  
351    END
352    result.should == '(- (+ (+ a b) c) d)'
353  end
354  
355  example "using tail recursion to build deeply-nested expression trees" do
356    result = parse( <<-'END', :s, '3 exp 4 exp 5' )
357      grammar TailRecursion;
358      options {language=Ruby;output=AST;}
359      s : a ;
360      a : atom ('exp'^ a)? ;
361      atom : INT ;
362      ID : 'a'..'z'+ ;
363      INT : '0'..'9'+;
364      WS : (' '|'\n') {$channel=HIDDEN;} ;
365  
366    END
367    result.should == '(exp 3 (exp 4 5))'
368  end
369  
370  example "simple token node from a token type set" do
371    result = parse( <<-'END', :a, 'abc' )
372      grammar TokenSet;
373      options {language=Ruby; output=AST;}
374      a : ID|INT ;
375      ID : 'a'..'z'+ ;
376      INT : '0'..'9'+;
377      WS : (' '|'\n') {$channel=HIDDEN;} ;
378    END
379    result.should == 'abc'
380  end
381  
382  example "hoisting a token-type set token as root with `^'" do
383    result = parse( <<-'END', :a, '+abc' )
384      grammar SetRoot;
385      options {language=Ruby;output=AST;}
386      a : ('+' | '-')^ ID ;
387      ID : 'a'..'z'+ ;
388      INT : '0'..'9'+;
389      WS : (' '|'\n') {$channel=HIDDEN;} ;
390  
391    END
392    result.should == '(+ abc)'
393  end
394  
395  example "hoisting a token-type set token as root with `^' (with a label)" do
396    result = parse( <<-'END', :a, '+abc' )
397      grammar SetRootWithLabel;
398      options {language=Ruby;output=AST;}
399      a : (x=('+' | '-'))^ ID ;
400      ID : 'a'..'z'+ ;
401      INT : '0'..'9'+;
402      WS : (' '|'\n') {$channel=HIDDEN;} ;
403  
404    END
405    result.should == '+ abc'
406  end
407  
408  example "hoisting a token-type set token as root from within a (...)* loop" do
409    result = parse( <<-'END', :a, 'a+b-c' )
410      grammar SetAsRuleRootInLoop;
411      options {language=Ruby;output=AST;}
412      a : ID (('+'|'-')^ ID)* ;
413      ID : 'a'..'z'+ ;
414      INT : '0'..'9'+;
415      WS : (' '|'\n') {$channel=HIDDEN;} ;
416  
417    END
418    result.should == '(- (+ a b) c)'
419  end
420  
421  example "an `~' inverted token-type set element" do
422    result = parse( <<-'END', :a, '34+2' )
423      grammar NotSet;
424      options {language=Ruby;output=AST;}
425      a : ~ID '+' INT ;
426      ID : 'a'..'z'+ ;
427      INT : '0'..'9'+;
428      WS : (' '|'\n') {$channel=HIDDEN;} ;
429  
430    END
431    result.should == '34 + 2'
432  end
433  
434  example "a `~' inverted token-type set in a rule (with a label)" do
435    result = parse( <<-'END', :a, '34+2' )
436      grammar NotSetWithLabel;
437      options {language=Ruby;output=AST;}
438      a : x=~ID '+' INT ;
439      ID : 'a'..'z'+ ;
440      INT : '0'..'9'+;
441      WS : (' '|'\n') {$channel=HIDDEN;} ;
442  
443    END
444    result.should == '34 + 2'
445  end
446  
447  example "a `~' inverted token-type set element in a rule (with a list += label)" do
448    result = parse( <<-'END', :a, '34+2' )
449      grammar NotSetWithListLabel;
450      options {language=Ruby;output=AST;}
451      a : x=~ID '+' INT ;
452      ID : 'a'..'z'+ ;
453      INT : '0'..'9'+;
454      WS : (' '|'\n') {$channel=HIDDEN;} ;
455  
456    END
457    result.should == '34 + 2'
458  end
459  
460  example "a `~' inverted token-type set element hoisted to root via `^'" do
461    result = parse( <<-'END', :a, '34 55' )
462      grammar NotSetRoot;
463      options {language=Ruby;output=AST;}
464      a : ~'+'^ INT ;
465      ID : 'a'..'z'+ ;
466      INT : '0'..'9'+;
467      WS : (' '|'\n') {$channel=HIDDEN;} ;
468  
469    END
470    result.should == '(34 55)'
471  end
472  
473  example "hoisting a `~' inverted token-type set to root using `^' (with label)" do
474    result = parse( <<-'END', :a, '34 55' )
475      grammar NotSetRootWithLabel;
476      options {language=Ruby;output=AST;}
477      a   : x=~'+'^ INT ;
478      ID  : 'a'..'z'+ ;
479      INT : '0'..'9'+;
480      WS  : (' '|'\n') {$channel=HIDDEN;} ;
481    END
482    result.should == '(34 55)'
483  end
484  
485  example "hoisting a `~' inverted token-type set to root using `^' (with list += label)" do
486    result = parse( <<-'END', :a, '34 55' )
487      grammar NotSetRootWithListLabel;
488      options {language=Ruby;output=AST;}
489      a : x+=~'+'^ INT ;
490      ID : 'a'..'z'+ ;
491      INT : '0'..'9'+;
492      WS : (' '|'\n') {$channel=HIDDEN;} ;
493    END
494    result.should == '(34 55)'
495  end
496  
497  example "hoisting a `~' inverted token-type set to root from within a (...)* loop" do
498    result = parse( <<-'END', :a, '3+4+5' )
499      grammar NotSetRuleRootInLoop;
500      options {language=Ruby;output=AST;}
501      a : INT (~INT^ INT)* ;
502      blort : '+' ;
503      ID : 'a'..'z'+ ;
504      INT : '0'..'9'+;
505      WS : (' '|'\n') {$channel=HIDDEN;} ;
506  
507    END
508    result.should == '(+ (+ 3 4) 5)'
509  end
510  
511  example "multiple tokens with the same label in a rule" do
512    result = parse( <<-'END', :a, 'a b' )
513      grammar TokenLabelReuse;
514      options {language=Ruby;output=AST;}
515      a returns [result] : id=ID id=ID {
516        $result = "2nd id=\%s," \% $id.text
517      } ;
518      ID : 'a'..'z'+ ;
519      INT : '0'..'9'+;
520      WS : (' '|'\n') {$channel=HIDDEN;} ;
521  
522    END
523    result.should == '2nd id=b,a b'
524  end
525  
526  example "multiple tokens with the same label in a rule (with a `^' root hoist)" do
527    result = parse( <<-'END', :a, 'a b' )
528      grammar TokenLabelReuse2;
529      options {language=Ruby;output=AST;}
530      a returns [result]: id=ID id=ID^ {$result = "2nd id=#{$id.text},"} ;
531      ID : 'a'..'z'+ ;
532      INT : '0'..'9'+;
533      WS : (' '|'\n') {$channel=HIDDEN;} ;
534  
535    END
536    result.should == '2nd id=b,(b a)'
537  end
538  
539  example "extra token in a simple declaration" do
540    result, errors = parse( <<-'END', :decl, 'int 34 x=1;', true )
541      grammar ExtraTokenInSimpleDecl;
542      options {language=Ruby;output=AST;}
543      decl : type^ ID '='! INT ';'! ;
544      type : 'int' | 'float' ;
545      ID : 'a'..'z'+ ;
546      INT : '0'..'9'+;
547      WS : (' '|'\n') {$channel=HIDDEN;} ;
548  
549    END
550    errors.should == [ "line 1:4 extraneous input \"34\" expecting ID" ]
551    result.should == '(int x 1)'
552  end
553  
554  example "missing ID in a simple declaration" do
555    result, errors = parse( <<-'END', :decl, 'int =1;', true )
556      grammar MissingIDInSimpleDecl;
557      options {language=Ruby;output=AST;}
558      tokens {EXPR;}
559      decl : type^ ID '='! INT ';'! ;
560      type : 'int' | 'float' ;
561      ID : 'a'..'z'+ ;
562      INT : '0'..'9'+;
563      WS : (' '|'\n') {$channel=HIDDEN;} ;
564    END
565    errors.should == [ "line 1:4 missing ID at \"=\"" ]
566    result.should == '(int <missing ID> 1)'
567  end
568  
569  example "missing token of a token-type set in a simple declaration" do
570    result, errors = parse( <<-'END', :decl, 'x=1;', true )
571      grammar MissingSetInSimpleDecl;
572      options {language=Ruby;output=AST;}
573      tokens {EXPR;}
574      decl : type^ ID '='! INT ';'! ;
575      type : 'int' | 'float' ;
576      ID : 'a'..'z'+ ;
577      INT : '0'..'9'+;
578      WS : (' '|'\n') {$channel=HIDDEN;} ;
579  
580    END
581    errors.should == [ "line 1:0 mismatched input \"x\" expecting set nil" ]
582    result.should == '(<error: x> x 1)'
583  end
584  
585  example "missing INT token simulated with a `<missing INT>' error node" do
586    result, errors = parse( <<-'END', :a, 'abc', true )
587      grammar MissingTokenGivesErrorNode;
588      options {language=Ruby;output=AST;}
589      a : ID INT ; // follow is EOF
590      ID : 'a'..'z'+ ;
591      INT : '0'..'9'+;
592      WS : (' '|'\n') {$channel=HIDDEN;} ;
593  
594    END
595    errors.should == [ "line 0:-1 missing INT at \"<EOF>\"" ]
596    result.should == 'abc <missing INT>'
597  end
598  
599  example "missing token from invoked rule results in error node with a resync attribute" do
600    result, errors = parse( <<-'END', :a, 'abc', true )
601      grammar MissingTokenGivesErrorNodeInInvokedRule;
602      options {language=Ruby;output=AST;}
603      a : b ;
604      b : ID INT ; // follow should see EOF
605      ID : 'a'..'z'+ ;
606      INT : '0'..'9'+;
607      WS : (' '|'\n') {$channel=HIDDEN;} ;
608  
609    END
610    errors.should == [ "line 0:-1 mismatched input \"<EOF>\" expecting INT" ]
611    result.should == '<mismatched token: <EOF>, resync = abc>'
612  end
613  
614  example "extraneous ID token displays error and is ignored in AST output" do
615    result, errors = parse( <<-'END', :a, 'abc ick 34', true )
616      grammar ExtraTokenGivesErrorNode;
617      options {language=Ruby;output=AST;}
618      a : b c ;
619      b : ID ;
620      c : INT ;
621      ID : 'a'..'z'+ ;
622      INT : '0'..'9'+;
623      WS : (' '|'\n') {$channel=HIDDEN;} ;
624  
625    END
626    errors.should == [ "line 1:4 extraneous input \"ick\" expecting INT" ]
627    result.should == 'abc 34'
628  end
629  
630  example "missing ID token simulated with a `<missing ID>' error node" do
631    result, errors = parse( <<-'END', :a, '34', true )
632      grammar MissingFirstTokenGivesErrorNode;
633      options {language=Ruby;output=AST;}
634      a : ID INT ;
635      ID : 'a'..'z'+ ;
636      INT : '0'..'9'+;
637      WS : (' '|'\n') {$channel=HIDDEN;} ;
638  
639    END
640    errors.should == [ "line 1:0 missing ID at \"34\"" ]
641    result.should == '<missing ID> 34'
642  end
643  
644  example "another case where a missing ID token is simulated with a `<missing ID>' error node" do
645    result, errors = parse( <<-'END', :a, '34', true )
646      grammar MissingFirstTokenGivesErrorNode2;
647      options {language=Ruby;output=AST;}
648      a : b c ;
649      b : ID ;
650      c : INT ;
651      ID : 'a'..'z'+ ;
652      INT : '0'..'9'+;
653      WS : (' '|'\n') {$channel=HIDDEN;} ;
654  
655    END
656    errors.should == [ "line 1:0 missing ID at \"34\"" ]
657    result.should == '<missing ID> 34'
658  end
659  
660  example "no viable alternative for rule is represented as a single `<unexpected: ...>' error node" do
661    result, errors = parse( <<-'END', :a, '*', true )
662      grammar NoViableAltGivesErrorNode;
663      options {language=Ruby;output=AST;}
664      a : b | c ;
665      b : ID ;
666      c : INT ;
667      ID : 'a'..'z'+ ;
668      S : '*' ;
669      INT : '0'..'9'+;
670      WS : (' '|'\n') {$channel=HIDDEN;} ;
671    END
672    errors.should == [ "line 1:0 no viable alternative at input \"*\"" ]
673    result.should == "<unexpected: 0 S[\"*\"] @ line 1 col 0 (0..0), resync = *>"
674  end
675  
676  example "token with a `+=' list label hoisted to root with `^'" do
677    result = parse( <<-'END', :a, 'a' )
678      grammar TokenListLabelRuleRoot;
679      options {language=Ruby;output=AST;}
680      a : id+=ID^ ;
681      ID : 'a'..'z'+ ;
682      INT : '0'..'9'+;
683      WS : (' '|'\n') {$channel=HIDDEN;} ;
684  
685    END
686    result.should == 'a'
687  end
688  
689  example "token with a list `+=' label trashed with `!'" do
690    result = parse( <<-'END', :a, 'a' )
691      grammar TokenListLabelBang;
692      options {language=Ruby;output=AST;}
693      a : id+=ID! ;
694      ID : 'a'..'z'+ ;
695      INT : '0'..'9'+;
696      WS : (' '|'\n') {$channel=HIDDEN;} ;
697  
698    END
699    result.should == ''
700  end
701  
702  example "using list `+=' labels to collect trees of invoked rules" do
703    result = parse( <<-'END', :a, 'a b' )
704      grammar RuleListLabel;
705      options {language=Ruby;output=AST;}
706      a returns [result]: x+=b x+=b {
707      t = $x[1]
708      $result = "2nd x=#{t.inspect},"
709      };
710      b : ID;
711      ID : 'a'..'z'+ ;
712      INT : '0'..'9'+;
713      WS : (' '|'\n') {$channel=HIDDEN;} ;
714  
715    END
716    result.should == '2nd x=b,a b'
717  end
718  
719  example "using a list `+=' label to collect the trees of invoked rules within a (...)+ block" do
720    result = parse( <<-'END', :a, 'a b' )
721      grammar RuleListLabelRuleRoot;
722      options {language=Ruby;output=AST;}
723      a returns [result] : ( x+=b^ )+ {
724      $result = "x=\%s," \% $x[1].inspect
725      } ;
726      b : ID;
727      ID : 'a'..'z'+ ;
728      INT : '0'..'9'+;
729      WS : (' '|'\n') {$channel=HIDDEN;} ;
730  
731    END
732    result.should == 'x=(b a),(b a)'
733  end
734  
735  example "trashing the tree of an invoked rule with `!' while collecting the tree with a list `+=' label" do
736    result = parse( <<-'END', :a, 'a b' )
737      grammar RuleListLabelBang;
738      options {language=Ruby;output=AST;}
739      a returns [result] : x+=b! x+=b {
740      $result = "1st x=#{$x[0].inspect},"
741      } ;
742      b : ID;
743      ID : 'a'..'z'+ ;
744      INT : '0'..'9'+;
745      WS : (' '|'\n') {$channel=HIDDEN;} ;
746  
747    END
748    result.should == '1st x=a,b'
749  end
750  
751  example "a whole bunch of different elements" do
752    result = parse( <<-'END', :a, 'a b b c c d' )
753      grammar ComplicatedMelange;
754      options {language=Ruby;output=AST;}
755      a : A b=B b=B c+=C c+=C D {s = $D.text} ;
756      A : 'a' ;
757      B : 'b' ;
758      C : 'c' ;
759      D : 'd' ;
760      WS : (' '|'\n') {$channel=HIDDEN;} ;
761    END
762    result.should == 'a b b c c d'
763  end
764  
765  example "rule return values in addition to AST output" do
766    result = parse( <<-'END', :a, 'abc 34' )
767      grammar ReturnValueWithAST;
768      options {language=Ruby;output=AST;}
769      a returns [result] : ID b { $result = $b.i.to_s + "\n" } ;
770      b returns [i] : INT {$i=$INT.text.to_i};
771      ID : 'a'..'z'+ ;
772      INT : '0'..'9'+;
773      WS : (' '|'\n') {$channel=HIDDEN;} ;
774  
775    END
776    result.should == "34\nabc 34"
777  end
778  
779  example "a (...)+ loop containing a token-type set" do
780    result = parse( <<-'END', :r, 'abc 34 d' )
781      grammar SetLoop;
782      options { language=Ruby;output=AST; }
783      r : (INT|ID)+ ; 
784      ID : 'a'..'z' + ;
785      INT : '0'..'9' +;
786      WS: (' ' | '\n' | '\t')+ {$channel = HIDDEN;};
787    
788    END
789    result.should == 'abc 34 d'
790  end
791  
792end
793