/*! * Copyright 2016 The ANTLR Project. All rights reserved. * Licensed under the BSD-3-Clause license. See LICENSE file in the project root for license information. */ import { Interval } from "./misc/Interval"; import { Token } from "./Token"; import { TokenStream } from "./TokenStream"; /** * Useful for rewriting out a buffered input token stream after doing some * augmentation or other manipulations on it. * * You can insert stuff, replace, and delete chunks. Note that the operations * are done lazily--only if you convert the buffer to a {@link String} with * {@link TokenStream#getText()}. This is very efficient because you are not * moving data around all the time. As the buffer of tokens is converted to * strings, the {@link #getText()} method(s) scan the input token stream and * check to see if there is an operation at the current index. If so, the * operation is done and then normal {@link String} rendering continues on the * buffer. This is like having multiple Turing machine instruction streams * (programs) operating on a single input tape. :) * * This rewriter makes no modifications to the token stream. It does not ask the * stream to fill itself up nor does it advance the input cursor. The token * stream `TokenStream.index` will return the same value before and * after any {@link #getText()} call. * * The rewriter only works on tokens that you have in the buffer and ignores the * current input cursor. If you are buffering tokens on-demand, calling * {@link #getText()} halfway through the input will only do rewrites for those * tokens in the first half of the file. * * Since the operations are done lazily at {@link #getText}-time, operations do * not screw up the token index values. That is, an insert operation at token * index `i` does not change the index values for tokens * `i`+1..n-1. * * Because operations never actually alter the buffer, you may always get the * original token stream back without undoing anything. Since the instructions * are queued up, you can easily simulate transactions and roll back any changes * if there is an error just by removing instructions. For example, * * ``` * CharStream input = new ANTLRFileStream("input"); * TLexer lex = new TLexer(input); * CommonTokenStream tokens = new CommonTokenStream(lex); * T parser = new T(tokens); * TokenStreamRewriter rewriter = new TokenStreamRewriter(tokens); * parser.startRule(); * ``` * * Then in the rules, you can execute (assuming rewriter is visible): * * ``` * Token t,u; * ... * rewriter.insertAfter(t, "text to put after t");} * rewriter.insertAfter(u, "text after u");} * System.out.println(rewriter.getText()); * ``` * * You can also have multiple "instruction streams" and get multiple rewrites * from a single pass over the input. Just name the instruction streams and use * that name again when printing the buffer. This could be useful for generating * a C file and also its header file--all from the same buffer: * * ``` * rewriter.insertAfter("pass1", t, "text to put after t");} * rewriter.insertAfter("pass2", u, "text after u");} * System.out.println(rewriter.getText("pass1")); * System.out.println(rewriter.getText("pass2")); * ``` * * If you don't use named rewrite streams, a "default" stream is used as the * first example shows. */ export declare class TokenStreamRewriter { static readonly DEFAULT_PROGRAM_NAME: string; static readonly PROGRAM_INIT_SIZE: number; static readonly MIN_TOKEN_INDEX: number; /** Our source stream */ protected tokens: TokenStream; /** You may have multiple, named streams of rewrite operations. * I'm calling these things "programs." * Maps String (name) → rewrite (List) */ protected programs: Map; /** Map String (program name) → Integer index */ protected lastRewriteTokenIndexes: Map; constructor(tokens: TokenStream); getTokenStream(): TokenStream; rollback(instructionIndex: number): void; /** Rollback the instruction stream for a program so that * the indicated instruction (via instructionIndex) is no * longer in the stream. UNTESTED! */ rollback(instructionIndex: number, programName: string): void; deleteProgram(): void; /** Reset the program so that no instructions exist */ deleteProgram(programName: string): void; insertAfter(t: Token, text: {}): void; insertAfter(index: number, text: {}): void; insertAfter(t: Token, text: {}, programName: string): void; insertAfter(index: number, text: {}, programName: string): void; insertBefore(t: Token, text: {}): void; insertBefore(index: number, text: {}): void; insertBefore(t: Token, text: {}, programName: string): void; insertBefore(index: number, text: {}, programName: string): void; replaceSingle(index: number, text: {}): void; replaceSingle(indexT: Token, text: {}): void; replace(from: number, to: number, text: {}): void; replace(from: Token, to: Token, text: {}): void; replace(from: number, to: number, text: {}, programName: string): void; replace(from: Token, to: Token, text: {}, programName: string): void; delete(index: number): void; delete(from: number, to: number): void; delete(indexT: Token): void; delete(from: Token, to: Token): void; delete(from: number, to: number, programName: string): void; delete(from: Token, to: Token, programName: string): void; protected getLastRewriteTokenIndex(): number; protected getLastRewriteTokenIndex(programName: string): number; protected setLastRewriteTokenIndex(programName: string, i: number): void; protected getProgram(name: string): RewriteOperation[]; private initializeProgram; /** Return the text from the original tokens altered per the * instructions given to this rewriter. */ getText(): string; /** Return the text from the original tokens altered per the * instructions given to this rewriter in programName. * * @since 4.5 */ getText(programName: string): string; /** Return the text associated with the tokens in the interval from the * original token stream but with the alterations given to this rewriter. * The interval refers to the indexes in the original token stream. * We do not alter the token stream in any way, so the indexes * and intervals are still consistent. Includes any operations done * to the first and last token in the interval. So, if you did an * insertBefore on the first token, you would get that insertion. * The same is true if you do an insertAfter the stop token. */ getText(interval: Interval): string; getText(interval: Interval, programName: string): string; /** We need to combine operations and report invalid operations (like * overlapping replaces that are not completed nested). Inserts to * same index need to be combined etc... Here are the cases: * * I.i.u I.j.v leave alone, nonoverlapping * I.i.u I.i.v combine: Iivu * * R.i-j.u R.x-y.v | i-j in x-y delete first R * R.i-j.u R.i-j.v delete first R * R.i-j.u R.x-y.v | x-y in i-j ERROR * R.i-j.u R.x-y.v | boundaries overlap ERROR * * Delete special case of replace (text==undefined): * D.i-j.u D.x-y.v | boundaries overlap combine to max(min)..max(right) * * I.i.u R.x-y.v | i in (x+1)-y delete I (since insert before * we're not deleting i) * I.i.u R.x-y.v | i not in (x+1)-y leave alone, nonoverlapping * R.x-y.v I.i.u | i in x-y ERROR * R.x-y.v I.x.u R.x-y.uv (combine, delete I) * R.x-y.v I.i.u | i not in x-y leave alone, nonoverlapping * * I.i.u = insert u before op @ index i * R.x-y.u = replace x-y indexed tokens with u * * First we need to examine replaces. For any replace op: * * 1. wipe out any insertions before op within that range. * 2. Drop any replace op before that is contained completely within * that range. * 3. Throw exception upon boundary overlap with any previous replace. * * Then we can deal with inserts: * * 1. for any inserts to same index, combine even if not adjacent. * 2. for any prior replace with same left boundary, combine this * insert with replace and delete this replace. * 3. throw exception if index in same range as previous replace * * Don't actually delete; make op undefined in list. Easier to walk list. * Later we can throw as we add to index → op map. * * Note that I.2 R.2-2 will wipe out I.2 even though, technically, the * inserted stuff would be before the replace range. But, if you * add tokens in front of a method body '{' and then delete the method * body, I think the stuff before the '{' you added should disappear too. * * Return a map from token index to operation. */ protected reduceToSingleOperationPerIndex(rewrites: Array): Map; protected catOpText(a: {}, b: {}): string; /** Get all operations before an index of a particular kind */ protected getKindOfOps(rewrites: Array, kind: { new (...args: any[]): T; }, before: number): T[]; } export declare class RewriteOperation { protected readonly tokens: TokenStream; /** What index into rewrites List are we? */ readonly instructionIndex: number; /** Token buffer index. */ index: number; text: {}; constructor(tokens: TokenStream, index: number, instructionIndex: number); constructor(tokens: TokenStream, index: number, instructionIndex: number, text: {}); /** Execute the rewrite operation by possibly adding to the buffer. * Return the index of the next token to operate on. */ execute(buf: string[]): number; toString(): string; }