antlr4-c3
Version:
A code completion core implementation for ANTLR4 based parsers
1,568 lines (1,546 loc) • 55.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);
// index.ts
var index_exports = {};
__export(index_exports, {
ArrayType: () => ArrayType,
BaseSymbol: () => BaseSymbol,
BlockSymbol: () => BlockSymbol,
CandidatesCollection: () => CandidatesCollection,
ClassSymbol: () => ClassSymbol,
CodeCompletionCore: () => CodeCompletionCore,
DuplicateSymbolError: () => DuplicateSymbolError,
FieldSymbol: () => FieldSymbol,
FundamentalType: () => FundamentalType,
InterfaceSymbol: () => InterfaceSymbol,
LiteralSymbol: () => LiteralSymbol,
MemberVisibility: () => MemberVisibility,
MethodFlags: () => MethodFlags,
MethodSymbol: () => MethodSymbol,
Modifier: () => Modifier,
NamespaceSymbol: () => NamespaceSymbol,
ParameterSymbol: () => ParameterSymbol,
ReferenceKind: () => ReferenceKind,
RoutineSymbol: () => RoutineSymbol,
ScopedSymbol: () => ScopedSymbol,
SymbolTable: () => SymbolTable,
TypeAlias: () => TypeAlias,
TypeKind: () => TypeKind,
TypedSymbol: () => TypedSymbol,
VariableSymbol: () => VariableSymbol
});
module.exports = __toCommonJS(index_exports);
// src/types.ts
var MemberVisibility = /* @__PURE__ */ ((MemberVisibility2) => {
MemberVisibility2[MemberVisibility2["Unknown"] = 0] = "Unknown";
MemberVisibility2[MemberVisibility2["Open"] = 1] = "Open";
MemberVisibility2[MemberVisibility2["Public"] = 2] = "Public";
MemberVisibility2[MemberVisibility2["Protected"] = 3] = "Protected";
MemberVisibility2[MemberVisibility2["Private"] = 4] = "Private";
MemberVisibility2[MemberVisibility2["FilePrivate"] = 5] = "FilePrivate";
MemberVisibility2[MemberVisibility2["Library"] = 6] = "Library";
return MemberVisibility2;
})(MemberVisibility || {});
var Modifier = /* @__PURE__ */ ((Modifier3) => {
Modifier3[Modifier3["Static"] = 0] = "Static";
Modifier3[Modifier3["Final"] = 1] = "Final";
Modifier3[Modifier3["Sealed"] = 2] = "Sealed";
Modifier3[Modifier3["Abstract"] = 3] = "Abstract";
Modifier3[Modifier3["Deprecated"] = 4] = "Deprecated";
Modifier3[Modifier3["Virtual"] = 5] = "Virtual";
Modifier3[Modifier3["Const"] = 6] = "Const";
Modifier3[Modifier3["Overwritten"] = 7] = "Overwritten";
return Modifier3;
})(Modifier || {});
var TypeKind = /* @__PURE__ */ ((TypeKind2) => {
TypeKind2[TypeKind2["Unknown"] = 0] = "Unknown";
TypeKind2[TypeKind2["Integer"] = 1] = "Integer";
TypeKind2[TypeKind2["Float"] = 2] = "Float";
TypeKind2[TypeKind2["Number"] = 3] = "Number";
TypeKind2[TypeKind2["String"] = 4] = "String";
TypeKind2[TypeKind2["Char"] = 5] = "Char";
TypeKind2[TypeKind2["Boolean"] = 6] = "Boolean";
TypeKind2[TypeKind2["Class"] = 7] = "Class";
TypeKind2[TypeKind2["Interface"] = 8] = "Interface";
TypeKind2[TypeKind2["Array"] = 9] = "Array";
TypeKind2[TypeKind2["Map"] = 10] = "Map";
TypeKind2[TypeKind2["Enum"] = 11] = "Enum";
TypeKind2[TypeKind2["Alias"] = 12] = "Alias";
return TypeKind2;
})(TypeKind || {});
var ReferenceKind = /* @__PURE__ */ ((ReferenceKind3) => {
ReferenceKind3[ReferenceKind3["Irrelevant"] = 0] = "Irrelevant";
ReferenceKind3[ReferenceKind3["Pointer"] = 1] = "Pointer";
ReferenceKind3[ReferenceKind3["Reference"] = 2] = "Reference";
ReferenceKind3[ReferenceKind3["Instance"] = 3] = "Instance";
return ReferenceKind3;
})(ReferenceKind || {});
// src/BaseSymbol.ts
var BaseSymbol = class {
static {
__name(this, "BaseSymbol");
}
/** The name of the symbol or empty if anonymous. */
name;
/** Reference to the parse tree which contains this symbol. */
context;
modifiers = /* @__PURE__ */ new Set();
visibility = 0 /* Unknown */;
#parent;
constructor(name = "") {
this.name = name;
}
get parent() {
return this.#parent;
}
get firstSibling() {
if (!this.#parent) {
return void 0;
}
return this.#parent?.firstChild;
}
/**
* @returns the symbol before this symbol in its scope.
*/
get previousSibling() {
if (!this.#parent) {
return void 0;
}
if (!this.#parent) {
return this;
}
return this.#parent.previousSiblingOf(this);
}
/**
* @returns the symbol following this symbol in its scope.
*/
get nextSibling() {
return this.#parent?.nextSiblingOf(this);
}
get lastSibling() {
return this.#parent?.lastChild;
}
/**
* @returns the next symbol in definition order, regardless of the scope.
*/
get next() {
return this.#parent?.nextOf(this);
}
/**
* @returns the outermost entity (below the symbol table) that holds us.
*/
get root() {
let run = this.#parent;
while (run) {
if (!run.parent || this.isSymbolTable(run.parent)) {
return run;
}
run = run.parent;
}
return run;
}
/**
* @returns the symbol table we belong too or undefined if we are not yet assigned.
*/
get symbolTable() {
if (this.isSymbolTable(this)) {
return this;
}
let run = this.#parent;
while (run) {
if (this.isSymbolTable(run)) {
return run;
}
run = run.parent;
}
return void 0;
}
/**
* @returns the list of symbols from this one up to root.
*/
get symbolPath() {
const result = [];
let run = this;
while (run) {
result.push(run);
if (!run.parent) {
break;
}
run = run.parent;
}
return result;
}
/**
* This is rather an internal method and should rarely be used by external code.
*
* @param parent The new parent to use.
*/
setParent(parent) {
this.#parent = parent;
}
/**
* Remove this symbol from its parent scope.
*/
removeFromParent() {
this.#parent?.removeSymbol(this);
this.#parent = void 0;
}
/**
* Asynchronously looks up a symbol with a given name, in a bottom-up manner.
*
* @param name The name of the symbol to find.
* @param localOnly If true only child symbols are returned, otherwise also symbols from the parent of this symbol
* (recursively).
*
* @returns A promise resolving to the first symbol with a given name, in the order of appearance in this scope
* or any of the parent scopes (conditionally).
*/
async resolve(name, localOnly = false) {
return this.#parent?.resolve(name, localOnly);
}
/**
* Synchronously looks up a symbol with a given name, in a bottom-up manner.
*
* @param name The name of the symbol to find.
* @param localOnly If true only child symbols are returned, otherwise also symbols from the parent of this symbol
* (recursively).
*
* @returns the first symbol with a given name, in the order of appearance in this scope
* or any of the parent scopes (conditionally).
*/
resolveSync(name, localOnly = false) {
return this.#parent?.resolveSync(name, localOnly);
}
/**
* @param t The type of objects to return.
*
* @returns the next enclosing parent of the given type.
*/
getParentOfType(t) {
let run = this.#parent;
while (run) {
if (run instanceof t) {
return run;
}
run = run.parent;
}
return void 0;
}
/**
* Creates a qualified identifier from this symbol and its parent.
* If `full` is true then all parents are traversed in addition to this instance.
*
* @param separator The string to be used between the parts.
* @param full A flag indicating if the full path is to be returned.
* @param includeAnonymous Use a special string for empty scope names.
*
* @returns the constructed qualified identifier.
*/
qualifiedName(separator = ".", full = false, includeAnonymous = false) {
if (!includeAnonymous && this.name.length === 0) {
return "";
}
let result = this.name.length === 0 ? "<anonymous>" : this.name;
let run = this.#parent;
while (run) {
if (includeAnonymous || run.name.length > 0) {
result = (run.name.length === 0 ? "<anonymous>" : run.name) + separator + result;
}
if (!full || !run.parent) {
break;
}
run = run.parent;
}
return result;
}
/**
* Type guard to check for ISymbolTable.
*
* @param candidate The object to check.
*
* @returns true if the object is a symbol table.
*/
isSymbolTable(candidate) {
return candidate.info !== void 0;
}
};
// src/ArrayType.ts
var ArrayType = class extends BaseSymbol {
static {
__name(this, "ArrayType");
}
elementType;
size;
// > 0 if fixed length.
referenceKind;
constructor(name, referenceKind, elemType, size = 0) {
super(name);
this.referenceKind = referenceKind;
this.elementType = elemType;
this.size = size;
}
get baseTypes() {
return [];
}
get kind() {
return 9 /* Array */;
}
get reference() {
return this.referenceKind;
}
};
// src/DuplicateSymbolError.ts
var DuplicateSymbolError = class extends Error {
static {
__name(this, "DuplicateSymbolError");
}
};
// src/ScopedSymbol.ts
var ScopedSymbol = class _ScopedSymbol extends BaseSymbol {
static {
__name(this, "ScopedSymbol");
}
/** All child symbols in definition order. */
#children = [];
// All used child names. Used to detect name collisions.
#names = /* @__PURE__ */ new Map();
constructor(name = "") {
super(name);
}
/**
* @returns A promise resolving to all direct child symbols with a scope (e.g. classes in a module).
*/
get directScopes() {
return this.getSymbolsOfType(_ScopedSymbol);
}
get children() {
return this.#children;
}
get firstChild() {
if (this.#children.length > 0) {
return this.#children[0];
}
return void 0;
}
get lastChild() {
if (this.#children.length > 0) {
return this.#children[this.#children.length - 1];
}
return void 0;
}
clear() {
this.#children = [];
this.#names.clear();
}
/**
* Adds the given symbol to this scope. If it belongs already to a different scope
* it is removed from that before adding it here.
*
* @param symbol The symbol to add as a child.
*/
addSymbol(symbol) {
symbol.removeFromParent();
const symbolTable = this.symbolTable;
const count = this.#names.get(symbol.name);
if (!symbolTable || !symbolTable.options.allowDuplicateSymbols) {
if (count !== void 0) {
throw new DuplicateSymbolError("Attempt to add duplicate symbol '" + (symbol.name ?? "<anonymous>") + "'");
} else {
this.#names.set(symbol.name, 1);
}
const index = this.#children.indexOf(symbol);
if (index > -1) {
throw new DuplicateSymbolError("Attempt to add duplicate symbol '" + (symbol.name ?? "<anonymous>") + "'");
}
} else {
this.#names.set(symbol.name, count === void 0 ? 1 : count + 1);
}
this.#children.push(symbol);
symbol.setParent(this);
}
removeSymbol(symbol) {
const index = this.#children.indexOf(symbol);
if (index > -1) {
this.#children.splice(index, 1);
symbol.setParent(void 0);
const count = this.#names.get(symbol.name);
if (count !== void 0) {
if (count === 1) {
this.#names.delete(symbol.name);
} else {
this.#names.set(symbol.name, count - 1);
}
}
}
}
/**
* Asynchronously retrieves child symbols of a given type from this symbol.
*
* @param t The type of of the objects to return.
*
* @returns A promise resolving to all (nested) children of the given type.
*/
async getNestedSymbolsOfType(t) {
const result = [];
const childPromises = [];
this.#children.forEach((child) => {
if (child instanceof t) {
result.push(child);
}
if (child instanceof _ScopedSymbol) {
childPromises.push(child.getNestedSymbolsOfType(t));
}
});
const childSymbols = await Promise.all(childPromises);
childSymbols.forEach((entry) => {
result.push(...entry);
});
return result;
}
/**
* Synchronously retrieves child symbols of a given type from this symbol.
*
* @param t The type of of the objects to return.
*
* @returns A list of all (nested) children of the given type.
*/
getNestedSymbolsOfTypeSync(t) {
const result = [];
this.#children.forEach((child) => {
if (child instanceof t) {
result.push(child);
}
if (child instanceof _ScopedSymbol) {
result.push(...child.getNestedSymbolsOfTypeSync(t));
}
});
return result;
}
/**
* @param name If given only returns symbols with that name.
*
* @returns A promise resolving to symbols from this and all nested scopes in the order they were defined.
*/
async getAllNestedSymbols(name) {
const result = [];
const childPromises = [];
this.#children.forEach((child) => {
if (!name || child.name === name) {
result.push(child);
}
if (child instanceof _ScopedSymbol) {
childPromises.push(child.getAllNestedSymbols(name));
}
});
const childSymbols = await Promise.all(childPromises);
childSymbols.forEach((entry) => {
result.push(...entry);
});
return result;
}
/**
* @param name If given only returns symbols with that name.
*
* @returns A list of all symbols from this and all nested scopes in the order they were defined.
*/
getAllNestedSymbolsSync(name) {
const result = [];
this.#children.forEach((child) => {
if (!name || child.name === name) {
result.push(child);
}
if (child instanceof _ScopedSymbol) {
result.push(...child.getAllNestedSymbolsSync(name));
}
});
return result;
}
/**
* @param t The type of of the objects to return.
*
* @returns A promise resolving to direct children of a given type.
*/
getSymbolsOfType(t) {
return new Promise((resolve) => {
const result = [];
this.#children.forEach((child) => {
if (child instanceof t) {
result.push(child);
}
});
resolve(result);
});
}
/**
* TODO: add optional position dependency (only symbols defined before a given caret pos are viable).
*
* @param t The type of the objects to return.
* @param localOnly If true only child symbols are returned, otherwise also symbols from the parent of this symbol
* (recursively).
*
* @returns A promise resolving to all symbols of the the given type, accessible from this scope (if localOnly is
* false), within the owning symbol table.
*/
async getAllSymbols(t, localOnly = false) {
const result = [];
for (const child of this.#children) {
if (child instanceof t) {
result.push(child);
}
if (this.isNamespace(child)) {
const childSymbols = await child.getAllSymbols(t, true);
result.push(...childSymbols);
}
}
if (!localOnly) {
if (this.parent) {
const childSymbols = await this.getAllSymbols(t, true);
result.push(...childSymbols);
}
}
return result;
}
/**
* TODO: add optional position dependency (only symbols defined before a given caret pos are viable).
*
* @param t The type of the objects to return.
* @param localOnly If true only child symbols are returned, otherwise also symbols from the parent of this symbol
* (recursively).
*
* @returns A list with all symbols of the the given type, accessible from this scope (if localOnly is
* false), within the owning symbol table.
*/
getAllSymbolsSync(t, localOnly = false) {
const result = [];
for (const child of this.#children) {
if (child instanceof t) {
result.push(child);
}
if (this.isNamespace(child)) {
const childSymbols = child.getAllSymbolsSync(t, true);
result.push(...childSymbols);
}
}
if (!localOnly) {
if (this.parent) {
const childSymbols = this.getAllSymbolsSync(t, true);
result.push(...childSymbols);
}
}
return result;
}
/**
* @param name The name of the symbol to resolve.
* @param localOnly If true only child symbols are returned, otherwise also symbols from the parent of this symbol
* (recursively).
*
* @returns A promise resolving to the first symbol with a given name, in the order of appearance in this scope
* or any of the parent scopes (conditionally).
*/
async resolve(name, localOnly = false) {
return new Promise((resolve) => {
for (const child of this.#children) {
if (child.name === name) {
resolve(child);
return;
}
}
if (!localOnly) {
if (this.parent) {
resolve(this.parent.resolve(name, false));
return;
}
}
resolve(void 0);
});
}
/**
* @param name The name of the symbol to resolve.
* @param localOnly If true only child symbols are returned, otherwise also symbols from the parent of this symbol
* (recursively).
*
* @returns the first symbol with a given name, in the order of appearance in this scope
* or any of the parent scopes (conditionally).
*/
resolveSync(name, localOnly = false) {
for (const child of this.#children) {
if (child.name === name) {
return child;
}
}
if (!localOnly) {
if (this.parent) {
return this.parent.resolveSync(name, false);
}
}
return void 0;
}
/**
* @param path The path consisting of symbol names separator by `separator`.
* @param separator The character to separate path segments.
*
* @returns the symbol located at the given path through the symbol hierarchy.
*/
symbolFromPath(path, separator = ".") {
const elements = path.split(separator);
let index = 0;
if (elements[0] === this.name || elements[0].length === 0) {
++index;
}
let result = this;
while (index < elements.length) {
if (!(result instanceof _ScopedSymbol)) {
return void 0;
}
const child = result.children.find((candidate) => {
return candidate.name === elements[index];
});
if (!child) {
return void 0;
}
result = child;
++index;
}
return result;
}
/**
* @param child The child to search for.
*
* @returns the index of the given child symbol in the child list or -1 if it couldn't be found.
*/
indexOfChild(child) {
return this.#children.findIndex((value) => {
return value === child;
});
}
/**
* @param child The reference node.
*
* @returns the sibling symbol after the given child symbol, if one exists.
*/
nextSiblingOf(child) {
const index = this.indexOfChild(child);
if (index === -1 || index >= this.#children.length - 1) {
return void 0;
}
return this.#children[index + 1];
}
/**
* @param child The reference node.
*
* @returns the sibling symbol before the given child symbol, if one exists.
*/
previousSiblingOf(child) {
const index = this.indexOfChild(child);
if (index < 1) {
return void 0;
}
return this.#children[index - 1];
}
/**
* @param child The reference node.
*
* @returns the next symbol in definition order, regardless of the scope.
*/
nextOf(child) {
if (!child.parent) {
return void 0;
}
if (child.parent !== this) {
return child.parent.nextOf(child);
}
if (child instanceof _ScopedSymbol && child.children.length > 0) {
return child.children[0];
}
const sibling = this.nextSiblingOf(child);
if (sibling) {
return sibling;
}
return this.parent.nextOf(this);
}
isNamespace(candidate) {
return candidate.inline !== void 0 && candidate.attributes !== void 0;
}
};
// src/BlockSymbol.ts
var BlockSymbol = class extends ScopedSymbol {
static {
__name(this, "BlockSymbol");
}
};
// src/TypedSymbol.ts
var TypedSymbol = class extends BaseSymbol {
static {
__name(this, "TypedSymbol");
}
type;
constructor(name, type) {
super(name);
this.type = type;
}
};
// src/VariableSymbol.ts
var VariableSymbol = class extends TypedSymbol {
static {
__name(this, "VariableSymbol");
}
value;
constructor(name, value, type) {
super(name, type);
this.value = value;
}
};
// src/FieldSymbol.ts
var FieldSymbol = class extends VariableSymbol {
static {
__name(this, "FieldSymbol");
}
setter;
getter;
};
// src/ParameterSymbol.ts
var ParameterSymbol = class extends VariableSymbol {
static {
__name(this, "ParameterSymbol");
}
};
// src/RoutineSymbol.ts
var RoutineSymbol = class extends ScopedSymbol {
static {
__name(this, "RoutineSymbol");
}
returnType;
// Can be null if result is void.
constructor(name, returnType) {
super(name);
this.returnType = returnType;
}
getVariables(_localOnly = true) {
return this.getSymbolsOfType(VariableSymbol);
}
getParameters(_localOnly = true) {
return this.getSymbolsOfType(ParameterSymbol);
}
};
// src/MethodSymbol.ts
var MethodFlags = /* @__PURE__ */ ((MethodFlags2) => {
MethodFlags2[MethodFlags2["None"] = 0] = "None";
MethodFlags2[MethodFlags2["Virtual"] = 1] = "Virtual";
MethodFlags2[MethodFlags2["Const"] = 2] = "Const";
MethodFlags2[MethodFlags2["Overwritten"] = 4] = "Overwritten";
MethodFlags2[MethodFlags2["SetterOrGetter"] = 8] = "SetterOrGetter";
MethodFlags2[MethodFlags2["Explicit"] = 16] = "Explicit";
return MethodFlags2;
})(MethodFlags || {});
var MethodSymbol = class extends RoutineSymbol {
static {
__name(this, "MethodSymbol");
}
methodFlags = 0 /* None */;
};
// src/ClassSymbol.ts
var ClassSymbol = class extends ScopedSymbol {
static {
__name(this, "ClassSymbol");
}
isStruct = false;
reference = 0 /* Irrelevant */;
/** Usually only one member, unless the language supports multiple inheritance (like C++). */
// eslint-disable-next-line no-use-before-define
extends;
/** Typescript allows a class to implement a class, not only interfaces. */
// eslint-disable-next-line no-use-before-define
implements;
constructor(name, ext, impl) {
super(name);
this.extends = ext;
this.implements = impl;
}
get baseTypes() {
return this.extends;
}
get kind() {
return 7 /* Class */;
}
/**
* @param _includeInherited Not used.
*
* @returns a list of all methods.
*/
getMethods(_includeInherited = false) {
return this.getSymbolsOfType(MethodSymbol);
}
/**
* @param _includeInherited Not used.
*
* @returns all fields.
*/
getFields(_includeInherited = false) {
return this.getSymbolsOfType(FieldSymbol);
}
};
// src/FundamentalType.ts
var FundamentalType = class _FundamentalType {
static {
__name(this, "FundamentalType");
}
static integerType = new _FundamentalType("int", 1 /* Integer */, 3 /* Instance */);
static floatType = new _FundamentalType("float", 2 /* Float */, 3 /* Instance */);
static stringType = new _FundamentalType("string", 4 /* String */, 3 /* Instance */);
static boolType = new _FundamentalType("bool", 6 /* Boolean */, 3 /* Instance */);
name;
typeKind;
referenceKind;
constructor(name, typeKind = 0 /* Unknown */, referenceKind = 0 /* Irrelevant */) {
this.name = name;
this.typeKind = typeKind;
this.referenceKind = referenceKind;
}
get baseTypes() {
return [];
}
get kind() {
return this.typeKind;
}
get reference() {
return this.referenceKind;
}
};
// src/InterfaceSymbol.ts
var InterfaceSymbol = class extends ScopedSymbol {
static {
__name(this, "InterfaceSymbol");
}
reference = 0 /* Irrelevant */;
/** Typescript allows an interface to extend a class, not only interfaces. */
// eslint-disable-next-line no-use-before-define
extends;
constructor(name, ext) {
super(name);
this.extends = ext;
}
get baseTypes() {
return this.extends;
}
get kind() {
return 8 /* Interface */;
}
/**
* @param _includeInherited not used
*
* @returns a list of all methods.
*/
getMethods(_includeInherited = false) {
return this.getSymbolsOfType(MethodSymbol);
}
/**
* @param _includeInherited Not used.
*
* @returns all fields.
*/
getFields(_includeInherited = false) {
return this.getSymbolsOfType(FieldSymbol);
}
};
// src/LiteralSymbol.ts
var LiteralSymbol = class extends TypedSymbol {
static {
__name(this, "LiteralSymbol");
}
value;
constructor(name, value, type) {
super(name, type);
this.value = value;
}
};
// src/NamespaceSymbol.ts
var NamespaceSymbol = class extends ScopedSymbol {
static {
__name(this, "NamespaceSymbol");
}
inline;
attributes;
constructor(name, inline = false, attributes = []) {
super(name);
this.inline = inline;
this.attributes = attributes;
}
};
// src/TypeAlias.ts
var TypeAlias = class extends BaseSymbol {
static {
__name(this, "TypeAlias");
}
targetType;
constructor(name, target) {
super(name);
this.targetType = target;
}
get baseTypes() {
return [this.targetType];
}
get kind() {
return 12 /* Alias */;
}
get reference() {
return 0 /* Irrelevant */;
}
};
// src/CodeCompletionCore.ts
var import_antlr4ng = require("antlr4ng");
// src/utils.ts
var longestCommonPrefix = /* @__PURE__ */ __name((arr1, arr2) => {
if (!arr1 || !arr2) {
return [];
}
let i;
for (i = 0; i < Math.min(arr1.length, arr2.length); i++) {
if (arr1[i] !== arr2[i]) {
break;
}
}
return arr1.slice(0, i);
}, "longestCommonPrefix");
// src/CodeCompletionCore.ts
var CandidatesCollection = class {
static {
__name(this, "CandidatesCollection");
}
tokens = /* @__PURE__ */ new Map();
rules = /* @__PURE__ */ new Map();
};
var FollowSetWithPath = class {
static {
__name(this, "FollowSetWithPath");
}
intervals;
path = [];
following = [];
};
var CodeCompletionCore = class _CodeCompletionCore {
static {
__name(this, "CodeCompletionCore");
}
static followSetsByATN = /* @__PURE__ */ new Map();
static atnStateTypeMap = [
"invalid",
"basic",
"rule start",
"block start",
"plus block start",
"star block start",
"token start",
"rule stop",
"block end",
"star loop back",
"star loop entry",
"plus loop back",
"loop end"
];
// Debugging options. Print human readable ATN state and other info.
/** Not dependent on showDebugOutput. Prints the collected rules + tokens to terminal. */
showResult = false;
/** Enables printing ATN state info to terminal. */
showDebugOutput = false;
/** Only relevant when showDebugOutput is true. Enables transition printing for a state. */
debugOutputWithTransitions = false;
/** Also depends on showDebugOutput. Enables call stack printing for each rule recursion. */
showRuleStack = false;
/**
* Tailoring of the result:
* Tokens which should not appear in the candidates set.
*/
ignoredTokens;
/**
* Rules which replace any candidate token they contain.
* This allows to return descriptive rules (e.g. className, instead of ID/identifier).
*/
preferredRules;
/**
* Specify if preferred rules should translated top-down (higher index rule returns first) or
* bottom-up (lower index rule returns first).
*/
translateRulesTopDown = false;
parser;
atn;
vocabulary;
ruleNames;
tokens;
precedenceStack;
tokenStartIndex = 0;
statesProcessed = 0;
/**
* A mapping of rule index + token stream position to end token positions.
* A rule which has been visited before with the same input position will always produce the same output positions.
*/
shortcutMap = /* @__PURE__ */ new Map();
/** The collected candidates (rules and tokens). */
candidates = new CandidatesCollection();
constructor(parser) {
this.parser = parser;
this.atn = parser.atn;
this.vocabulary = parser.vocabulary;
this.ruleNames = parser.ruleNames;
this.ignoredTokens = /* @__PURE__ */ new Set();
this.preferredRules = /* @__PURE__ */ new Set();
}
/**
* This is the main entry point. The caret token index specifies the token stream index for the token which
* currently covers the caret (or any other position you want to get code completion candidates for).
* Optionally you can pass in a parser rule context which limits the ATN walk to only that or called rules.
* This can significantly speed up the retrieval process but might miss some candidates (if they are outside of
* the given context).
*
* @param caretTokenIndex The index of the token at the caret position.
* @param context An option parser rule context to limit the search space.
* @returns The collection of completion candidates.
*/
collectCandidates(caretTokenIndex, context) {
this.shortcutMap.clear();
this.candidates.rules.clear();
this.candidates.tokens.clear();
this.statesProcessed = 0;
this.precedenceStack = [];
this.tokenStartIndex = context?.start ? context.start.tokenIndex : 0;
const tokenStream = this.parser.tokenStream;
this.tokens = [];
let offset = this.tokenStartIndex;
while (true) {
const token = tokenStream.get(offset++);
if (!token) {
break;
}
if (token.channel === import_antlr4ng.Token.DEFAULT_CHANNEL) {
this.tokens.push(token);
if (token.tokenIndex >= caretTokenIndex || token.type === import_antlr4ng.Token.EOF) {
break;
}
}
if (token.type === import_antlr4ng.Token.EOF) {
break;
}
}
const callStack = [];
const startRule = context ? context.ruleIndex : 0;
this.processRule(this.atn.ruleToStartState[startRule], 0, callStack, 0, 0);
if (this.showResult) {
console.log(`States processed: ${this.statesProcessed}`);
console.log("\n\nCollected rules:\n");
for (const rule of this.candidates.rules) {
let path = "";
for (const token of rule[1].ruleList) {
path += this.ruleNames[token] + " ";
}
console.log(this.ruleNames[rule[0]] + ", path: ", path);
}
const sortedTokens = /* @__PURE__ */ new Set();
for (const token of this.candidates.tokens) {
let value = this.vocabulary.getDisplayName(token[0]) ?? "";
for (const following of token[1]) {
value += " " + this.vocabulary.getDisplayName(following);
}
sortedTokens.add(value);
}
console.log("\n\nCollected tokens:\n");
for (const symbol of sortedTokens) {
console.log(symbol);
}
console.log("\n\n");
}
return this.candidates;
}
/**
* Checks if the predicate associated with the given transition evaluates to true.
*
* @param transition The transition to check.
* @returns the evaluation result of the predicate.
*/
checkPredicate(transition) {
return transition.getPredicate().evaluate(this.parser, import_antlr4ng.ParserRuleContext.empty);
}
/**
* Walks the rule chain upwards or downwards (depending on translateRulesTopDown) to see if that matches any of the
* preferred rules. If found, that rule is added to the collection candidates and true is returned.
*
* @param ruleWithStartTokenList The list to convert.
* @returns true if any of the stack entries was converted.
*/
translateStackToRuleIndex(ruleWithStartTokenList) {
if (this.preferredRules.size === 0) {
return false;
}
if (this.translateRulesTopDown) {
for (let i = ruleWithStartTokenList.length - 1; i >= 0; i--) {
if (this.translateToRuleIndex(i, ruleWithStartTokenList)) {
return true;
}
}
} else {
for (let i = 0; i < ruleWithStartTokenList.length; i++) {
if (this.translateToRuleIndex(i, ruleWithStartTokenList)) {
return true;
}
}
}
return false;
}
/**
* Given the index of a rule from a rule chain, check if that matches any of the preferred rules. If it matches,
* that rule is added to the collection candidates and true is returned.
*
* @param i The rule index.
* @param ruleWithStartTokenList The list to check.
* @returns true if the specified rule is in the list of preferred rules.
*/
translateToRuleIndex(i, ruleWithStartTokenList) {
const { ruleIndex, startTokenIndex } = ruleWithStartTokenList[i];
if (this.preferredRules.has(ruleIndex)) {
const path = ruleWithStartTokenList.slice(0, i).map(({ ruleIndex: candidate }) => {
return candidate;
});
let addNew = true;
for (const rule of this.candidates.rules) {
if (rule[0] !== ruleIndex || rule[1].ruleList.length !== path.length) {
continue;
}
if (path.every((v, j) => {
return v === rule[1].ruleList[j];
})) {
addNew = false;
break;
}
}
if (addNew) {
this.candidates.rules.set(ruleIndex, {
startTokenIndex,
ruleList: path
});
if (this.showDebugOutput) {
console.log("=====> collected: ", this.ruleNames[ruleIndex]);
}
}
return true;
}
return false;
}
/**
* This method follows the given transition and collects all symbols within the same rule that directly follow it
* without intermediate transitions to other rules and only if there is a single symbol for a transition.
*
* @param transition The transition from which to start.
* @returns A list of toke types.
*/
getFollowingTokens(transition) {
const result = [];
const pipeline = [transition.target];
while (pipeline.length > 0) {
const state = pipeline.pop();
if (state) {
state.transitions.forEach((outgoing) => {
if (outgoing.transitionType === import_antlr4ng.Transition.ATOM) {
if (!outgoing.isEpsilon) {
const list = outgoing.label.toArray();
if (list.length === 1 && !this.ignoredTokens.has(list[0])) {
result.push(list[0]);
pipeline.push(outgoing.target);
}
} else {
pipeline.push(outgoing.target);
}
}
});
}
}
return result;
}
/**
* Entry point for the recursive follow set collection function.
*
* @param start Start state.
* @param stop Stop state.
* @returns Follow sets.
*/
determineFollowSets(start, stop) {
const sets = [];
const stateStack = [];
const ruleStack = [];
const isExhaustive = this.collectFollowSets(start, stop, sets, stateStack, ruleStack);
const combined = new import_antlr4ng.IntervalSet();
for (const set of sets) {
combined.addSet(set.intervals);
}
return { sets, isExhaustive, combined };
}
/**
* Collects possible tokens which could be matched following the given ATN state. This is essentially the same
* algorithm as used in the LL1Analyzer class, but here we consider predicates also and use no parser rule context.
*
* @param s The state to continue from.
* @param stopState The state which ends the collection routine.
* @param followSets A pass through parameter to add found sets to.
* @param stateStack A stack to avoid endless recursions.
* @param ruleStack The current rule stack.
* @returns true if the follow sets is exhaustive, i.e. we terminated before the rule end was reached, so no
* subsequent rules could add tokens
*/
collectFollowSets(s, stopState, followSets, stateStack, ruleStack) {
if (stateStack.find((x) => {
return x === s;
})) {
return true;
}
stateStack.push(s);
if (s === stopState || s.constructor.stateType === import_antlr4ng.ATNState.RULE_STOP) {
stateStack.pop();
return false;
}
let isExhaustive = true;
for (const transition of s.transitions) {
if (transition.transitionType === import_antlr4ng.Transition.RULE) {
const ruleTransition = transition;
if (ruleStack.indexOf(ruleTransition.target.ruleIndex) !== -1) {
continue;
}
ruleStack.push(ruleTransition.target.ruleIndex);
const ruleFollowSetsIsExhaustive = this.collectFollowSets(
transition.target,
stopState,
followSets,
stateStack,
ruleStack
);
ruleStack.pop();
if (!ruleFollowSetsIsExhaustive) {
const nextStateFollowSetsIsExhaustive = this.collectFollowSets(
ruleTransition.followState,
stopState,
followSets,
stateStack,
ruleStack
);
isExhaustive &&= nextStateFollowSetsIsExhaustive;
}
} else if (transition.transitionType === import_antlr4ng.Transition.PREDICATE) {
if (this.checkPredicate(transition)) {
const nextStateFollowSetsIsExhaustive = this.collectFollowSets(
transition.target,
stopState,
followSets,
stateStack,
ruleStack
);
isExhaustive &&= nextStateFollowSetsIsExhaustive;
}
} else if (transition.isEpsilon) {
const nextStateFollowSetsIsExhaustive = this.collectFollowSets(
transition.target,
stopState,
followSets,
stateStack,
ruleStack
);
isExhaustive &&= nextStateFollowSetsIsExhaustive;
} else if (transition.transitionType === import_antlr4ng.Transition.WILDCARD) {
const set = new FollowSetWithPath();
set.intervals = import_antlr4ng.IntervalSet.of(import_antlr4ng.Token.MIN_USER_TOKEN_TYPE, this.atn.maxTokenType);
set.path = ruleStack.slice();
followSets.push(set);
} else {
let label = transition.label;
if (label && label.length > 0) {
if (transition.transitionType === import_antlr4ng.Transition.NOT_SET) {
label = label.complement(import_antlr4ng.Token.MIN_USER_TOKEN_TYPE, this.atn.maxTokenType);
}
const set = new FollowSetWithPath();
set.intervals = label ?? new import_antlr4ng.IntervalSet();
set.path = ruleStack.slice();
set.following = this.getFollowingTokens(transition);
followSets.push(set);
}
}
}
stateStack.pop();
return isExhaustive;
}
/**
* Walks the ATN for a single rule only. It returns the token stream position for each path that could be matched
* in this rule.
* The result can be empty in case we hit only non-epsilon transitions that didn't match the current input or if we
* hit the caret position.
*
* @param startState The start state.
* @param tokenListIndex The token index we are currently at.
* @param callStack The stack that indicates where in the ATN we are currently.
* @param precedence The current precedence level.
* @param indentation A value to determine the current indentation when doing debug prints.
* @returns the set of token stream indexes (which depend on the ways that had to be taken).
*/
processRule(startState, tokenListIndex, callStack, precedence, indentation) {
let positionMap = this.shortcutMap.get(startState.ruleIndex);
if (!positionMap) {
positionMap = /* @__PURE__ */ new Map();
this.shortcutMap.set(startState.ruleIndex, positionMap);
} else {
if (positionMap.has(tokenListIndex)) {
if (this.showDebugOutput) {
console.log("=====> shortcut");
}
return positionMap.get(tokenListIndex);
}
}
const result = /* @__PURE__ */ new Set();
let setsPerState = _CodeCompletionCore.followSetsByATN.get(this.parser.constructor.name);
if (!setsPerState) {
setsPerState = /* @__PURE__ */ new Map();
_CodeCompletionCore.followSetsByATN.set(this.parser.constructor.name, setsPerState);
}
let followSets = setsPerState.get(startState.stateNumber);
if (!followSets) {
const stop = this.atn.ruleToStopState[startState.ruleIndex];
followSets = this.determineFollowSets(startState, stop);
setsPerState.set(startState.stateNumber, followSets);
}
const startTokenIndex = this.tokens[tokenListIndex].tokenIndex;
callStack.push({
startTokenIndex,
ruleIndex: startState.ruleIndex
});
if (tokenListIndex >= this.tokens.length - 1) {
if (this.preferredRules.has(startState.ruleIndex)) {
this.translateStackToRuleIndex(callStack);
} else {
for (const set of followSets.sets) {
const fullPath = callStack.slice();
const followSetPath = set.path.map((path) => {
return {
startTokenIndex,
ruleIndex: path
};
});
fullPath.push(...followSetPath);
if (!this.translateStackToRuleIndex(fullPath)) {
for (const symbol of set.intervals.toArray()) {
if (!this.ignoredTokens.has(symbol)) {
if (this.showDebugOutput) {
console.log("=====> collected: ", this.vocabulary.getDisplayName(symbol));
}
if (!this.candidates.tokens.has(symbol)) {
this.candidates.tokens.set(symbol, set.following);
} else {
if (this.candidates.tokens.get(symbol) !== set.following) {
this.candidates.tokens.set(symbol, []);
}
}
}
}
}
}
}
if (!followSets.isExhaustive) {
result.add(tokenListIndex);
}
callStack.pop();
return result;
} else {
const currentSymbol = this.tokens[tokenListIndex].type;
if (followSets.isExhaustive && !followSets.combined.contains(currentSymbol)) {
callStack.pop();
return result;
}
}
if (startState.isLeftRecursiveRule) {
this.precedenceStack.push(precedence);
}
const statePipeline = [];
let currentEntry;
statePipeline.push({ state: startState, tokenListIndex });
while (statePipeline.length > 0) {
currentEntry = statePipeline.pop();
++this.statesProcessed;
const currentSymbol = this.tokens[currentEntry.tokenListIndex].type;
const atCaret = currentEntry.tokenListIndex >= this.tokens.length - 1;
if (this.showDebugOutput) {
this.printDescription(
indentation,
currentEntry.state,
this.generateBaseDescription(currentEntry.state),
currentEntry.tokenListIndex
);
if (this.showRuleStack) {
this.printRuleState(callStack);
}
}
if (currentEntry.state.constructor.stateType === import_antlr4ng.ATNState.RULE_STOP) {
result.add(currentEntry.tokenListIndex);
continue;
}
const transitions = currentEntry.state.transitions;
for (const transition of transitions) {
switch (transition.transitionType) {
case import_antlr4ng.Transition.RULE: {
const ruleTransition = transition;
const endStatus = this.processRule(
transition.target,
currentEntry.tokenListIndex,
callStack,
ruleTransition.precedence,
indentation + 1
);
for (const position of endStatus) {
statePipeline.push({
state: transition.followState,
tokenListIndex: position
});
}
break;
}
case import_antlr4ng.Transition.PREDICATE: {
if (this.checkPredicate(transition)) {
statePipeline.push({
state: transition.target,
tokenListIndex: currentEntry.tokenListIndex
});
}
break;
}
case import_antlr4ng.Transition.PRECEDENCE: {
const predTransition = transition;
if (predTransition.precedence >= this.precedenceStack[this.precedenceStack.length - 1]) {
statePipeline.push({
state: transition.target,
tokenListIndex: currentEntry.tokenListIndex
});
}
break;
}
case import_antlr4ng.Transition.WILDCARD: {
if (atCaret) {
if (!this.translateStackToRuleIndex(callStack)) {
for (const token of import_antlr4ng.IntervalSet.of(import_antlr4ng.Token.MIN_USER_TOKEN_TYPE, this.atn.maxTokenType).toArray()) {
if (!this.ignoredTokens.has(token)) {
this.candidates.tokens.set(token, []);
}
}
}
} else {
statePipeline.push({
state: transition.target,
tokenListIndex: currentEntry.tokenListIndex + 1
});
}
break;
}
default: {
if (transition.isEpsilon) {
statePipeline.push({
state: transition.target,
tokenListIndex: currentEntry.tokenListIndex
});
continue;
}
let set = transition.label;
if (set && set.length > 0) {
if (transition.transitionType === import_antlr4ng.Transition.NOT_SET) {
set = set.complement(import_antlr4ng.Token.MIN_USER_TOKEN_TYPE, this.atn.maxTokenType);
}
if (atCaret) {
if (!this.translateStackToRuleIndex(callStack)) {
const list = set.toArray();
const hasTokenSequence = list.length === 1;
for (const symbol of list) {
if (!this.ignoredTokens.has(symbol)) {
if (this.showDebugOutput) {
console.log(
"=====> collected: ",
this.vocabulary.getDisplayName(symbol)
);
}
const followingTokens = hasTokenSequence ? this.getFollowingTokens(transition) : [];
if (!this.candidates.tokens.has(symbol)) {
this.candidates.tokens.set(symbol, followingTokens);
} else {
this.candidates.tokens.set(
symbol,
longestCommonPrefix(
followingTokens,
this.candidates.tokens.get(symbol)
)
);
}
}
}
}
} else if (set.contains(currentSymbol)) {
if (this.showDebugOutput) {
console.log("=====> consumed: ", this.vocabulary.getDisplayName(currentSymbol));
}
statePipeline.push({
state: transition.target,
tokenListIndex: currentEntry.tokenListIndex + 1
});
}
}
}
}
}
}
callStack.pop();
if (startState.isLeftRecursiveRule) {
this.precedenceStack.pop();
}
positionMap.set(tokenListIndex, result);
return result;
}
generateBaseDescription(state) {
const stateValue = state.stateNumber === import_antlr4ng.ATNState.INVALID_STATE_NUMBER ? "Invalid" : state.stateNumber;
const typeName = _CodeCompletionCore.atnStateTypeMap[state.constructor.stateType];
return `[${stateValue} ${typeName}] in ${this.ruleNames[state.ruleIndex]}`;
}
printDescription(indentation, state, baseDescription, tokenIndex) {
const indent = " ".repeat(indentation);
let output = indent;
let transitionDescription = "";
if (this.debugOutputWithTransitions) {
for (const transition of state.transitions) {
let labels = "";
const symbols = transition.label ? transition.label.toArray() : [];
if (symbols.length > 2) {
labels = this.vocabulary.getDisplayName(symbols[0]) + " .. " + this.vocabulary.getDisplayName(symbols[symbols.length - 1]);
} else {
for (const symbol of symbols) {
if (labels.length > 0) {
labels += ", ";
}
labels += this.vocabulary.getDisplayName(symbol);
}
}
if (labels.length === 0) {
labels = "\u03B5";
}
const typeName = _CodeCompletionCore.atnStateTypeMap[transition.target.constructor.stateType];
transitionDescription += `
${indent} (${labels}) [${transition.target.stateNumber} ${typeName}] in ${this.ruleNames[transition.target.ruleIndex]}`;
}
}
if (tokenIndex >= this.tokens.length - 1) {
output += `<<${this.tokenStartIndex + tokenIndex}>> `;
} else {
output += `<${this.tokenStartIndex + tokenIndex}> `;
}
console.log(output + "Current state: " + ba