capture
[rrq/gorite.git] / com / intendico / sdp / Grammar.java
1 /*********************************************************************
2 Copyright 2012, Ralph Ronnquist.
3
4 This file is part of GORITE.
5
6 GORITE is free software: you can redistribute it and/or modify it
7 under the terms of the Lesser GNU General Public License as published
8 by the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 GORITE is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14 License for more details.
15
16 You should have received a copy of the Lesser GNU General Public
17 License along with GORITE.  If not, see <http://www.gnu.org/licenses/>.
18 **********************************************************************/
19
20 package com.intendico.sdp;
21
22 import java.io.FileReader;
23 import java.io.IOException;
24 import java.io.StringWriter;
25 import java.io.PrintWriter;
26 import java.text.ParseException;
27 import java.util.Comparator;
28 import java.util.HashSet;
29 import java.util.Hashtable;
30 import java.util.Iterator;
31 import java.util.TreeMap;
32 import java.util.TreeSet;
33 import java.util.Set;
34 import java.util.Vector;
35
36 /**
37  * The Grammar class is used for defining a grammar.
38  */
39 public class Grammar {
40
41     //
42     // Support methods for text processing.
43     //
44
45     /**
46      * Utility method to make a string from a named file.
47      */
48     static public String loadFile(String filename)
49         throws IOException {
50         FileReader f = new FileReader( filename );
51         char [] buffer = new char [ 1024 ];
52         int length;
53         StringBuffer s = new StringBuffer();
54         while ( ( length = f.read( buffer ) ) >= 0 ) {
55             if ( length > 0 )
56                 s.append( buffer, 0, length );
57         }
58         f.close();
59         return s.toString();
60     }
61
62     private String strip(String a) {
63         Class [] c = { BNFGrammar.class, getClass() };
64         for ( int i=0; i < c.length; i++ ) {
65             if ( a.startsWith( c[i].getName() + "$" ) )
66                 return a.substring( c[i].getName().length() + 1 );
67         }
68         return a;
69     }
70
71     /**
72      * Utility method to map a string index into a line number, by
73      * counting new-lines.
74      */
75     static public int lineNumber(CharSequence s,int index) {
76         int lineno = 1;
77         for ( int i = 0; i < index; i++ ) {
78             if ( s.charAt( i ) == '\n' ) {
79                 lineno++;
80             }
81         }
82         return lineno;
83     }
84
85     /**
86      * Utility method that forms a string to mark out the indexed
87      * position in the input string.
88      */
89     static public String inputPosition(CharSequence input,int index) {
90         return inputPosition( input, index, 0 );
91     }
92     
93     /**
94      * Utility method that forms a string to mark out the indexed
95      * position and span in the input string.
96      */
97     static public String inputPosition(CharSequence input,int index,int span) {
98         if ( index < 0 )
99             return "" ;
100
101         int from = lastIndexOf( input, "\n", index - 1 ) + 1;
102         int to = indexOf( input, "\n", index );
103         boolean spandots = false;
104
105         if ( to < from )
106             to = input.length();
107
108         int lineno = lineNumber( input, from );
109         int at = index - from;
110
111         if ( span < 1 )
112             span = 1;
113
114         String line = lineno + ": ";
115
116         if ( span > 43 ) {
117             span = 40;
118             spandots = true;
119         }
120
121         if ( at + span > 60 ) {
122             // Adjust to fit on a line
123             int clip = at + span - 55;
124             at -= clip;
125             if ( at < 0 ) {
126                 clip -= at;
127                 at = 0;
128             }
129             from += clip;
130             line += "...";
131         }
132
133         if ( to - from > 70 )
134             to = from + 70;
135
136         at  += line.length();
137         line += input.subSequence( from, to ).toString();
138         if ( at + span > to )
139             span = to - at;
140
141         if ( span < 1 )
142             span = 1;
143
144         StringBuffer out = new StringBuffer( line );
145         out.append( "\n" );
146
147         for (int i=0; i<at; i++ ) {
148             if ( Character.isWhitespace( line.charAt( i ) ) ) {
149                 out.append( line.charAt( i ) );
150             } else {
151                 out.append( " " );
152             }
153         }
154
155         for (int i=0; i<span; i++ ) {
156             out.append( "^" );
157         }
158
159         if ( spandots )
160             out.append( "..." );
161
162         return out.toString();
163     }
164
165     /**
166      * Utility method to determine if an CharSequence starts with a given
167      * string at a given position.
168      */
169     static public boolean startsWith(CharSequence input,int from,String what) {
170         int end = from + what.length();
171         if ( end > input.length() )
172             return false;
173         return what.contentEquals( input.subSequence( from, end ) );
174     }
175
176     /**
177      * Returns the last occurence of a given string before a given index.
178      */
179     static public int lastIndexOf(CharSequence s,String what,int from) {
180         int endw = what.length();
181         if ( endw == 0 )
182             return from;
183         int end = s.length();
184         int end1 = end - endw;
185         char first = what.charAt( endw - 1  );
186         for ( from = from - 1; from >= endw; from-- ) {
187             if ( s.charAt( from ) != first )
188                 continue;
189             for ( int p = from - 1, k = endw - 2; p >= 0; p--, k-- ) {
190                 if ( k < 0 )
191                     return from;
192                 if ( s.charAt( p ) != what.charAt( k ) )
193                     break;
194             }
195         }
196         return -1;
197     }
198
199     /**
200      * Returns the first occurence of a given string.
201      */
202     static public int indexOf(CharSequence s,String what,int from) {
203         int endw = what.length();
204         if ( endw == 0 )
205             return from;
206         int end = s.length();
207         int end1 = end - endw;
208         char first = what.charAt( 0 );
209         for ( ; from < end1; from++ ) {
210             if ( s.charAt( from ) != first )
211                 continue;
212             for ( int p = from + 1, k = 1;; p++, k++ ) {
213                 if ( k >= endw )
214                     return from;
215                 if ( s.charAt( p ) != what.charAt( k ) )
216                     break;
217             }
218         }
219         return -1;
220     }
221
222     /**
223      * Utility method to scan past blank or comment.
224      */
225     static public int skipBlanksAndComments(CharSequence s,int from) {
226         int end = s.length();
227         for ( ; from < end; from++ ) {
228             if ( Character.isWhitespace( s.charAt( from ) ) )
229                 continue;
230             if ( s.charAt( from ) != '/' )
231                 return from;
232             if ( s.length() < 2 )
233                 return from;
234             if ( s.charAt( from + 1 ) == '*' ) {
235                 int x = indexOf( s, "*/", from );
236                 if ( x < 0 ) {
237                     // Unterminated comment
238                     return end;
239                 }
240                 from = x + 1;
241                 continue;
242             }
243             if ( s.charAt( from + 1 ) == '/' ) {
244                 int x = indexOf( s, "\n", from );
245                 if ( x < 0 ) {
246                     // Unterminated comment
247                     return end;
248                 }
249                 from = x;
250                 continue;
251             }
252             return from;
253         }
254         return end;
255     }
256
257     /**
258      * The Parse interface represents the result from parsing, prior
259      * to semantic processing.
260      */
261     public interface ParseTree {
262
263         /**
264          * Returns the first character position spanned by this parse
265          * tree.
266          */
267         public int getFrom();
268
269         /**
270          * Returns the first significant character position within the
271          * region spanned by this parse tree.
272          */
273         public int getFirst();
274
275         /**
276          * Returns the first character position after the region
277          * spanned by this parse tree.
278          */
279         public int getTo();
280
281         /**
282          * Returns the grammar rule index for this result.
283          */
284         public int getIndex();
285
286         /**
287          * Returns the grammar rule for this result, if any.
288          */
289         public String getRule();
290
291         /**
292          * Returns the 'substance' portion of the span.
293          */
294         public String getText();
295
296         /**
297          * Returns the parsetree's generic syntax name, which is the
298          * rule name for production syntax, and ":" otherwise.
299          */
300         public String getSyntaxName();
301
302         /**
303          * Utility method to form a string representation of this
304          * parse tree element, with an indentation argument.
305          */
306         public String toString(String head);
307
308         /**
309          * Forms a string representation of two lines to indicate the
310          * span of this result on the input line.
311          */
312         public String inputSpan();
313
314         /**
315          * Forms a string representation of the span region.
316          */
317         public String spanString();
318
319         /**
320          * Processing method for this parse tree.
321          */
322         public Object process() throws Throwable;
323
324         /**
325          * Processing method for the children of this parse tree.
326          */
327         public Vector processChildren() throws Throwable;
328     }
329
330     /**
331      * The Result class is for carrying parsing result.
332      */
333     public class Result implements ParseTree {
334
335         /**
336          * Refers to the input being parsed.
337          */
338         public final CharSequence input;
339
340         /**
341          * The character position in the input from where this Result
342          * object spans.
343          */
344         public final int from;
345
346         /**
347          * The first character position recognised as not whitespace
348          * or comment; the beginning of 'substance'.
349          */
350         public final int first;
351
352         /**
353          * The character position in the input to where this Result
354          * object spans. Thus, the span is input.substring(from,to).
355          */
356         public final int to;
357
358         /**
359          * Flag as to whether or not the text should be preserved upon
360          * semantic processing.
361          */
362         public final boolean preserve;
363
364         /**
365          * Returns the first character position spanned by this parse
366          * tree.
367          */
368         public int getFrom() {
369             return from;
370         }
371
372         /**
373          * Returns the first significant character position within the
374          * region spanned by this parse tree.
375          */
376         public int getFirst() {
377             return first;
378         }
379
380         /**
381          * Returns the first character position after the region
382          * spanned by this parse tree.
383          */
384         public int getTo() {
385             return to;
386         }
387
388         /**
389          * Constructor.
390          */
391         public Result(CharSequence s,int a,int b,int c) {
392             this( s, a, b, c, false );
393         }
394
395         /**
396          * Constructor.
397          */
398         public Result(CharSequence s,int a,int b,int c,boolean p) {
399             from = a;
400             first = b;
401             to = c;
402             input = s;
403             preserve = p;
404         }
405
406         /**
407          * Returns the 'substance' portion of the span.
408          */
409         public String getText() {
410             return input.subSequence( first, to ).toString();
411         }
412
413         /**
414          * Returns the grammar rule index for this result.
415          */
416         public int getIndex() {
417             return -1;
418         }
419
420         /**
421          * Returns the grammar rule for this result.
422          */
423         public String getRule() {
424             return null;
425         }
426
427         /**
428          * Returns the parsetree's generic syntax name, which is the
429          * rule name for production syntax, and ":" otherwise.
430          */
431         public String getSyntaxName() {
432             return ":";
433         }
434
435         /**
436          * Returns the canonical token string for this result.
437          */
438         public String getToken() {
439             return "'" + getText();
440         }
441
442         /**
443          * Forms a String representaion of this Result object.
444          * The argument is ignored.
445          */
446         public String toString(String head) {
447             return toString();
448         }
449
450         /**
451          * Forms a string representation of the region spanned.
452          */
453         public String spanString() {
454             return "[" + from + "," + to + "]";
455         }
456
457         /**
458          * Presents this result bmo of an input context on one line,
459          * followed by a 'marker' for the span beneath it.
460          */
461         public String inputSpan() {
462             return inputPosition( input, first, to - first );
463         }
464
465         /**
466          * Forms a String representaion of this Result object.
467          */
468         public String toString() {
469             return "{" + from + "," + first + "," + to +
470                 ( preserve? "=!'" : "='" ) +
471                 getText() + "'}" ;
472         }
473
474         /**
475          * Default result processing returns null.
476          */
477         public Object process() throws Throwable {
478             if ( preserve )
479                 return getText();
480             return null;
481         }
482
483         /**
484          * Processing method for the children of this parse tree.
485          */
486         public Vector processChildren() throws Throwable {
487             return null;
488         }
489     }
490
491     /**
492      * The ProductionResult class is for carrrying production rule
493      * result.
494      */
495     public class ProductionResult extends Result {
496
497         /**
498          * Tells the name of the non-terminal produced.
499          */
500         public String rule;
501
502         /**
503          * Tells the index for the production used.
504          */
505         public int index;
506
507         /**
508          * Holds the result objects of associated with production rule
509          * tokens.
510          */
511         public ParseTree [] results;
512
513         /**
514          * Returns the grammar rule index for this result.
515          */
516         public int getIndex() {
517             return index;
518         }
519
520         /**
521          * Returns the grammar rule for this result.
522          */
523         public String getRule() {
524             return rule;
525         }
526
527         /**
528          * Constructor intended for Lexical rules.
529          */
530         public ProductionResult(String r,CharSequence s,int a,int b,int c) {
531             super( s, a, b, c );
532             rule = r;
533             index = -1;
534         }
535
536         /**
537          * General constructor.
538          */
539         public ProductionResult(String r,CharSequence s,int i,ParseTree [] rs) {
540             this( r, s,
541                   rs == null? 0 : rs[0].getFrom(),
542                   rs == null? 0 : rs[0].getFirst(),
543                   rs == null? -1 : rs[ rs.length - 1 ].getTo() );
544             index = i;
545             results = rs;
546         }
547
548         /**
549          * Returns the parsetree's generic syntax name, which is the
550          * rule name for production syntax, and ":" otherwise.
551          */
552         public String getSyntaxName() {
553             return rule;
554         }
555
556         /**
557          * Returns the canonical token string for this result.
558          */
559         public String getToken() {
560             return "<" + getRule() + ">";
561         }
562
563         /**
564          * Forms a string representation of the parse tree.
565          */
566         public String toString() {
567             return toString( "" );
568         }
569
570         /**
571          * Forms a string representation of the parse tree, using the
572          * given string as indentation prefix.
573          */
574         public String toString(String head) {
575             StringBuffer s = new StringBuffer();
576             s.append( rule );
577             s.append( ":" );
578             s.append( index );
579             s.append( "[" );
580             head += " ";
581             if ( results == null ) {
582                 s.append( super.toString() );
583             } else {
584                 for ( int i=0; i<results.length; i++ ) {
585                     if ( i > 0 ) {
586                         s.append( "," );
587                     }
588                     s.append( "\n" );
589                     s.append( head );
590                     s.append( results[i].toString( head ) );
591                 }
592             }
593             s.append( "]" );
594             return s.toString();
595         }
596
597         /**
598          * Default result processing invokes the process method of the
599          * syntax rule.
600          */
601         public Object process() throws Throwable {
602             try {
603                 return getDefinition( rule ).process( this );
604             } catch (Throwable e) {
605                 throw hideParse( e );
606             }
607         }
608
609         /**
610          * Utility method to process the children
611          */
612         public Vector processChildren() throws Throwable {
613             Vector v = new Vector();
614             if ( results != null ) {
615                 for ( int i=0; i<results.length; i++ ) {
616                     Object x = results[i].process();
617                     if ( x != null )
618                         v.add( x );
619                 }
620             }
621             return v;
622         }
623     }
624
625
626     /**
627      * The non-terminals.
628      */
629     public TreeMap rules = new TreeMap();
630
631     //
632     // Inner interfaces to support concept generalisation.
633     // These are: Rule, Token and ErrorToken.
634     //
635
636     /**
637      * The Rule interface is for all kinds of syntax definitions.
638      * Presently there are only the two kinds of ProductionList and
639      * Lexical.
640      */
641     public interface Rule {
642
643         /**
644          * Returns a ParseTree object for successful parse, or
645          * null.
646          */
647         public ParseTree parse(CharSequence s,int from) throws ParseException;
648
649         /**
650          * Returns the rule name; the non-terminal defined.
651          */
652         public String getName();
653
654         /**
655          * Performs synthesising operations on a ParseTree
656          * parse tree, and returns a value representing the parse tree
657          * value.
658          */
659         public Object process(ParseTree pr) throws Throwable;
660     }
661
662     //
663     // The parsing elements.
664     //
665
666     /**
667      * Bit field for stderr tracing of parser operation.
668      */
669     public int trace = 0;
670
671     /**
672      * A counter of entries to syntax rules; "attempts"
673      */
674     public int trace_count = 0;
675
676     public static int TRACE_TERMINALS = 1;
677     public static int TRACE_PARSE = 2;
678     public static int TRACE_RECURSION = 4;
679     public static int TRACE_ACTION = 8;
680     public static int TRACE_OPTIONS = 16;
681     public static int TRACE_LINK = 32;
682     public static int TRACE_STACK = 64;
683
684     public static int REUSE_ALWAYS = 0;
685     public static int REUSE_RECURSIVE = 1;
686     public static int REUSE_RARELY = 2;
687     public int reuse_mode = 0;
688     /**
689      * Trace variable.
690      */
691     public int current_from = 0;
692
693     /**
694      * Utility method for tracing.
695      */
696     public void log(CharSequence input,int from,String rule,String m) {
697         ParseTree last = last( from, rule );
698
699         String text = "At " + from + ", " + rule + " " +
700             ( last != null? last.spanString() : "[null]" ) + " - " + m ;
701
702         if ( from != current_from && input != null ) {
703             System.err.println();
704             int span = current_from > from? current_from - from : 0;
705             text = inputPosition( input, from, span ) + "\n" + text;
706         }
707         current_from = from;
708
709         System.err.println( text );
710     }
711
712     /**
713      * Utility method to test trace mode.
714      */
715     public boolean tracing(int mode) {
716         return ( trace & mode ) != 0;
717     }
718
719     /**
720      * Table of successful parse results.
721      */
722     public Hashtable results = null;
723
724     /**
725      * Table of known failed parse postions.
726      */
727     public HashSet failed = null;
728
729     /**
730      * Current parsing level.
731      */
732     public int level = 0;
733
734     /**
735      * Maps positions to tried lexical rules and terminals
736      */
737     public TreeMap errors;
738
739     /**
740      * Support method to create a hash key for a rule at a position.
741      */
742     public String key(int from,String rule) {
743         return rule + " at " + from ;
744     }
745
746     /**
747      * Support method to create a hash key for a rule at a position.
748      */
749     public String key(int from,String rule,int index) {
750         return rule + ":" + index + " at " + from;
751     }
752
753     /**
754      * Returns the last parse result for a rule at a position.
755      */
756     public ParseTree last(int from,String rule) {
757         if ( results == null )
758             return null;
759         return (ParseTree) results.get( key( from, rule ) );
760     }
761
762     /**
763      * Support method to set the parse result for a rule at a position.
764      */
765     public void saveLast(int from,String rule,ParseTree x) {
766         if ( x == null )
767             return;
768         if ( results == null )
769             results = new Hashtable();
770         results.put( key( from, rule ), x );
771     }
772
773     /**
774      * Support method to remove the parse result for a rule at a
775      * position.
776      */
777     public void clearLast(int from,String rule) {
778         if ( results != null )
779             results.remove( key( from, rule ) );
780     }
781
782     /**
783      * Tells whether a parse position is known to be failed.
784      */
785     public boolean isFailed(int from,String rule) {
786         return failed != null && failed.contains( key( from, rule ) );
787     }
788
789     /**
790      * Marks a parse position as failed.
791      */
792     public void setFailed(int from,String rule) {
793         if ( failed == null )
794             failed = new HashSet();
795         failed.add( key( from, rule ) );
796     }
797
798     /**
799      * Unmarks a parse position as failed.
800      */
801     public void clearFailed(int from,String rule) {
802         if ( failed != null )
803             failed.remove( key( from, rule ) );
804     }
805
806     /**
807      * Tells whether a parse position is known to be failed.
808      */
809     public boolean isFailed(int from,String rule,int index) {
810         return failed != null && failed.contains( key( from, rule, index ) );
811     }
812
813     /**
814      * Marks a parse option position as failed.
815      */
816     public void setFailed(int from,String rule,int index) {
817         if ( failed == null )
818             failed = new HashSet();
819         failed.add( key( from, rule, index ) );
820     }
821
822     /**
823      * Unmarks a parse position as failed.
824      */
825     public void clearFailed(int from,String rule,int index) {
826         if ( failed != null )
827             failed.remove( key( from, rule, index ) );
828     }
829
830     //
831     // Error handling...
832     //
833
834     /**
835      * Support method to log an attempted lexical match.
836      */
837     public void tryToken(ErrorToken token,int from) {
838         if ( errors == null )
839             errors = new TreeMap(
840                 new Comparator() {
841                     public int compare(Object a,Object b)
842                     {
843                         return ((Integer) a).compareTo( (Integer) b );
844                     }
845                 } );
846         Integer i = new Integer( from );
847         TreeSet t = (TreeSet) errors.get( i );
848         if ( t == null ) {
849             t = new TreeSet( new Comparator() {
850                     public int compare(Object a,Object b) {
851                         return compare( (ErrorToken) a, (ErrorToken) b );
852                     }
853
854                     public int compare(ErrorToken a,ErrorToken b) {
855                         return a.errorName().compareTo( b.errorName() );
856                     }
857                 } );
858             errors.put( i, t );
859         }
860         t.add( token );
861     }
862
863     /**
864      * Report all error tokens.
865      */
866     public String allErrors() {
867         StringBuffer s = new StringBuffer();
868         for ( Iterator i = errors.keySet().iterator(); i.hasNext(); ) {
869             Integer index = (Integer) i.next();
870             TreeSet list = getErrors( index.intValue() );
871             s.append( "Pos " );
872             s.append( index.toString() );
873             s.append( ":" );
874             for ( Iterator e = list.iterator(); e.hasNext(); ) {
875                 s.append( " " );
876                 s.append( (String) e.next() );
877             }
878             s.append( "\n" );
879         }
880         return s.toString();
881     }
882
883     /**
884      * Utility method that presents the deepest error in format.
885      * <lineno>: <line>
886      *             ^
887      * Expected: ...
888      */
889     public String lastError(CharSequence input) {
890         return errors == null? null : lastError( input, lastErrorPosition() );
891     }
892
893     public int lastErrorPosition() {
894         if ( errors == null )
895             return -1;
896         return ((Integer) errors.lastKey()).intValue();
897     }
898
899     /**
900      * Returns a string of the error names produced by the indicated
901      * error tokens.
902      */
903     public TreeSet getErrors(int from) {
904         return (TreeSet) errors.get( new Integer( from ) );
905     }
906
907     /**
908      * Utility method that presents the deepest error in format.
909      * <lineno>: <line>
910      *             ^
911      * Expected: ...
912      */
913     public String lastError(CharSequence input,int from) {
914         StringBuffer out = new StringBuffer();
915         out.append( inputPosition( input, from ) );
916         out.append( " [" + from + "]" );
917         out.append( "\nExpected: " );
918
919         TreeSet set = getErrors( from );
920
921         int at = set.size();
922         int i = at;
923
924         for ( Iterator e = set.iterator(); e.hasNext(); i--) {
925             if ( i == 1 ) {
926                 if ( at > 1 )
927                     out.append( " or " );
928             } else {
929                 if ( i != at )
930                     out.append( ", " );
931             }
932             out.append( ((ErrorToken) e.next()).errorName() );
933         }
934         out.append( "." );
935         return out.toString();
936     }
937
938     /**
939      * The ErrorToken interface is for producing error token name.
940      */
941     public interface ErrorToken {
942
943         /**
944          * Returns the token name.
945          */
946         public String errorName();
947     }
948
949
950     //
951     // Parsing elements
952     //
953
954     /**
955      * A ProductionList defines a rule by a list of alternative
956      * productions.
957      */
958     public class ProductionList implements Rule {
959
960         /**
961          * The rule defined by the productions.
962          */
963         public final String rule;
964
965         /**
966          * The list of optional productions for this rule.
967          */
968         public final Production [] options;
969
970         /**
971          * Constructor.
972          */
973         public ProductionList(String r,Production [] list) {
974             rule = r;
975             options = list;
976             if ( list != null ) {
977                 for ( int i=0; i<list.length; i++ ) {
978                     list[i].setRule( r, i );
979                 }
980             }
981         }
982
983         /**
984          * Returns the name of the non-terminal defined.
985          */
986         public String getName() {
987             return rule;
988         }
989
990         int usage = -1;
991
992         /**
993          * The default parsing of a ProductionList rule is to apply a
994          * modified recursive decent algorithm, which allows
995          * left-recursive as grammars.
996          */
997         public ParseTree parse(CharSequence input,int from)
998             throws ParseException {
999             if ( options == null ) {
1000                 // Missing syntax definition
1001                 throw new ParseException(
1002                     "<" + rule + "> is not defined.", from );
1003             }
1004
1005             boolean recursive = usage == from; // Re-entering this rule
1006
1007             ParseTree last = last( from, rule );
1008             ParseTree result = null;
1009
1010             boolean reuse = reuse_mode < 2 && last != null &&
1011                 ( reuse_mode == 0 || recursive );
1012
1013             if ( reuse ) {
1014                 if ( tracing( TRACE_OPTIONS ) )
1015                     log( input, from, rule,
1016                          "re-using last (mode=" + reuse_mode + ")" );
1017                 return last;
1018             }
1019
1020             int prior_usage = usage;
1021             usage = from;
1022
1023             boolean again = false;
1024
1025             try {
1026                 do { 
1027                     trace_count ++;
1028                     again = false;
1029                     result = null;
1030                     for (int i=0; result == null && i<options.length; i++ ) {
1031                         if ( tracing( TRACE_OPTIONS ) )
1032                             log( input, from, rule, "check option " + i );
1033                         result = options[i].parse( input, from );
1034                     }
1035                     if ( result == null ) {
1036                         if ( tracing( TRACE_OPTIONS ) )
1037                             log( input, from, rule, "all options failed" );
1038                         result = last;
1039                         again = false;
1040                     } else {
1041                         if ( last == null || last.getTo() < result.getTo() ) {
1042                             saveLast( from, rule, result );
1043                             if ( tracing( TRACE_OPTIONS ) )
1044                                 log( input, from, rule, "new result" );
1045                             again = true;
1046                             last = result;
1047                         } else {
1048                             if ( tracing( TRACE_OPTIONS ) )
1049                                 log( input, from, rule, "old result" );
1050                             result = last;
1051                             again = false;
1052                         }
1053                     }
1054                 } while ( again );
1055             } finally {
1056                 usage = prior_usage;
1057             }
1058
1059             return result;
1060         }
1061
1062         /**
1063          * Forms a string representation of this rule.
1064          */
1065         public String toString() {
1066             if ( options == null ) {
1067                 // Missing syntax definition
1068                 return rule + " IS NOT DEFINED";
1069             }
1070
1071             StringBuffer s = new StringBuffer();
1072
1073             s.append( rule );
1074             s.append( " ::= " );
1075             for ( int i=0; i<options.length; i++ ) {
1076                 if ( i > 0 )
1077                     s.append( "\n    | " );
1078                 s.append( options[i].toString() );
1079             }
1080             s.append( "\n" );
1081             return s.toString();
1082         }
1083
1084         /**
1085          * Default rule processing applies the option action to the
1086          * processed children.
1087          */
1088         public Object process(ParseTree pr) throws Throwable {
1089             try { 
1090                 return options[ pr.getIndex() ].
1091                     action( pr, pr.processChildren() );
1092             } catch (Throwable t) {
1093                 throw hideParse( t );
1094             }
1095         }
1096
1097     }
1098
1099     /**
1100      * Represents a production alternative.
1101      */
1102     public class Production {
1103
1104         /**
1105          * The production rule tokens.
1106          */
1107         public Token [] tokens;
1108
1109         /**
1110          * Set to mark where this rule is attempted
1111          */
1112         public int usage = -1;
1113
1114         /**
1115          * Set to the non-terminal that this is a production for, if
1116          * any.
1117          */
1118         public String rule = null;
1119
1120         /**
1121          * Help variable used for tracing purposes.
1122          */
1123         private String rulex;
1124
1125         /**
1126          * Set to be the option index for the rule the Production
1127          * belongs to.
1128          */
1129         public int index = -1;
1130
1131         /**
1132          * Constructor.
1133          */
1134         public Production() {
1135         }
1136
1137         /**
1138          * Alternative constructor.
1139          */
1140         public Production(Token [] t) {
1141             tokens = t;
1142         }
1143
1144         /**
1145          * Utility method to set the tokens of the production.
1146          */
1147         public void setTokens(Token [] t) {
1148             tokens = t;
1149         }
1150
1151         /**
1152          * Utility method to set the rule name.
1153          */
1154         public void setRule(String r,int i) {
1155             rule = r;
1156             index = i;
1157             rulex = rule + ":" + i;
1158         }
1159
1160         /**
1161          * Utility variable to hold the key string.
1162          */
1163         public String key = null;
1164
1165         /**
1166          * Returns the key string; computes it once first.
1167          */
1168         public String getKey() {
1169             if ( key == null )
1170                 key = toString();
1171             return key;
1172         }
1173
1174         /**
1175          * Equality method is based on production string.
1176          */
1177         public boolean equals(Object x) {
1178             return x instanceof Production &&
1179                 getKey().equals( ((Production) x).getKey() );
1180         }
1181
1182         /**
1183          * Hash code method; uses the key string.
1184          */
1185         public int hashCode() {
1186             return getKey().hashCode();
1187         }
1188
1189         /**
1190          * Returns the number of tokens.
1191          */
1192         public int length() {
1193             return tokens == null? -1 : tokens.length;
1194         }
1195
1196         boolean recursive;
1197
1198         /**
1199          * The default parsing of a Production is to try the tokens
1200          * from left to right.
1201          */
1202         public ParseTree parse(CharSequence input,int from)
1203             throws ParseException {
1204             if ( tokens == null ) {
1205                 throw new ParseException(
1206                     rule + ":" + index + " has no tokens", from );
1207             }
1208
1209             recursive = usage == from;
1210
1211             if ( usage == from ) {
1212                 // Reentering this Production recursively.
1213                 if ( tokens.length > 1 ) {
1214                     if ( tracing( TRACE_OPTIONS ) )
1215                         log( input, from, rulex, "recursive fail");
1216                     return null;
1217                 }
1218                 // Recursive singleton
1219                 if ( tracing( TRACE_OPTIONS ) )
1220                     log( input, from, rulex, "recursive singleton");
1221                 return null;
1222             }
1223
1224             if ( tracing( TRACE_OPTIONS ) )
1225                 log( input, from, rulex, "previous usage = " + usage );
1226
1227             int prior = usage;
1228             usage = from;
1229
1230             try {
1231                 ParseTree [] results = new ParseTree [ tokens.length ];
1232                 ParseTree result = null;
1233                 for ( int i=0; i<tokens.length; i++ ) {
1234                     results[i] = tokens[i].parse( input, from );
1235                     if ( results[i] == null ) {
1236                         if ( tracing( TRACE_OPTIONS ) )
1237                             log( input, from, rulex,
1238                                  "failed at token " + tokens[i] );
1239                         return null;
1240                     }
1241                     usage = -1;
1242                     from = results[i].getTo() ;
1243                 }
1244                 result = new ProductionResult( rule, input, index, results );
1245
1246                 if ( tracing( TRACE_OPTIONS ) )
1247                     log( input, prior, rulex,
1248                          "result = " + result.spanString() );
1249                 return result;
1250             } finally {
1251                 usage = prior;
1252             }
1253
1254         }
1255
1256         /**
1257          * Forms a string representation of this Production.
1258          */
1259         public String toString() {
1260             StringBuffer s = new StringBuffer();
1261
1262             for ( int i=0; i<tokens.length; i++ ) {
1263                 s.append( tokens[i].toString() );
1264                 s.append( " " );
1265             }
1266             String actual = getClass().getName();
1267             if ( ! actual.equals( Production.class.getName() ) ) {
1268                 s.append( "# " );
1269                 s.append( strip( actual ) );
1270                 s.append( " " );
1271             }
1272             return s.toString();
1273         }
1274
1275         /**
1276          * Default option action is to call action(Vector), and
1277          * thereby ignore the source position.
1278          */
1279         public Object action(ParseTree pr,Vector v) throws Throwable {
1280             try {
1281                 if ( tracing( TRACE_ACTION ) )
1282                     System.err.println(
1283                         "action " + pr.getRule() + ":" + pr.getIndex() +
1284                         " with " + v.size() + " arguments" );
1285                 return action( v );
1286             } catch (Throwable t) {
1287                 t = new Exception(
1288                     t + "\n" + pr.inputSpan() + " " + pr.spanString(), t );
1289                 t.setStackTrace( new StackTraceElement [0] );
1290                 throw t;
1291             }
1292         }
1293
1294         /**
1295          * Default Production action is to return the first argument
1296          * if there is a single one, otherwise the whole argument
1297          * vector.
1298          */
1299         public Object action(Vector v) throws Exception {
1300             if ( tracing( TRACE_ACTION ) )
1301                 System.err.println( "default Production action with " +
1302                                     v.size() + " arguments" );
1303             if ( v.size() == 0 )
1304                 return null;
1305             if ( v.size() == 1 )
1306                 return v.elementAt( 0 );
1307             return v;
1308         }
1309     }
1310
1311     /**
1312      * The Token interface is for production rule tokens.
1313      */
1314     public interface Token {
1315
1316         /**
1317          * Returns the token name.
1318          */
1319         public String getName();
1320
1321         /**
1322          * Returns the token's generic syntax name, which is the rule
1323          * name for production syntax, and ":" otherwise.
1324          */
1325         public String getSyntaxName();
1326
1327         /**
1328          * Returns a parse Result object, or null
1329          */
1330         public ParseTree parse(CharSequence s,int from) throws ParseException;
1331
1332     }
1333
1334     /**
1335      * A Terminal is a symbol to find verbatim.
1336      */
1337     public class Terminal implements Token, ErrorToken {
1338
1339         /**
1340          * The symbol to find.
1341          */
1342         public final String text;
1343
1344         /**
1345          * The key symbol to use for this token.
1346          */
1347         public final String ttext;
1348
1349         /**
1350          * Flag to mark whether or not this terminal token is visible
1351          * in semantics.
1352          */
1353         public final boolean preserve;
1354
1355         /**
1356          * Flag to mark whether or not this terminal token is visible
1357          * in semantics.
1358          */
1359         public final boolean optional;
1360
1361         /**
1362          * Constructor.
1363          */
1364         public Terminal(String t) {
1365             this( t, false, false );
1366         }
1367
1368         /**
1369          * Constructor.
1370          */
1371         public Terminal(String t,boolean p) {
1372             this( t, p, false );
1373         }
1374
1375         /**
1376          * Constructor.
1377          */
1378         public Terminal(String t,boolean p,boolean opt) {
1379             text = t;
1380             preserve = p;
1381             optional = opt;
1382             if ( optional )
1383                 ttext = "?" + text;
1384             else if ( preserve )
1385                 ttext = "!" + text;
1386             else
1387                 ttext = "'" + text;
1388         }
1389
1390         /**
1391          * Equality for terminals are measured on their name strings.
1392          */
1393         public boolean equals(Object x) {
1394             return x instanceof Terminal &&
1395                 getName().equals( ((Terminal) x).getName() ) ;
1396         }
1397
1398         /**
1399          * Hash code method; uses the key string.
1400          */
1401         public int hashCode() {
1402             return getName().hashCode();
1403         }
1404
1405         /**
1406          * Returns the rule name; the non-terminal defined.
1407          */
1408         public String getName() {
1409             return text;
1410         }
1411
1412         /**
1413          * Returns the token's generic syntax name, which is the rule
1414          * name for production syntax, and ":" otherwise.
1415          */
1416         public String getSyntaxName() {
1417             return ":";
1418         }
1419
1420         /**
1421          * The default parsing of a terminal token is to match it
1422          * against the current position, after ignoring whitespace and
1423          * comments.
1424          */
1425         public ParseTree parse(CharSequence s,int from) throws ParseException {
1426             ParseTree result = last( from, ttext );
1427             if ( result != null )
1428                 return result;
1429             if ( isFailed( from, ttext ) )
1430                 return null;
1431             tryToken( this, from );
1432             if ( tracing( TRACE_TERMINALS ) )
1433                 System.err.println( "Find " + text + " at pos " + from );
1434             int first = skipBlanksAndComments( s, from );
1435             if ( ! startsWith( s, first, text ) ) {
1436                 if ( optional ) {
1437                     if ( tracing( TRACE_TERMINALS ) )
1438                         System.err.println(
1439                             "Found omitted optional " + text +
1440                             " at pos " + first );
1441                     result = new Result(
1442                         s, from, first, first, false );
1443                     saveLast( from, "", result );
1444                     return result;
1445                 } else {
1446                     setFailed( from, ttext );
1447                     return null;
1448                 }
1449             }
1450             if ( tracing( TRACE_TERMINALS ) )
1451                 System.err.println( "Found " + text + " at pos " + first );
1452             result = new Result(
1453                 s, from, first, first + text.length(), preserve );
1454             saveLast( from, ttext, result );
1455             return result;
1456         }
1457
1458         /**
1459          * Forms a string representation of this terminal token.
1460          */
1461         public String toString() {
1462             return ttext ;
1463         }
1464
1465         /**
1466          * The standard error name for a terminal token.
1467          */
1468         public String errorName() {
1469             return "'" + text + "'" ;
1470         }
1471     }
1472
1473     /**
1474      * A NonTerminal is a reference to a syntax rule.
1475      */
1476     public class NonTerminal implements Token {
1477
1478         /**
1479          * Holds the name of the non-terminal.
1480          */
1481         public final String rule;
1482
1483         /**
1484          * Constructor.
1485          */
1486         public NonTerminal(String r) {
1487             rule = r;
1488         }
1489
1490         /**
1491          * Returns the rule name; the non-terminal defined.
1492          */
1493         public String getName() {
1494             return rule;
1495         }
1496
1497         /**
1498          * Returns the token's generic syntax name, which is the rule
1499          * name for production syntax, and ":" otherwise.
1500          */
1501         public String getSyntaxName() {
1502             return getDefinition( rule ) instanceof ProductionList?
1503                 rule : ":" ;
1504         }
1505
1506         /**
1507          * The default parsing of a non-terminal token is to defer to
1508          * apply its definition to the current input position.
1509          */
1510         public ParseTree parse(CharSequence input,int from)
1511             throws ParseException {
1512             try {
1513                 if ( tracing( TRACE_PARSE ) ) {
1514                     log( input, from, rule, ">>-- " + (++level) + " -->>" );
1515                 }
1516
1517                 Rule r = getDefinition( rule );
1518                 if ( r == null ) {
1519                     throw new ParseException(
1520                         "<" + rule + "> is not defined.", from );
1521                 }
1522
1523                 ParseTree result = r.parse( input, from );
1524
1525                 if ( result == null ) {
1526                     if ( tracing( TRACE_PARSE ) )
1527                         log( input, from, rule,
1528                              "<<-- " + (level--) + " --<< failed" );
1529                     return null;
1530                 }
1531
1532                 if ( tracing( TRACE_PARSE ) )
1533                     log( input, from, rule, "<<-- " + (level--) + " --<< " +
1534                          result.spanString() );
1535
1536                 return result;
1537             } catch (ParseException e) {
1538                 throw (ParseException) hideParse( e );
1539             } catch (Throwable e) {
1540                 ParseException pe = new ParseException( e.toString(), from );
1541                 pe.initCause( e );
1542                 throw pe;
1543             }
1544         }
1545
1546         /**
1547          * Forms a string representation of a non-terminal token.
1548          */
1549         public String toString() {
1550             return "<" + rule + ">" ;
1551         }
1552     }
1553
1554     /**
1555      * The Lexical class is a base class for lexical tokens.
1556      */
1557     abstract public class Lexical implements Rule, ErrorToken {
1558
1559         /**
1560          * The rule name.
1561          */
1562         final public String rule;
1563
1564         /**
1565          * Constructor.
1566          */
1567         public Lexical(String r) {
1568             rule = r;
1569         }
1570
1571         /**
1572          * Returns the rule name.
1573          */
1574         public String getName() {
1575             return rule;
1576         }
1577
1578         /**
1579          * Equality for terminals are measured on their error name
1580          * strings. 
1581          */
1582         public boolean equals(Object x) {
1583             return x instanceof Lexical &&
1584                 errorName().equals( ((Lexical) x).errorName() ) ;
1585         }
1586
1587         /**
1588          * Hash code method; uses the key string.
1589          */
1590         public int hashCode() {
1591             return errorName().hashCode();
1592         }
1593
1594         /**
1595          * Handle token ingress, which typically means to skip
1596          * whitespace.
1597          */
1598         public int ingress(CharSequence input,int from) {
1599             return skipBlanksAndComments( input, from );
1600         }
1601
1602         /**
1603          * The default parsing of a lexical rule: skips blanks and
1604          * comments, and then invokes scan.
1605          */
1606         public ParseTree parse(CharSequence input,int from)
1607             throws ParseException {
1608             ParseTree last = last( from, rule );
1609             if ( isFailed( from, rule ) ) {
1610                 if ( tracing( TRACE_OPTIONS ) )
1611                     log( input, from, rule, "lexical known failed" );
1612                 return null;
1613             }
1614             if ( last != null ) {
1615                 if ( tracing( TRACE_OPTIONS ) )
1616                     log( input, from, rule,
1617                          "lexical reuse " + last.spanString() );
1618                 return last;
1619             }
1620             tryToken( this, from );
1621             int first = ingress( input, from );
1622             int to = scan( input, first );
1623             if ( to < 0 ) {
1624                 setFailed( from, rule );
1625                 if ( tracing( TRACE_OPTIONS ) )
1626                     log( input, from, rule, "lexical failed" );
1627                 return null;
1628             }
1629             last = new ProductionResult( rule, input, from, first, to );
1630             saveLast( from, rule, last );
1631             if ( tracing( TRACE_OPTIONS ) )
1632                 log( input, from, rule, "lexical success" );
1633             return last;
1634         }
1635
1636         /**
1637          * The lexical function, which returns the end of the span
1638          * covered by the lexical rule, or -1. The method is given the
1639          * input string and the position to scan, which is neither
1640          * whitespace nor in a comment.
1641          */
1642         abstract public int scan(CharSequence s,int from);
1643
1644         /**
1645          * Forms a string representation of this lexical rule.
1646          */
1647         public String toString() {
1648             return
1649                 rule + " is lexical " + strip( getClass().getName() ) + "\n" ;
1650         }
1651
1652         /**
1653          * The standard error name for a lexical rule.
1654          */
1655         public String errorName() {
1656             return "<" + rule + ">";
1657         }
1658
1659         /**
1660          * Semantic processing for a lexical will return the matched
1661          * string unless it's an empty string.
1662          */
1663         public Object process(ParseTree pr) {
1664             return pr.getFirst() == pr.getTo()? null : pr.getText();
1665         }
1666     }
1667
1668     /**
1669      * The Link class is a base class for rules that link to
1670      * other grammars.
1671      */
1672     public class Link implements Rule {
1673
1674         /**
1675          * The grammar linked to.
1676          */
1677         public final Grammar grammar;
1678
1679         /**
1680          * The name of the non-terminal in this grammar.
1681          */
1682         public final String rule;
1683
1684         /**
1685          * The corresponding name in the linked grammar.
1686          */
1687         public final String goal;
1688
1689         /**
1690          * Constructor.
1691          */
1692         public Link(String r,String g,Grammar gr) {
1693             grammar = gr;
1694             rule = r;
1695             goal = g;
1696         }
1697
1698         /**
1699          * Constructor. Alternative constructor using the same rule
1700          * name in both grammars.
1701          */
1702         public Link(String r,Grammar gr) {
1703             this( r, r, gr );
1704         }
1705
1706         /**
1707          * Returns the 'local' rule name
1708          */
1709         public String getName() {
1710             return rule;
1711         }
1712
1713         /**
1714          * Parse input, save result, and return true if successful.
1715          */
1716         public ParseTree parse(CharSequence input,int from)
1717             throws ParseException {
1718             if ( grammar.tracing( TRACE_LINK ) )
1719                 grammar.log( input, from, rule, "link: " + goal );
1720             int trace_start = grammar.trace_count;
1721
1722             ParseTree p = grammar.parse( goal, input, from );
1723
1724             trace_count += grammar.trace_count - trace_start;
1725             int index = grammar.lastErrorPosition();
1726             TreeSet t = grammar.getErrors( index );
1727             for ( Iterator i = t.iterator(); i.hasNext(); )
1728                 tryToken( (ErrorToken) i.next(), index );
1729
1730             if ( p != null ) {
1731                 if ( tracing( TRACE_LINK ) )
1732                     log(
1733                         input, from, rule, "link return: " + goal + " = " +
1734                         p.spanString() + " with last error at " + index );
1735             } else {    
1736                 if ( tracing( TRACE_LINK ) )
1737                     log(
1738                         input, from, rule,
1739                         "link return: " + goal + " failed" );
1740             }
1741             return p;
1742         }
1743
1744         /**
1745          * Semantic processing for a Link
1746          */
1747         public Object process(ParseTree pr) throws Throwable {
1748             // The parse tree is contained in the other grammar.
1749             return grammar.process( pr );
1750         }
1751
1752         public String toString() {
1753             String pkg = Grammar.this.getClass().getName();
1754             pkg = pkg.substring( 0, pkg.lastIndexOf( "." ) + 1 );
1755             String name = grammar.getClass().getName();
1756             if ( name.startsWith( pkg ) )
1757                 name = name.substring( pkg.length() );
1758             return 
1759                 rule + " is linked to <" + goal + "> in " + name + "\n";
1760         }
1761     }
1762
1763     //
1764     // Additional Grammar methods
1765     //
1766
1767     /**
1768      * Constructor. Invokes the initialisation() method.
1769      */
1770     public Grammar() {
1771         initialisation();
1772     }
1773
1774     /**
1775      * Overridable method to perform grammar initialisation.
1776      */
1777     public void initialisation() {
1778     }
1779
1780     /**
1781      * Utility method to add a syntax rule.
1782      */
1783     public void addRule(Rule rule) {
1784         rules.put( rule.getName(), rule );
1785     }
1786
1787     /**
1788      * Applies a syntax rule to a string.
1789      */
1790     public ParseTree parse(String rule,CharSequence input)
1791         throws ParseException {
1792         for ( Iterator i = getGrammars().iterator(); i.hasNext(); ) {
1793             Grammar g = (Grammar) i.next();
1794             g.reset();
1795         }
1796         return parse( rule, input, 0 );
1797     }
1798
1799     /**
1800      * Resets the parsing machinery.
1801      */
1802     public void reset() {
1803         errors = null;
1804         results = null;
1805         failed = null;
1806         trace_count = 0;
1807     }
1808
1809     /**
1810      * Applies a syntax rule to a string.
1811      */
1812     public ParseTree parse(String rule,CharSequence input,int from)
1813         throws ParseException {
1814         Rule r = getDefinition( rule );
1815         if ( r == null )
1816             throw new ParseException(
1817                 "<" + rule + "> is not defined.", from );
1818         return r.parse( input, from );
1819     }
1820
1821     /**
1822      * Utility method to change the stack trace of an exception by
1823      * removing all Grammar elements.
1824      */
1825     public Throwable hideParse(Throwable e) {
1826         return hideParse( e, Grammar.class.getName() );
1827     }
1828
1829     /**
1830      * Utility method to change the stack trace of an exception by
1831      * removing all elements with agiven class name prefix.
1832      */
1833     public Throwable hideParse(Throwable e,String prefix) {
1834         if ( tracing( TRACE_STACK ) )
1835             return e;
1836
1837         for ( Throwable t = e; t != null; t = t.getCause() ) {
1838             StackTraceElement[] stack = t.getStackTrace();
1839             Vector v = new Vector();
1840             for ( int i=0; i<stack.length; i++ ) {
1841                 String s = stack[i].getClassName();
1842                 if ( ! ( s.startsWith( prefix ) ||
1843                          s.startsWith( "java." ) ||
1844                          s.startsWith( "sun." ) ) )
1845                     v.add( stack[i] );
1846             }
1847             t.setStackTrace( (StackTraceElement[]) v.toArray(
1848                                  new StackTraceElement [ v.size() ] ) );
1849         }
1850         return e;
1851     }
1852
1853     public Rule getDefinition(String rule) {
1854         return (Rule) rules.get( rule );
1855     }
1856
1857     /**
1858      * Utility method to process a parse, and catch exception
1859      */
1860     public Object process(ParseTree pr) throws Throwable {
1861         return pr.process();
1862     }
1863
1864     /**
1865      * Applies a syntax rule to a string and performs its semantics.
1866      */
1867     public Object parseAndProcess(String rule,String input)
1868         throws Throwable {
1869         ParseTree p = parse( rule, input );
1870         if ( p != null )
1871             return process( p );
1872         System.err.println( lastError( input ) );
1873         return null;
1874     }
1875
1876     /**
1877      * Returns a TreeSet of all included grammars, sorted by class
1878      * name.
1879      */
1880     public Vector getGrammars() {
1881         return getGrammars( null );
1882     }
1883
1884     /**
1885      * Collect all grammars involved, by linking.
1886      */
1887     private Vector getGrammars(Vector v) {
1888         Vector links = v != null? v : new Vector();
1889
1890         links.add( this );
1891
1892         for ( Iterator i = rules.keySet().iterator(); i.hasNext(); ) {
1893             Rule r = getDefinition( (String) i.next() );
1894             if ( r instanceof Link ) {
1895                 Link link = (Link) r;
1896                 if ( ! links.contains( link.grammar ) )
1897                     link.grammar.getGrammars( links );
1898             }
1899         }
1900         return links;
1901     }
1902
1903     /**
1904      * Utility method for text representation.
1905      */
1906     public String toStringAlone() {
1907         StringBuffer s = new StringBuffer();
1908         for ( Iterator i = rules.keySet().iterator(); i.hasNext(); ) {
1909             Rule r = getDefinition( (String) i.next() );
1910             s.append( r.toString() );
1911         }
1912         return s.toString();
1913     }
1914
1915     /**
1916      * Textual representation of a grammar.
1917      */
1918     public String toString() {
1919         Vector t = getGrammars( null );
1920         StringBuffer s = new StringBuffer();
1921         boolean rest = false;
1922         for ( Iterator i = t.iterator(); i.hasNext(); ) {
1923             Grammar g = (Grammar) i.next();
1924             if ( rest )
1925                 s.append( "\n" );
1926             rest = true;
1927             s.append( "/*** Definitions of grammar " );
1928             s.append( g.getClass().getName() );
1929             s.append( " follow ***/\n" );
1930             s.append( g.toStringAlone() );
1931         }
1932         return s.toString();
1933     }
1934
1935     //
1936     // Auxillary support for 'remoteHCyl2 semantics'. These are used by the
1937     // action tagging of LLBNF.
1938     //
1939
1940     /**
1941      * An interface for production rule actions.
1942      */
1943     public interface RuleAction {
1944         public Object action(Vector v)
1945             throws Exception;
1946     }
1947
1948     /**
1949      * A table of tagged rule actions.
1950      */
1951     Hashtable rule_actions = new Hashtable();
1952
1953     /**
1954      * Utility method for defining rule actions.
1955      */
1956     public void setAction(String tag,RuleAction r) {
1957         rule_actions.put( tag, r );
1958     }
1959
1960     /**
1961      * Utility method for defining rule actions.
1962      */
1963     public void setAction(Class c,String tag,RuleAction r) {
1964         for ( Iterator i = getGrammars().iterator(); i.hasNext(); ) {
1965             Grammar g = (Grammar) i.next();
1966             if ( c.isInstance( g ) )
1967                 g.rule_actions.put( tag, r );
1968         }
1969     }
1970 }
1971