UNPKG

21.2 kBJavaScriptView Raw
1"use strict";
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 */
6var __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};
12Object.defineProperty(exports, "__esModule", { value: true });
13exports.RewriteOperation = exports.TokenStreamRewriter = void 0;
14// ConvertTo-TS run at 2016-10-04T11:26:58.1768850-07:00
15const Interval_1 = require("./misc/Interval");
16const Decorators_1 = require("./Decorators");
17const 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 */
86class 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 &rarr; 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}
424exports.TokenStreamRewriter = TokenStreamRewriter;
425TokenStreamRewriter.DEFAULT_PROGRAM_NAME = "default";
426TokenStreamRewriter.PROGRAM_INIT_SIZE = 100;
427TokenStreamRewriter.MIN_TOKEN_INDEX = 0;
428// Define the rewrite operation hierarchy
429class 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);
453exports.RewriteOperation = RewriteOperation;
454class 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 */
473class 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 */
481class 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