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     using System.Collections.Generic;
35 
36     using ArgumentException = System.ArgumentException;
37     using DebuggerDisplay = System.Diagnostics.DebuggerDisplayAttribute;
38     using Exception = System.Exception;
39     using StringBuilder = System.Text.StringBuilder;
40     using Type = System.Type;
41 
42     /** Useful for dumping out the input stream after doing some
43      *  augmentation or other manipulations.
44      *
45      *  You can insert stuff, replace, and delete chunks.  Note that the
46      *  operations are done lazily--only if you convert the buffer to a
47      *  String.  This is very efficient because you are not moving data around
48      *  all the time.  As the buffer of tokens is converted to strings, the
49      *  toString() method(s) check to see if there is an operation at the
50      *  current index.  If so, the operation is done and then normal String
51      *  rendering continues on the buffer.  This is like having multiple Turing
52      *  machine instruction streams (programs) operating on a single input tape. :)
53      *
54      *  Since the operations are done lazily at toString-time, operations do not
55      *  screw up the token index values.  That is, an insert operation at token
56      *  index i does not change the index values for tokens i+1..n-1.
57      *
58      *  Because operations never actually alter the buffer, you may always get
59      *  the original token stream back without undoing anything.  Since
60      *  the instructions are queued up, you can easily simulate transactions and
61      *  roll back any changes if there is an error just by removing instructions.
62      *  For example,
63      *
64      *   CharStream input = new ANTLRFileStream("input");
65      *   TLexer lex = new TLexer(input);
66      *   TokenRewriteStream tokens = new TokenRewriteStream(lex);
67      *   T parser = new T(tokens);
68      *   parser.startRule();
69      *
70      * 	 Then in the rules, you can execute
71      *      Token t,u;
72      *      ...
73      *      input.insertAfter(t, "text to put after t");}
74      * 		input.insertAfter(u, "text after u");}
75      * 		System.out.println(tokens.toString());
76      *
77      *  Actually, you have to cast the 'input' to a TokenRewriteStream. :(
78      *
79      *  You can also have multiple "instruction streams" and get multiple
80      *  rewrites from a single pass over the input.  Just name the instruction
81      *  streams and use that name again when printing the buffer.  This could be
82      *  useful for generating a C file and also its header file--all from the
83      *  same buffer:
84      *
85      *      tokens.insertAfter("pass1", t, "text to put after t");}
86      * 		tokens.insertAfter("pass2", u, "text after u");}
87      * 		System.out.println(tokens.toString("pass1"));
88      * 		System.out.println(tokens.toString("pass2"));
89      *
90      *  If you don't use named rewrite streams, a "default" stream is used as
91      *  the first example shows.
92      */
93     [System.Serializable]
94     [DebuggerDisplay("TODO: TokenRewriteStream debugger display")]
95     public class TokenRewriteStream : CommonTokenStream {
96         public const string DEFAULT_PROGRAM_NAME = "default";
97         public const int PROGRAM_INIT_SIZE = 100;
98         public const int MIN_TOKEN_INDEX = 0;
99 
100         // Define the rewrite operation hierarchy
101 
102         protected class RewriteOperation {
103             /** <summary>What index into rewrites List are we?</summary> */
104             public int instructionIndex;
105             /** <summary>Token buffer index.</summary> */
106             public int index;
107             public object text;
108             // outer
109             protected TokenRewriteStream stream;
110 
RewriteOperation(TokenRewriteStream stream, int index, object text)111             protected RewriteOperation(TokenRewriteStream stream, int index, object text) {
112                 this.index = index;
113                 this.text = text;
114                 this.stream = stream;
115             }
116             /** <summary>
117              *  Execute the rewrite operation by possibly adding to the buffer.
118              *  Return the index of the next token to operate on.
119              *  </summary>
120              */
Execute(StringBuilder buf)121             public virtual int Execute(StringBuilder buf) {
122                 return index;
123             }
ToString()124             public override string ToString() {
125                 string opName = this.GetType().Name;
126                 int index = opName.IndexOf('$');
127                 opName = opName.Substring(index + 1);
128                 return "<" + opName + "@" + this.index + ":\"" + text + "\">";
129             }
130         }
131 
132         class InsertBeforeOp : RewriteOperation {
InsertBeforeOp(TokenRewriteStream stream, int index, object text)133             public InsertBeforeOp(TokenRewriteStream stream, int index, object text) :
134                 base(stream, index, text) {
135             }
136 
Execute(StringBuilder buf)137             public override int Execute(StringBuilder buf) {
138                 buf.Append(text);
139                 if (stream._tokens[index].Type != CharStreamConstants.EndOfFile)
140                     buf.Append(stream._tokens[index].Text);
141                 return index + 1;
142             }
143         }
144 
145         /** <summary>
146          *  I'm going to try replacing range from x..y with (y-x)+1 ReplaceOp
147          *  instructions.
148          *  </summary>
149          */
150         class ReplaceOp : RewriteOperation {
151             public int lastIndex;
ReplaceOp(TokenRewriteStream stream, int from, int to, object text)152             public ReplaceOp(TokenRewriteStream stream, int from, int to, object text)
153                 : base(stream, from, text) {
154                 lastIndex = to;
155             }
Execute(StringBuilder buf)156             public override int Execute(StringBuilder buf) {
157                 if (text != null) {
158                     buf.Append(text);
159                 }
160                 return lastIndex + 1;
161             }
ToString()162             public override string ToString() {
163                 return "<ReplaceOp@" + index + ".." + lastIndex + ":\"" + text + "\">";
164             }
165         }
166 
167         class DeleteOp : ReplaceOp {
DeleteOp(TokenRewriteStream stream, int from, int to)168             public DeleteOp(TokenRewriteStream stream, int from, int to) :
169                 base(stream, from, to, null) {
170             }
ToString()171             public override string ToString() {
172                 return "<DeleteOp@" + index + ".." + lastIndex + ">";
173             }
174         }
175 
176         /** <summary>
177          *  You may have multiple, named streams of rewrite operations.
178          *  I'm calling these things "programs."
179          *  Maps String (name) -> rewrite (List)
180          *  </summary>
181          */
182         protected IDictionary<string, IList<RewriteOperation>> programs = null;
183 
184         /** <summary>Map String (program name) -> Integer index</summary> */
185         protected IDictionary<string, int> lastRewriteTokenIndexes = null;
186 
TokenRewriteStream()187         public TokenRewriteStream() {
188             Init();
189         }
190 
Init()191         protected void Init() {
192             programs = new Dictionary<string, IList<RewriteOperation>>();
193             programs[DEFAULT_PROGRAM_NAME] = new List<RewriteOperation>(PROGRAM_INIT_SIZE);
194             lastRewriteTokenIndexes = new Dictionary<string, int>();
195         }
196 
TokenRewriteStream(ITokenSource tokenSource)197         public TokenRewriteStream(ITokenSource tokenSource)
198             : base(tokenSource) {
199             Init();
200         }
201 
TokenRewriteStream(ITokenSource tokenSource, int channel)202         public TokenRewriteStream(ITokenSource tokenSource, int channel)
203             : base(tokenSource, channel) {
204             Init();
205         }
206 
Rollback(int instructionIndex)207         public virtual void Rollback(int instructionIndex) {
208             Rollback(DEFAULT_PROGRAM_NAME, instructionIndex);
209         }
210 
211         /** <summary>
212          *  Rollback the instruction stream for a program so that
213          *  the indicated instruction (via instructionIndex) is no
214          *  longer in the stream.  UNTESTED!
215          *  </summary>
216          */
Rollback(string programName, int instructionIndex)217         public virtual void Rollback(string programName, int instructionIndex) {
218             IList<RewriteOperation> @is;
219             if (programs.TryGetValue(programName, out @is) && @is != null) {
220                 List<RewriteOperation> sublist = new List<RewriteOperation>();
221                 for (int i = MIN_TOKEN_INDEX; i <= instructionIndex; i++)
222                     sublist.Add(@is[i]);
223 
224                 programs[programName] = sublist;
225             }
226         }
227 
DeleteProgram()228         public virtual void DeleteProgram() {
229             DeleteProgram(DEFAULT_PROGRAM_NAME);
230         }
231 
232         /** <summary>Reset the program so that no instructions exist</summary> */
DeleteProgram(string programName)233         public virtual void DeleteProgram(string programName) {
234             Rollback(programName, MIN_TOKEN_INDEX);
235         }
236 
InsertAfter(IToken t, object text)237         public virtual void InsertAfter(IToken t, object text) {
238             InsertAfter(DEFAULT_PROGRAM_NAME, t, text);
239         }
240 
InsertAfter(int index, object text)241         public virtual void InsertAfter(int index, object text) {
242             InsertAfter(DEFAULT_PROGRAM_NAME, index, text);
243         }
244 
InsertAfter(string programName, IToken t, object text)245         public virtual void InsertAfter(string programName, IToken t, object text) {
246             InsertAfter(programName, t.TokenIndex, text);
247         }
248 
InsertAfter(string programName, int index, object text)249         public virtual void InsertAfter(string programName, int index, object text) {
250             // to insert after, just insert before next index (even if past end)
251             InsertBefore(programName, index + 1, text);
252             //addToSortedRewriteList(programName, new InsertAfterOp(index,text));
253         }
254 
InsertBefore(IToken t, object text)255         public virtual void InsertBefore(IToken t, object text) {
256             InsertBefore(DEFAULT_PROGRAM_NAME, t, text);
257         }
258 
InsertBefore(int index, object text)259         public virtual void InsertBefore(int index, object text) {
260             InsertBefore(DEFAULT_PROGRAM_NAME, index, text);
261         }
262 
InsertBefore(string programName, IToken t, object text)263         public virtual void InsertBefore(string programName, IToken t, object text) {
264             InsertBefore(programName, t.TokenIndex, text);
265         }
266 
InsertBefore(string programName, int index, object text)267         public virtual void InsertBefore(string programName, int index, object text) {
268             //addToSortedRewriteList(programName, new InsertBeforeOp(index,text));
269             RewriteOperation op = new InsertBeforeOp(this, index, text);
270             IList<RewriteOperation> rewrites = GetProgram(programName);
271             op.instructionIndex = rewrites.Count;
272             rewrites.Add(op);
273         }
274 
Replace(int index, object text)275         public virtual void Replace(int index, object text) {
276             Replace(DEFAULT_PROGRAM_NAME, index, index, text);
277         }
278 
Replace(int from, int to, object text)279         public virtual void Replace(int from, int to, object text) {
280             Replace(DEFAULT_PROGRAM_NAME, from, to, text);
281         }
282 
Replace(IToken indexT, object text)283         public virtual void Replace(IToken indexT, object text) {
284             Replace(DEFAULT_PROGRAM_NAME, indexT, indexT, text);
285         }
286 
Replace(IToken from, IToken to, object text)287         public virtual void Replace(IToken from, IToken to, object text) {
288             Replace(DEFAULT_PROGRAM_NAME, from, to, text);
289         }
290 
Replace(string programName, int from, int to, object text)291         public virtual void Replace(string programName, int from, int to, object text) {
292             if (from > to || from < 0 || to < 0 || to >= _tokens.Count) {
293                 throw new ArgumentException("replace: range invalid: " + from + ".." + to + "(size=" + _tokens.Count + ")");
294             }
295             RewriteOperation op = new ReplaceOp(this, from, to, text);
296             IList<RewriteOperation> rewrites = GetProgram(programName);
297             op.instructionIndex = rewrites.Count;
298             rewrites.Add(op);
299         }
300 
Replace(string programName, IToken from, IToken to, object text)301         public virtual void Replace(string programName, IToken from, IToken to, object text) {
302             Replace(programName,
303                     from.TokenIndex,
304                     to.TokenIndex,
305                     text);
306         }
307 
Delete(int index)308         public virtual void Delete(int index) {
309             Delete(DEFAULT_PROGRAM_NAME, index, index);
310         }
311 
Delete(int from, int to)312         public virtual void Delete(int from, int to) {
313             Delete(DEFAULT_PROGRAM_NAME, from, to);
314         }
315 
Delete(IToken indexT)316         public virtual void Delete(IToken indexT) {
317             Delete(DEFAULT_PROGRAM_NAME, indexT, indexT);
318         }
319 
Delete(IToken from, IToken to)320         public virtual void Delete(IToken from, IToken to) {
321             Delete(DEFAULT_PROGRAM_NAME, from, to);
322         }
323 
Delete(string programName, int from, int to)324         public virtual void Delete(string programName, int from, int to) {
325             Replace(programName, from, to, null);
326         }
327 
Delete(string programName, IToken from, IToken to)328         public virtual void Delete(string programName, IToken from, IToken to) {
329             Replace(programName, from, to, null);
330         }
331 
GetLastRewriteTokenIndex()332         public virtual int GetLastRewriteTokenIndex() {
333             return GetLastRewriteTokenIndex(DEFAULT_PROGRAM_NAME);
334         }
335 
GetLastRewriteTokenIndex(string programName)336         protected virtual int GetLastRewriteTokenIndex(string programName) {
337             int value;
338             if (lastRewriteTokenIndexes.TryGetValue(programName, out value))
339                 return value;
340 
341             return -1;
342         }
343 
SetLastRewriteTokenIndex(string programName, int i)344         protected virtual void SetLastRewriteTokenIndex(string programName, int i) {
345             lastRewriteTokenIndexes[programName] = i;
346         }
347 
GetProgram(string name)348         protected virtual IList<RewriteOperation> GetProgram(string name) {
349             IList<RewriteOperation> @is;
350             if (!programs.TryGetValue(name, out @is) || @is == null) {
351                 @is = InitializeProgram(name);
352             }
353             return @is;
354         }
355 
InitializeProgram(string name)356         private IList<RewriteOperation> InitializeProgram(string name) {
357             IList<RewriteOperation> @is = new List<RewriteOperation>(PROGRAM_INIT_SIZE);
358             programs[name] = @is;
359             return @is;
360         }
361 
ToOriginalString()362         public virtual string ToOriginalString() {
363             Fill();
364             return ToOriginalString(MIN_TOKEN_INDEX, Count - 1);
365         }
366 
ToOriginalString(int start, int end)367         public virtual string ToOriginalString(int start, int end) {
368             StringBuilder buf = new StringBuilder();
369             for (int i = start; i >= MIN_TOKEN_INDEX && i <= end && i < _tokens.Count; i++) {
370                 if (Get(i).Type != CharStreamConstants.EndOfFile)
371                     buf.Append(Get(i).Text);
372             }
373             return buf.ToString();
374         }
375 
ToString()376         public override string ToString() {
377             Fill();
378             return ToString(MIN_TOKEN_INDEX, Count - 1);
379         }
380 
ToString(string programName)381         public virtual string ToString(string programName) {
382             Fill();
383             return ToString(programName, MIN_TOKEN_INDEX, Count - 1);
384         }
385 
ToString(int start, int end)386         public override string ToString(int start, int end) {
387             return ToString(DEFAULT_PROGRAM_NAME, start, end);
388         }
389 
ToString(string programName, int start, int end)390         public virtual string ToString(string programName, int start, int end) {
391             IList<RewriteOperation> rewrites;
392             if (!programs.TryGetValue(programName, out rewrites))
393                 rewrites = null;
394 
395             // ensure start/end are in range
396             if (end > _tokens.Count - 1)
397                 end = _tokens.Count - 1;
398             if (start < 0)
399                 start = 0;
400 
401             if (rewrites == null || rewrites.Count == 0) {
402                 return ToOriginalString(start, end); // no instructions to execute
403             }
404             StringBuilder buf = new StringBuilder();
405 
406             // First, optimize instruction stream
407             IDictionary<int, RewriteOperation> indexToOp = ReduceToSingleOperationPerIndex(rewrites);
408 
409             // Walk buffer, executing instructions and emitting tokens
410             int i = start;
411             while (i <= end && i < _tokens.Count) {
412                 RewriteOperation op;
413                 bool exists = indexToOp.TryGetValue(i, out op);
414 
415                 if (exists) {
416                     // remove so any left have index size-1
417                     indexToOp.Remove(i);
418                 }
419 
420                 if (!exists || op == null) {
421                     IToken t = _tokens[i];
422                     // no operation at that index, just dump token
423                     if (t.Type != CharStreamConstants.EndOfFile)
424                         buf.Append(t.Text);
425                     i++; // move to next token
426                 } else {
427                     i = op.Execute(buf); // execute operation and skip
428                 }
429             }
430 
431             // include stuff after end if it's last index in buffer
432             // So, if they did an insertAfter(lastValidIndex, "foo"), include
433             // foo if end==lastValidIndex.
434             if (end == _tokens.Count - 1) {
435                 // Scan any remaining operations after last token
436                 // should be included (they will be inserts).
437                 foreach (RewriteOperation op in indexToOp.Values) {
438                     if (op.index >= _tokens.Count - 1)
439                         buf.Append(op.text);
440                 }
441             }
442             return buf.ToString();
443         }
444 
445         /** We need to combine operations and report invalid operations (like
446          *  overlapping replaces that are not completed nested).  Inserts to
447          *  same index need to be combined etc...   Here are the cases:
448          *
449          *  I.i.u I.j.v								leave alone, nonoverlapping
450          *  I.i.u I.i.v								combine: Iivu
451          *
452          *  R.i-j.u R.x-y.v	| i-j in x-y			delete first R
453          *  R.i-j.u R.i-j.v							delete first R
454          *  R.i-j.u R.x-y.v	| x-y in i-j			ERROR
455          *  R.i-j.u R.x-y.v	| boundaries overlap	ERROR
456          *
457          *  I.i.u R.x-y.v | i in x-y				delete I
458          *  I.i.u R.x-y.v | i not in x-y			leave alone, nonoverlapping
459          *  R.x-y.v I.i.u | i in x-y				ERROR
460          *  R.x-y.v I.x.u 							R.x-y.uv (combine, delete I)
461          *  R.x-y.v I.i.u | i not in x-y			leave alone, nonoverlapping
462          *
463          *  I.i.u = insert u before op @ index i
464          *  R.x-y.u = replace x-y indexed tokens with u
465          *
466          *  First we need to examine replaces.  For any replace op:
467          *
468          * 		1. wipe out any insertions before op within that range.
469          *		2. Drop any replace op before that is contained completely within
470          *         that range.
471          *		3. Throw exception upon boundary overlap with any previous replace.
472          *
473          *  Then we can deal with inserts:
474          *
475          * 		1. for any inserts to same index, combine even if not adjacent.
476          * 		2. for any prior replace with same left boundary, combine this
477          *         insert with replace and delete this replace.
478          * 		3. throw exception if index in same range as previous replace
479          *
480          *  Don't actually delete; make op null in list. Easier to walk list.
481          *  Later we can throw as we add to index -> op map.
482          *
483          *  Note that I.2 R.2-2 will wipe out I.2 even though, technically, the
484          *  inserted stuff would be before the replace range.  But, if you
485          *  add tokens in front of a method body '{' and then delete the method
486          *  body, I think the stuff before the '{' you added should disappear too.
487          *
488          *  Return a map from token index to operation.
489          */
ReduceToSingleOperationPerIndex(IList<RewriteOperation> rewrites)490         protected virtual IDictionary<int, RewriteOperation> ReduceToSingleOperationPerIndex(IList<RewriteOperation> rewrites) {
491             //System.out.println("rewrites="+rewrites);
492 
493             // WALK REPLACES
494             for (int i = 0; i < rewrites.Count; i++) {
495                 RewriteOperation op = rewrites[i];
496                 if (op == null)
497                     continue;
498                 if (!(op is ReplaceOp))
499                     continue;
500                 ReplaceOp rop = (ReplaceOp)rewrites[i];
501                 // Wipe prior inserts within range
502                 var inserts = GetKindOfOps(rewrites, typeof(InsertBeforeOp), i);
503                 for (int j = 0; j < inserts.Count; j++) {
504                     InsertBeforeOp iop = (InsertBeforeOp)inserts[j];
505                     if (iop.index >= rop.index && iop.index <= rop.lastIndex) {
506                         // delete insert as it's a no-op.
507                         rewrites[iop.instructionIndex] = null;
508                     }
509                 }
510                 // Drop any prior replaces contained within
511                 var prevReplaces = GetKindOfOps(rewrites, typeof(ReplaceOp), i);
512                 for (int j = 0; j < prevReplaces.Count; j++) {
513                     ReplaceOp prevRop = (ReplaceOp)prevReplaces[j];
514                     if (prevRop.index >= rop.index && prevRop.lastIndex <= rop.lastIndex) {
515                         // delete replace as it's a no-op.
516                         rewrites[prevRop.instructionIndex] = null;
517                         continue;
518                     }
519                     // throw exception unless disjoint or identical
520                     bool disjoint =
521                         prevRop.lastIndex < rop.index || prevRop.index > rop.lastIndex;
522                     bool same =
523                         prevRop.index == rop.index && prevRop.lastIndex == rop.lastIndex;
524                     if (!disjoint && !same) {
525                         throw new ArgumentException("replace op boundaries of " + rop +
526                                                            " overlap with previous " + prevRop);
527                     }
528                 }
529             }
530 
531             // WALK INSERTS
532             for (int i = 0; i < rewrites.Count; i++) {
533                 RewriteOperation op = (RewriteOperation)rewrites[i];
534                 if (op == null)
535                     continue;
536                 if (!(op is InsertBeforeOp))
537                     continue;
538                 InsertBeforeOp iop = (InsertBeforeOp)rewrites[i];
539                 // combine current insert with prior if any at same index
540                 var prevInserts = GetKindOfOps(rewrites, typeof(InsertBeforeOp), i);
541                 for (int j = 0; j < prevInserts.Count; j++) {
542                     InsertBeforeOp prevIop = (InsertBeforeOp)prevInserts[j];
543                     if (prevIop.index == iop.index) { // combine objects
544                         // convert to strings...we're in process of toString'ing
545                         // whole token buffer so no lazy eval issue with any templates
546                         iop.text = CatOpText(iop.text, prevIop.text);
547                         // delete redundant prior insert
548                         rewrites[prevIop.instructionIndex] = null;
549                     }
550                 }
551                 // look for replaces where iop.index is in range; error
552                 var prevReplaces = GetKindOfOps(rewrites, typeof(ReplaceOp), i);
553                 for (int j = 0; j < prevReplaces.Count; j++) {
554                     ReplaceOp rop = (ReplaceOp)prevReplaces[j];
555                     if (iop.index == rop.index) {
556                         rop.text = CatOpText(iop.text, rop.text);
557                         rewrites[i] = null;  // delete current insert
558                         continue;
559                     }
560                     if (iop.index >= rop.index && iop.index <= rop.lastIndex) {
561                         throw new ArgumentException("insert op " + iop +
562                                                            " within boundaries of previous " + rop);
563                     }
564                 }
565             }
566             // System.out.println("rewrites after="+rewrites);
567             IDictionary<int, RewriteOperation> m = new Dictionary<int, RewriteOperation>();
568             for (int i = 0; i < rewrites.Count; i++) {
569                 RewriteOperation op = (RewriteOperation)rewrites[i];
570                 if (op == null)
571                     continue; // ignore deleted ops
572 
573                 RewriteOperation existing;
574                 if (m.TryGetValue(op.index, out existing) && existing != null) {
575                     throw new Exception("should only be one op per index");
576                 }
577                 m[op.index] = op;
578             }
579             //System.out.println("index to op: "+m);
580             return m;
581         }
582 
CatOpText(object a, object b)583         protected virtual string CatOpText(object a, object b) {
584             return string.Concat(a, b);
585         }
GetKindOfOps(IList<RewriteOperation> rewrites, Type kind)586         protected virtual IList<RewriteOperation> GetKindOfOps(IList<RewriteOperation> rewrites, Type kind) {
587             return GetKindOfOps(rewrites, kind, rewrites.Count);
588         }
589 
590         /** <summary>Get all operations before an index of a particular kind</summary> */
GetKindOfOps(IList<RewriteOperation> rewrites, Type kind, int before)591         protected virtual IList<RewriteOperation> GetKindOfOps(IList<RewriteOperation> rewrites, Type kind, int before) {
592             IList<RewriteOperation> ops = new List<RewriteOperation>();
593             for (int i = 0; i < before && i < rewrites.Count; i++) {
594                 RewriteOperation op = rewrites[i];
595                 if (op == null)
596                     continue; // ignore deleted
597                 if (op.GetType() == kind)
598                     ops.Add(op);
599             }
600             return ops;
601         }
602 
ToDebugString()603         public virtual string ToDebugString() {
604             return ToDebugString(MIN_TOKEN_INDEX, Count - 1);
605         }
606 
ToDebugString(int start, int end)607         public virtual string ToDebugString(int start, int end) {
608             StringBuilder buf = new StringBuilder();
609             for (int i = start; i >= MIN_TOKEN_INDEX && i <= end && i < _tokens.Count; i++) {
610                 buf.Append(Get(i));
611             }
612             return buf.ToString();
613         }
614     }
615 }
616