• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1//
2//  TokenRewriteStream.m
3//  ANTLR
4//
5//  Created by Alan Condit on 6/19/10.
6// [The "BSD licence"]
7// Copyright (c) 2010 Alan Condit
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#import "TokenRewriteStream.h"
33#import "RuntimeException.h"
34
35static NSString *DEFAULT_PROGRAM_NAME = @"default";
36static NSInteger PROGRAM_INIT_SIZE = 100;
37static NSInteger MIN_TOKEN_INDEX = 0;
38
39extern NSInteger debug;
40
41// Define the rewrite operation hierarchy
42
43@implementation RewriteOperation
44
45@synthesize instructionIndex;
46@synthesize rwIndex;
47@synthesize text;
48
49+ (RewriteOperation *) newRewriteOperation:(NSInteger)anIndex Text:(NSString *)theText
50{
51    return [[RewriteOperation alloc] initWithIndex:anIndex Text:theText];
52}
53
54- (id) initWithIndex:(NSInteger)anIndex Text:(NSString *)theText
55{
56    if ((self = [super init]) != nil) {
57        rwIndex = anIndex;
58        text = theText;
59    }
60    return self;
61}
62
63/** Execute the rewrite operation by possibly adding to the buffer.
64 *  Return the rwIndex of the next token to operate on.
65 */
66- (NSInteger) execute:(NSString *)buf
67{
68    return rwIndex;
69}
70
71- (NSString *)toString
72{
73    NSString *opName = [self className];
74    int $index = [self indexOf:'$' inString:opName];
75    opName = [opName substringWithRange:NSMakeRange($index+1, [opName length])];
76    return [NSString stringWithFormat:@"<%@%d:\"%@\">", opName, rwIndex, opName];
77}
78
79- (NSInteger) indexOf:(char)aChar inString:(NSString *)aString
80{
81    char indexedChar;
82
83    for( int i = 0; i < [aString length]; i++ ) {
84        indexedChar = [aString characterAtIndex:i];
85        if (indexedChar == aChar) {
86            return i;
87        }
88    }
89    return -1;
90}
91
92@end
93
94@implementation ANTLRInsertBeforeOp
95
96+ (ANTLRInsertBeforeOp *) newANTLRInsertBeforeOp:(NSInteger) anIndex Text:(NSString *)theText
97{
98    return [[ANTLRInsertBeforeOp alloc] initWithIndex:anIndex Text:theText];
99}
100
101- (id) initWithIndex:(NSInteger)anIndex Text:(NSString *)theText
102{
103    if ((self = [super initWithIndex:anIndex Text:theText]) != nil) {
104        rwIndex = anIndex;
105        text = theText;
106    }
107    return self;
108}
109
110
111- (NSInteger) execute:(NSMutableString *)buf
112{
113    [buf appendString:text];
114    if ( ((CommonToken *)[tokens objectAtIndex:rwIndex]).type != TokenTypeEOF ) {
115        [buf appendString:[[tokens objectAtIndex:rwIndex] text]];
116    }
117    return rwIndex+1;
118}
119
120@end
121
122/** I'm going to try replacing range from x..y with (y-x)+1 ANTLRReplaceOp
123 *  instructions.
124 */
125@implementation ANTLRReplaceOp
126
127@synthesize lastIndex;
128
129+ (ANTLRReplaceOp *) newANTLRReplaceOp:(NSInteger)from ToIndex:(NSInteger)to Text:(NSString*)theText
130{
131    return [[ANTLRReplaceOp alloc] initWithIndex:from ToIndex:to Text:theText];
132}
133
134- (id) initWithIndex:(NSInteger)from ToIndex:(NSInteger)to Text:(NSString *)theText
135{
136    if ((self = [super initWithIndex:from Text:theText]) != nil) {
137        lastIndex = to;
138    }
139    return self;
140}
141
142
143- (NSInteger) execute:(NSMutableString *)buf
144{
145    if ( text!=nil ) {
146        [buf appendString:text];
147    }
148        return lastIndex+1;
149}
150
151- (NSString *)toString
152{
153    return [NSString stringWithFormat:@"<ANTLRReplaceOp@ %d..%d :>%@\n", rwIndex, lastIndex, text];
154}
155
156@end
157
158@implementation ANTLRDeleteOp
159
160+ (ANTLRDeleteOp *) newANTLRDeleteOp:(NSInteger)from ToIndex:(NSInteger)to
161{
162    // super(from To:to, null);
163    return [[ANTLRDeleteOp alloc] initWithIndex:from ToIndex:to];
164}
165
166 - (id) initWithIndex:(NSInteger)from ToIndex:(NSInteger)to
167{
168    if ((self = [super initWithIndex:from ToIndex:to Text:nil]) != nil) {
169        lastIndex = to;
170    }
171    return self;
172}
173
174- (NSString *)toString
175{
176    return [NSString stringWithFormat:@"<DeleteOp@ %d..%d\n",  rwIndex, lastIndex];
177}
178
179@end
180
181
182@implementation TokenRewriteStream
183
184@synthesize programs;
185@synthesize lastRewriteTokenIndexes;
186
187+ (TokenRewriteStream *)newTokenRewriteStream
188{
189    return [[TokenRewriteStream alloc] init];
190}
191
192+ (TokenRewriteStream *)newTokenRewriteStream:(id<TokenSource>) aTokenSource
193{
194    return [[TokenRewriteStream alloc] initWithTokenSource:aTokenSource];
195}
196
197+ (TokenRewriteStream *)newTokenRewriteStream:(id<TokenSource>) aTokenSource Channel:(NSInteger)aChannel
198{
199    return [[TokenRewriteStream alloc] initWithTokenSource:aTokenSource Channel:aChannel];
200}
201
202- (id) init
203{
204    if ((self = [super init]) != nil) {
205        programs = [HashMap newHashMap];
206        [programs addObject:[MapElement newMapElementWithName:DEFAULT_PROGRAM_NAME Node:[HashMap newHashMapWithLen:PROGRAM_INIT_SIZE]]];
207        lastRewriteTokenIndexes = [HashMap newHashMap];
208    }
209    return self;
210}
211
212- (id)initWithTokenSource:(id<TokenSource>)aTokenSource
213{
214    if ((self = [super init]) != nil) {
215        programs = [HashMap newHashMap];
216        [programs addObject:[MapElement newMapElementWithName:DEFAULT_PROGRAM_NAME Node:[HashMap newHashMapWithLen:PROGRAM_INIT_SIZE]]];
217        lastRewriteTokenIndexes = [HashMap newHashMap];
218        tokenSource = aTokenSource;
219    }
220    return self;
221}
222
223- (id)initWithTokenSource:(id<TokenSource>)aTokenSource Channel:(NSInteger)aChannel
224{
225    if ((self = [super init]) != nil) {
226        programs = [HashMap newHashMap];
227        [programs addObject:[MapElement newMapElementWithName:DEFAULT_PROGRAM_NAME Node:[HashMap newHashMapWithLen:PROGRAM_INIT_SIZE]]];
228        lastRewriteTokenIndexes = [HashMap newHashMap];
229        tokenSource = aTokenSource;
230        channel = aChannel;
231    }
232    return self;
233}
234
235- (HashMap *)getPrograms
236{
237    return programs;
238}
239
240- (void)setPrograms:(HashMap *)aProgList
241{
242    programs = aProgList;
243}
244
245- (void) rollback:(NSInteger)instructionIndex
246{
247    [self rollback:DEFAULT_PROGRAM_NAME Index:instructionIndex];
248}
249
250/** Rollback the instruction stream for a program so that
251 *  the indicated instruction (via instructionIndex) is no
252 *  longer in the stream.  UNTESTED!
253 */
254- (void) rollback:(NSString *)programName Index:(NSInteger)anInstructionIndex
255{
256    id object;
257    HashMap *is;
258
259    //    AMutableArray *is = [programs get(programName)];
260    is = [self getPrograms];
261    object = [is getName:programName];
262    if ( is != nil ) {
263#pragma warning this has to be fixed
264        [programs insertObject:programName  atIndex:anInstructionIndex];
265    }
266}
267
268- (void) deleteProgram
269{
270    [self deleteProgram:DEFAULT_PROGRAM_NAME];
271}
272
273/** Reset the program so that no instructions exist */
274- (void) deleteProgram:(NSString *)programName
275{
276    [self rollback:programName Index:MIN_TOKEN_INDEX];
277}
278
279- (void) insertAfterToken:(id<Token>)t Text:(NSString *)theText
280{
281    [self insertAfterProgNam:DEFAULT_PROGRAM_NAME Index:[t getTokenIndex] Text:theText];
282}
283
284- (void) insertAfterIndex:(NSInteger)anIndex Text:(NSString *)theText
285{
286    [self insertAfterProgNam:DEFAULT_PROGRAM_NAME Index:(NSInteger)anIndex Text:(NSString *)theText];
287}
288
289- (void) insertAfterProgNam:(NSString *)programName Index:(NSInteger)anIndex Text:(NSString *)theText
290{
291    // to insert after, just insert before next rwIndex (even if past end)
292    [self insertBeforeProgName:programName Index:anIndex+1 Text:theText];
293    //addToSortedRewriteList(programName, new InsertAfterOp(rwIndex,text));
294}
295
296
297
298
299
300
301
302
303
304- (void) insertBeforeToken:(id<Token>)t Text:(NSString *)theText
305{
306    [self insertBeforeProgName:DEFAULT_PROGRAM_NAME Index:[t getTokenIndex] Text:theText];
307}
308
309- (void) insertBeforeIndex:(NSInteger)anIndex Text:(NSString *)theText
310{
311    [self insertBeforeProgName:DEFAULT_PROGRAM_NAME Index:anIndex Text:theText];
312}
313
314- (void) insertBeforeProgName:(NSString *)programName Index:(NSInteger)rwIndex Text:(NSString *)theText
315{
316    //addToSortedRewriteList(programName, new ANTLRInsertBeforeOp(rwIndex,text));
317    RewriteOperation *op = [ANTLRInsertBeforeOp newANTLRInsertBeforeOp:rwIndex Text:theText];
318    HashMap *rewrites = [self getProgram:programName];
319    op.instructionIndex = [rewrites count];
320    [rewrites addObject:op];
321}
322
323- (void) replaceFromIndex:(NSInteger)anIndex Text:(NSString *)theText
324{
325    [self replaceProgNam:DEFAULT_PROGRAM_NAME FromIndex:anIndex ToIndex:anIndex Text:theText];
326}
327
328- (void) replaceFromIndex:(NSInteger)from ToIndex:(NSInteger)to Text:(NSString *)theText
329{
330    [self replaceProgNam:DEFAULT_PROGRAM_NAME FromIndex:from ToIndex:to Text:theText];
331}
332
333- (void) replaceFromToken:(id<Token>)anIndexT Text:(NSString *)theText
334{
335    [self replaceProgNam:DEFAULT_PROGRAM_NAME FromIndex:[anIndexT getTokenIndex] ToIndex:[anIndexT getTokenIndex] Text:theText];
336}
337
338- (void) replaceFromToken:(id<Token>)from ToToken:(id<Token>)to Text:(NSString *)theText
339{
340    [self replaceProgNam:DEFAULT_PROGRAM_NAME FromIndex:[from getTokenIndex] ToIndex:[to getTokenIndex] Text:theText];
341}
342
343- (void) replaceProgNam:(NSString *)programName Token:(id<Token>)from Token:(id<Token>)to Text:(NSString *)theText
344{
345    [self replaceProgNam:programName FromIndex:[from getTokenIndex] ToIndex:[to getTokenIndex] Text:theText];
346}
347
348- (void) replaceProgNam:(NSString *)programName FromIndex:(NSInteger)from ToIndex:(NSInteger)to Text:(NSString *)theText
349{
350    if ( from > to || from < 0 || to < 0 || to >= [tokens count] ) {
351        @throw [IllegalArgumentException newException:[NSString stringWithFormat:@"replace: range invalid: %d..%d size=%d\n", from, to, [tokens count]]];
352    }
353    RewriteOperation *op = [ANTLRReplaceOp newANTLRReplaceOp:from ToIndex:to Text:theText];
354    HashMap *rewrites = (HashMap *)[lastRewriteTokenIndexes getName:programName];
355    op.instructionIndex = [rewrites count];
356    [rewrites addObject:op];
357}
358
359- (void) delete:(NSInteger)anIndex
360{
361    [self delete:DEFAULT_PROGRAM_NAME  FromIndex:(NSInteger)anIndex  ToIndex:(NSInteger)anIndex];
362}
363
364- (void) delete:(NSInteger)from ToIndex:(NSInteger)to
365{
366    [self delete:DEFAULT_PROGRAM_NAME FromIndex:from ToIndex:to];
367}
368
369- (void) deleteToken:(id<Token>)anIndexT
370{
371    [self delete:DEFAULT_PROGRAM_NAME FromIndex:[anIndexT getTokenIndex] ToIndex:[anIndexT getTokenIndex]];
372}
373
374- (void) deleteFromToken:(id<Token>)from ToToken:(id<Token>)to
375{
376    [self delete:DEFAULT_PROGRAM_NAME FromIndex:[from getTokenIndex] ToIndex:[to getTokenIndex]];
377}
378
379- (void) delete:(NSString *)programName FromToken:(id<Token>)from ToToken:(id<Token>)to
380{
381    [self replaceProgNam:programName FromIndex:[from getTokenIndex] ToIndex:[to getTokenIndex] Text:nil];
382}
383
384- (void) delete:(NSString *)programName FromIndex:(NSInteger)from ToIndex:(NSInteger)to
385{
386    [self replaceProgNam:programName FromIndex:from ToIndex:to Text:nil];
387}
388
389- (NSInteger)getLastRewriteTokenIndex
390{
391    return [self getLastRewriteTokenIndex:DEFAULT_PROGRAM_NAME];
392}
393
394- (NSInteger)getLastRewriteTokenIndex:(NSString *)programName
395{
396#pragma warning fix this to look up the hashed name
397    NSInteger anInt = -1;
398    MapElement *node = [lastRewriteTokenIndexes lookup:programName Scope:0];
399    if ( node != nil ) {
400        anInt = [lastRewriteTokenIndexes hash:programName];
401    }
402    return anInt;
403}
404
405- (void)setLastRewriteTokenIndex:(NSString *)programName Index:(NSInteger)anInt
406{
407    [lastRewriteTokenIndexes insertObject:programName atIndex:anInt];
408}
409
410-(HashMap *) getProgram:(NSString *)name
411{
412   HashMap *is = (HashMap *)[programs getName:name];
413    if ( is == nil ) {
414        is = [self initializeProgram:name];
415    }
416    return is;
417}
418
419-(HashMap *) initializeProgram:(NSString *)name
420{
421    HashMap *is = [HashMap newHashMapWithLen:PROGRAM_INIT_SIZE];
422    [is putName:name Node:nil];
423    return is;
424}
425
426- (NSString *)toOriginalString
427{
428    [super fill];
429    return [self toOriginalString:MIN_TOKEN_INDEX End:[tokens count]-1];
430}
431
432- (NSString *)toOriginalString:(NSInteger)start End:(NSInteger)end
433{
434    NSMutableString *buf = [NSMutableString stringWithCapacity:100];
435    for (int i = start; i >= MIN_TOKEN_INDEX && i <= end && i< [tokens count]; i++) {
436        if ( [((CommonToken *)[lastRewriteTokenIndexes objectAtIndex:i]) type] != TokenTypeEOF )
437            [buf appendString:[[tokens objectAtIndex:i] text]];
438    }
439    return [NSString stringWithString:buf];
440}
441
442- (NSString *)toString
443{
444    [super fill];
445    return [self toStringFromStart:MIN_TOKEN_INDEX ToEnd:[tokens count]-1];
446}
447
448- (NSString *)toString:(NSString *)programName
449{
450    [super fill];
451    return [self toString:programName FromStart:MIN_TOKEN_INDEX ToEnd:[[programs objectAtIndex:MIN_TOKEN_INDEX] count]-1];
452}
453
454- (NSString *)toStringFromStart:(NSInteger)start ToEnd:(NSInteger)end
455{
456    return [self toString:DEFAULT_PROGRAM_NAME FromStart:start ToEnd:end];
457}
458
459- (NSString *)toString:(NSString *)programName FromStart:(NSInteger)start ToEnd:(NSInteger)end
460{
461    HashMap *rewrites = (HashMap *)[programs getName:programName];
462
463    // ensure start/end are in range
464    if ( end > [tokens count]-1 ) end = [tokens count]-1;
465    if ( start < 0 )
466        start = 0;
467
468    if ( rewrites == nil || [rewrites count] == 0 ) {
469        return [self toOriginalString:start End:end]; // no instructions to execute
470    }
471    NSMutableString *buf = [NSMutableString stringWithCapacity:100];
472
473    // First, optimize instruction stream
474    HashMap *indexToOp = [self reduceToSingleOperationPerIndex:rewrites];
475
476    // Walk buffer, executing instructions and emitting tokens
477    int i = start;
478    while ( i <= end && i < [tokens count] ) {
479        RewriteOperation *op = (RewriteOperation *)[indexToOp objectAtIndex:i];
480        [indexToOp setObject:nil atIndex:i]; // remove so any left have rwIndex size-1
481        id<Token>t = (id<Token>) [tokens objectAtIndex:i];
482        if ( op == nil ) {
483            // no operation at that rwIndex, just dump token
484            if ( t.type != TokenTypeEOF )
485                [buf appendString:t.text];
486            i++; // move to next token
487        }
488        else {
489            i = [op execute:buf]; // execute operation and skip
490        }
491    }
492
493    // include stuff after end if it's last rwIndex in buffer
494    // So, if they did an insertAfter(lastValidIndex, "foo"), include
495    // foo if end==lastValidIndex.
496    //if ( end == [tokens size]-1 ) {
497    if ( end == [tokens count]-1 ) {
498        // Scan any remaining operations after last token
499        // should be included (they will be inserts).
500        int i2 = 0;
501        while ( i2 < [indexToOp count] - 1 ) {
502            RewriteOperation *op = [indexToOp objectAtIndex:i2];
503            if ( op.rwIndex >= [tokens count]-1 ) {
504                [buf appendString:op.text];
505            }
506        }
507    }
508    return [NSString stringWithString:buf];
509}
510
511/** We need to combine operations and report invalid operations (like
512 *  overlapping replaces that are not completed nested).  Inserts to
513 *  same rwIndex need to be combined etc...   Here are the cases:
514 *
515 *  I.i.u I.j.v								leave alone, nonoverlapping
516 *  I.i.u I.i.v								combine: Iivu
517 *
518 *  R.i-j.u R.x-y.v	| i-j in x-y			delete first R
519 *  R.i-j.u R.i-j.v							delete first R
520 *  R.i-j.u R.x-y.v	| x-y in i-j			ERROR
521 *  R.i-j.u R.x-y.v	| boundaries overlap	ERROR
522 *
523 *  I.i.u R.x-y.v | i in x-y				delete I
524 *  I.i.u R.x-y.v | i not in x-y			leave alone, nonoverlapping
525 *  R.x-y.v I.i.u | i in x-y				ERROR
526 *  R.x-y.v I.x.u 							R.x-y.uv (combine, delete I)
527 *  R.x-y.v I.i.u | i not in x-y			leave alone, nonoverlapping
528 *
529 *  I.i.u = insert u before op @ rwIndex i
530 *  R.x-y.u = replace x-y indexed tokens with u
531 *
532 *  First we need to examine replaces.  For any replace op:
533 *
534 * 		1. wipe out any insertions before op within that range.
535 *		2. Drop any replace op before that is contained completely within
536 *         that range.
537 *		3. Throw exception upon boundary overlap with any previous replace.
538 *
539 *  Then we can deal with inserts:
540 *
541 * 		1. for any inserts to same rwIndex, combine even if not adjacent.
542 * 		2. for any prior replace with same left boundary, combine this
543 *         insert with replace and delete this replace.
544 * 		3. throw exception if rwIndex in same range as previous replace
545 *
546 *  Don't actually delete; make op null in list. Easier to walk list.
547 *  Later we can throw as we add to rwIndex -> op map.
548 *
549 *  Note that I.2 R.2-2 will wipe out I.2 even though, technically, the
550 *  inserted stuff would be before the replace range.  But, if you
551 *  add tokens in front of a method body '{' and then delete the method
552 *  body, I think the stuff before the '{' you added should disappear too.
553 *
554 *  Return a map from token rwIndex to operation.
555 */
556- (HashMap *)reduceToSingleOperationPerIndex:(HashMap *)rewrites
557{
558    //System.out.println("rewrites="+rewrites);
559    if (debug > 1) NSLog(@"rewrites=%@\n", [rewrites getName:DEFAULT_PROGRAM_NAME]);
560    // WALK REPLACES
561    for (int i = 0; i < [rewrites count]; i++) {
562        RewriteOperation *op = (RewriteOperation *)[rewrites objectAtIndex:i];
563        if ( op==nil )
564            continue;
565        if ( !([[op class] isKindOfClass:[ANTLRReplaceOp class]]) )
566            continue;
567        ANTLRReplaceOp *rop = (ANTLRReplaceOp *)[rewrites objectAtIndex:i];
568        // Wipe prior inserts within range
569        //List inserts = getKindOfOps(rewrites, ANTLRInsertBeforeOp.class, i);
570        HashMap *inserts = [self getKindOfOps:rewrites KindOfClass:[ANTLRInsertBeforeOp class] Index:i];
571        for (int j = 0; j < [inserts size]; j++) {
572            ANTLRInsertBeforeOp *iop = (ANTLRInsertBeforeOp *)[inserts objectAtIndex:j];
573            if ( iop.rwIndex >= rop.rwIndex && iop.rwIndex <= rop.lastIndex ) {
574                // delete insert as it's a no-op.
575                [rewrites insertObject:nil atIndex:iop.instructionIndex];
576            }
577        }
578        // Drop any prior replaces contained within
579        HashMap *prevReplaces = [self getKindOfOps:rewrites KindOfClass:[ANTLRReplaceOp class] Index:i];
580        for (int j = 0; j < [prevReplaces count]; j++) {
581            ANTLRReplaceOp *prevRop = (ANTLRReplaceOp *) [prevReplaces objectAtIndex:j];
582            if ( prevRop.rwIndex>=rop.rwIndex && prevRop.lastIndex <= rop.lastIndex ) {
583                // delete replace as it's a no-op.
584                [rewrites setObject:nil atIndex:prevRop.instructionIndex];
585                continue;
586            }
587            // throw exception unless disjoint or identical
588            BOOL disjoint = prevRop.lastIndex<rop.rwIndex || prevRop.rwIndex > rop.lastIndex;
589            BOOL same = prevRop.rwIndex==rop.rwIndex && prevRop.lastIndex==rop.lastIndex;
590            if ( !disjoint && !same ) {
591                @throw [IllegalArgumentException newException:
592                        [NSString stringWithFormat:@"replace op boundaries of %@, overlap with previous %@\n", rop, prevRop]];
593            }
594        }
595    }
596
597    // WALK INSERTS
598    for (int i = 0; i < [rewrites count]; i++) {
599        RewriteOperation *op = (RewriteOperation *)[rewrites objectAtIndex:i];
600        if ( op == nil )
601            continue;
602        if ( !([[op class] isKindOfClass:[ANTLRInsertBeforeOp class]]) )
603            continue;
604        ANTLRInsertBeforeOp *iop = (ANTLRInsertBeforeOp *)[rewrites objectAtIndex:i];
605        // combine current insert with prior if any at same rwIndex
606        HashMap *prevInserts = (HashMap *)[self getKindOfOps:rewrites KindOfClass:[ANTLRInsertBeforeOp class] Index:i];
607        for (int j = 0; j < [prevInserts count]; j++) {
608            ANTLRInsertBeforeOp *prevIop = (ANTLRInsertBeforeOp *) [prevInserts objectAtIndex:j];
609            if ( prevIop.rwIndex == iop.rwIndex ) { // combine objects
610                                                // convert to strings...we're in process of toString'ing
611                                                // whole token buffer so no lazy eval issue with any templates
612                iop.text = [self catOpText:iop.text PrevText:prevIop.text];
613                // delete redundant prior insert
614                [rewrites setObject:nil atIndex:prevIop.instructionIndex];
615            }
616        }
617        // look for replaces where iop.rwIndex is in range; error
618        HashMap *prevReplaces = (HashMap *)[self getKindOfOps:rewrites KindOfClass:[ANTLRReplaceOp class] Index:i];
619        for (int j = 0; j < [prevReplaces count]; j++) {
620            ANTLRReplaceOp *rop = (ANTLRReplaceOp *) [prevReplaces objectAtIndex:j];
621            if ( iop.rwIndex == rop.rwIndex ) {
622                rop.text = [self catOpText:iop.text PrevText:rop.text];
623                [rewrites setObject:nil atIndex:i];  // delete current insert
624                continue;
625            }
626            if ( iop.rwIndex >= rop.rwIndex && iop.rwIndex <= rop.lastIndex ) {
627                @throw [IllegalArgumentException newException:[NSString stringWithFormat:@"insert op %d within boundaries of previous %d", iop, rop]];
628            }
629        }
630    }
631    // System.out.println("rewrites after="+rewrites);
632    HashMap *m = [HashMap newHashMapWithLen:15];
633    for (int i = 0; i < [rewrites count]; i++) {
634        RewriteOperation *op = (RewriteOperation *)[rewrites objectAtIndex:i];
635        if ( op == nil )
636            continue; // ignore deleted ops
637        if ( [m objectAtIndex:op.rwIndex] != nil ) {
638            @throw [RuntimeException newException:@"should only be one op per rwIndex\n"];
639        }
640        //[m put(new Integer(op.rwIndex), op);
641        [m setObject:op atIndex:op.rwIndex];
642    }
643    //System.out.println("rwIndex to op: "+m);
644    if (debug > 1) NSLog(@"rwIndex to  op %d\n", (NSInteger)m);
645    return m;
646}
647
648- (NSString *)catOpText:(id)a PrevText:(id)b
649{
650    NSString *x = @"";
651    NSString *y = @"";
652    if ( a != nil )
653        x = [a toString];
654    if ( b != nil )
655        y = [b toString];
656    return [NSString stringWithFormat:@"%@%@",x, y];
657}
658
659- (HashMap *)getKindOfOps:(HashMap *)rewrites KindOfClass:(Class)kind
660{
661    return [self getKindOfOps:rewrites KindOfClass:kind Index:[rewrites count]];
662}
663
664/** Get all operations before an rwIndex of a particular kind */
665- (HashMap *)getKindOfOps:(HashMap *)rewrites KindOfClass:(Class)kind Index:(NSInteger)before
666{
667    HashMap *ops = [HashMap newHashMapWithLen:15];
668    for (int i = 0; i < before && i < [rewrites count]; i++) {
669        RewriteOperation *op = (RewriteOperation *)[rewrites objectAtIndex:i];
670        if ( op == nil )
671            continue; // ignore deleted
672        if ( [op isKindOfClass:(Class)kind] )
673            [ops addObject:op];
674    }
675    return ops;
676}
677
678- (NSMutableString *)toDebugString
679{
680    return [self toDebugStringFromStart:MIN_TOKEN_INDEX ToEnd:[tokens count]-1];
681}
682
683- (NSMutableString *)toDebugStringFromStart:(NSInteger)start ToEnd:(NSInteger)end
684{
685    NSMutableString *buf = [NSMutableString stringWithCapacity:100];
686    for (int i = start; i >= MIN_TOKEN_INDEX && i <= end && i < [tokens count]; i++) {
687        [buf appendString:[[tokens objectAtIndex:i] text]];
688    }
689    return [NSString stringWithString:buf];
690}
691
692@end
693