antlr-ng
Version:
Next generation ANTLR Tool
337 lines (336 loc) • 9.76 kB
JavaScript
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
import { Interval, Token } from "antlr4ng";
class CommonTree {
static {
__name(this, "CommonTree");
}
/** A single token is the payload. */
token;
/** Who is the parent node of this node? If null, implies node is root. */
parent = null;
/** What token indexes bracket all tokens associated with this node and below? */
startIndex = -1;
/** What token indexes bracket all tokens associated with this node and below? */
stopIndex = -1;
/** What index is this node in the child list? Range: 0..n-1 */
childIndex = -1;
children = [];
constructor(nodeOrToken) {
if (nodeOrToken instanceof CommonTree) {
this.token = nodeOrToken.token;
this.startIndex = nodeOrToken.startIndex;
this.stopIndex = nodeOrToken.stopIndex;
} else if (nodeOrToken) {
this.token = nodeOrToken;
this.startIndex = nodeOrToken.tokenIndex;
this.stopIndex = nodeOrToken.tokenIndex;
}
}
getFirstChildWithType(type) {
for (const t of this.children) {
if (t.getType() === type) {
return t;
}
}
return null;
}
dupNode() {
return new CommonTree(this);
}
/**
* Adds t as child of this node.
*
* Warning: if t has no children, but child does and child isNil then this routine moves children to t via
* `t.children = child.children`, i.e., without copying the array.
*
* @param t The child to add.
*/
addChild(t) {
if (!t) {
return;
}
if (t.isNil()) {
if (this.children === t.children) {
throw new Error("attempt to add child list to itself");
}
if (this.children.length > 0) {
const n = t.children.length;
for (let i = 0; i < n; i++) {
const c = t.children[i];
this.children.push(c);
c.parent = this;
c.childIndex = this.children.length - 1;
}
} else {
this.children = t.children;
this.freshenParentAndChildIndexes();
}
} else {
this.children.push(t);
t.parent = this;
t.childIndex = this.children.length - 1;
}
}
/**
* Adds all elements of kids list as children of this node.
*
* @param kids The children to add.
*/
addChildren(kids) {
for (const kid of kids) {
this.addChild(kid);
}
}
setChild(i, t) {
if (t.isNil()) {
throw new Error("Can't set single child to a list");
}
this.children[i] = t;
t.parent = this;
t.childIndex = i;
}
/**
* Inserts child t at child position i (0..n - 1) by shifting children i + 1..n - 1 to the right one position. Sets
* parent/indexes properly but does NOT collapse nil-rooted t's that come in here like addChild.
*
* @param i The index to insert the child at.
* @param t The child to insert.
*/
insertChild(i, t) {
if (i < 0 || i > this.children.length) {
throw new Error(`${i} out or range`);
}
this.children.splice(i, 0, t);
this.freshenParentAndChildIndexes(i);
}
deleteChild(i) {
const killed = this.children.splice(i, 1);
this.freshenParentAndChildIndexes(i);
return killed[0] ?? null;
}
/**
* Deletes children from start to stop and replaces with t even if t is a list (nil-root tree). Number of children
* can increase or decrease. For huge child lists, inserting children can force walking rest of
* children to set their child index - could be slow.
*
* @param startChildIndex The index to start deleting children.
* @param stopChildIndex The index to stop deleting children.
* @param t The tree to replace the deleted children with.
*/
replaceChildren(startChildIndex, stopChildIndex, t) {
if (this.children.length === 0) {
throw new Error("indexes invalid; no children in list");
}
const replacingHowMany = stopChildIndex - startChildIndex + 1;
const newTree = t;
let newChildren = [];
if (newTree.isNil()) {
newChildren = newTree.children;
} else {
newChildren.push(newTree);
}
const replacingWithHowMany = newChildren.length;
const numNewChildren = newChildren.length;
const delta = replacingHowMany - replacingWithHowMany;
if (delta === 0) {
let j = 0;
for (let i = startChildIndex; i <= stopChildIndex; i++) {
const child = newChildren[j];
this.children.splice(i, 1, child);
child.parent = this;
child.childIndex = i;
j++;
}
} else if (delta > 0) {
for (let j = 0; j < numNewChildren; j++) {
this.children[startChildIndex + j] = newChildren[j];
}
const indexToDelete = startChildIndex + numNewChildren;
for (let c = indexToDelete; c <= stopChildIndex; c++) {
this.children.splice(indexToDelete, 1);
}
this.freshenParentAndChildIndexes(startChildIndex);
} else {
for (let j = 0; j < replacingHowMany; j++) {
this.children[startChildIndex + j] = newChildren[j];
}
for (let j = replacingHowMany; j < replacingWithHowMany; j++) {
this.children.splice(startChildIndex + j, 0, newChildren[j]);
}
this.freshenParentAndChildIndexes(startChildIndex);
}
}
/**
* Sets the parent and child index values for all child of t.
*
* @param offset The index to start from.
*/
freshenParentAndChildIndexes(offset) {
offset ??= 0;
for (let i = offset; i < this.children.length; ++i) {
const child = this.children[i];
child.childIndex = i;
child.parent = this;
}
}
sanityCheckParentAndChildIndexes(...args) {
const parent = args[0];
const i = args[1] ?? -1;
if (parent !== (this.parent ?? void 0)) {
throw new Error(`parents don't match; expected ${parent} found ${this.parent}`);
}
if (i !== this.childIndex) {
throw new Error(`child indexes don't match; expected ${i} found ${this.childIndex}`);
}
const n = this.children.length;
for (let c = 0; c < n; c++) {
const child = this.children[c];
child.sanityCheckParentAndChildIndexes(this, c);
}
}
isNil() {
return this.token === void 0;
}
getType() {
if (!this.token) {
return Token.INVALID_TYPE;
}
return this.token.type;
}
getText() {
return this.token?.text ?? "";
}
getLine() {
if (!this.token || this.token.line === 0) {
if (this.children.length > 0) {
return this.children[0].getLine();
}
return 0;
}
return this.token.line;
}
getCharPositionInLine() {
if (!this.token || this.token.column === -1) {
if (this.children.length > 0) {
return this.children[0].getCharPositionInLine();
}
return 0;
}
return this.token.column;
}
getTokenStartIndex() {
if (this.startIndex === -1 && this.token) {
return this.token.tokenIndex;
}
return this.startIndex;
}
setTokenStartIndex(index) {
this.startIndex = index;
}
getTokenStopIndex() {
if (this.stopIndex === -1 && this.token) {
return this.token.tokenIndex;
}
return this.stopIndex;
}
setTokenStopIndex(index) {
this.stopIndex = index;
}
/**
* For every node in this subtree, make sure it's start/stop token's are set. Walks depth first, visits bottom up.
* Only updates nodes with at least one token index < 0.
*/
setUnknownTokenBoundaries() {
if (this.children.length === 0) {
if (this.startIndex < 0 || this.stopIndex < 0) {
this.startIndex = this.stopIndex = this.token?.tokenIndex ?? 0;
}
return;
}
for (const child of this.children) {
child.setUnknownTokenBoundaries();
}
if (this.startIndex >= 0 && this.stopIndex >= 0) {
return;
}
const firstChild = this.children[0];
const lastChild = this.children[this.children.length - 1];
this.startIndex = firstChild.getTokenStartIndex();
this.stopIndex = lastChild.getTokenStopIndex();
}
toString() {
if (!this.token) {
return "nil";
}
if (this.getType() === Token.INVALID_TYPE) {
return "<errornode>";
}
return this.token.text ?? "nil";
}
getSourceInterval() {
return Interval.of(this.getTokenStartIndex(), this.getTokenStopIndex());
}
/**
* Walks upwards and get first ancestor with this token type.
*
* @param ttype The token type to check for.
*
* @returns The first ancestor of this node with the specified token type, or `null` if no ancestor with
* the type exists.
*/
getAncestor(ttype) {
let run = this.parent;
while (run !== null) {
if (run.getType() === ttype) {
return run;
}
run = run.parent;
}
return null;
}
/**
* Prints out a whole tree not just a node.
*
* @returns A string representation of the tree.
*/
toStringTree() {
if (this.children.length === 0) {
return this.toString();
}
let result = "";
if (!this.isNil()) {
result += `(${this.toString()} `;
}
for (let i = 0; i < this.children.length; ++i) {
const t = this.children[i];
if (i > 0) {
result += " ";
}
result += t.toStringTree();
}
if (!this.isNil()) {
result += ")";
}
return result;
}
/**
* @returns a list of all ancestors of this node. The first node of list is the root and the last is the parent
* of this node.
*/
getAncestors() {
if (this.parent === null) {
return null;
}
const ancestors = new Array();
let t = this.parent;
while (t !== null) {
ancestors.unshift(t);
t = t.parent;
}
return ancestors;
}
}
export {
CommonTree
};