1/*
2 * [The "BSD licence"]
3 * Copyright (c) 2005-2008 Terence Parr
4 * All rights reserved.
5 *
6 * Conversion to C#:
7 * Copyright (c) 2008-2009 Sam Harwell, Pixel Mine, Inc.
8 * All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 3. The name of the author may not be used to endorse or promote products
19 *    derived from this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33namespace Antlr.Runtime
34{
35    using System.Collections.Generic;
36
37    using ArgumentException = System.ArgumentException;
38    using Console = System.Console;
39    using Math = System.Math;
40    using DebuggerDisplay = System.Diagnostics.DebuggerDisplayAttribute;
41    using Exception = System.Exception;
42    using StringBuilder = System.Text.StringBuilder;
43    using Type = System.Type;
44
45    /** Useful for dumping out the input stream after doing some
46     *  augmentation or other manipulations.
47     *
48     *  You can insert stuff, replace, and delete chunks.  Note that the
49     *  operations are done lazily--only if you convert the buffer to a
50     *  String.  This is very efficient because you are not moving data around
51     *  all the time.  As the buffer of tokens is converted to strings, the
52     *  toString() method(s) check to see if there is an operation at the
53     *  current index.  If so, the operation is done and then normal String
54     *  rendering continues on the buffer.  This is like having multiple Turing
55     *  machine instruction streams (programs) operating on a single input tape. :)
56     *
57     *  Since the operations are done lazily at toString-time, operations do not
58     *  screw up the token index values.  That is, an insert operation at token
59     *  index i does not change the index values for tokens i+1..n-1.
60     *
61     *  Because operations never actually alter the buffer, you may always get
62     *  the original token stream back without undoing anything.  Since
63     *  the instructions are queued up, you can easily simulate transactions and
64     *  roll back any changes if there is an error just by removing instructions.
65     *  For example,
66     *
67     *   CharStream input = new ANTLRFileStream("input");
68     *   TLexer lex = new TLexer(input);
69     *   TokenRewriteStream tokens = new TokenRewriteStream(lex);
70     *   T parser = new T(tokens);
71     *   parser.startRule();
72     *
73     * 	 Then in the rules, you can execute
74     *      Token t,u;
75     *      ...
76     *      input.insertAfter(t, "text to put after t");}
77     * 		input.insertAfter(u, "text after u");}
78     * 		System.out.println(tokens.toString());
79     *
80     *  Actually, you have to cast the 'input' to a TokenRewriteStream. :(
81     *
82     *  You can also have multiple "instruction streams" and get multiple
83     *  rewrites from a single pass over the input.  Just name the instruction
84     *  streams and use that name again when printing the buffer.  This could be
85     *  useful for generating a C file and also its header file--all from the
86     *  same buffer:
87     *
88     *      tokens.insertAfter("pass1", t, "text to put after t");}
89     * 		tokens.insertAfter("pass2", u, "text after u");}
90     * 		System.out.println(tokens.toString("pass1"));
91     * 		System.out.println(tokens.toString("pass2"));
92     *
93     *  If you don't use named rewrite streams, a "default" stream is used as
94     *  the first example shows.
95     */
96    [System.Serializable]
97    [DebuggerDisplay( "TODO: TokenRewriteStream debugger display" )]
98    public class TokenRewriteStream : CommonTokenStream
99    {
100        public const string DEFAULT_PROGRAM_NAME = "default";
101        public const int PROGRAM_INIT_SIZE = 100;
102        public const int MIN_TOKEN_INDEX = 0;
103
104        // Define the rewrite operation hierarchy
105
106        protected class RewriteOperation
107        {
108            /** <summary>What index into rewrites List are we?</summary> */
109            public int instructionIndex;
110            /** <summary>Token buffer index.</summary> */
111            public int index;
112            public object text;
113            // outer
114            protected TokenRewriteStream stream;
115
116            protected RewriteOperation(TokenRewriteStream stream, int index)
117            {
118                this.stream = stream;
119                this.index = index;
120            }
121
122            protected RewriteOperation( TokenRewriteStream stream, int index, object text )
123            {
124                this.index = index;
125                this.text = text;
126                this.stream = stream;
127            }
128
129            /** <summary>
130             *  Execute the rewrite operation by possibly adding to the buffer.
131             *  Return the index of the next token to operate on.
132             *  </summary>
133             */
134            public virtual int Execute( StringBuilder buf )
135            {
136                return index;
137            }
138
139            public override string ToString()
140            {
141                string opName = this.GetType().Name;
142                int dindex = opName.IndexOf( '$' );
143                opName = opName.Substring( dindex + 1 );
144                return string.Format("<{0}@{1}:\"{2}\">", opName, stream._tokens[index], text);
145            }
146        }
147
148        private class InsertBeforeOp : RewriteOperation
149        {
150            public InsertBeforeOp( TokenRewriteStream stream, int index, object text ) :
151                base( stream, index, text )
152            {
153            }
154
155            public override int Execute( StringBuilder buf )
156            {
157                buf.Append( text );
158                if (stream._tokens[index].Type != CharStreamConstants.EndOfFile)
159                    buf.Append(stream._tokens[index].Text);
160                return index + 1;
161            }
162        }
163
164        /** <summary>
165         *  I'm going to try replacing range from x..y with (y-x)+1 ReplaceOp
166         *  instructions.
167         *  </summary>
168         */
169        private class ReplaceOp : RewriteOperation
170        {
171            public int lastIndex;
172            public ReplaceOp( TokenRewriteStream stream, int from, int to, object text )
173                : base( stream, from, text )
174            {
175                lastIndex = to;
176            }
177
178            public override int Execute( StringBuilder buf )
179            {
180                if ( text != null )
181                {
182                    buf.Append( text );
183                }
184                return lastIndex + 1;
185            }
186
187            public override string ToString()
188            {
189                if (text == null)
190                {
191                    return string.Format("<DeleteOp@{0}..{1}>", stream._tokens[index], stream._tokens[lastIndex]);
192                }
193
194                return string.Format("<ReplaceOp@{0}..{1}:\"{2}\">", stream._tokens[index], stream._tokens[lastIndex], text);
195            }
196        }
197
198        /** <summary>
199         *  You may have multiple, named streams of rewrite operations.
200         *  I'm calling these things "programs."
201         *  Maps String (name) -> rewrite (List)
202         *  </summary>
203         */
204        protected IDictionary<string, IList<RewriteOperation>> programs = null;
205
206        /** <summary>Map String (program name) -> Integer index</summary> */
207        protected IDictionary<string, int> lastRewriteTokenIndexes = null;
208
209        public TokenRewriteStream()
210        {
211            Init();
212        }
213
214        protected void Init()
215        {
216            programs = new Dictionary<string, IList<RewriteOperation>>();
217            programs[DEFAULT_PROGRAM_NAME] = new List<RewriteOperation>( PROGRAM_INIT_SIZE );
218            lastRewriteTokenIndexes = new Dictionary<string, int>();
219        }
220
221        public TokenRewriteStream( ITokenSource tokenSource )
222            : base( tokenSource )
223        {
224            Init();
225        }
226
227        public TokenRewriteStream( ITokenSource tokenSource, int channel )
228            : base( tokenSource, channel )
229        {
230            Init();
231        }
232
233        public virtual void Rollback( int instructionIndex )
234        {
235            Rollback( DEFAULT_PROGRAM_NAME, instructionIndex );
236        }
237
238        /** <summary>
239         *  Rollback the instruction stream for a program so that
240         *  the indicated instruction (via instructionIndex) is no
241         *  longer in the stream.  UNTESTED!
242         *  </summary>
243         */
244        public virtual void Rollback( string programName, int instructionIndex )
245        {
246            IList<RewriteOperation> @is;
247            if ( programs.TryGetValue( programName, out @is ) && @is != null )
248            {
249                List<RewriteOperation> sublist = new List<RewriteOperation>();
250                for ( int i = MIN_TOKEN_INDEX; i <= instructionIndex; i++ )
251                    sublist.Add( @is[i] );
252
253                programs[programName] = sublist;
254            }
255        }
256
257        public virtual void DeleteProgram()
258        {
259            DeleteProgram( DEFAULT_PROGRAM_NAME );
260        }
261
262        /** <summary>Reset the program so that no instructions exist</summary> */
263        public virtual void DeleteProgram( string programName )
264        {
265            Rollback( programName, MIN_TOKEN_INDEX );
266        }
267
268        public virtual void InsertAfter( IToken t, object text )
269        {
270            InsertAfter( DEFAULT_PROGRAM_NAME, t, text );
271        }
272
273        public virtual void InsertAfter( int index, object text )
274        {
275            InsertAfter( DEFAULT_PROGRAM_NAME, index, text );
276        }
277
278        public virtual void InsertAfter( string programName, IToken t, object text )
279        {
280            InsertAfter( programName, t.TokenIndex, text );
281        }
282
283        public virtual void InsertAfter( string programName, int index, object text )
284        {
285            // to insert after, just insert before next index (even if past end)
286            InsertBefore( programName, index + 1, text );
287        }
288
289        public virtual void InsertBefore( IToken t, object text )
290        {
291            InsertBefore( DEFAULT_PROGRAM_NAME, t, text );
292        }
293
294        public virtual void InsertBefore( int index, object text )
295        {
296            InsertBefore( DEFAULT_PROGRAM_NAME, index, text );
297        }
298
299        public virtual void InsertBefore( string programName, IToken t, object text )
300        {
301            InsertBefore( programName, t.TokenIndex, text );
302        }
303
304        public virtual void InsertBefore( string programName, int index, object text )
305        {
306            RewriteOperation op = new InsertBeforeOp( this, index, text );
307            IList<RewriteOperation> rewrites = GetProgram( programName );
308            op.instructionIndex = rewrites.Count;
309            rewrites.Add( op );
310        }
311
312        public virtual void Replace( int index, object text )
313        {
314            Replace( DEFAULT_PROGRAM_NAME, index, index, text );
315        }
316
317        public virtual void Replace( int from, int to, object text )
318        {
319            Replace( DEFAULT_PROGRAM_NAME, from, to, text );
320        }
321
322        public virtual void Replace( IToken indexT, object text )
323        {
324            Replace( DEFAULT_PROGRAM_NAME, indexT, indexT, text );
325        }
326
327        public virtual void Replace( IToken from, IToken to, object text )
328        {
329            Replace( DEFAULT_PROGRAM_NAME, from, to, text );
330        }
331
332        public virtual void Replace( string programName, int from, int to, object text )
333        {
334            if ( from > to || from < 0 || to < 0 || to >= _tokens.Count )
335            {
336                throw new ArgumentException( "replace: range invalid: " + from + ".." + to + "(size=" + _tokens.Count + ")" );
337            }
338            RewriteOperation op = new ReplaceOp( this, from, to, text );
339            IList<RewriteOperation> rewrites = GetProgram( programName );
340            op.instructionIndex = rewrites.Count;
341            rewrites.Add( op );
342        }
343
344        public virtual void Replace( string programName, IToken from, IToken to, object text )
345        {
346            Replace( programName,
347                    from.TokenIndex,
348                    to.TokenIndex,
349                    text );
350        }
351
352        public virtual void Delete( int index )
353        {
354            Delete( DEFAULT_PROGRAM_NAME, index, index );
355        }
356
357        public virtual void Delete( int from, int to )
358        {
359            Delete( DEFAULT_PROGRAM_NAME, from, to );
360        }
361
362        public virtual void Delete( IToken indexT )
363        {
364            Delete( DEFAULT_PROGRAM_NAME, indexT, indexT );
365        }
366
367        public virtual void Delete( IToken from, IToken to )
368        {
369            Delete( DEFAULT_PROGRAM_NAME, from, to );
370        }
371
372        public virtual void Delete( string programName, int from, int to )
373        {
374            Replace( programName, from, to, null );
375        }
376
377        public virtual void Delete( string programName, IToken from, IToken to )
378        {
379            Replace( programName, from, to, null );
380        }
381
382        public virtual int GetLastRewriteTokenIndex()
383        {
384            return GetLastRewriteTokenIndex( DEFAULT_PROGRAM_NAME );
385        }
386
387        protected virtual int GetLastRewriteTokenIndex( string programName )
388        {
389            int value;
390            if ( lastRewriteTokenIndexes.TryGetValue( programName, out value ) )
391                return value;
392
393            return -1;
394        }
395
396        protected virtual void SetLastRewriteTokenIndex( string programName, int i )
397        {
398            lastRewriteTokenIndexes[programName] = i;
399        }
400
401        protected virtual IList<RewriteOperation> GetProgram( string name )
402        {
403            IList<RewriteOperation> @is;
404            if ( !programs.TryGetValue( name, out @is ) || @is == null )
405            {
406                @is = InitializeProgram( name );
407            }
408            return @is;
409        }
410
411        private IList<RewriteOperation> InitializeProgram( string name )
412        {
413            IList<RewriteOperation> @is = new List<RewriteOperation>( PROGRAM_INIT_SIZE );
414            programs[name] = @is;
415            return @is;
416        }
417
418        public virtual string ToOriginalString()
419        {
420            Fill();
421            return ToOriginalString( MIN_TOKEN_INDEX, Count - 1 );
422        }
423
424        public virtual string ToOriginalString( int start, int end )
425        {
426            StringBuilder buf = new StringBuilder();
427            for ( int i = start; i >= MIN_TOKEN_INDEX && i <= end && i < _tokens.Count; i++ )
428            {
429                if (Get(i).Type != CharStreamConstants.EndOfFile)
430                    buf.Append(Get(i).Text);
431            }
432            return buf.ToString();
433        }
434
435        public override string ToString()
436        {
437            Fill();
438            return ToString( MIN_TOKEN_INDEX, Count - 1 );
439        }
440
441        public virtual string ToString( string programName )
442        {
443            Fill();
444            return ToString(programName, MIN_TOKEN_INDEX, Count - 1);
445        }
446
447        public override string ToString( int start, int end )
448        {
449            return ToString( DEFAULT_PROGRAM_NAME, start, end );
450        }
451
452        public virtual string ToString( string programName, int start, int end )
453        {
454            IList<RewriteOperation> rewrites;
455            if ( !programs.TryGetValue( programName, out rewrites ) )
456                rewrites = null;
457
458            // ensure start/end are in range
459            if ( end > _tokens.Count - 1 )
460                end = _tokens.Count - 1;
461            if ( start < 0 )
462                start = 0;
463
464            if ( rewrites == null || rewrites.Count == 0 )
465            {
466                return ToOriginalString( start, end ); // no instructions to execute
467            }
468            StringBuilder buf = new StringBuilder();
469
470            // First, optimize instruction stream
471            IDictionary<int, RewriteOperation> indexToOp = ReduceToSingleOperationPerIndex( rewrites );
472
473            // Walk buffer, executing instructions and emitting tokens
474            int i = start;
475            while ( i <= end && i < _tokens.Count )
476            {
477                RewriteOperation op;
478                bool exists = indexToOp.TryGetValue( i, out op );
479
480                if ( exists )
481                {
482                    // remove so any left have index size-1
483                    indexToOp.Remove( i );
484                }
485
486                if ( !exists || op == null )
487                {
488                    IToken t = _tokens[i];
489                    // no operation at that index, just dump token
490                    if (t.Type != CharStreamConstants.EndOfFile)
491                        buf.Append(t.Text);
492                    i++; // move to next token
493                }
494                else
495                {
496                    i = op.Execute( buf ); // execute operation and skip
497                }
498            }
499
500            // include stuff after end if it's last index in buffer
501            // So, if they did an insertAfter(lastValidIndex, "foo"), include
502            // foo if end==lastValidIndex.
503            if ( end == _tokens.Count - 1 )
504            {
505                // Scan any remaining operations after last token
506                // should be included (they will be inserts).
507                foreach ( RewriteOperation op in indexToOp.Values )
508                {
509                    if ( op.index >= _tokens.Count - 1 )
510                        buf.Append( op.text );
511                }
512            }
513            return buf.ToString();
514        }
515
516        /** We need to combine operations and report invalid operations (like
517         *  overlapping replaces that are not completed nested).  Inserts to
518         *  same index need to be combined etc...   Here are the cases:
519         *
520         *  I.i.u I.j.v								leave alone, nonoverlapping
521         *  I.i.u I.i.v								combine: Iivu
522         *
523         *  R.i-j.u R.x-y.v	| i-j in x-y			delete first R
524         *  R.i-j.u R.i-j.v							delete first R
525         *  R.i-j.u R.x-y.v	| x-y in i-j			ERROR
526         *  R.i-j.u R.x-y.v	| boundaries overlap	ERROR
527         *
528         *  Delete special case of replace (text==null):
529         *  D.i-j.u D.x-y.v	| boundaries overlap	combine to max(min)..max(right)
530         *
531         *  I.i.u R.x-y.v | i in (x+1)-y			delete I (since insert before
532         *											we're not deleting i)
533         *  I.i.u R.x-y.v | i not in (x+1)-y		leave alone, nonoverlapping
534         *  R.x-y.v I.i.u | i in x-y				ERROR
535         *  R.x-y.v I.x.u 							R.x-y.uv (combine, delete I)
536         *  R.x-y.v I.i.u | i not in x-y			leave alone, nonoverlapping
537         *
538         *  I.i.u = insert u before op @ index i
539         *  R.x-y.u = replace x-y indexed tokens with u
540         *
541         *  First we need to examine replaces.  For any replace op:
542         *
543         * 		1. wipe out any insertions before op within that range.
544         *		2. Drop any replace op before that is contained completely within
545         *         that range.
546         *		3. Throw exception upon boundary overlap with any previous replace.
547         *
548         *  Then we can deal with inserts:
549         *
550         * 		1. for any inserts to same index, combine even if not adjacent.
551         * 		2. for any prior replace with same left boundary, combine this
552         *         insert with replace and delete this replace.
553         * 		3. throw exception if index in same range as previous replace
554         *
555         *  Don't actually delete; make op null in list. Easier to walk list.
556         *  Later we can throw as we add to index -> op map.
557         *
558         *  Note that I.2 R.2-2 will wipe out I.2 even though, technically, the
559         *  inserted stuff would be before the replace range.  But, if you
560         *  add tokens in front of a method body '{' and then delete the method
561         *  body, I think the stuff before the '{' you added should disappear too.
562         *
563         *  Return a map from token index to operation.
564         */
565        protected virtual IDictionary<int, RewriteOperation> ReduceToSingleOperationPerIndex( IList<RewriteOperation> rewrites )
566        {
567            //System.out.println("rewrites="+rewrites);
568
569            // WALK REPLACES
570            for ( int i = 0; i < rewrites.Count; i++ )
571            {
572                RewriteOperation op = rewrites[i];
573                if ( op == null )
574                    continue;
575                if ( !( op is ReplaceOp ) )
576                    continue;
577                ReplaceOp rop = (ReplaceOp)rewrites[i];
578                // Wipe prior inserts within range
579                var inserts = GetKindOfOps( rewrites, typeof( InsertBeforeOp ), i );
580                for ( int j = 0; j < inserts.Count; j++ )
581                {
582                    InsertBeforeOp iop = (InsertBeforeOp)inserts[j];
583                    if (iop.index == rop.index)
584                    {
585                        // E.g., insert before 2, delete 2..2; update replace
586                        // text to include insert before, kill insert
587                        rewrites[iop.instructionIndex] = null;
588                        rop.text = iop.text.ToString() + (rop.text != null ? rop.text.ToString() : string.Empty);
589                    }
590                    else if (iop.index > rop.index && iop.index <= rop.lastIndex)
591                    {
592                        // delete insert as it's a no-op.
593                        rewrites[iop.instructionIndex] = null;
594                    }
595                }
596                // Drop any prior replaces contained within
597                var prevReplaces = GetKindOfOps( rewrites, typeof( ReplaceOp ), i );
598                for ( int j = 0; j < prevReplaces.Count; j++ )
599                {
600                    ReplaceOp prevRop = (ReplaceOp)prevReplaces[j];
601                    if ( prevRop.index >= rop.index && prevRop.lastIndex <= rop.lastIndex )
602                    {
603                        // delete replace as it's a no-op.
604                        rewrites[prevRop.instructionIndex] = null;
605                        continue;
606                    }
607                    // throw exception unless disjoint or identical
608                    bool disjoint =
609                        prevRop.lastIndex < rop.index || prevRop.index > rop.lastIndex;
610                    bool same =
611                        prevRop.index == rop.index && prevRop.lastIndex == rop.lastIndex;
612                    // Delete special case of replace (text==null):
613                    // D.i-j.u D.x-y.v	| boundaries overlap	combine to max(min)..max(right)
614                    if (prevRop.text == null && rop.text == null && !disjoint)
615                    {
616                        //System.out.println("overlapping deletes: "+prevRop+", "+rop);
617                        rewrites[prevRop.instructionIndex] = null; // kill first delete
618                        rop.index = Math.Min(prevRop.index, rop.index);
619                        rop.lastIndex = Math.Max(prevRop.lastIndex, rop.lastIndex);
620                        Console.WriteLine("new rop " + rop);
621                    }
622                    else if ( !disjoint && !same )
623                    {
624                        throw new ArgumentException( "replace op boundaries of " + rop +
625                                                           " overlap with previous " + prevRop );
626                    }
627                }
628            }
629
630            // WALK INSERTS
631            for ( int i = 0; i < rewrites.Count; i++ )
632            {
633                RewriteOperation op = (RewriteOperation)rewrites[i];
634                if ( op == null )
635                    continue;
636                if ( !( op is InsertBeforeOp ) )
637                    continue;
638                InsertBeforeOp iop = (InsertBeforeOp)rewrites[i];
639                // combine current insert with prior if any at same index
640                var prevInserts = GetKindOfOps( rewrites, typeof( InsertBeforeOp ), i );
641                for ( int j = 0; j < prevInserts.Count; j++ )
642                {
643                    InsertBeforeOp prevIop = (InsertBeforeOp)prevInserts[j];
644                    if ( prevIop.index == iop.index )
645                    { // combine objects
646                        // convert to strings...we're in process of toString'ing
647                        // whole token buffer so no lazy eval issue with any templates
648                        iop.text = CatOpText( iop.text, prevIop.text );
649                        // delete redundant prior insert
650                        rewrites[prevIop.instructionIndex] = null;
651                    }
652                }
653                // look for replaces where iop.index is in range; error
654                var prevReplaces = GetKindOfOps( rewrites, typeof( ReplaceOp ), i );
655                for ( int j = 0; j < prevReplaces.Count; j++ )
656                {
657                    ReplaceOp rop = (ReplaceOp)prevReplaces[j];
658                    if ( iop.index == rop.index )
659                    {
660                        rop.text = CatOpText( iop.text, rop.text );
661                        rewrites[i] = null;  // delete current insert
662                        continue;
663                    }
664                    if ( iop.index >= rop.index && iop.index <= rop.lastIndex )
665                    {
666                        throw new ArgumentException( "insert op " + iop +
667                                                           " within boundaries of previous " + rop );
668                    }
669                }
670            }
671            // System.out.println("rewrites after="+rewrites);
672            IDictionary<int, RewriteOperation> m = new Dictionary<int, RewriteOperation>();
673            for ( int i = 0; i < rewrites.Count; i++ )
674            {
675                RewriteOperation op = (RewriteOperation)rewrites[i];
676                if ( op == null )
677                    continue; // ignore deleted ops
678
679                RewriteOperation existing;
680                if ( m.TryGetValue( op.index, out existing ) && existing != null )
681                {
682                    throw new Exception( "should only be one op per index" );
683                }
684                m[op.index] = op;
685            }
686            //System.out.println("index to op: "+m);
687            return m;
688        }
689
690        protected virtual string CatOpText( object a, object b )
691        {
692            return string.Concat( a, b );
693        }
694        protected virtual IList<RewriteOperation> GetKindOfOps( IList<RewriteOperation> rewrites, Type kind )
695        {
696            return GetKindOfOps( rewrites, kind, rewrites.Count );
697        }
698
699        /** <summary>Get all operations before an index of a particular kind</summary> */
700        protected virtual IList<RewriteOperation> GetKindOfOps( IList<RewriteOperation> rewrites, Type kind, int before )
701        {
702            IList<RewriteOperation> ops = new List<RewriteOperation>();
703            for ( int i = 0; i < before && i < rewrites.Count; i++ )
704            {
705                RewriteOperation op = rewrites[i];
706                if ( op == null )
707                    continue; // ignore deleted
708                if ( op.GetType() == kind )
709                    ops.Add( op );
710            }
711            return ops;
712        }
713
714        public virtual string ToDebugString()
715        {
716            return ToDebugString( MIN_TOKEN_INDEX, Count - 1 );
717        }
718
719        public virtual string ToDebugString( int start, int end )
720        {
721            StringBuilder buf = new StringBuilder();
722            for ( int i = start; i >= MIN_TOKEN_INDEX && i <= end && i < _tokens.Count; i++ )
723            {
724                buf.Append( Get( i ) );
725            }
726            return buf.ToString();
727        }
728    }
729}
730