1 | ;
|
2 | /*!
|
3 | * Copyright 2016 The ANTLR Project. All rights reserved.
|
4 | * Licensed under the BSD-3-Clause license. See LICENSE file in the project root for license information.
|
5 | */
|
6 | var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
7 | var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
8 | if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
9 | else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
10 | return c > 3 && r && Object.defineProperty(target, key, r), r;
|
11 | };
|
12 | Object.defineProperty(exports, "__esModule", { value: true });
|
13 | exports.RewriteOperation = exports.TokenStreamRewriter = void 0;
|
14 | // ConvertTo-TS run at 2016-10-04T11:26:58.1768850-07:00
|
15 | const Interval_1 = require("./misc/Interval");
|
16 | const Decorators_1 = require("./Decorators");
|
17 | const Token_1 = require("./Token");
|
18 | /**
|
19 | * Useful for rewriting out a buffered input token stream after doing some
|
20 | * augmentation or other manipulations on it.
|
21 | *
|
22 | * You can insert stuff, replace, and delete chunks. Note that the operations
|
23 | * are done lazily--only if you convert the buffer to a {@link String} with
|
24 | * {@link TokenStream#getText()}. This is very efficient because you are not
|
25 | * moving data around all the time. As the buffer of tokens is converted to
|
26 | * strings, the {@link #getText()} method(s) scan the input token stream and
|
27 | * check to see if there is an operation at the current index. If so, the
|
28 | * operation is done and then normal {@link String} rendering continues on the
|
29 | * buffer. This is like having multiple Turing machine instruction streams
|
30 | * (programs) operating on a single input tape. :)
|
31 | *
|
32 | * This rewriter makes no modifications to the token stream. It does not ask the
|
33 | * stream to fill itself up nor does it advance the input cursor. The token
|
34 | * stream `TokenStream.index` will return the same value before and
|
35 | * after any {@link #getText()} call.
|
36 | *
|
37 | * The rewriter only works on tokens that you have in the buffer and ignores the
|
38 | * current input cursor. If you are buffering tokens on-demand, calling
|
39 | * {@link #getText()} halfway through the input will only do rewrites for those
|
40 | * tokens in the first half of the file.
|
41 | *
|
42 | * Since the operations are done lazily at {@link #getText}-time, operations do
|
43 | * not screw up the token index values. That is, an insert operation at token
|
44 | * index `i` does not change the index values for tokens
|
45 | * `i`+1..n-1.
|
46 | *
|
47 | * Because operations never actually alter the buffer, you may always get the
|
48 | * original token stream back without undoing anything. Since the instructions
|
49 | * are queued up, you can easily simulate transactions and roll back any changes
|
50 | * if there is an error just by removing instructions. For example,
|
51 | *
|
52 | * ```
|
53 | * CharStream input = new ANTLRFileStream("input");
|
54 | * TLexer lex = new TLexer(input);
|
55 | * CommonTokenStream tokens = new CommonTokenStream(lex);
|
56 | * T parser = new T(tokens);
|
57 | * TokenStreamRewriter rewriter = new TokenStreamRewriter(tokens);
|
58 | * parser.startRule();
|
59 | * ```
|
60 | *
|
61 | * Then in the rules, you can execute (assuming rewriter is visible):
|
62 | *
|
63 | * ```
|
64 | * Token t,u;
|
65 | * ...
|
66 | * rewriter.insertAfter(t, "text to put after t");}
|
67 | * rewriter.insertAfter(u, "text after u");}
|
68 | * System.out.println(rewriter.getText());
|
69 | * ```
|
70 | *
|
71 | * You can also have multiple "instruction streams" and get multiple rewrites
|
72 | * from a single pass over the input. Just name the instruction streams and use
|
73 | * that name again when printing the buffer. This could be useful for generating
|
74 | * a C file and also its header file--all from the same buffer:
|
75 | *
|
76 | * ```
|
77 | * rewriter.insertAfter("pass1", t, "text to put after t");}
|
78 | * rewriter.insertAfter("pass2", u, "text after u");}
|
79 | * System.out.println(rewriter.getText("pass1"));
|
80 | * System.out.println(rewriter.getText("pass2"));
|
81 | * ```
|
82 | *
|
83 | * If you don't use named rewrite streams, a "default" stream is used as the
|
84 | * first example shows.
|
85 | */
|
86 | class TokenStreamRewriter {
|
87 | constructor(tokens) {
|
88 | this.tokens = tokens;
|
89 | this.programs = new Map();
|
90 | this.programs.set(TokenStreamRewriter.DEFAULT_PROGRAM_NAME, []);
|
91 | this.lastRewriteTokenIndexes = new Map();
|
92 | }
|
93 | getTokenStream() {
|
94 | return this.tokens;
|
95 | }
|
96 | rollback(instructionIndex, programName = TokenStreamRewriter.DEFAULT_PROGRAM_NAME) {
|
97 | let is = this.programs.get(programName);
|
98 | if (is != null) {
|
99 | this.programs.set(programName, is.slice(TokenStreamRewriter.MIN_TOKEN_INDEX, instructionIndex));
|
100 | }
|
101 | }
|
102 | deleteProgram(programName = TokenStreamRewriter.DEFAULT_PROGRAM_NAME) {
|
103 | this.rollback(TokenStreamRewriter.MIN_TOKEN_INDEX, programName);
|
104 | }
|
105 | insertAfter(tokenOrIndex, text, programName = TokenStreamRewriter.DEFAULT_PROGRAM_NAME) {
|
106 | let index;
|
107 | if (typeof tokenOrIndex === "number") {
|
108 | index = tokenOrIndex;
|
109 | }
|
110 | else {
|
111 | index = tokenOrIndex.tokenIndex;
|
112 | }
|
113 | // to insert after, just insert before next index (even if past end)
|
114 | let rewrites = this.getProgram(programName);
|
115 | let op = new InsertAfterOp(this.tokens, index, rewrites.length, text);
|
116 | rewrites.push(op);
|
117 | }
|
118 | insertBefore(tokenOrIndex, text, programName = TokenStreamRewriter.DEFAULT_PROGRAM_NAME) {
|
119 | let index;
|
120 | if (typeof tokenOrIndex === "number") {
|
121 | index = tokenOrIndex;
|
122 | }
|
123 | else {
|
124 | index = tokenOrIndex.tokenIndex;
|
125 | }
|
126 | let rewrites = this.getProgram(programName);
|
127 | let op = new InsertBeforeOp(this.tokens, index, rewrites.length, text);
|
128 | rewrites.push(op);
|
129 | }
|
130 | replaceSingle(index, text) {
|
131 | if (typeof index === "number") {
|
132 | this.replace(index, index, text);
|
133 | }
|
134 | else {
|
135 | this.replace(index, index, text);
|
136 | }
|
137 | }
|
138 | replace(from, to, text, programName = TokenStreamRewriter.DEFAULT_PROGRAM_NAME) {
|
139 | if (typeof from !== "number") {
|
140 | from = from.tokenIndex;
|
141 | }
|
142 | if (typeof to !== "number") {
|
143 | to = to.tokenIndex;
|
144 | }
|
145 | if (from > to || from < 0 || to < 0 || to >= this.tokens.size) {
|
146 | throw new RangeError(`replace: range invalid: ${from}..${to}(size=${this.tokens.size})`);
|
147 | }
|
148 | let rewrites = this.getProgram(programName);
|
149 | let op = new ReplaceOp(this.tokens, from, to, rewrites.length, text);
|
150 | rewrites.push(op);
|
151 | }
|
152 | delete(from, to, programName = TokenStreamRewriter.DEFAULT_PROGRAM_NAME) {
|
153 | if (to === undefined) {
|
154 | to = from;
|
155 | }
|
156 | if (typeof from === "number") {
|
157 | this.replace(from, to, "", programName);
|
158 | }
|
159 | else {
|
160 | this.replace(from, to, "", programName);
|
161 | }
|
162 | }
|
163 | getLastRewriteTokenIndex(programName = TokenStreamRewriter.DEFAULT_PROGRAM_NAME) {
|
164 | let I = this.lastRewriteTokenIndexes.get(programName);
|
165 | if (I == null) {
|
166 | return -1;
|
167 | }
|
168 | return I;
|
169 | }
|
170 | setLastRewriteTokenIndex(programName, i) {
|
171 | this.lastRewriteTokenIndexes.set(programName, i);
|
172 | }
|
173 | getProgram(name) {
|
174 | let is = this.programs.get(name);
|
175 | if (is == null) {
|
176 | is = this.initializeProgram(name);
|
177 | }
|
178 | return is;
|
179 | }
|
180 | initializeProgram(name) {
|
181 | let is = [];
|
182 | this.programs.set(name, is);
|
183 | return is;
|
184 | }
|
185 | getText(intervalOrProgram, programName = TokenStreamRewriter.DEFAULT_PROGRAM_NAME) {
|
186 | let interval;
|
187 | if (intervalOrProgram instanceof Interval_1.Interval) {
|
188 | interval = intervalOrProgram;
|
189 | }
|
190 | else {
|
191 | interval = Interval_1.Interval.of(0, this.tokens.size - 1);
|
192 | }
|
193 | if (typeof intervalOrProgram === "string") {
|
194 | programName = intervalOrProgram;
|
195 | }
|
196 | let rewrites = this.programs.get(programName);
|
197 | let start = interval.a;
|
198 | let stop = interval.b;
|
199 | // ensure start/end are in range
|
200 | if (stop > this.tokens.size - 1) {
|
201 | stop = this.tokens.size - 1;
|
202 | }
|
203 | if (start < 0) {
|
204 | start = 0;
|
205 | }
|
206 | if (rewrites == null || rewrites.length === 0) {
|
207 | return this.tokens.getText(interval); // no instructions to execute
|
208 | }
|
209 | let buf = [];
|
210 | // First, optimize instruction stream
|
211 | let indexToOp = this.reduceToSingleOperationPerIndex(rewrites);
|
212 | // Walk buffer, executing instructions and emitting tokens
|
213 | let i = start;
|
214 | while (i <= stop && i < this.tokens.size) {
|
215 | let op = indexToOp.get(i);
|
216 | indexToOp.delete(i); // remove so any left have index size-1
|
217 | let t = this.tokens.get(i);
|
218 | if (op == null) {
|
219 | // no operation at that index, just dump token
|
220 | if (t.type !== Token_1.Token.EOF) {
|
221 | buf.push(String(t.text));
|
222 | }
|
223 | i++; // move to next token
|
224 | }
|
225 | else {
|
226 | i = op.execute(buf); // execute operation and skip
|
227 | }
|
228 | }
|
229 | // include stuff after end if it's last index in buffer
|
230 | // So, if they did an insertAfter(lastValidIndex, "foo"), include
|
231 | // foo if end==lastValidIndex.
|
232 | if (stop === this.tokens.size - 1) {
|
233 | // Scan any remaining operations after last token
|
234 | // should be included (they will be inserts).
|
235 | for (let op of indexToOp.values()) {
|
236 | if (op.index >= this.tokens.size - 1) {
|
237 | buf.push(op.text.toString());
|
238 | }
|
239 | }
|
240 | }
|
241 | return buf.join("");
|
242 | }
|
243 | /** We need to combine operations and report invalid operations (like
|
244 | * overlapping replaces that are not completed nested). Inserts to
|
245 | * same index need to be combined etc... Here are the cases:
|
246 | *
|
247 | * I.i.u I.j.v leave alone, nonoverlapping
|
248 | * I.i.u I.i.v combine: Iivu
|
249 | *
|
250 | * R.i-j.u R.x-y.v | i-j in x-y delete first R
|
251 | * R.i-j.u R.i-j.v delete first R
|
252 | * R.i-j.u R.x-y.v | x-y in i-j ERROR
|
253 | * R.i-j.u R.x-y.v | boundaries overlap ERROR
|
254 | *
|
255 | * Delete special case of replace (text==undefined):
|
256 | * D.i-j.u D.x-y.v | boundaries overlap combine to max(min)..max(right)
|
257 | *
|
258 | * I.i.u R.x-y.v | i in (x+1)-y delete I (since insert before
|
259 | * we're not deleting i)
|
260 | * I.i.u R.x-y.v | i not in (x+1)-y leave alone, nonoverlapping
|
261 | * R.x-y.v I.i.u | i in x-y ERROR
|
262 | * R.x-y.v I.x.u R.x-y.uv (combine, delete I)
|
263 | * R.x-y.v I.i.u | i not in x-y leave alone, nonoverlapping
|
264 | *
|
265 | * I.i.u = insert u before op @ index i
|
266 | * R.x-y.u = replace x-y indexed tokens with u
|
267 | *
|
268 | * First we need to examine replaces. For any replace op:
|
269 | *
|
270 | * 1. wipe out any insertions before op within that range.
|
271 | * 2. Drop any replace op before that is contained completely within
|
272 | * that range.
|
273 | * 3. Throw exception upon boundary overlap with any previous replace.
|
274 | *
|
275 | * Then we can deal with inserts:
|
276 | *
|
277 | * 1. for any inserts to same index, combine even if not adjacent.
|
278 | * 2. for any prior replace with same left boundary, combine this
|
279 | * insert with replace and delete this replace.
|
280 | * 3. throw exception if index in same range as previous replace
|
281 | *
|
282 | * Don't actually delete; make op undefined in list. Easier to walk list.
|
283 | * Later we can throw as we add to index → op map.
|
284 | *
|
285 | * Note that I.2 R.2-2 will wipe out I.2 even though, technically, the
|
286 | * inserted stuff would be before the replace range. But, if you
|
287 | * add tokens in front of a method body '{' and then delete the method
|
288 | * body, I think the stuff before the '{' you added should disappear too.
|
289 | *
|
290 | * Return a map from token index to operation.
|
291 | */
|
292 | reduceToSingleOperationPerIndex(rewrites) {
|
293 | // console.log(`rewrites=[${Utils.join(rewrites, ", ")}]`);
|
294 | // WALK REPLACES
|
295 | for (let i = 0; i < rewrites.length; i++) {
|
296 | let op = rewrites[i];
|
297 | if (op == null) {
|
298 | continue;
|
299 | }
|
300 | if (!(op instanceof ReplaceOp)) {
|
301 | continue;
|
302 | }
|
303 | let rop = op;
|
304 | // Wipe prior inserts within range
|
305 | let inserts = this.getKindOfOps(rewrites, InsertBeforeOp, i);
|
306 | for (let iop of inserts) {
|
307 | if (iop.index === rop.index) {
|
308 | // E.g., insert before 2, delete 2..2; update replace
|
309 | // text to include insert before, kill insert
|
310 | rewrites[iop.instructionIndex] = undefined;
|
311 | rop.text = iop.text.toString() + (rop.text != null ? rop.text.toString() : "");
|
312 | }
|
313 | else if (iop.index > rop.index && iop.index <= rop.lastIndex) {
|
314 | // delete insert as it's a no-op.
|
315 | rewrites[iop.instructionIndex] = undefined;
|
316 | }
|
317 | }
|
318 | // Drop any prior replaces contained within
|
319 | let prevReplaces = this.getKindOfOps(rewrites, ReplaceOp, i);
|
320 | for (let prevRop of prevReplaces) {
|
321 | if (prevRop.index >= rop.index && prevRop.lastIndex <= rop.lastIndex) {
|
322 | // delete replace as it's a no-op.
|
323 | rewrites[prevRop.instructionIndex] = undefined;
|
324 | continue;
|
325 | }
|
326 | // throw exception unless disjoint or identical
|
327 | let disjoint = prevRop.lastIndex < rop.index || prevRop.index > rop.lastIndex;
|
328 | // Delete special case of replace (text==null):
|
329 | // D.i-j.u D.x-y.v | boundaries overlap combine to max(min)..max(right)
|
330 | if (prevRop.text == null && rop.text == null && !disjoint) {
|
331 | // console.log(`overlapping deletes: ${prevRop}, ${rop}`);
|
332 | rewrites[prevRop.instructionIndex] = undefined; // kill first delete
|
333 | rop.index = Math.min(prevRop.index, rop.index);
|
334 | rop.lastIndex = Math.max(prevRop.lastIndex, rop.lastIndex);
|
335 | // console.log(`new rop ${rop}`);
|
336 | }
|
337 | else if (!disjoint) {
|
338 | throw new Error(`replace op boundaries of ${rop} overlap with previous ${prevRop}`);
|
339 | }
|
340 | }
|
341 | }
|
342 | // WALK INSERTS
|
343 | for (let i = 0; i < rewrites.length; i++) {
|
344 | let op = rewrites[i];
|
345 | if (op == null) {
|
346 | continue;
|
347 | }
|
348 | if (!(op instanceof InsertBeforeOp)) {
|
349 | continue;
|
350 | }
|
351 | let iop = op;
|
352 | // combine current insert with prior if any at same index
|
353 | let prevInserts = this.getKindOfOps(rewrites, InsertBeforeOp, i);
|
354 | for (let prevIop of prevInserts) {
|
355 | if (prevIop.index === iop.index) {
|
356 | if (prevIop instanceof InsertAfterOp) {
|
357 | iop.text = this.catOpText(prevIop.text, iop.text);
|
358 | rewrites[prevIop.instructionIndex] = undefined;
|
359 | }
|
360 | else if (prevIop instanceof InsertBeforeOp) { // combine objects
|
361 | // convert to strings...we're in process of toString'ing
|
362 | // whole token buffer so no lazy eval issue with any templates
|
363 | iop.text = this.catOpText(iop.text, prevIop.text);
|
364 | // delete redundant prior insert
|
365 | rewrites[prevIop.instructionIndex] = undefined;
|
366 | }
|
367 | }
|
368 | }
|
369 | // look for replaces where iop.index is in range; error
|
370 | let prevReplaces = this.getKindOfOps(rewrites, ReplaceOp, i);
|
371 | for (let rop of prevReplaces) {
|
372 | if (iop.index === rop.index) {
|
373 | rop.text = this.catOpText(iop.text, rop.text);
|
374 | rewrites[i] = undefined; // delete current insert
|
375 | continue;
|
376 | }
|
377 | if (iop.index >= rop.index && iop.index <= rop.lastIndex) {
|
378 | throw new Error(`insert op ${iop} within boundaries of previous ${rop}`);
|
379 | }
|
380 | }
|
381 | }
|
382 | // console.log(`rewrites after=[${Utils.join(rewrites, ", ")}]`);
|
383 | let m = new Map();
|
384 | for (let op of rewrites) {
|
385 | if (op == null) {
|
386 | // ignore deleted ops
|
387 | continue;
|
388 | }
|
389 | if (m.get(op.index) != null) {
|
390 | throw new Error("should only be one op per index");
|
391 | }
|
392 | m.set(op.index, op);
|
393 | }
|
394 | // console.log(`index to op: ${m}`);
|
395 | return m;
|
396 | }
|
397 | catOpText(a, b) {
|
398 | let x = "";
|
399 | let y = "";
|
400 | if (a != null) {
|
401 | x = a.toString();
|
402 | }
|
403 | if (b != null) {
|
404 | y = b.toString();
|
405 | }
|
406 | return x + y;
|
407 | }
|
408 | /** Get all operations before an index of a particular kind */
|
409 | getKindOfOps(rewrites, kind, before) {
|
410 | let ops = [];
|
411 | for (let i = 0; i < before && i < rewrites.length; i++) {
|
412 | let op = rewrites[i];
|
413 | if (op == null) {
|
414 | // ignore deleted
|
415 | continue;
|
416 | }
|
417 | if (op instanceof kind) {
|
418 | ops.push(op);
|
419 | }
|
420 | }
|
421 | return ops;
|
422 | }
|
423 | }
|
424 | exports.TokenStreamRewriter = TokenStreamRewriter;
|
425 | TokenStreamRewriter.DEFAULT_PROGRAM_NAME = "default";
|
426 | TokenStreamRewriter.PROGRAM_INIT_SIZE = 100;
|
427 | TokenStreamRewriter.MIN_TOKEN_INDEX = 0;
|
428 | // Define the rewrite operation hierarchy
|
429 | class RewriteOperation {
|
430 | constructor(tokens, index, instructionIndex, text) {
|
431 | this.tokens = tokens;
|
432 | this.instructionIndex = instructionIndex;
|
433 | this.index = index;
|
434 | this.text = text === undefined ? "" : text;
|
435 | }
|
436 | /** Execute the rewrite operation by possibly adding to the buffer.
|
437 | * Return the index of the next token to operate on.
|
438 | */
|
439 | execute(buf) {
|
440 | return this.index;
|
441 | }
|
442 | toString() {
|
443 | let opName = this.constructor.name;
|
444 | let $index = opName.indexOf("$");
|
445 | opName = opName.substring($index + 1, opName.length);
|
446 | return "<" + opName + "@" + this.tokens.get(this.index) +
|
447 | ":\"" + this.text + "\">";
|
448 | }
|
449 | }
|
450 | __decorate([
|
451 | Decorators_1.Override
|
452 | ], RewriteOperation.prototype, "toString", null);
|
453 | exports.RewriteOperation = RewriteOperation;
|
454 | class InsertBeforeOp extends RewriteOperation {
|
455 | constructor(tokens, index, instructionIndex, text) {
|
456 | super(tokens, index, instructionIndex, text);
|
457 | }
|
458 | execute(buf) {
|
459 | buf.push(this.text.toString());
|
460 | if (this.tokens.get(this.index).type !== Token_1.Token.EOF) {
|
461 | buf.push(String(this.tokens.get(this.index).text));
|
462 | }
|
463 | return this.index + 1;
|
464 | }
|
465 | }
|
466 | __decorate([
|
467 | Decorators_1.Override
|
468 | ], InsertBeforeOp.prototype, "execute", null);
|
469 | /** Distinguish between insert after/before to do the "insert afters"
|
470 | * first and then the "insert befores" at same index. Implementation
|
471 | * of "insert after" is "insert before index+1".
|
472 | */
|
473 | class InsertAfterOp extends InsertBeforeOp {
|
474 | constructor(tokens, index, instructionIndex, text) {
|
475 | super(tokens, index + 1, instructionIndex, text); // insert after is insert before index+1
|
476 | }
|
477 | }
|
478 | /** I'm going to try replacing range from x..y with (y-x)+1 ReplaceOp
|
479 | * instructions.
|
480 | */
|
481 | class ReplaceOp extends RewriteOperation {
|
482 | constructor(tokens, from, to, instructionIndex, text) {
|
483 | super(tokens, from, instructionIndex, text);
|
484 | this.lastIndex = to;
|
485 | }
|
486 | execute(buf) {
|
487 | if (this.text != null) {
|
488 | buf.push(this.text.toString());
|
489 | }
|
490 | return this.lastIndex + 1;
|
491 | }
|
492 | toString() {
|
493 | if (this.text == null) {
|
494 | return "<DeleteOp@" + this.tokens.get(this.index) +
|
495 | ".." + this.tokens.get(this.lastIndex) + ">";
|
496 | }
|
497 | return "<ReplaceOp@" + this.tokens.get(this.index) +
|
498 | ".." + this.tokens.get(this.lastIndex) + ":\"" + this.text + "\">";
|
499 | }
|
500 | }
|
501 | __decorate([
|
502 | Decorators_1.Override
|
503 | ], ReplaceOp.prototype, "execute", null);
|
504 | __decorate([
|
505 | Decorators_1.Override
|
506 | ], ReplaceOp.prototype, "toString", null);
|
507 | //# sourceMappingURL=TokenStreamRewriter.js.map |
\ | No newline at end of file |