TokenRewriteStream.cs revision 324c4644fee44b9898524c09511bd33c3f12e2df
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    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
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             */
121            public virtual int Execute(StringBuilder buf) {
122                return index;
123            }
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 {
133            public InsertBeforeOp(TokenRewriteStream stream, int index, object text) :
134                base(stream, index, text) {
135            }
136
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;
152            public ReplaceOp(TokenRewriteStream stream, int from, int to, object text)
153                : base(stream, from, text) {
154                lastIndex = to;
155            }
156            public override int Execute(StringBuilder buf) {
157                if (text != null) {
158                    buf.Append(text);
159                }
160                return lastIndex + 1;
161            }
162            public override string ToString() {
163                return "<ReplaceOp@" + index + ".." + lastIndex + ":\"" + text + "\">";
164            }
165        }
166
167        class DeleteOp : ReplaceOp {
168            public DeleteOp(TokenRewriteStream stream, int from, int to) :
169                base(stream, from, to, null) {
170            }
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
187        public TokenRewriteStream() {
188            Init();
189        }
190
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
197        public TokenRewriteStream(ITokenSource tokenSource)
198            : base(tokenSource) {
199            Init();
200        }
201
202        public TokenRewriteStream(ITokenSource tokenSource, int channel)
203            : base(tokenSource, channel) {
204            Init();
205        }
206
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         */
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
228        public virtual void DeleteProgram() {
229            DeleteProgram(DEFAULT_PROGRAM_NAME);
230        }
231
232        /** <summary>Reset the program so that no instructions exist</summary> */
233        public virtual void DeleteProgram(string programName) {
234            Rollback(programName, MIN_TOKEN_INDEX);
235        }
236
237        public virtual void InsertAfter(IToken t, object text) {
238            InsertAfter(DEFAULT_PROGRAM_NAME, t, text);
239        }
240
241        public virtual void InsertAfter(int index, object text) {
242            InsertAfter(DEFAULT_PROGRAM_NAME, index, text);
243        }
244
245        public virtual void InsertAfter(string programName, IToken t, object text) {
246            InsertAfter(programName, t.TokenIndex, text);
247        }
248
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
255        public virtual void InsertBefore(IToken t, object text) {
256            InsertBefore(DEFAULT_PROGRAM_NAME, t, text);
257        }
258
259        public virtual void InsertBefore(int index, object text) {
260            InsertBefore(DEFAULT_PROGRAM_NAME, index, text);
261        }
262
263        public virtual void InsertBefore(string programName, IToken t, object text) {
264            InsertBefore(programName, t.TokenIndex, text);
265        }
266
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
275        public virtual void Replace(int index, object text) {
276            Replace(DEFAULT_PROGRAM_NAME, index, index, text);
277        }
278
279        public virtual void Replace(int from, int to, object text) {
280            Replace(DEFAULT_PROGRAM_NAME, from, to, text);
281        }
282
283        public virtual void Replace(IToken indexT, object text) {
284            Replace(DEFAULT_PROGRAM_NAME, indexT, indexT, text);
285        }
286
287        public virtual void Replace(IToken from, IToken to, object text) {
288            Replace(DEFAULT_PROGRAM_NAME, from, to, text);
289        }
290
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
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
308        public virtual void Delete(int index) {
309            Delete(DEFAULT_PROGRAM_NAME, index, index);
310        }
311
312        public virtual void Delete(int from, int to) {
313            Delete(DEFAULT_PROGRAM_NAME, from, to);
314        }
315
316        public virtual void Delete(IToken indexT) {
317            Delete(DEFAULT_PROGRAM_NAME, indexT, indexT);
318        }
319
320        public virtual void Delete(IToken from, IToken to) {
321            Delete(DEFAULT_PROGRAM_NAME, from, to);
322        }
323
324        public virtual void Delete(string programName, int from, int to) {
325            Replace(programName, from, to, null);
326        }
327
328        public virtual void Delete(string programName, IToken from, IToken to) {
329            Replace(programName, from, to, null);
330        }
331
332        public virtual int GetLastRewriteTokenIndex() {
333            return GetLastRewriteTokenIndex(DEFAULT_PROGRAM_NAME);
334        }
335
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
344        protected virtual void SetLastRewriteTokenIndex(string programName, int i) {
345            lastRewriteTokenIndexes[programName] = i;
346        }
347
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
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
362        public virtual string ToOriginalString() {
363            Fill();
364            return ToOriginalString(MIN_TOKEN_INDEX, Count - 1);
365        }
366
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
376        public override string ToString() {
377            Fill();
378            return ToString(MIN_TOKEN_INDEX, Count - 1);
379        }
380
381        public virtual string ToString(string programName) {
382            Fill();
383            return ToString(programName, MIN_TOKEN_INDEX, Count - 1);
384        }
385
386        public override string ToString(int start, int end) {
387            return ToString(DEFAULT_PROGRAM_NAME, start, end);
388        }
389
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         */
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
583        protected virtual string CatOpText(object a, object b) {
584            return string.Concat(a, b);
585        }
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> */
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
603        public virtual string ToDebugString() {
604            return ToDebugString(MIN_TOKEN_INDEX, Count - 1);
605        }
606
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