antlr4ng
Version:
Alternative JavaScript/TypeScript runtime for ANTLR4
922 lines (911 loc) • 26.9 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/atn/NotSetTransition.ts
var NotSetTransition_exports = {};
__export(NotSetTransition_exports, {
NotSetTransition: () => NotSetTransition
});
module.exports = __toCommonJS(NotSetTransition_exports);
// src/IntStream.ts
var IntStream;
((IntStream2) => {
IntStream2.EOF = -1;
IntStream2.UNKNOWN_SOURCE_NAME = "<unknown>";
})(IntStream || (IntStream = {}));
// src/Token.ts
var Token;
((Token2) => {
Token2.INVALID_TYPE = 0;
Token2.EPSILON = -2;
Token2.MIN_USER_TOKEN_TYPE = 1;
Token2.EOF = IntStream.EOF;
Token2.DEFAULT_CHANNEL = 0;
Token2.HIDDEN_CHANNEL = 1;
Token2.MIN_USER_CHANNEL_VALUE = 2;
})(Token || (Token = {}));
// src/misc/Interval.ts
var Interval = class _Interval {
static {
__name(this, "Interval");
}
static INVALID_INTERVAL = new _Interval(-1, -2);
static INTERVAL_POOL_MAX_VALUE = 1e3;
static cache = [];
start;
stop;
cachedHashCode;
constructor(start, stop) {
this.start = start;
this.stop = stop;
this.cachedHashCode = Math.imul(651 + start, 31) + stop;
}
/**
* Creates a new interval from the given values.
*
* Interval objects are used readonly so share all with the
* same single value a==b up to some max size. Use an array as a perfect hash.
* Return shared object for 0..INTERVAL_POOL_MAX_VALUE or a new
* Interval object with a..a in it. On Java.g4, 218623 IntervalSets
* have a..a (set with 1 element).
*
* @param a The start of the interval.
* @param b The end of the interval (inclusive).
*
* @returns A cached or new interval.
*/
static of(a, b) {
if (a !== b || a < 0 || a > _Interval.INTERVAL_POOL_MAX_VALUE) {
return new _Interval(a, b);
}
if (!_Interval.cache[a]) {
_Interval.cache[a] = new _Interval(a, a);
}
return _Interval.cache[a];
}
equals(o) {
return this.start === o.start && this.stop === o.stop;
}
hashCode() {
return this.cachedHashCode;
}
/** Does this start completely before other? Disjoint */
startsBeforeDisjoint(other) {
return this.start < other.start && this.stop < other.start;
}
/** Does this start at or before other? Nondisjoint */
startsBeforeNonDisjoint(other) {
return this.start <= other.start && this.stop >= other.start;
}
/** Does this.start start after other.stop? May or may not be disjoint */
startsAfter(other) {
return this.start > other.start;
}
/** Does this start completely after other? Disjoint */
startsAfterDisjoint(other) {
return this.start > other.stop;
}
/** Does this start after other? NonDisjoint */
startsAfterNonDisjoint(other) {
return this.start > other.start && this.start <= other.stop;
}
/** Are both ranges disjoint? I.e., no overlap? */
disjoint(other) {
return this.startsBeforeDisjoint(other) || this.startsAfterDisjoint(other);
}
/** Are two intervals adjacent such as 0..41 and 42..42? */
adjacent(other) {
return this.start === other.stop + 1 || this.stop === other.start - 1;
}
properlyContains(other) {
return other.start >= this.start && other.stop <= this.stop;
}
/** Return the interval computed from combining this and other */
union(other) {
return _Interval.of(Math.min(this.start, other.start), Math.max(this.stop, other.stop));
}
/** Return the interval in common between this and o */
intersection(other) {
return _Interval.of(Math.max(this.start, other.start), Math.min(this.stop, other.stop));
}
/**
* Return the interval with elements from this not in other;
* other must not be totally enclosed (properly contained)
* within this, which would result in two disjoint intervals
* instead of the single one returned by this method.
*/
differenceNotProperlyContained(other) {
let diff = null;
if (other.startsBeforeNonDisjoint(this)) {
diff = _Interval.of(Math.max(this.start, other.stop + 1), this.stop);
} else if (other.startsAfterNonDisjoint(this)) {
diff = _Interval.of(this.start, other.start - 1);
}
return diff;
}
toString() {
return `${this.start}..${this.stop}`;
}
get length() {
if (this.stop < this.start) {
return 0;
}
return this.stop - this.start + 1;
}
};
// src/Vocabulary.ts
var Vocabulary = class _Vocabulary {
static {
__name(this, "Vocabulary");
}
static EMPTY_NAMES = [];
/**
* Gets an empty {@link Vocabulary} instance.
*
*
* No literal or symbol names are assigned to token types, so
* {@link #getDisplayName(int)} returns the numeric value for all tokens
* except {@link Token#EOF}.
*/
static EMPTY_VOCABULARY = new _Vocabulary(_Vocabulary.EMPTY_NAMES, _Vocabulary.EMPTY_NAMES, _Vocabulary.EMPTY_NAMES);
maxTokenType;
literalNames;
symbolicNames;
displayNames;
/**
* Constructs a new instance of {@link Vocabulary} from the specified
* literal, symbolic, and display token names.
*
* @param literalNames The literal names assigned to tokens, or `null`
* if no literal names are assigned.
* @param symbolicNames The symbolic names assigned to tokens, or
* `null` if no symbolic names are assigned.
* @param displayNames The display names assigned to tokens, or `null`
* to use the values in `literalNames` and `symbolicNames` as
* the source of display names, as described in
* {@link #getDisplayName(int)}.
*/
constructor(literalNames, symbolicNames, displayNames) {
this.literalNames = literalNames ?? _Vocabulary.EMPTY_NAMES;
this.symbolicNames = symbolicNames ?? _Vocabulary.EMPTY_NAMES;
this.displayNames = displayNames ?? _Vocabulary.EMPTY_NAMES;
this.maxTokenType = Math.max(this.displayNames.length, Math.max(
this.literalNames.length,
this.symbolicNames.length
)) - 1;
}
/**
* Returns a {@link Vocabulary} instance from the specified set of token
* names. This method acts as a compatibility layer for the single
* `tokenNames` array generated by previous releases of ANTLR.
*
* The resulting vocabulary instance returns `null` for
* {@link getLiteralName getLiteralName(int)} and {@link getSymbolicName getSymbolicName(int)}, and the
* value from `tokenNames` for the display names.
*
* @param tokenNames The token names, or `null` if no token names are
* available.
* @returns A {@link Vocabulary} instance which uses `tokenNames` for
* the display names of tokens.
*/
static fromTokenNames(tokenNames) {
if (tokenNames == null || tokenNames.length === 0) {
return _Vocabulary.EMPTY_VOCABULARY;
}
const literalNames = [...tokenNames];
const symbolicNames = [...tokenNames];
for (let i = 0; i < tokenNames.length; i++) {
const tokenName = tokenNames[i];
if (tokenName == null) {
continue;
}
if (tokenName.length > 0) {
const firstChar = tokenName.codePointAt(0);
if (firstChar === 39) {
symbolicNames[i] = null;
continue;
} else if (firstChar >= 65 && firstChar <= 90) {
literalNames[i] = null;
continue;
}
}
literalNames[i] = null;
symbolicNames[i] = null;
}
return new _Vocabulary(literalNames, symbolicNames, tokenNames);
}
getMaxTokenType() {
return this.maxTokenType;
}
getLiteralName(tokenType) {
if (tokenType >= 0 && tokenType < this.literalNames.length) {
return this.literalNames[tokenType];
}
return null;
}
getSymbolicName(tokenType) {
if (tokenType >= 0 && tokenType < this.symbolicNames.length) {
return this.symbolicNames[tokenType];
}
if (tokenType === Token.EOF) {
return "EOF";
}
return null;
}
getDisplayName(tokenType) {
if (tokenType >= 0 && tokenType < this.displayNames.length) {
const displayName = this.displayNames[tokenType];
if (displayName != null) {
return displayName;
}
}
const literalName = this.getLiteralName(tokenType);
if (literalName != null) {
return literalName;
}
const symbolicName = this.getSymbolicName(tokenType);
if (symbolicName != null) {
return symbolicName;
}
return `${tokenType}`;
}
getLiteralNames() {
return this.literalNames;
}
getSymbolicNames() {
return this.symbolicNames;
}
getDisplayNames() {
return this.displayNames;
}
};
// src/utils/MurmurHash.ts
var c1 = 3432918353;
var c2 = 461845907;
var r1 = 15;
var r2 = 13;
var m = 5;
var n = 3864292196;
var MurmurHash = class _MurmurHash {
static {
__name(this, "MurmurHash");
}
static defaultSeed = 701;
constructor() {
}
/**
* Initialize the hash using the specified {@code seed}.
*
* @param seed the seed
*
* @returns the intermediate hash value
*/
static initialize(seed = _MurmurHash.defaultSeed) {
return seed;
}
static updateFromComparable(hash, value) {
return this.update(hash, value?.hashCode() ?? 0);
}
/**
* Update the intermediate hash value for the next input {@code value}.
*
* @param hash The intermediate hash value.
* @param value the value to add to the current hash.
*
* @returns the updated intermediate hash value
*/
static update(hash, value) {
value = Math.imul(value, c1);
value = value << r1 | value >>> 32 - r1;
value = Math.imul(value, c2);
hash = hash ^ value;
hash = hash << r2 | hash >>> 32 - r2;
hash = Math.imul(hash, m) + n;
return hash;
}
/**
* Apply the final computation steps to the intermediate value {@code hash}
* to form the final result of the MurmurHash 3 hash function.
*
* @param hash The intermediate hash value.
* @param entryCount The number of values added to the hash.
*
* @returns the final hash result
*/
static finish(hash, entryCount) {
hash ^= entryCount * 4;
hash ^= hash >>> 16;
hash = Math.imul(hash, 2246822507);
hash ^= hash >>> 13;
hash = Math.imul(hash, 3266489909);
hash ^= hash >>> 16;
return hash;
}
/**
* An all-in-one convenience method to compute a hash for a single value.
*
* @param value The value to hash.
* @param seed The seed for the hash value.
*
* @returns The computed hash.
*/
static hashCode(value, seed) {
return _MurmurHash.finish(_MurmurHash.update(seed ?? _MurmurHash.defaultSeed, value), 1);
}
};
// src/misc/IntervalSet.ts
var IntervalSet = class _IntervalSet {
static {
__name(this, "IntervalSet");
}
/** The list of sorted, disjoint intervals. */
intervals = [];
cachedHashCode;
constructor(set) {
if (set) {
if (Array.isArray(set)) {
for (const el of set) {
this.addOne(el);
}
} else {
this.addSet(set);
}
}
}
/** Create a set with all ints within range [a..b] (inclusive) */
static of(a, b) {
const s = new _IntervalSet();
s.addRange(a, b);
return s;
}
/** Combine all sets in the array and return the union of them */
static or(sets) {
const result = new _IntervalSet();
for (const set of sets) {
result.addSet(set);
}
return result;
}
[Symbol.iterator]() {
return this.intervals[Symbol.iterator]();
}
get(index) {
return this.intervals[index];
}
/**
* Returns the minimum value contained in the set if not isNil().
*
* @returns the minimum value contained in the set.
*/
get minElement() {
if (this.intervals.length === 0) {
return Token.INVALID_TYPE;
}
return this.intervals[0].start;
}
/**
* Returns the maximum value contained in the set if not isNil().
*
* @returns the maximum value contained in the set.
*/
get maxElement() {
if (this.intervals.length === 0) {
return Token.INVALID_TYPE;
}
return this.intervals[this.intervals.length - 1].stop;
}
clear() {
this.cachedHashCode = void 0;
this.intervals = [];
}
/**
* Add a single element to the set. An isolated element is stored
* as a range el..el.
*/
addOne(v) {
this.addInterval(new Interval(v, v));
}
/**
* Add interval; i.e., add all integers from a to b to set.
* If b < a, do nothing.
* Keep list in sorted order (by left range value).
* If overlap, combine ranges. For example,
* If this is {1..5, 10..20}, adding 6..7 yields
* {1..5, 6..7, 10..20}. Adding 4..8 yields {1..8, 10..20}.
*/
addRange(l, h) {
this.addInterval(new Interval(l, h));
}
addInterval(addition) {
this.cachedHashCode = void 0;
if (this.intervals.length === 0) {
this.intervals.push(addition);
} else {
for (let pos = 0; pos < this.intervals.length; pos++) {
const existing = this.intervals[pos];
if (addition.equals(existing)) {
return;
}
if (addition.adjacent(existing) || !addition.disjoint(existing)) {
const bigger = addition.union(existing);
this.intervals[pos] = bigger;
for (let sub = pos + 1; sub < this.intervals.length; ) {
const next = this.intervals[sub];
if (!bigger.adjacent(next) && bigger.disjoint(next)) {
break;
}
this.intervals.splice(sub, 1);
this.intervals[pos] = bigger.union(next);
}
return;
}
if (addition.startsBeforeDisjoint(existing)) {
this.intervals.splice(pos, 0, addition);
return;
}
}
this.intervals.push(addition);
}
}
addSet(other) {
other.intervals.forEach((toAdd) => {
return this.addInterval(toAdd);
}, this);
return this;
}
complementWithVocabulary(vocabulary) {
const result = new _IntervalSet();
if (!vocabulary) {
return result;
}
if (vocabulary.length === 0) {
return result;
}
result.addSet(vocabulary);
return result.subtract(this);
}
complement(minElement, maxElement) {
const result = new _IntervalSet();
result.addInterval(new Interval(minElement, maxElement));
return result.subtract(this);
}
/** combine all sets in the array returned the or'd value */
or(sets) {
const result = new _IntervalSet();
result.addSet(this);
sets.forEach((set) => {
return result.addSet(set);
});
return result;
}
and(other) {
if (other.length === 0) {
return new _IntervalSet();
}
const myIntervals = this.intervals;
const theirIntervals = other.intervals;
let intersection;
const mySize = myIntervals.length;
const theirSize = theirIntervals.length;
let i = 0;
let j = 0;
while (i < mySize && j < theirSize) {
const mine = myIntervals[i];
const theirs = theirIntervals[j];
if (mine.startsBeforeDisjoint(theirs)) {
i++;
} else if (theirs.startsBeforeDisjoint(mine)) {
j++;
} else if (mine.properlyContains(theirs)) {
if (!intersection) {
intersection = new _IntervalSet();
}
intersection.addInterval(mine.intersection(theirs));
j++;
} else if (theirs.properlyContains(mine)) {
if (!intersection) {
intersection = new _IntervalSet();
}
intersection.addInterval(mine.intersection(theirs));
i++;
} else if (!mine.disjoint(theirs)) {
if (!intersection) {
intersection = new _IntervalSet();
}
intersection.addInterval(mine.intersection(theirs));
if (mine.startsAfterNonDisjoint(theirs)) {
j++;
} else if (theirs.startsAfterNonDisjoint(mine)) {
i++;
}
}
}
if (!intersection) {
return new _IntervalSet();
}
return intersection;
}
/**
* Compute the set difference between two interval sets. The specific
* operation is `left - right`. If either of the input sets is
* `null`, it is treated as though it was an empty set.
*/
subtract(other) {
if (this.length === 0) {
return new _IntervalSet();
}
const result = new _IntervalSet(this);
if (other.length === 0) {
return result;
}
let resultI = 0;
let rightI = 0;
while (resultI < result.intervals.length && rightI < other.intervals.length) {
const resultInterval = result.intervals[resultI];
const rightInterval = other.intervals[rightI];
if (rightInterval.stop < resultInterval.start) {
rightI++;
continue;
}
if (rightInterval.start > resultInterval.stop) {
resultI++;
continue;
}
let beforeCurrent;
let afterCurrent;
if (rightInterval.start > resultInterval.start) {
beforeCurrent = new Interval(resultInterval.start, rightInterval.start - 1);
}
if (rightInterval.stop < resultInterval.stop) {
afterCurrent = new Interval(rightInterval.stop + 1, resultInterval.stop);
}
if (beforeCurrent) {
if (afterCurrent) {
result.intervals[resultI] = beforeCurrent;
result.intervals.splice(resultI + 1, 0, afterCurrent);
resultI++;
rightI++;
} else {
result.intervals[resultI] = beforeCurrent;
resultI++;
}
} else {
if (afterCurrent) {
result.intervals[resultI] = afterCurrent;
rightI++;
} else {
result.intervals.splice(resultI, 1);
}
}
}
return result;
}
contains(el) {
const n2 = this.intervals.length;
let l = 0;
let r = n2 - 1;
while (l <= r) {
const m2 = Math.floor((l + r) / 2);
const interval = this.intervals[m2];
if (interval.stop < el) {
l = m2 + 1;
} else if (interval.start > el) {
r = m2 - 1;
} else {
return true;
}
}
return false;
}
removeRange(toRemove) {
this.cachedHashCode = void 0;
if (toRemove.start === toRemove.stop) {
this.removeOne(toRemove.start);
} else if (this.intervals !== null) {
let pos = 0;
for (const existing of this.intervals) {
if (toRemove.stop <= existing.start) {
return;
} else if (toRemove.start > existing.start && toRemove.stop < existing.stop) {
this.intervals[pos] = new Interval(existing.start, toRemove.start);
const x = new Interval(toRemove.stop, existing.stop);
this.intervals.splice(pos, 0, x);
return;
} else if (toRemove.start <= existing.start && toRemove.stop >= existing.stop) {
this.intervals.splice(pos, 1);
pos = pos - 1;
} else if (toRemove.start < existing.stop) {
this.intervals[pos] = new Interval(existing.start, toRemove.start);
} else if (toRemove.stop < existing.stop) {
this.intervals[pos] = new Interval(toRemove.stop, existing.stop);
}
pos += 1;
}
}
}
removeOne(value) {
this.cachedHashCode = void 0;
for (let i = 0; i < this.intervals.length; i++) {
const existing = this.intervals[i];
if (value < existing.start) {
return;
} else if (value === existing.start && value === existing.stop) {
this.intervals.splice(i, 1);
return;
} else if (value === existing.start) {
this.intervals[i] = new Interval(existing.start + 1, existing.stop);
return;
} else if (value === existing.stop) {
this.intervals[i] = new Interval(existing.start, existing.stop - 1);
return;
} else if (value < existing.stop) {
const replace = new Interval(existing.start, value - 1);
this.intervals[i] = new Interval(value + 1, existing.stop);
this.intervals.splice(i, 0, replace);
return;
}
}
}
hashCode() {
if (this.cachedHashCode === void 0) {
let hash = MurmurHash.initialize();
for (const interval of this.intervals) {
hash = MurmurHash.update(hash, interval.start);
hash = MurmurHash.update(hash, interval.stop);
}
this.cachedHashCode = MurmurHash.finish(hash, this.intervals.length * 2);
}
return this.cachedHashCode;
}
/**
* Are two IntervalSets equal? Because all intervals are sorted and disjoint, equals is a simple linear walk over
* both lists to make sure they are the same. Interval.equals() is used by the List.equals() method to check
* the ranges.
*/
equals(other) {
if (this === other) {
return true;
}
if (this.intervals.length !== other.intervals.length) {
return false;
}
for (let i = 0; i < this.intervals.length; i++) {
if (!this.intervals[i].equals(other.intervals[i])) {
return false;
}
}
return true;
}
toString(elementsAreChar) {
if (this.intervals.length === 0) {
return "{}";
}
let result = "";
if (this.length > 1) {
result += "{";
}
for (let i = 0; i < this.intervals.length; ++i) {
const interval = this.intervals[i];
const start = interval.start;
const stop = interval.stop;
if (start === stop) {
if (start === Token.EOF) {
result += "<EOF>";
} else if (elementsAreChar) {
result += "'" + String.fromCodePoint(start) + "'";
} else {
result += start;
}
} else {
if (elementsAreChar) {
result += "'" + String.fromCodePoint(start) + "'..'" + String.fromCodePoint(stop) + "'";
} else {
result += start + ".." + stop;
}
}
if (i < this.intervals.length - 1) {
result += ", ";
}
}
if (this.length > 1) {
result += "}";
}
return result;
}
toStringWithVocabulary(vocabulary) {
if (this.intervals.length === 0) {
return "{}";
}
let result = "";
if (this.length > 1) {
result += "{";
}
for (let i = 0; i < this.intervals.length; ++i) {
const interval = this.intervals[i];
const start = interval.start;
const stop = interval.stop;
if (start === stop) {
if (start === Token.EOF) {
result += "<EOF>";
} else {
result += this.elementName(vocabulary, start);
}
} else {
for (let i2 = start; i2 <= stop; ++i2) {
if (i2 > start) {
result += ", ";
}
result += this.elementName(vocabulary, i2);
}
}
if (i < this.intervals.length - 1) {
result += ", ";
}
}
if (this.length > 1) {
result += "}";
}
return result;
}
toStringWithRuleNames(ruleNames) {
if (this.intervals.length === 0) {
return "{}";
}
let result = "";
if (this.length > 1) {
result += "{";
}
const vocabulary = Vocabulary.fromTokenNames(ruleNames);
for (let i = 0; i < this.intervals.length; ++i) {
const interval = this.intervals[i];
const start = interval.start;
const stop = interval.stop;
if (start === stop) {
if (start === Token.EOF) {
result += "<EOF>";
} else {
result += this.elementName(vocabulary, start);
}
} else {
for (let i2 = start; i2 <= stop; ++i2) {
if (i2 > start) {
result += ", ";
}
result += this.elementName(vocabulary, i2);
}
}
if (i < this.intervals.length - 1) {
result += ", ";
}
}
if (this.length > 1) {
result += "}";
}
return result;
}
toArray() {
const data = [];
for (const interval of this.intervals) {
for (let j = interval.start; j <= interval.stop; j++) {
data.push(j);
}
}
return data;
}
/** @returns the number of elements in this set. */
get length() {
let result = 0;
for (const interval of this.intervals) {
result += interval.length;
}
return result;
}
elementName(vocabulary, token) {
if (token === Token.EOF) {
return "<EOF>";
}
if (token === Token.EPSILON) {
return "<EPSILON>";
}
return vocabulary.getDisplayName(token);
}
};
// src/atn/Transition.ts
var Transition = class {
static {
__name(this, "Transition");
}
static INVALID = 0;
static EPSILON = 1;
static RANGE = 2;
static RULE = 3;
static PREDICATE = 4;
// e.g., {isType(input.LT(1))}
static ATOM = 5;
static ACTION = 6;
static SET = 7;
// ~(A|B) or ~atom, wildcard, which convert to next
static NOT_SET = 8;
static WILDCARD = 9;
static PRECEDENCE = 10;
/** The target of this transition. */
target;
constructor(target) {
this.target = target;
}
/**
* Determines if the transition is an "epsilon" transition.
*
* The default implementation returns `false`.
*
* @returns `true` if traversing this transition in the ATN does not
* consume an input symbol; otherwise, `false` if traversing this
* transition consumes (matches) an input symbol.
*/
get isEpsilon() {
return false;
}
get label() {
return null;
}
toString() {
return "";
}
};
// src/atn/SetTransition.ts
var SetTransition = class extends Transition {
static {
__name(this, "SetTransition");
}
set;
constructor(target, set) {
super(target);
if (set) {
this.set = set;
} else {
this.set = IntervalSet.of(Token.INVALID_TYPE, Token.INVALID_TYPE);
}
}
get transitionType() {
return Transition.SET;
}
get label() {
return this.set;
}
matches(symbol, _minVocabSymbol, _maxVocabSymbol) {
return this.set.contains(symbol);
}
toString() {
return this.set.toString();
}
};
// src/atn/NotSetTransition.ts
var NotSetTransition = class extends SetTransition {
static {
__name(this, "NotSetTransition");
}
get transitionType() {
return Transition.NOT_SET;
}
matches(symbol, minVocabSymbol, maxVocabSymbol) {
return symbol >= minVocabSymbol && symbol <= maxVocabSymbol && !super.matches(symbol, minVocabSymbol, maxVocabSymbol);
}
toString() {
return "~" + super.toString();
}
};