antlr4ng
Version:
Alternative JavaScript/TypeScript runtime for ANTLR4
983 lines (966 loc) • 26.5 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/ATNSimulator.ts
var ATNSimulator_exports = {};
__export(ATNSimulator_exports, {
ATNSimulator: () => ATNSimulator
});
module.exports = __toCommonJS(ATNSimulator_exports);
// src/utils/helpers.ts
var valueToString = /* @__PURE__ */ __name((v) => {
return v === null ? "null" : v;
}, "valueToString");
var arrayToString = /* @__PURE__ */ __name((value) => {
return Array.isArray(value) ? "[" + value.map(valueToString).join(", ") + "]" : "null";
}, "arrayToString");
var equalArrays = /* @__PURE__ */ __name((a, b) => {
if (a === b) {
return true;
}
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
const left = a[i];
const right = b[i];
if (left === right) {
continue;
}
if (!left || !left.equals(right)) {
return false;
}
}
return true;
}, "equalArrays");
var equalNumberArrays = /* @__PURE__ */ __name((a, b) => {
if (a === b) {
return true;
}
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}, "equalNumberArrays");
// src/dfa/DFAState.ts
var DFAState = class _DFAState {
static {
__name(this, "DFAState");
}
stateNumber = -1;
configs;
/**
* `edges[symbol]` points to target of symbol. Shift up by 1 so (-1) {@link Token.EOF} maps to `edges[0]`.
*/
edges = [];
isAcceptState = false;
/**
* If accept state, what ttype do we match or alt do we predict? This is set to {@link ATN.INVALID_ALT_NUMBER}
* when {@link predicates} `!= null` or {@link requiresFullContext}.
*/
prediction = -1;
lexerActionExecutor = null;
/**
* Indicates that this state was created during SLL prediction that discovered a conflict between the configurations
* in the state. Future {@link ParserATNSimulator.execATN} invocations immediately jumped doing
* full context prediction if this field is true.
*/
requiresFullContext = false;
/**
* During SLL parsing, this is a list of predicates associated with the ATN configurations of the DFA state.
* When we have predicates, {@link requiresFullContext} is `false` since full context prediction evaluates
* predicates on-the-fly. If this is not null, then {@link prediction} is `ATN.INVALID_ALT_NUMBER`.
*
* We only use these for non-{@link #requiresFullContext} but conflicting states. That
* means we know from the context (it's $ or we don't dip into outer
* context) that it's an ambiguity not a conflict.
*
* This list is computed by {@link ParserATNSimulator#predicateDFAState}.
*/
predicates = null;
constructor(configs) {
if (configs) {
this.configs = configs;
}
}
static fromState(stateNumber) {
const result = new _DFAState();
result.stateNumber = stateNumber;
return result;
}
static fromConfigs(configs) {
return new _DFAState(configs);
}
static hashCode(state) {
return state.configs.hashCode();
}
/**
* Two {@link DFAState} instances are equal if their ATN configuration sets
* are the same. This method is used to see if a state already exists.
*
* Because the number of alternatives and number of ATN configurations are
* finite, there is a finite number of DFA states that can be processed.
* This is necessary to show that the algorithm terminates.
*
* Cannot test the DFA state numbers here because in
* {@link ParserATNSimulator#addDFAState} we need to know if any other state
* exists that has this exact set of ATN configurations. The
* {@link #stateNumber} is irrelevant.
*
* @param a The first {@link DFAState}.
* @param b The second {@link DFAState}.
*
* @returns `true` if the two states are equal, otherwise `false`.
*/
static equals(a, b) {
return a.configs.equals(b.configs);
}
/**
* @returns the set of all alts mentioned by all ATN configurations in this DFA state.
*/
getAltSet() {
const alts = /* @__PURE__ */ new Set();
for (const config of this.configs) {
alts.add(config.alt);
}
if (alts.size === 0) {
return null;
}
return alts;
}
toString() {
let buf = "";
buf += this.stateNumber;
buf += ":";
buf += this.configs ? this.configs.toString() : "";
if (this.isAcceptState) {
buf += "=>";
if (this.predicates) {
buf += arrayToString(this.predicates);
} else {
buf += this.prediction;
}
}
return buf.toString();
}
};
// src/misc/ObjectEqualityComparator.ts
var ObjectEqualityComparator = class _ObjectEqualityComparator {
static {
__name(this, "ObjectEqualityComparator");
}
static instance = new _ObjectEqualityComparator();
hashCode(obj) {
if (obj == null) {
return 0;
}
return obj.hashCode();
}
equals(a, b) {
if (a == null) {
return b == null;
}
return a.equals(b);
}
};
// src/misc/DefaultEqualityComparator.ts
var DefaultEqualityComparator = class _DefaultEqualityComparator {
static {
__name(this, "DefaultEqualityComparator");
}
static instance = new _DefaultEqualityComparator();
hashCode(obj) {
if (obj == null) {
return 0;
}
return ObjectEqualityComparator.instance.hashCode(obj);
}
equals(a, b) {
if (a == null) {
return b == null;
}
if (typeof a === "string" || typeof a === "number") {
return a === b;
}
return ObjectEqualityComparator.instance.equals(a, b);
}
};
// 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/HashSet.ts
var HashSet = class _HashSet {
static {
__name(this, "HashSet");
}
static defaultLoadFactor = 0.75;
static initialCapacity = 16;
// must be power of 2
comparator;
buckets;
threshold;
/** How many elements in set */
itemCount = 0;
constructor(comparatorOrSet, initialCapacity = _HashSet.initialCapacity) {
if (comparatorOrSet instanceof _HashSet) {
this.comparator = comparatorOrSet.comparator;
this.buckets = comparatorOrSet.buckets.slice(0);
for (let i = 0; i < this.buckets.length; i++) {
const bucket = this.buckets[i];
if (bucket) {
this.buckets[i] = bucket.slice(0);
}
}
this.itemCount = comparatorOrSet.itemCount;
this.threshold = comparatorOrSet.threshold;
} else {
this.comparator = comparatorOrSet ?? DefaultEqualityComparator.instance;
this.buckets = this.createBuckets(initialCapacity);
this.threshold = Math.floor(_HashSet.initialCapacity * _HashSet.defaultLoadFactor);
}
}
/**
* Add `o` to set if not there; return existing value if already
* there. This method performs the same operation as {@link #add} aside from
* the return value.
*
* @param o the object to add to the set.
*
* @returns An existing element that equals to `o` if already in set, otherwise `o`.
*/
getOrAdd(o) {
if (this.itemCount > this.threshold) {
this.expand();
}
const b = this.getBucket(o);
let bucket = this.buckets[b];
if (!bucket) {
bucket = [o];
this.buckets[b] = bucket;
++this.itemCount;
return o;
}
for (const existing of bucket) {
if (this.comparator.equals(existing, o)) {
return existing;
}
}
bucket.push(o);
++this.itemCount;
return o;
}
get(o) {
if (o == null) {
return o;
}
const b = this.getBucket(o);
const bucket = this.buckets[b];
if (!bucket) {
return void 0;
}
for (const e of bucket) {
if (this.comparator.equals(e, o)) {
return e;
}
}
return void 0;
}
/**
* Removes the specified element from this set if it is present.
*
* @param o object to be removed from this set, if present.
*
* @returns `true` if the set contained the specified element.
*/
remove(o) {
if (o == null) {
return false;
}
const b = this.getBucket(o);
const bucket = this.buckets[b];
if (!bucket) {
return false;
}
for (let i = 0; i < bucket.length; i++) {
const existing = bucket[i];
if (this.comparator.equals(existing, o)) {
bucket.splice(i, 1);
--this.itemCount;
return true;
}
}
return false;
}
hashCode() {
let hash = MurmurHash.initialize();
for (const bucket of this.buckets) {
if (bucket == null) {
continue;
}
for (const o of bucket) {
if (o == null) {
break;
}
hash = MurmurHash.update(hash, this.comparator.hashCode(o));
}
}
hash = MurmurHash.finish(hash, this.size);
return hash;
}
equals(o) {
if (o === this) {
return true;
}
if (!(o instanceof _HashSet)) {
return false;
}
if (o.size !== this.size) {
return false;
}
return this.containsAll(o);
}
add(t) {
const existing = this.getOrAdd(t);
return existing === t;
}
contains(o) {
return this.containsFast(o);
}
containsFast(obj) {
if (obj == null) {
return false;
}
return this.get(obj) !== void 0;
}
*[Symbol.iterator]() {
yield* this.toArray();
}
toArray() {
const a = new Array(this.size);
let i = 0;
for (const bucket of this.buckets) {
if (bucket == null) {
continue;
}
for (const o of bucket) {
if (o == null) {
break;
}
a[i++] = o;
}
}
return a;
}
containsAll(collection) {
if (collection instanceof _HashSet) {
for (const bucket of collection.buckets) {
if (bucket == null) {
continue;
}
for (const o of bucket) {
if (o == null) {
break;
}
if (!this.containsFast(o)) {
return false;
}
}
}
} else {
for (const o of collection) {
if (!this.containsFast(o)) {
return false;
}
}
}
return true;
}
addAll(c) {
let changed = false;
for (const o of c) {
const existing = this.getOrAdd(o);
if (existing !== o) {
changed = true;
}
}
return changed;
}
clear() {
this.buckets = this.createBuckets(_HashSet.initialCapacity);
this.itemCount = 0;
this.threshold = Math.floor(_HashSet.initialCapacity * _HashSet.defaultLoadFactor);
}
toString() {
if (this.size === 0) {
return "{}";
}
let buf = "{";
let first = true;
for (const bucket of this.buckets) {
if (bucket == null) {
continue;
}
for (const o of bucket) {
if (o == null) {
break;
}
if (first) {
first = false;
} else {
buf += ", ";
}
buf += o.toString();
}
}
buf += "}";
return buf;
}
toTableString() {
let buf = "";
for (const bucket of this.buckets) {
if (bucket == null) {
buf += "null\n";
continue;
}
buf += "[";
let first = true;
for (const o of bucket) {
if (first) {
first = false;
} else {
buf += " ";
}
if (o == null) {
buf += "_";
} else {
buf += o.toString();
}
}
buf += "]\n";
}
return buf;
}
getBucket(o) {
const hash = this.comparator.hashCode(o);
const b = hash & this.buckets.length - 1;
return b;
}
expand() {
const old = this.buckets;
const newCapacity = this.buckets.length * 2;
const newTable = this.createBuckets(newCapacity);
this.buckets = newTable;
this.threshold = Math.floor(newCapacity * _HashSet.defaultLoadFactor);
for (const bucket of old) {
if (!bucket) {
continue;
}
for (const o of bucket) {
const b = this.getBucket(o);
let newBucket = this.buckets[b];
if (!newBucket) {
newBucket = [];
this.buckets[b] = newBucket;
}
newBucket.push(o);
}
}
}
get size() {
return this.itemCount;
}
get isEmpty() {
return this.itemCount === 0;
}
/**
* Return an array of `T[]` with length `capacity`.
*
* @param capacity the length of the array to return
* @returns the newly constructed array
*/
createBuckets(capacity) {
return new Array(capacity);
}
};
// src/misc/MapKeyEqualityOperator.ts
var MapKeyEqualityComparator = class {
static {
__name(this, "MapKeyEqualityComparator");
}
keyComparator;
constructor(keyComparator) {
this.keyComparator = keyComparator;
}
hashCode(obj) {
return this.keyComparator.hashCode(obj.key);
}
equals(a, b) {
return this.keyComparator.equals(a.key, b.key);
}
};
// src/misc/HashMap.ts
var HashMap = class _HashMap {
static {
__name(this, "HashMap");
}
backingStore;
constructor(keyComparer) {
if (keyComparer instanceof _HashMap) {
this.backingStore = new HashSet(keyComparer.backingStore);
} else {
keyComparer = keyComparer ?? DefaultEqualityComparator.instance;
this.backingStore = new HashSet(new MapKeyEqualityComparator(keyComparer));
}
}
clear() {
this.backingStore.clear();
}
containsKey(key) {
return this.backingStore.contains({ key });
}
get(key) {
const bucket = this.backingStore.get({ key });
if (!bucket) {
return void 0;
}
return bucket.value;
}
get isEmpty() {
return this.backingStore.isEmpty;
}
/**
* Sets the value for a key in the map. If the key is not present in the map, it is added.
* If the key is present, the value is updated and the old value is returned.
*
* @param key The key to set.
* @param value The value to set.
*
* @returns The old value for the key, if present.
*/
set(key, value) {
const element = this.backingStore.get({ key, value });
let result;
if (!element) {
this.backingStore.add({ key, value });
} else {
result = element.value;
element.value = value;
}
return result;
}
/**
* Sets the value for a key in the map if the key is not already present. Otherwise the value is not changed and
* the old value is returned.
*
* @param key The key to set.
* @param value The value to set.
*
* @returns The current value for the key, if present.
*/
setIfAbsent(key, value) {
const element = this.backingStore.get({ key, value });
let result;
if (!element) {
this.backingStore.add({ key, value });
} else {
result = element.value;
}
return result;
}
keys() {
return this.backingStore.toArray().map((bucket) => {
return bucket.key;
});
}
values() {
return this.backingStore.toArray().map((bucket) => {
return bucket.value;
});
}
get size() {
return this.backingStore.size;
}
hashCode() {
return this.backingStore.hashCode();
}
equals(o) {
return this.backingStore.equals(o.backingStore);
}
};
// src/atn/PredictionContext.ts
var PredictionContext = class _PredictionContext {
static {
__name(this, "PredictionContext");
}
/**
* Represents `$` in an array in full context mode, when `$`
* doesn't mean wildcard: `$ + x = [$,x]`. Here,
* `$` = {@link EMPTY_RETURN_STATE}.
*/
static EMPTY_RETURN_STATE = 2147483647;
static traceATNSimulator = false;
cachedHashCode;
constructor(cachedHashCode) {
this.cachedHashCode = cachedHashCode;
}
static calculateEmptyHashCode() {
let hash = MurmurHash.initialize(31);
hash = MurmurHash.finish(hash, 0);
return hash;
}
static calculateHashCodeSingle(parent, returnState) {
let hash = MurmurHash.initialize(31);
hash = MurmurHash.updateFromComparable(hash, parent);
hash = MurmurHash.update(hash, returnState);
hash = MurmurHash.finish(hash, 2);
return hash;
}
static calculateHashCodeList(parents, returnStates) {
let hash = MurmurHash.initialize(31);
for (const parent of parents) {
hash = MurmurHash.updateFromComparable(hash, parent);
}
for (const returnState of returnStates) {
hash = MurmurHash.update(hash, returnState);
}
hash = MurmurHash.finish(hash, 2 * parents.length);
return hash;
}
isEmpty() {
return false;
}
hasEmptyPath() {
return this.getReturnState(this.length - 1) === _PredictionContext.EMPTY_RETURN_STATE;
}
hashCode() {
return this.cachedHashCode;
}
toString(_recog) {
return "";
}
};
// src/atn/SingletonPredictionContext.ts
var SingletonPredictionContext = class _SingletonPredictionContext extends PredictionContext {
static {
__name(this, "SingletonPredictionContext");
}
parent;
returnState;
constructor(parent, returnState) {
super(
parent ? PredictionContext.calculateHashCodeSingle(parent, returnState) : PredictionContext.calculateEmptyHashCode()
);
this.parent = parent ?? null;
this.returnState = returnState;
}
getParent(_index) {
return this.parent;
}
getReturnState(_index) {
return this.returnState;
}
equals(other) {
if (this === other) {
return true;
}
if (!(other instanceof _SingletonPredictionContext)) {
return false;
}
if (this.hashCode() !== other.hashCode()) {
return false;
}
if (this.returnState !== other.returnState) {
return false;
}
if (this.parent == null) {
return other.parent == null;
}
return this.parent.equals(other.parent);
}
toString() {
const up = this.parent === null ? "" : this.parent.toString();
if (up.length === 0) {
if (this.returnState === PredictionContext.EMPTY_RETURN_STATE) {
return "$";
}
return "" + this.returnState;
} else {
return "" + this.returnState + " " + up;
}
}
get length() {
return 1;
}
};
// src/atn/EmptyPredictionContext.ts
var EmptyPredictionContext = class _EmptyPredictionContext extends SingletonPredictionContext {
static {
__name(this, "EmptyPredictionContext");
}
/**
* Represents `$` in local context prediction, which means wildcard.
* `*+x = *`.
*/
static instance = new _EmptyPredictionContext();
constructor() {
super(void 0, PredictionContext.EMPTY_RETURN_STATE);
}
isEmpty() {
return true;
}
getParent() {
return null;
}
getReturnState() {
return this.returnState;
}
equals(other) {
return this === other;
}
toString() {
return "$";
}
};
// src/atn/helpers.ts
var createSingletonPredictionContext = /* @__PURE__ */ __name((parent, returnState) => {
if (returnState === PredictionContext.EMPTY_RETURN_STATE && parent === null) {
return EmptyPredictionContext.instance;
} else {
return new SingletonPredictionContext(parent, returnState);
}
}, "createSingletonPredictionContext");
// src/atn/ArrayPredictionContext.ts
var ArrayPredictionContext = class _ArrayPredictionContext extends PredictionContext {
static {
__name(this, "ArrayPredictionContext");
}
parents = [];
returnStates = [];
constructor(parents, returnStates) {
super(PredictionContext.calculateHashCodeList(parents, returnStates));
this.parents = parents;
this.returnStates = returnStates;
return this;
}
isEmpty() {
return this.returnStates[0] === PredictionContext.EMPTY_RETURN_STATE;
}
get length() {
return this.returnStates.length;
}
getParent(index) {
return this.parents[index];
}
getReturnState(index) {
return this.returnStates[index];
}
equals(other) {
if (this === other) {
return true;
}
if (!(other instanceof _ArrayPredictionContext) || this.hashCode() !== other.hashCode()) {
return false;
}
return equalNumberArrays(this.returnStates, other.returnStates) && equalArrays(this.parents, other.parents);
}
toString() {
if (this.isEmpty()) {
return "[]";
}
const entries = [];
for (let i = 0; i < this.returnStates.length; i++) {
if (this.returnStates[i] === PredictionContext.EMPTY_RETURN_STATE) {
entries.push("$");
continue;
}
entries.push(this.returnStates[i].toString());
if (this.parents[i]) {
entries.push(this.parents[i].toString());
} else {
entries.push("null");
}
}
return `[${entries.join(", ")}]`;
}
};
// src/atn/PredictionContextUtils.ts
var getCachedPredictionContext = /* @__PURE__ */ __name((context, contextCache, visited) => {
if (context.isEmpty()) {
return context;
}
let existing = visited.get(context);
if (existing) {
return existing;
}
existing = contextCache.get(context);
if (existing) {
visited.set(context, existing);
return existing;
}
let changed = false;
let parents = [];
for (let i = 0; i < parents.length; i++) {
const parent = getCachedPredictionContext(context.getParent(i), contextCache, visited);
if (changed || parent !== context.getParent(i)) {
if (!changed) {
parents = [];
for (let j = 0; j < context.length; j++) {
parents[j] = context.getParent(j);
}
changed = true;
}
parents[i] = parent;
}
}
if (!changed) {
contextCache.add(context);
visited.set(context, context);
return context;
}
let updated;
if (parents.length === 0) {
updated = EmptyPredictionContext.instance;
} else if (parents.length === 1) {
updated = createSingletonPredictionContext(parents[0] ?? void 0, context.getReturnState(0));
} else {
updated = new ArrayPredictionContext(parents, context.returnStates);
}
contextCache.add(updated);
visited.set(updated, updated);
visited.set(context, updated);
return updated;
}, "getCachedPredictionContext");
// src/atn/ATNSimulator.ts
var ATNSimulator = class {
static {
__name(this, "ATNSimulator");
}
/** Must distinguish between missing edge and edge we know leads nowhere */
static ERROR = DFAState.fromState(2147483647);
atn;
/**
* The context cache maps all PredictionContext objects that are ==
* to a single cached copy. This cache is shared across all contexts
* in all ATNConfigs in all DFA states. We rebuild each ATNConfigSet
* to use only cached nodes/graphs in addDFAState(). We don't want to
* fill this during closure() since there are lots of contexts that
* pop up but are not used ever again. It also greatly slows down closure().
*
* This cache makes a huge difference in memory and a little bit in speed.
* For the Java grammar on java.*, it dropped the memory requirements
* at the end from 25M to 16M. We don't store any of the full context
* graphs in the DFA because they are limited to local context only,
* but apparently there's a lot of repetition there as well. We optimize
* the config contexts before storing the config set in the DFA states
* by literally rebuilding them with cached subgraphs only.
*
* I tried a cache for use during closure operations, that was
* whacked after each adaptivePredict(). It cost a little bit
* more time I think and doesn't save on the overall footprint
* so it's not worth the complexity.
*/
sharedContextCache;
constructor(atn, sharedContextCache) {
this.atn = atn;
this.sharedContextCache = sharedContextCache;
return this;
}
getCachedContext(context) {
if (!this.sharedContextCache) {
return context;
}
const visited = new HashMap(ObjectEqualityComparator.instance);
return getCachedPredictionContext(context, this.sharedContextCache, visited);
}
};