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 
33 namespace 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 
RewriteOperation(TokenRewriteStream stream, int index)116             protected RewriteOperation(TokenRewriteStream stream, int index)
117             {
118                 this.stream = stream;
119                 this.index = index;
120             }
121 
RewriteOperation( TokenRewriteStream stream, int index, object text )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              */
Execute( StringBuilder buf )134             public virtual int Execute( StringBuilder buf )
135             {
136                 return index;
137             }
138 
ToString()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         {
InsertBeforeOp( TokenRewriteStream stream, int index, object text )150             public InsertBeforeOp( TokenRewriteStream stream, int index, object text ) :
151                 base( stream, index, text )
152             {
153             }
154 
Execute( StringBuilder buf )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;
ReplaceOp( TokenRewriteStream stream, int from, int to, object text )172             public ReplaceOp( TokenRewriteStream stream, int from, int to, object text )
173                 : base( stream, from, text )
174             {
175                 lastIndex = to;
176             }
177 
Execute( StringBuilder buf )178             public override int Execute( StringBuilder buf )
179             {
180                 if ( text != null )
181                 {
182                     buf.Append( text );
183                 }
184                 return lastIndex + 1;
185             }
186 
ToString()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 
TokenRewriteStream()209         public TokenRewriteStream()
210         {
211             Init();
212         }
213 
Init()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 
TokenRewriteStream( ITokenSource tokenSource )221         public TokenRewriteStream( ITokenSource tokenSource )
222             : base( tokenSource )
223         {
224             Init();
225         }
226 
TokenRewriteStream( ITokenSource tokenSource, int channel )227         public TokenRewriteStream( ITokenSource tokenSource, int channel )
228             : base( tokenSource, channel )
229         {
230             Init();
231         }
232 
Rollback( int instructionIndex )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          */
Rollback( string programName, int instructionIndex )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 
DeleteProgram()257         public virtual void DeleteProgram()
258         {
259             DeleteProgram( DEFAULT_PROGRAM_NAME );
260         }
261 
262         /** <summary>Reset the program so that no instructions exist</summary> */
DeleteProgram( string programName )263         public virtual void DeleteProgram( string programName )
264         {
265             Rollback( programName, MIN_TOKEN_INDEX );
266         }
267 
InsertAfter( IToken t, object text )268         public virtual void InsertAfter( IToken t, object text )
269         {
270             InsertAfter( DEFAULT_PROGRAM_NAME, t, text );
271         }
272 
InsertAfter( int index, object text )273         public virtual void InsertAfter( int index, object text )
274         {
275             InsertAfter( DEFAULT_PROGRAM_NAME, index, text );
276         }
277 
InsertAfter( string programName, IToken t, object text )278         public virtual void InsertAfter( string programName, IToken t, object text )
279         {
280             InsertAfter( programName, t.TokenIndex, text );
281         }
282 
InsertAfter( string programName, int index, object text )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 
InsertBefore( IToken t, object text )289         public virtual void InsertBefore( IToken t, object text )
290         {
291             InsertBefore( DEFAULT_PROGRAM_NAME, t, text );
292         }
293 
InsertBefore( int index, object text )294         public virtual void InsertBefore( int index, object text )
295         {
296             InsertBefore( DEFAULT_PROGRAM_NAME, index, text );
297         }
298 
InsertBefore( string programName, IToken t, object text )299         public virtual void InsertBefore( string programName, IToken t, object text )
300         {
301             InsertBefore( programName, t.TokenIndex, text );
302         }
303 
InsertBefore( string programName, int index, object text )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 
Replace( int index, object text )312         public virtual void Replace( int index, object text )
313         {
314             Replace( DEFAULT_PROGRAM_NAME, index, index, text );
315         }
316 
Replace( int from, int to, object text )317         public virtual void Replace( int from, int to, object text )
318         {
319             Replace( DEFAULT_PROGRAM_NAME, from, to, text );
320         }
321 
Replace( IToken indexT, object text )322         public virtual void Replace( IToken indexT, object text )
323         {
324             Replace( DEFAULT_PROGRAM_NAME, indexT, indexT, text );
325         }
326 
Replace( IToken from, IToken to, object text )327         public virtual void Replace( IToken from, IToken to, object text )
328         {
329             Replace( DEFAULT_PROGRAM_NAME, from, to, text );
330         }
331 
Replace( string programName, int from, int to, object text )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 
Replace( string programName, IToken from, IToken to, object text )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 
Delete( int index )352         public virtual void Delete( int index )
353         {
354             Delete( DEFAULT_PROGRAM_NAME, index, index );
355         }
356 
Delete( int from, int to )357         public virtual void Delete( int from, int to )
358         {
359             Delete( DEFAULT_PROGRAM_NAME, from, to );
360         }
361 
Delete( IToken indexT )362         public virtual void Delete( IToken indexT )
363         {
364             Delete( DEFAULT_PROGRAM_NAME, indexT, indexT );
365         }
366 
Delete( IToken from, IToken to )367         public virtual void Delete( IToken from, IToken to )
368         {
369             Delete( DEFAULT_PROGRAM_NAME, from, to );
370         }
371 
Delete( string programName, int from, int to )372         public virtual void Delete( string programName, int from, int to )
373         {
374             Replace( programName, from, to, null );
375         }
376 
Delete( string programName, IToken from, IToken to )377         public virtual void Delete( string programName, IToken from, IToken to )
378         {
379             Replace( programName, from, to, null );
380         }
381 
GetLastRewriteTokenIndex()382         public virtual int GetLastRewriteTokenIndex()
383         {
384             return GetLastRewriteTokenIndex( DEFAULT_PROGRAM_NAME );
385         }
386 
GetLastRewriteTokenIndex( string programName )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 
SetLastRewriteTokenIndex( string programName, int i )396         protected virtual void SetLastRewriteTokenIndex( string programName, int i )
397         {
398             lastRewriteTokenIndexes[programName] = i;
399         }
400 
GetProgram( string name )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 
InitializeProgram( string name )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 
ToOriginalString()418         public virtual string ToOriginalString()
419         {
420             Fill();
421             return ToOriginalString( MIN_TOKEN_INDEX, Count - 1 );
422         }
423 
ToOriginalString( int start, int end )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 
ToString()435         public override string ToString()
436         {
437             Fill();
438             return ToString( MIN_TOKEN_INDEX, Count - 1 );
439         }
440 
ToString( string programName )441         public virtual string ToString( string programName )
442         {
443             Fill();
444             return ToString(programName, MIN_TOKEN_INDEX, Count - 1);
445         }
446 
ToString( int start, int end )447         public override string ToString( int start, int end )
448         {
449             return ToString( DEFAULT_PROGRAM_NAME, start, end );
450         }
451 
ToString( string programName, int start, int end )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          */
ReduceToSingleOperationPerIndex( IList<RewriteOperation> rewrites )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 
CatOpText( object a, object b )690         protected virtual string CatOpText( object a, object b )
691         {
692             return string.Concat( a, b );
693         }
GetKindOfOps( IList<RewriteOperation> rewrites, Type kind )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> */
GetKindOfOps( IList<RewriteOperation> rewrites, Type kind, int before )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 
ToDebugString()714         public virtual string ToDebugString()
715         {
716             return ToDebugString( MIN_TOKEN_INDEX, Count - 1 );
717         }
718 
ToDebugString( int start, int end )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