+103
@@ -0,0 +1,103 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import type { Range } from 'vscode-languageserver-types';
|
||||
import type { AstNode, AstReflection, CstNode, MultiReference, Reference, ReferenceInfo } from '../syntax-tree.js';
|
||||
import type { Stream, TreeStream } from './stream.js';
|
||||
import type { LangiumDocument } from '../workspace/documents.js';
|
||||
/**
|
||||
* Link the `$container` and other related properties of every AST node that is directly contained
|
||||
* in the given `node`.
|
||||
*/
|
||||
export declare function linkContentToContainer(node: AstNode, options?: {
|
||||
/**
|
||||
* If true, the function will also link the content of the contained nodes.
|
||||
* Otherwise, only the immediate children of the given node are linked to their container.
|
||||
*/
|
||||
deep?: boolean;
|
||||
}): void;
|
||||
/**
|
||||
* Walk along the hierarchy of containers from the given AST node to the root and return the first
|
||||
* node that matches the type predicate. If the start node itself matches, it is returned.
|
||||
* If no container matches, `undefined` is returned.
|
||||
*/
|
||||
export declare function getContainerOfType<T extends AstNode>(node: AstNode | undefined, typePredicate: (n: AstNode) => n is T): T | undefined;
|
||||
/**
|
||||
* Walk along the hierarchy of containers from the given AST node to the root and check for existence
|
||||
* of a container that matches the given predicate. The start node is included in the checks.
|
||||
*/
|
||||
export declare function hasContainerOfType(node: AstNode | undefined, predicate: (n: AstNode) => boolean): boolean;
|
||||
/**
|
||||
* Retrieve the document in which the given AST node is contained. A reference to the document is
|
||||
* usually held by the root node of the AST.
|
||||
*
|
||||
* @throws an error if the node is not contained in a document.
|
||||
*/
|
||||
export declare function getDocument<T extends AstNode = AstNode>(node: AstNode): LangiumDocument<T>;
|
||||
/**
|
||||
* Returns the root node of the given AST node by following the `$container` references.
|
||||
*/
|
||||
export declare function findRootNode(node: AstNode): AstNode;
|
||||
/**
|
||||
* Returns all AST nodes that are referenced by the given reference or multi-reference.
|
||||
*/
|
||||
export declare function getReferenceNodes(reference: Reference | MultiReference): AstNode[];
|
||||
export interface AstStreamOptions {
|
||||
/**
|
||||
* Optional target range that the nodes in the stream need to intersect
|
||||
*/
|
||||
range?: Range;
|
||||
}
|
||||
/**
|
||||
* Create a stream of all AST nodes that are directly contained in the given node. This includes
|
||||
* single-valued as well as multi-valued (array) properties.
|
||||
*/
|
||||
export declare function streamContents(node: AstNode, options?: AstStreamOptions): Stream<AstNode>;
|
||||
/**
|
||||
* Create a stream of all AST nodes that are directly and indirectly contained in the given root node.
|
||||
* This does not include the root node itself.
|
||||
*/
|
||||
export declare function streamAllContents(root: AstNode, options?: AstStreamOptions): TreeStream<AstNode>;
|
||||
/**
|
||||
* Create a stream of all AST nodes that are directly and indirectly contained in the given root node,
|
||||
* including the root node itself.
|
||||
*/
|
||||
export declare function streamAst(root: AstNode, options?: AstStreamOptions): TreeStream<AstNode>;
|
||||
/**
|
||||
* Create a stream of all cross-references that are held by the given AST node. This includes
|
||||
* single-valued as well as multi-valued (array) properties.
|
||||
*/
|
||||
export declare function streamReferences(node: AstNode): Stream<ReferenceInfo>;
|
||||
/**
|
||||
* Assigns all mandatory AST properties to the specified node.
|
||||
*
|
||||
* @param reflection Reflection object used to gather mandatory properties for the node.
|
||||
* @param node Specified node is modified in place and properties are directly assigned.
|
||||
*/
|
||||
export declare function assignMandatoryProperties(reflection: AstReflection, node: AstNode): void;
|
||||
/**
|
||||
* Creates a deep copy of the specified AST node.
|
||||
* The resulting copy will only contain semantically relevant information, such as the `$type` property and AST properties.
|
||||
*
|
||||
* @param node The AST node to deeply copy.
|
||||
* @param buildReference References are not copied, instead this function is called to rebuild them.
|
||||
* @param trace For the sake of tracking copied nodes and their originals a `trace` map can be provided (optional).
|
||||
*/
|
||||
export declare function copyAstNode<T extends AstNode = AstNode>(node: T, buildReference: (node: AstNode, property: string, refNode: CstNode | undefined, refText: string, origReference: Reference<AstNode>) => Reference<AstNode>, trace?: Map<AstNode, AstNode>): T;
|
||||
/**
|
||||
* Recursively makes all properties of an AstNode optional, except for those
|
||||
* that start with a dollar sign ($) or are of type boolean or are of type array.
|
||||
* If the type is a Reference or an Array, it applies the transformation recursively
|
||||
* to the inner type.
|
||||
* Otherwise the type is returned as is.
|
||||
*
|
||||
* @template T - The type to be transformed.
|
||||
*/
|
||||
export type DeepPartialAstNode<T> = T extends Reference<infer U extends AstNode> ? Reference<DeepPartialAstNode<U>> : T extends AstNode ? {
|
||||
[K in keyof T as K extends `$${string}` | (T[K] extends (boolean | any[]) ? K : never) ? K : never]: DeepPartialAstNode<T[K]>;
|
||||
} & {
|
||||
[K in keyof T as K extends `$${string}` ? never : T[K] extends (boolean | any[]) ? never : K]?: DeepPartialAstNode<T[K]>;
|
||||
} : T extends Array<infer U> ? Array<DeepPartialAstNode<U>> : T;
|
||||
//# sourceMappingURL=ast-utils.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ast-utils.d.ts","sourceRoot":"","sources":["../../src/utils/ast-utils.ts"],"names":[],"mappings":"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"}
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import { isAstNode, isMultiReference, isReference } from '../syntax-tree.js';
|
||||
import { DONE_RESULT, StreamImpl, TreeStreamImpl } from './stream.js';
|
||||
import { inRange } from './cst-utils.js';
|
||||
/**
|
||||
* Link the `$container` and other related properties of every AST node that is directly contained
|
||||
* in the given `node`.
|
||||
*/
|
||||
export function linkContentToContainer(node, options = {}) {
|
||||
for (const [name, value] of Object.entries(node)) {
|
||||
if (!name.startsWith('$')) {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((item, index) => {
|
||||
if (isAstNode(item)) {
|
||||
item.$container = node;
|
||||
item.$containerProperty = name;
|
||||
item.$containerIndex = index;
|
||||
if (options.deep) {
|
||||
linkContentToContainer(item, options);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (isAstNode(value)) {
|
||||
value.$container = node;
|
||||
value.$containerProperty = name;
|
||||
if (options.deep) {
|
||||
linkContentToContainer(value, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Walk along the hierarchy of containers from the given AST node to the root and return the first
|
||||
* node that matches the type predicate. If the start node itself matches, it is returned.
|
||||
* If no container matches, `undefined` is returned.
|
||||
*/
|
||||
export function getContainerOfType(node, typePredicate) {
|
||||
let item = node;
|
||||
while (item) {
|
||||
if (typePredicate(item)) {
|
||||
return item;
|
||||
}
|
||||
item = item.$container;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Walk along the hierarchy of containers from the given AST node to the root and check for existence
|
||||
* of a container that matches the given predicate. The start node is included in the checks.
|
||||
*/
|
||||
export function hasContainerOfType(node, predicate) {
|
||||
let item = node;
|
||||
while (item) {
|
||||
if (predicate(item)) {
|
||||
return true;
|
||||
}
|
||||
item = item.$container;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Retrieve the document in which the given AST node is contained. A reference to the document is
|
||||
* usually held by the root node of the AST.
|
||||
*
|
||||
* @throws an error if the node is not contained in a document.
|
||||
*/
|
||||
export function getDocument(node) {
|
||||
const rootNode = findRootNode(node);
|
||||
const result = rootNode.$document;
|
||||
if (!result) {
|
||||
throw new Error('AST node has no document.');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Returns the root node of the given AST node by following the `$container` references.
|
||||
*/
|
||||
export function findRootNode(node) {
|
||||
while (node.$container) {
|
||||
node = node.$container;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
/**
|
||||
* Returns all AST nodes that are referenced by the given reference or multi-reference.
|
||||
*/
|
||||
export function getReferenceNodes(reference) {
|
||||
if (isReference(reference)) {
|
||||
return reference.ref ? [reference.ref] : [];
|
||||
}
|
||||
else if (isMultiReference(reference)) {
|
||||
return reference.items.map(item => item.ref);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
/**
|
||||
* Create a stream of all AST nodes that are directly contained in the given node. This includes
|
||||
* single-valued as well as multi-valued (array) properties.
|
||||
*/
|
||||
export function streamContents(node, options) {
|
||||
if (!node) {
|
||||
throw new Error('Node must be an AstNode.');
|
||||
}
|
||||
const range = options?.range;
|
||||
return new StreamImpl(() => ({
|
||||
keys: Object.keys(node),
|
||||
keyIndex: 0,
|
||||
arrayIndex: 0
|
||||
}), state => {
|
||||
while (state.keyIndex < state.keys.length) {
|
||||
const property = state.keys[state.keyIndex];
|
||||
if (!property.startsWith('$')) {
|
||||
const value = node[property];
|
||||
if (isAstNode(value)) {
|
||||
state.keyIndex++;
|
||||
if (isAstNodeInRange(value, range)) {
|
||||
return { done: false, value };
|
||||
}
|
||||
}
|
||||
else if (Array.isArray(value)) {
|
||||
while (state.arrayIndex < value.length) {
|
||||
const index = state.arrayIndex++;
|
||||
const element = value[index];
|
||||
if (isAstNode(element) && isAstNodeInRange(element, range)) {
|
||||
return { done: false, value: element };
|
||||
}
|
||||
}
|
||||
state.arrayIndex = 0;
|
||||
}
|
||||
}
|
||||
state.keyIndex++;
|
||||
}
|
||||
return DONE_RESULT;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Create a stream of all AST nodes that are directly and indirectly contained in the given root node.
|
||||
* This does not include the root node itself.
|
||||
*/
|
||||
export function streamAllContents(root, options) {
|
||||
if (!root) {
|
||||
throw new Error('Root node must be an AstNode.');
|
||||
}
|
||||
return new TreeStreamImpl(root, node => streamContents(node, options));
|
||||
}
|
||||
/**
|
||||
* Create a stream of all AST nodes that are directly and indirectly contained in the given root node,
|
||||
* including the root node itself.
|
||||
*/
|
||||
export function streamAst(root, options) {
|
||||
if (!root) {
|
||||
throw new Error('Root node must be an AstNode.');
|
||||
}
|
||||
else if (options?.range && !isAstNodeInRange(root, options.range)) {
|
||||
// Return an empty stream if the root node isn't in range
|
||||
return new TreeStreamImpl(root, () => []);
|
||||
}
|
||||
return new TreeStreamImpl(root, node => streamContents(node, options), { includeRoot: true });
|
||||
}
|
||||
function isAstNodeInRange(astNode, range) {
|
||||
if (!range) {
|
||||
return true;
|
||||
}
|
||||
const nodeRange = astNode.$cstNode?.range;
|
||||
if (!nodeRange) {
|
||||
return false;
|
||||
}
|
||||
return inRange(nodeRange, range);
|
||||
}
|
||||
/**
|
||||
* Create a stream of all cross-references that are held by the given AST node. This includes
|
||||
* single-valued as well as multi-valued (array) properties.
|
||||
*/
|
||||
export function streamReferences(node) {
|
||||
return new StreamImpl(() => ({
|
||||
keys: Object.keys(node),
|
||||
keyIndex: 0,
|
||||
arrayIndex: 0
|
||||
}), state => {
|
||||
while (state.keyIndex < state.keys.length) {
|
||||
const property = state.keys[state.keyIndex];
|
||||
if (!property.startsWith('$')) {
|
||||
const value = node[property];
|
||||
if (isReference(value) || isMultiReference(value)) {
|
||||
state.keyIndex++;
|
||||
return { done: false, value: { reference: value, container: node, property } };
|
||||
}
|
||||
else if (Array.isArray(value)) {
|
||||
while (state.arrayIndex < value.length) {
|
||||
const index = state.arrayIndex++;
|
||||
const element = value[index];
|
||||
if (isReference(element) || isMultiReference(value)) {
|
||||
return { done: false, value: { reference: element, container: node, property, index } };
|
||||
}
|
||||
}
|
||||
state.arrayIndex = 0;
|
||||
}
|
||||
}
|
||||
state.keyIndex++;
|
||||
}
|
||||
return DONE_RESULT;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Assigns all mandatory AST properties to the specified node.
|
||||
*
|
||||
* @param reflection Reflection object used to gather mandatory properties for the node.
|
||||
* @param node Specified node is modified in place and properties are directly assigned.
|
||||
*/
|
||||
export function assignMandatoryProperties(reflection, node) {
|
||||
const typeMetaData = reflection.getTypeMetaData(node.$type);
|
||||
const genericNode = node;
|
||||
for (const property of Object.values(typeMetaData.properties)) {
|
||||
// Only set the value if the property is not already set and if it has a default value
|
||||
if (property.defaultValue !== undefined && genericNode[property.name] === undefined) {
|
||||
genericNode[property.name] = copyDefaultValue(property.defaultValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
function copyDefaultValue(propertyType) {
|
||||
if (Array.isArray(propertyType)) {
|
||||
return [...propertyType.map(copyDefaultValue)];
|
||||
}
|
||||
else {
|
||||
return propertyType;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Creates a deep copy of the specified AST node.
|
||||
* The resulting copy will only contain semantically relevant information, such as the `$type` property and AST properties.
|
||||
*
|
||||
* @param node The AST node to deeply copy.
|
||||
* @param buildReference References are not copied, instead this function is called to rebuild them.
|
||||
* @param trace For the sake of tracking copied nodes and their originals a `trace` map can be provided (optional).
|
||||
*/
|
||||
export function copyAstNode(node, buildReference, trace) {
|
||||
const copy = { $type: node.$type };
|
||||
if (trace) {
|
||||
trace.set(node, copy);
|
||||
trace.set(copy, node);
|
||||
}
|
||||
for (const [name, value] of Object.entries(node)) {
|
||||
if (!name.startsWith('$')) {
|
||||
if (isAstNode(value)) {
|
||||
copy[name] = copyAstNode(value, buildReference, trace);
|
||||
}
|
||||
else if (isReference(value)) {
|
||||
copy[name] = buildReference(copy, name, value.$refNode, value.$refText, value);
|
||||
}
|
||||
else if (Array.isArray(value)) {
|
||||
const copiedArray = [];
|
||||
for (const element of value) {
|
||||
if (isAstNode(element)) {
|
||||
copiedArray.push(copyAstNode(element, buildReference, trace));
|
||||
}
|
||||
else if (isReference(element)) {
|
||||
copiedArray.push(buildReference(copy, name, element.$refNode, element.$refText, element));
|
||||
}
|
||||
else {
|
||||
copiedArray.push(element);
|
||||
}
|
||||
}
|
||||
copy[name] = copiedArray;
|
||||
}
|
||||
else {
|
||||
copy[name] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
linkContentToContainer(copy, { deep: true });
|
||||
return copy;
|
||||
}
|
||||
//# sourceMappingURL=ast-utils.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+74
@@ -0,0 +1,74 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2023 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import type { Disposable } from './disposable.js';
|
||||
import type { URI } from './uri-utils.js';
|
||||
import type { LangiumSharedCoreServices } from '../services.js';
|
||||
import type { DocumentState } from '../workspace/documents.js';
|
||||
export declare abstract class DisposableCache implements Disposable {
|
||||
protected toDispose: Disposable[];
|
||||
protected isDisposed: boolean;
|
||||
onDispose(disposable: Disposable): void;
|
||||
dispose(): void;
|
||||
protected throwIfDisposed(): void;
|
||||
abstract clear(): void;
|
||||
}
|
||||
export declare class SimpleCache<K, V> extends DisposableCache {
|
||||
protected readonly cache: Map<K, V>;
|
||||
has(key: K): boolean;
|
||||
set(key: K, value: V): void;
|
||||
get(key: K): V | undefined;
|
||||
get(key: K, provider: () => V): V;
|
||||
delete(key: K): boolean;
|
||||
clear(): void;
|
||||
}
|
||||
export declare class ContextCache<Context, Key, Value, ContextKey = Context> extends DisposableCache {
|
||||
private readonly cache;
|
||||
private readonly converter;
|
||||
constructor(converter?: (input: Context) => ContextKey);
|
||||
has(contextKey: Context, key: Key): boolean;
|
||||
set(contextKey: Context, key: Key, value: Value): void;
|
||||
get(contextKey: Context, key: Key): Value | undefined;
|
||||
get(contextKey: Context, key: Key, provider: () => Value): Value;
|
||||
delete(contextKey: Context, key: Key): boolean;
|
||||
clear(): void;
|
||||
clear(contextKey: Context): void;
|
||||
protected cacheForContext(contextKey: Context): Map<Key, Value>;
|
||||
}
|
||||
/**
|
||||
* Every key/value pair in this cache is scoped to a document.
|
||||
* If this document is changed or deleted, all associated key/value pairs are deleted.
|
||||
*/
|
||||
export declare class DocumentCache<K, V> extends ContextCache<URI | string, K, V, string> {
|
||||
/**
|
||||
* Creates a new document cache.
|
||||
*
|
||||
* @param sharedServices Service container instance to hook into document lifecycle events.
|
||||
* @param state Optional document state on which the cache should evict.
|
||||
* If not provided, the cache will evict on `DocumentBuilder#onUpdate`.
|
||||
* *Deleted* documents are considered in both cases.
|
||||
*
|
||||
* Providing a state here will use `DocumentBuilder#onDocumentPhase` instead,
|
||||
* which triggers on all documents that have been affected by this change, assuming that the
|
||||
* state is `DocumentState.Linked` or a later state.
|
||||
*/
|
||||
constructor(sharedServices: LangiumSharedCoreServices, state?: DocumentState);
|
||||
}
|
||||
/**
|
||||
* Every key/value pair in this cache is scoped to the whole workspace.
|
||||
* If any document in the workspace is added, changed or deleted, the whole cache is evicted.
|
||||
*/
|
||||
export declare class WorkspaceCache<K, V> extends SimpleCache<K, V> {
|
||||
/**
|
||||
* Creates a new workspace cache.
|
||||
*
|
||||
* @param sharedServices Service container instance to hook into document lifecycle events.
|
||||
* @param state Optional document state on which the cache should evict.
|
||||
* If not provided, the cache will evict on `DocumentBuilder#onUpdate`.
|
||||
* *Deleted* documents are considered in both cases.
|
||||
*/
|
||||
constructor(sharedServices: LangiumSharedCoreServices, state?: DocumentState);
|
||||
}
|
||||
//# sourceMappingURL=caching.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"caching.d.ts","sourceRoot":"","sources":["../../src/utils/caching.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,8BAAsB,eAAgB,YAAW,UAAU;IAEvD,SAAS,CAAC,SAAS,EAAE,UAAU,EAAE,CAAM;IACvC,SAAS,CAAC,UAAU,UAAS;IAE7B,SAAS,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;IAIvC,OAAO,IAAI,IAAI;IAOf,SAAS,CAAC,eAAe,IAAI,IAAI;IAMjC,QAAQ,CAAC,KAAK,IAAI,IAAI;CACzB;AAED,qBAAa,WAAW,CAAC,CAAC,EAAE,CAAC,CAAE,SAAQ,eAAe;IAClD,SAAS,CAAC,QAAQ,CAAC,KAAK,YAAmB;IAE3C,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAKpB,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAK3B,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS;IAC1B,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC;IAcjC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAKvB,KAAK,IAAI,IAAI;CAIhB;AAED,qBAAa,YAAY,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAE,SAAQ,eAAe;IAExF,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoD;IAC1E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA2C;gBAEzD,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,UAAU;IAKtD,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO;IAK3C,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI;IAKtD,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,KAAK,GAAG,SAAS;IACrD,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,KAAK,GAAG,KAAK;IAehE,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO;IAK9C,KAAK,IAAI,IAAI;IACb,KAAK,CAAC,UAAU,EAAE,OAAO,GAAG,IAAI;IAWhC,SAAS,CAAC,eAAe,CAAC,UAAU,EAAE,OAAO,GAAG,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC;CASlE;AAED;;;GAGG;AACH,qBAAa,aAAa,CAAC,CAAC,EAAE,CAAC,CAAE,SAAQ,YAAY,CAAC,GAAG,GAAG,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAE7E;;;;;;;;;;;OAWG;gBACS,cAAc,EAAE,yBAAyB,EAAE,KAAK,CAAC,EAAE,aAAa;CAoB/E;AAED;;;GAGG;AACH,qBAAa,cAAc,CAAC,CAAC,EAAE,CAAC,CAAE,SAAQ,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;IAEvD;;;;;;;OAOG;gBACS,cAAc,EAAE,yBAAyB,EAAE,KAAK,CAAC,EAAE,aAAa;CAiB/E"}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2023 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
export class DisposableCache {
|
||||
constructor() {
|
||||
this.toDispose = [];
|
||||
this.isDisposed = false;
|
||||
}
|
||||
onDispose(disposable) {
|
||||
this.toDispose.push(disposable);
|
||||
}
|
||||
dispose() {
|
||||
this.throwIfDisposed();
|
||||
this.clear();
|
||||
this.isDisposed = true;
|
||||
this.toDispose.forEach(disposable => disposable.dispose());
|
||||
}
|
||||
throwIfDisposed() {
|
||||
if (this.isDisposed) {
|
||||
throw new Error('This cache has already been disposed');
|
||||
}
|
||||
}
|
||||
}
|
||||
export class SimpleCache extends DisposableCache {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.cache = new Map();
|
||||
}
|
||||
has(key) {
|
||||
this.throwIfDisposed();
|
||||
return this.cache.has(key);
|
||||
}
|
||||
set(key, value) {
|
||||
this.throwIfDisposed();
|
||||
this.cache.set(key, value);
|
||||
}
|
||||
get(key, provider) {
|
||||
this.throwIfDisposed();
|
||||
if (this.cache.has(key)) {
|
||||
return this.cache.get(key);
|
||||
}
|
||||
else if (provider) {
|
||||
const value = provider();
|
||||
this.cache.set(key, value);
|
||||
return value;
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
delete(key) {
|
||||
this.throwIfDisposed();
|
||||
return this.cache.delete(key);
|
||||
}
|
||||
clear() {
|
||||
this.throwIfDisposed();
|
||||
this.cache.clear();
|
||||
}
|
||||
}
|
||||
export class ContextCache extends DisposableCache {
|
||||
constructor(converter) {
|
||||
super();
|
||||
this.cache = new Map();
|
||||
this.converter = converter ?? (value => value);
|
||||
}
|
||||
has(contextKey, key) {
|
||||
this.throwIfDisposed();
|
||||
return this.cacheForContext(contextKey).has(key);
|
||||
}
|
||||
set(contextKey, key, value) {
|
||||
this.throwIfDisposed();
|
||||
this.cacheForContext(contextKey).set(key, value);
|
||||
}
|
||||
get(contextKey, key, provider) {
|
||||
this.throwIfDisposed();
|
||||
const contextCache = this.cacheForContext(contextKey);
|
||||
if (contextCache.has(key)) {
|
||||
return contextCache.get(key);
|
||||
}
|
||||
else if (provider) {
|
||||
const value = provider();
|
||||
contextCache.set(key, value);
|
||||
return value;
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
delete(contextKey, key) {
|
||||
this.throwIfDisposed();
|
||||
return this.cacheForContext(contextKey).delete(key);
|
||||
}
|
||||
clear(contextKey) {
|
||||
this.throwIfDisposed();
|
||||
if (contextKey) {
|
||||
const mapKey = this.converter(contextKey);
|
||||
this.cache.delete(mapKey);
|
||||
}
|
||||
else {
|
||||
this.cache.clear();
|
||||
}
|
||||
}
|
||||
cacheForContext(contextKey) {
|
||||
const mapKey = this.converter(contextKey);
|
||||
let documentCache = this.cache.get(mapKey);
|
||||
if (!documentCache) {
|
||||
documentCache = new Map();
|
||||
this.cache.set(mapKey, documentCache);
|
||||
}
|
||||
return documentCache;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Every key/value pair in this cache is scoped to a document.
|
||||
* If this document is changed or deleted, all associated key/value pairs are deleted.
|
||||
*/
|
||||
export class DocumentCache extends ContextCache {
|
||||
/**
|
||||
* Creates a new document cache.
|
||||
*
|
||||
* @param sharedServices Service container instance to hook into document lifecycle events.
|
||||
* @param state Optional document state on which the cache should evict.
|
||||
* If not provided, the cache will evict on `DocumentBuilder#onUpdate`.
|
||||
* *Deleted* documents are considered in both cases.
|
||||
*
|
||||
* Providing a state here will use `DocumentBuilder#onDocumentPhase` instead,
|
||||
* which triggers on all documents that have been affected by this change, assuming that the
|
||||
* state is `DocumentState.Linked` or a later state.
|
||||
*/
|
||||
constructor(sharedServices, state) {
|
||||
super(uri => uri.toString());
|
||||
if (state) {
|
||||
this.toDispose.push(sharedServices.workspace.DocumentBuilder.onDocumentPhase(state, document => {
|
||||
this.clear(document.uri.toString());
|
||||
}));
|
||||
this.toDispose.push(sharedServices.workspace.DocumentBuilder.onUpdate((_changed, deleted) => {
|
||||
for (const uri of deleted) { // react only on deleted documents
|
||||
this.clear(uri);
|
||||
}
|
||||
}));
|
||||
}
|
||||
else {
|
||||
this.toDispose.push(sharedServices.workspace.DocumentBuilder.onUpdate((changed, deleted) => {
|
||||
const allUris = changed.concat(deleted); // react on both changed and deleted documents
|
||||
for (const uri of allUris) {
|
||||
this.clear(uri);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Every key/value pair in this cache is scoped to the whole workspace.
|
||||
* If any document in the workspace is added, changed or deleted, the whole cache is evicted.
|
||||
*/
|
||||
export class WorkspaceCache extends SimpleCache {
|
||||
/**
|
||||
* Creates a new workspace cache.
|
||||
*
|
||||
* @param sharedServices Service container instance to hook into document lifecycle events.
|
||||
* @param state Optional document state on which the cache should evict.
|
||||
* If not provided, the cache will evict on `DocumentBuilder#onUpdate`.
|
||||
* *Deleted* documents are considered in both cases.
|
||||
*/
|
||||
constructor(sharedServices, state) {
|
||||
super();
|
||||
if (state) {
|
||||
this.toDispose.push(sharedServices.workspace.DocumentBuilder.onBuildPhase(state, () => {
|
||||
this.clear();
|
||||
}));
|
||||
this.toDispose.push(sharedServices.workspace.DocumentBuilder.onUpdate((_changed, deleted) => {
|
||||
if (deleted.length > 0) { // react only on deleted documents
|
||||
this.clear();
|
||||
}
|
||||
}));
|
||||
}
|
||||
else {
|
||||
this.toDispose.push(sharedServices.workspace.DocumentBuilder.onUpdate(() => {
|
||||
this.clear();
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=caching.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"caching.js","sourceRoot":"","sources":["../../src/utils/caching.ts"],"names":[],"mappings":"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"}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2024 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
export * from 'vscode-jsonrpc/lib/common/cancellation.js';
|
||||
//# sourceMappingURL=cancellation.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"cancellation.d.ts","sourceRoot":"","sources":["../../src/utils/cancellation.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAGhF,cAAc,2CAA2C,CAAC"}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2024 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
export * from 'vscode-jsonrpc/lib/common/cancellation.js';
|
||||
//# sourceMappingURL=cancellation.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"cancellation.js","sourceRoot":"","sources":["../../src/utils/cancellation.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,iDAAiD;AACjD,cAAc,2CAA2C,CAAC"}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import type { Stream } from './stream.js';
|
||||
/**
|
||||
* A multimap is a variation of a Map that has potentially multiple values for every key.
|
||||
*/
|
||||
export declare class MultiMap<K, V> {
|
||||
private map;
|
||||
constructor();
|
||||
constructor(elements: Iterable<[K, V]>);
|
||||
/**
|
||||
* The total number of values in the multimap.
|
||||
*/
|
||||
get size(): number;
|
||||
/**
|
||||
* Clear all entries in the multimap.
|
||||
*/
|
||||
clear(): void;
|
||||
/**
|
||||
* Operates differently depending on whether a `value` is given:
|
||||
* * With a value, this method deletes the specific key / value pair from the multimap.
|
||||
* * Without a value, all values associated with the given key are deleted.
|
||||
*
|
||||
* @returns `true` if a value existed and has been removed, or `false` if the specified
|
||||
* key / value does not exist.
|
||||
*/
|
||||
delete(key: K, value?: V): boolean;
|
||||
/**
|
||||
* Returns an array of all values associated with the given key. If no value exists,
|
||||
* an empty array is returned.
|
||||
*
|
||||
* _Note:_ The returned array is assumed not to be modified. Use the `set` method to add a
|
||||
* value and `delete` to remove a value from the multimap.
|
||||
*/
|
||||
get(key: K): readonly V[];
|
||||
/**
|
||||
* Returns a stream of all values associated with the given key. If no value exists,
|
||||
* {@link EMPTY_STREAM} is returned.
|
||||
*/
|
||||
getStream(key: K): Stream<V>;
|
||||
/**
|
||||
* Operates differently depending on whether a `value` is given:
|
||||
* * With a value, this method returns `true` if the specific key / value pair is present in the multimap.
|
||||
* * Without a value, this method returns `true` if the given key is present in the multimap.
|
||||
*/
|
||||
has(key: K, value?: V): boolean;
|
||||
/**
|
||||
* Add the given key / value pair to the multimap.
|
||||
*/
|
||||
add(key: K, value: V): this;
|
||||
/**
|
||||
* Add the given set of key / value pairs to the multimap.
|
||||
*/
|
||||
addAll(key: K, values: Iterable<V>): this;
|
||||
/**
|
||||
* Invokes the given callback function for every key / value pair in the multimap.
|
||||
*/
|
||||
forEach(callbackfn: (value: V, key: K, map: this) => void): void;
|
||||
/**
|
||||
* Returns an iterator of key, value pairs for every entry in the map.
|
||||
*/
|
||||
[Symbol.iterator](): Iterator<[K, V]>;
|
||||
/**
|
||||
* Returns a stream of key, value pairs for every entry in the map.
|
||||
*/
|
||||
entries(): Stream<[K, V]>;
|
||||
/**
|
||||
* Returns a stream of keys in the map.
|
||||
*/
|
||||
keys(): Stream<K>;
|
||||
/**
|
||||
* Returns a stream of values in the map.
|
||||
*/
|
||||
values(): Stream<V>;
|
||||
/**
|
||||
* Returns a stream of key, value set pairs for every key in the map.
|
||||
*/
|
||||
entriesGroupedByKey(): Stream<[K, V[]]>;
|
||||
}
|
||||
export declare class BiMap<K, V> {
|
||||
private map;
|
||||
private inverse;
|
||||
get size(): number;
|
||||
constructor();
|
||||
constructor(elements: Array<[K, V]>);
|
||||
clear(): void;
|
||||
set(key: K, value: V): this;
|
||||
get(key: K): V | undefined;
|
||||
getKey(value: V): K | undefined;
|
||||
delete(key: K): boolean;
|
||||
}
|
||||
//# sourceMappingURL=collections.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"collections.d.ts","sourceRoot":"","sources":["../../src/utils/collections.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAG1C;;GAEG;AACH,qBAAa,QAAQ,CAAC,CAAC,EAAE,CAAC;IAEtB,OAAO,CAAC,GAAG,CAAqB;;gBAGpB,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAStC;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;;;;;;OAOG;IACH,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,OAAO;IAoBlC;;;;;;OAMG;IACH,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,SAAS,CAAC,EAAE;IAIzB;;;OAGG;IACH,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;IAK5B;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,OAAO;IAY/B;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAS3B;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI;IASzC;;OAEG;IACH,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,IAAI,KAAK,IAAI,GAAG,IAAI;IAMhE;;OAEG;IACH,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAIrC;;OAEG;IACH,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAKzB;;OAEG;IACH,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC;IAIjB;;OAEG;IACH,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC;IAInB;;OAEG;IACH,mBAAmB,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;CAI1C;AAED,qBAAa,KAAK,CAAC,CAAC,EAAE,CAAC;IAEnB,OAAO,CAAC,GAAG,CAAmB;IAC9B,OAAO,CAAC,OAAO,CAAmB;IAElC,IAAI,IAAI,IAAI,MAAM,CAEjB;;gBAGW,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IASnC,KAAK,IAAI,IAAI;IAKb,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAM3B,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS;IAI1B,MAAM,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS;IAI/B,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;CAS1B"}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import { EMPTY_STREAM, Reduction, stream } from './stream.js';
|
||||
/**
|
||||
* A multimap is a variation of a Map that has potentially multiple values for every key.
|
||||
*/
|
||||
export class MultiMap {
|
||||
constructor(elements) {
|
||||
this.map = new Map();
|
||||
if (elements) {
|
||||
for (const [key, value] of elements) {
|
||||
this.add(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* The total number of values in the multimap.
|
||||
*/
|
||||
get size() {
|
||||
return Reduction.sum(stream(this.map.values()).map(a => a.length));
|
||||
}
|
||||
/**
|
||||
* Clear all entries in the multimap.
|
||||
*/
|
||||
clear() {
|
||||
this.map.clear();
|
||||
}
|
||||
/**
|
||||
* Operates differently depending on whether a `value` is given:
|
||||
* * With a value, this method deletes the specific key / value pair from the multimap.
|
||||
* * Without a value, all values associated with the given key are deleted.
|
||||
*
|
||||
* @returns `true` if a value existed and has been removed, or `false` if the specified
|
||||
* key / value does not exist.
|
||||
*/
|
||||
delete(key, value) {
|
||||
if (value === undefined) {
|
||||
return this.map.delete(key);
|
||||
}
|
||||
else {
|
||||
const values = this.map.get(key);
|
||||
if (values) {
|
||||
const index = values.indexOf(value);
|
||||
if (index >= 0) {
|
||||
if (values.length === 1) {
|
||||
this.map.delete(key);
|
||||
}
|
||||
else {
|
||||
values.splice(index, 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns an array of all values associated with the given key. If no value exists,
|
||||
* an empty array is returned.
|
||||
*
|
||||
* _Note:_ The returned array is assumed not to be modified. Use the `set` method to add a
|
||||
* value and `delete` to remove a value from the multimap.
|
||||
*/
|
||||
get(key) {
|
||||
return this.map.get(key) ?? [];
|
||||
}
|
||||
/**
|
||||
* Returns a stream of all values associated with the given key. If no value exists,
|
||||
* {@link EMPTY_STREAM} is returned.
|
||||
*/
|
||||
getStream(key) {
|
||||
const values = this.map.get(key);
|
||||
return values ? stream(values) : EMPTY_STREAM;
|
||||
}
|
||||
/**
|
||||
* Operates differently depending on whether a `value` is given:
|
||||
* * With a value, this method returns `true` if the specific key / value pair is present in the multimap.
|
||||
* * Without a value, this method returns `true` if the given key is present in the multimap.
|
||||
*/
|
||||
has(key, value) {
|
||||
if (value === undefined) {
|
||||
return this.map.has(key);
|
||||
}
|
||||
else {
|
||||
const values = this.map.get(key);
|
||||
if (values) {
|
||||
return values.indexOf(value) >= 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Add the given key / value pair to the multimap.
|
||||
*/
|
||||
add(key, value) {
|
||||
if (this.map.has(key)) {
|
||||
this.map.get(key).push(value);
|
||||
}
|
||||
else {
|
||||
this.map.set(key, [value]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Add the given set of key / value pairs to the multimap.
|
||||
*/
|
||||
addAll(key, values) {
|
||||
if (this.map.has(key)) {
|
||||
this.map.get(key).push(...values);
|
||||
}
|
||||
else {
|
||||
this.map.set(key, Array.from(values));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Invokes the given callback function for every key / value pair in the multimap.
|
||||
*/
|
||||
forEach(callbackfn) {
|
||||
this.map.forEach((array, key) => array.forEach(value => callbackfn(value, key, this)));
|
||||
}
|
||||
/**
|
||||
* Returns an iterator of key, value pairs for every entry in the map.
|
||||
*/
|
||||
[Symbol.iterator]() {
|
||||
return this.entries().iterator();
|
||||
}
|
||||
/**
|
||||
* Returns a stream of key, value pairs for every entry in the map.
|
||||
*/
|
||||
entries() {
|
||||
return stream(this.map.entries())
|
||||
.flatMap(([key, array]) => array.map(value => [key, value]));
|
||||
}
|
||||
/**
|
||||
* Returns a stream of keys in the map.
|
||||
*/
|
||||
keys() {
|
||||
return stream(this.map.keys());
|
||||
}
|
||||
/**
|
||||
* Returns a stream of values in the map.
|
||||
*/
|
||||
values() {
|
||||
return stream(this.map.values()).flat();
|
||||
}
|
||||
/**
|
||||
* Returns a stream of key, value set pairs for every key in the map.
|
||||
*/
|
||||
entriesGroupedByKey() {
|
||||
return stream(this.map.entries());
|
||||
}
|
||||
}
|
||||
export class BiMap {
|
||||
get size() {
|
||||
return this.map.size;
|
||||
}
|
||||
constructor(elements) {
|
||||
this.map = new Map();
|
||||
this.inverse = new Map();
|
||||
if (elements) {
|
||||
for (const [key, value] of elements) {
|
||||
this.set(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
clear() {
|
||||
this.map.clear();
|
||||
this.inverse.clear();
|
||||
}
|
||||
set(key, value) {
|
||||
this.map.set(key, value);
|
||||
this.inverse.set(value, key);
|
||||
return this;
|
||||
}
|
||||
get(key) {
|
||||
return this.map.get(key);
|
||||
}
|
||||
getKey(value) {
|
||||
return this.inverse.get(value);
|
||||
}
|
||||
delete(key) {
|
||||
const value = this.map.get(key);
|
||||
if (value !== undefined) {
|
||||
this.map.delete(key);
|
||||
this.inverse.delete(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=collections.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"collections.js","sourceRoot":"","sources":["../../src/utils/collections.ts"],"names":[],"mappings":"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"}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import type { IToken } from '@chevrotain/types';
|
||||
import type { Range } from 'vscode-languageserver-types';
|
||||
import type { CstNode, LeafCstNode } from '../syntax-tree.js';
|
||||
import type { DocumentSegment } from '../workspace/documents.js';
|
||||
import type { Stream, TreeStream } from './stream.js';
|
||||
/**
|
||||
* Attempts to find the CST node that belongs to the datatype element that contains the given CST node.
|
||||
*
|
||||
* @param cstNode The CST node for which to find the datatype node.
|
||||
* @returns The CST node corresponding to the datatype element, or the undefined if no such element exists.
|
||||
*/
|
||||
export declare function getDatatypeNode(cstNode: CstNode): CstNode | undefined;
|
||||
/**
|
||||
* Create a stream of all CST nodes that are directly and indirectly contained in the given root node,
|
||||
* including the root node itself.
|
||||
*/
|
||||
export declare function streamCst(node: CstNode): TreeStream<CstNode>;
|
||||
/**
|
||||
* Create a stream of all leaf nodes that are directly and indirectly contained in the given root node.
|
||||
*/
|
||||
export declare function flattenCst(node: CstNode): Stream<LeafCstNode>;
|
||||
/**
|
||||
* Determines whether the specified cst node is a child of the specified parent node.
|
||||
*/
|
||||
export declare function isChildNode(child: CstNode, parent: CstNode): boolean;
|
||||
export declare function tokenToRange(token: IToken): Range;
|
||||
export declare function toDocumentSegment(node: CstNode): DocumentSegment;
|
||||
export declare function toDocumentSegment(node?: CstNode): DocumentSegment | undefined;
|
||||
export declare enum RangeComparison {
|
||||
Before = 0,
|
||||
After = 1,
|
||||
OverlapFront = 2,
|
||||
OverlapBack = 3,
|
||||
Inside = 4,
|
||||
Outside = 5
|
||||
}
|
||||
export declare function compareRange(range: Range, to: Range): RangeComparison;
|
||||
export declare function inRange(range: Range, to: Range): boolean;
|
||||
export declare const DefaultNameRegexp: RegExp;
|
||||
/**
|
||||
* Performs `findLeafNodeAtOffset` with a minor difference: When encountering a character that matches the `nameRegexp` argument,
|
||||
* it will instead return the leaf node at the `offset - 1` position.
|
||||
*
|
||||
* For LSP services, users expect that the declaration of an element is available if the cursor is directly after the element.
|
||||
*/
|
||||
export declare function findDeclarationNodeAtOffset(cstNode: CstNode | undefined, offset: number, nameRegexp?: RegExp): LeafCstNode | undefined;
|
||||
export declare function findCommentNode(cstNode: CstNode | undefined, commentNames: string[]): CstNode | undefined;
|
||||
export declare function isCommentNode(cstNode: CstNode, commentNames: string[]): boolean;
|
||||
/**
|
||||
* Finds the leaf CST node at the specified 0-based string offset.
|
||||
* Note that the given offset will be within the range of the returned leaf node.
|
||||
*
|
||||
* If the offset does not point to a CST node (but just white space), this method will return `undefined`.
|
||||
*
|
||||
* @param node The CST node to search through.
|
||||
* @param offset The specified offset.
|
||||
* @returns The CST node at the specified offset.
|
||||
*/
|
||||
export declare function findLeafNodeAtOffset(node: CstNode, offset: number): LeafCstNode | undefined;
|
||||
/**
|
||||
* Finds the leaf CST node at the specified 0-based string offset.
|
||||
* If no CST node exists at the specified position, it will return the leaf node before it.
|
||||
*
|
||||
* If there is no leaf node before the specified offset, this method will return `undefined`.
|
||||
*
|
||||
* @param node The CST node to search through.
|
||||
* @param offset The specified offset.
|
||||
* @returns The CST node closest to the specified offset.
|
||||
*/
|
||||
export declare function findLeafNodeBeforeOffset(node: CstNode, offset: number): LeafCstNode | undefined;
|
||||
export declare function getPreviousNode(node: CstNode, hidden?: boolean): CstNode | undefined;
|
||||
export declare function getNextNode(node: CstNode, hidden?: boolean): CstNode | undefined;
|
||||
export declare function getStartlineNode(node: CstNode): CstNode;
|
||||
export declare function getInteriorNodes(start: CstNode, end: CstNode): CstNode[];
|
||||
//# sourceMappingURL=cst-utils.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"cst-utils.d.ts","sourceRoot":"","sources":["../../src/utils/cst-utils.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAoB,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAMtD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,CAkBrE;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,CAQ5D;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,WAAW,CAAC,CAE7D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAQpE;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,CAajD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,eAAe,CAAC;AAClE,wBAAgB,iBAAiB,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,CAAC;AAc/E,oBAAY,eAAe;IACvB,MAAM,IAAI;IACV,KAAK,IAAI;IACT,YAAY,IAAI;IAChB,WAAW,IAAI;IACf,MAAM,IAAI;IACV,OAAO,IAAI;CACd;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,GAAG,eAAe,CAiBrE;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAGxD;AAID,eAAO,MAAM,iBAAiB,QAAiB,CAAC;AAEhD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,SAAoB,GAAG,WAAW,GAAG,SAAS,CAYjJ;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,GAAG,SAAS,CAmBzG;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,CAE/E;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAU3F;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAU/F;AA6BD,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,UAAO,GAAG,OAAO,GAAG,SAAS,CAcjF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,UAAO,GAAG,OAAO,GAAG,SAAS,CAe7E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAuBvD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,EAAE,CAMxE"}
|
||||
+338
@@ -0,0 +1,338 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import { isCompositeCstNode, isLeafCstNode, isRootCstNode } from '../syntax-tree.js';
|
||||
import { TreeStreamImpl } from './stream.js';
|
||||
import { getContainerOfType } from './ast-utils.js';
|
||||
import { isParserRule } from '../languages/generated/ast.js';
|
||||
/**
|
||||
* Attempts to find the CST node that belongs to the datatype element that contains the given CST node.
|
||||
*
|
||||
* @param cstNode The CST node for which to find the datatype node.
|
||||
* @returns The CST node corresponding to the datatype element, or the undefined if no such element exists.
|
||||
*/
|
||||
export function getDatatypeNode(cstNode) {
|
||||
let current = cstNode;
|
||||
let found = false;
|
||||
while (current) {
|
||||
const definingRule = getContainerOfType(current.grammarSource, isParserRule);
|
||||
if (definingRule && definingRule.dataType) {
|
||||
// Go up the chain. This element might be part of a larger datatype rule
|
||||
current = current.container;
|
||||
found = true;
|
||||
}
|
||||
else if (found) {
|
||||
// The last datatype node is the one we are looking for
|
||||
return current;
|
||||
}
|
||||
else {
|
||||
// We haven't found any datatype node yet and we've reached a non-datatype rule
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Create a stream of all CST nodes that are directly and indirectly contained in the given root node,
|
||||
* including the root node itself.
|
||||
*/
|
||||
export function streamCst(node) {
|
||||
return new TreeStreamImpl(node, element => {
|
||||
if (isCompositeCstNode(element)) {
|
||||
return element.content;
|
||||
}
|
||||
else {
|
||||
return [];
|
||||
}
|
||||
}, { includeRoot: true });
|
||||
}
|
||||
/**
|
||||
* Create a stream of all leaf nodes that are directly and indirectly contained in the given root node.
|
||||
*/
|
||||
export function flattenCst(node) {
|
||||
return streamCst(node).filter(isLeafCstNode);
|
||||
}
|
||||
/**
|
||||
* Determines whether the specified cst node is a child of the specified parent node.
|
||||
*/
|
||||
export function isChildNode(child, parent) {
|
||||
while (child.container) {
|
||||
child = child.container;
|
||||
if (child === parent) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
export function tokenToRange(token) {
|
||||
// Chevrotain uses 1-based indices everywhere
|
||||
// So we subtract 1 from every value to align with the LSP
|
||||
return {
|
||||
start: {
|
||||
character: token.startColumn - 1,
|
||||
line: token.startLine - 1
|
||||
},
|
||||
end: {
|
||||
character: token.endColumn, // endColumn uses the correct index
|
||||
line: token.endLine - 1
|
||||
}
|
||||
};
|
||||
}
|
||||
export function toDocumentSegment(node) {
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
const { offset, end, range } = node;
|
||||
return {
|
||||
range,
|
||||
offset,
|
||||
end,
|
||||
length: end - offset
|
||||
};
|
||||
}
|
||||
export var RangeComparison;
|
||||
(function (RangeComparison) {
|
||||
RangeComparison[RangeComparison["Before"] = 0] = "Before";
|
||||
RangeComparison[RangeComparison["After"] = 1] = "After";
|
||||
RangeComparison[RangeComparison["OverlapFront"] = 2] = "OverlapFront";
|
||||
RangeComparison[RangeComparison["OverlapBack"] = 3] = "OverlapBack";
|
||||
RangeComparison[RangeComparison["Inside"] = 4] = "Inside";
|
||||
RangeComparison[RangeComparison["Outside"] = 5] = "Outside";
|
||||
})(RangeComparison || (RangeComparison = {}));
|
||||
export function compareRange(range, to) {
|
||||
if (range.end.line < to.start.line || (range.end.line === to.start.line && range.end.character <= to.start.character)) {
|
||||
return RangeComparison.Before;
|
||||
}
|
||||
else if (range.start.line > to.end.line || (range.start.line === to.end.line && range.start.character >= to.end.character)) {
|
||||
return RangeComparison.After;
|
||||
}
|
||||
const startInside = range.start.line > to.start.line || (range.start.line === to.start.line && range.start.character >= to.start.character);
|
||||
const endInside = range.end.line < to.end.line || (range.end.line === to.end.line && range.end.character <= to.end.character);
|
||||
if (startInside && endInside) {
|
||||
return RangeComparison.Inside;
|
||||
}
|
||||
else if (startInside) {
|
||||
return RangeComparison.OverlapBack;
|
||||
}
|
||||
else if (endInside) {
|
||||
return RangeComparison.OverlapFront;
|
||||
}
|
||||
else {
|
||||
return RangeComparison.Outside;
|
||||
}
|
||||
}
|
||||
export function inRange(range, to) {
|
||||
const comparison = compareRange(range, to);
|
||||
return comparison > RangeComparison.After;
|
||||
}
|
||||
// The \p{L} regex matches any unicode letter character, i.e. characters from non-english alphabets
|
||||
// Together with \w it matches any kind of character which can commonly appear in IDs
|
||||
export const DefaultNameRegexp = /^[\w\p{L}]$/u;
|
||||
/**
|
||||
* Performs `findLeafNodeAtOffset` with a minor difference: When encountering a character that matches the `nameRegexp` argument,
|
||||
* it will instead return the leaf node at the `offset - 1` position.
|
||||
*
|
||||
* For LSP services, users expect that the declaration of an element is available if the cursor is directly after the element.
|
||||
*/
|
||||
export function findDeclarationNodeAtOffset(cstNode, offset, nameRegexp = DefaultNameRegexp) {
|
||||
if (cstNode) {
|
||||
if (offset > 0) {
|
||||
const localOffset = offset - cstNode.offset;
|
||||
const textAtOffset = cstNode.text.charAt(localOffset);
|
||||
if (!nameRegexp.test(textAtOffset)) {
|
||||
offset--;
|
||||
}
|
||||
}
|
||||
return findLeafNodeAtOffset(cstNode, offset);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
export function findCommentNode(cstNode, commentNames) {
|
||||
if (cstNode) {
|
||||
const previous = getPreviousNode(cstNode, true);
|
||||
if (previous && isCommentNode(previous, commentNames)) {
|
||||
return previous;
|
||||
}
|
||||
if (isRootCstNode(cstNode)) {
|
||||
// Go from the first non-hidden node through all nodes in reverse order
|
||||
// We do this to find the comment node which directly precedes the root node
|
||||
const endIndex = cstNode.content.findIndex(e => !e.hidden);
|
||||
for (let i = endIndex - 1; i >= 0; i--) {
|
||||
const child = cstNode.content[i];
|
||||
if (isCommentNode(child, commentNames)) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
export function isCommentNode(cstNode, commentNames) {
|
||||
return isLeafCstNode(cstNode) && commentNames.includes(cstNode.tokenType.name);
|
||||
}
|
||||
/**
|
||||
* Finds the leaf CST node at the specified 0-based string offset.
|
||||
* Note that the given offset will be within the range of the returned leaf node.
|
||||
*
|
||||
* If the offset does not point to a CST node (but just white space), this method will return `undefined`.
|
||||
*
|
||||
* @param node The CST node to search through.
|
||||
* @param offset The specified offset.
|
||||
* @returns The CST node at the specified offset.
|
||||
*/
|
||||
export function findLeafNodeAtOffset(node, offset) {
|
||||
if (isLeafCstNode(node)) {
|
||||
return node;
|
||||
}
|
||||
else if (isCompositeCstNode(node)) {
|
||||
const searchResult = binarySearch(node, offset, false);
|
||||
if (searchResult) {
|
||||
return findLeafNodeAtOffset(searchResult, offset);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Finds the leaf CST node at the specified 0-based string offset.
|
||||
* If no CST node exists at the specified position, it will return the leaf node before it.
|
||||
*
|
||||
* If there is no leaf node before the specified offset, this method will return `undefined`.
|
||||
*
|
||||
* @param node The CST node to search through.
|
||||
* @param offset The specified offset.
|
||||
* @returns The CST node closest to the specified offset.
|
||||
*/
|
||||
export function findLeafNodeBeforeOffset(node, offset) {
|
||||
if (isLeafCstNode(node)) {
|
||||
return node;
|
||||
}
|
||||
else if (isCompositeCstNode(node)) {
|
||||
const searchResult = binarySearch(node, offset, true);
|
||||
if (searchResult) {
|
||||
return findLeafNodeBeforeOffset(searchResult, offset);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
function binarySearch(node, offset, closest) {
|
||||
let left = 0;
|
||||
let right = node.content.length - 1;
|
||||
let closestNode = undefined;
|
||||
while (left <= right) {
|
||||
const middle = Math.floor((left + right) / 2);
|
||||
const middleNode = node.content[middle];
|
||||
if (middleNode.offset <= offset && middleNode.end > offset) {
|
||||
// Found an exact match
|
||||
return middleNode;
|
||||
}
|
||||
if (middleNode.end <= offset) {
|
||||
// Update the closest node (less than offset) and move to the right half
|
||||
closestNode = closest ? middleNode : undefined;
|
||||
left = middle + 1;
|
||||
}
|
||||
else {
|
||||
// Move to the left half
|
||||
right = middle - 1;
|
||||
}
|
||||
}
|
||||
return closestNode;
|
||||
}
|
||||
export function getPreviousNode(node, hidden = true) {
|
||||
while (node.container) {
|
||||
const parent = node.container;
|
||||
let index = parent.content.indexOf(node);
|
||||
while (index > 0) {
|
||||
index--;
|
||||
const previous = parent.content[index];
|
||||
if (hidden || !previous.hidden) {
|
||||
return previous;
|
||||
}
|
||||
}
|
||||
node = parent;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
export function getNextNode(node, hidden = true) {
|
||||
while (node.container) {
|
||||
const parent = node.container;
|
||||
let index = parent.content.indexOf(node);
|
||||
const last = parent.content.length - 1;
|
||||
while (index < last) {
|
||||
index++;
|
||||
const next = parent.content[index];
|
||||
if (hidden || !next.hidden) {
|
||||
return next;
|
||||
}
|
||||
}
|
||||
node = parent;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
export function getStartlineNode(node) {
|
||||
if (node.range.start.character === 0) {
|
||||
return node;
|
||||
}
|
||||
const line = node.range.start.line;
|
||||
let last = node;
|
||||
let index;
|
||||
while (node.container) {
|
||||
const parent = node.container;
|
||||
const selfIndex = index ?? parent.content.indexOf(node);
|
||||
if (selfIndex === 0) {
|
||||
node = parent;
|
||||
index = undefined;
|
||||
}
|
||||
else {
|
||||
index = selfIndex - 1;
|
||||
node = parent.content[index];
|
||||
}
|
||||
if (node.range.start.line !== line) {
|
||||
break;
|
||||
}
|
||||
last = node;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
export function getInteriorNodes(start, end) {
|
||||
const commonParent = getCommonParent(start, end);
|
||||
if (!commonParent) {
|
||||
return [];
|
||||
}
|
||||
return commonParent.parent.content.slice(commonParent.a + 1, commonParent.b);
|
||||
}
|
||||
function getCommonParent(a, b) {
|
||||
const aParents = getParentChain(a);
|
||||
const bParents = getParentChain(b);
|
||||
let current;
|
||||
for (let i = 0; i < aParents.length && i < bParents.length; i++) {
|
||||
const aParent = aParents[i];
|
||||
const bParent = bParents[i];
|
||||
if (aParent.parent === bParent.parent) {
|
||||
current = {
|
||||
parent: aParent.parent,
|
||||
a: aParent.index,
|
||||
b: bParent.index
|
||||
};
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
function getParentChain(node) {
|
||||
const chain = [];
|
||||
while (node.container) {
|
||||
const parent = node.container;
|
||||
const index = parent.content.indexOf(node);
|
||||
chain.push({
|
||||
parent,
|
||||
index
|
||||
});
|
||||
node = parent;
|
||||
}
|
||||
return chain.reverse();
|
||||
}
|
||||
//# sourceMappingURL=cst-utils.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+22
@@ -0,0 +1,22 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
export interface Disposable {
|
||||
/**
|
||||
* Dispose this object.
|
||||
*/
|
||||
dispose(): void;
|
||||
}
|
||||
export interface AsyncDisposable {
|
||||
/**
|
||||
* Dispose this object.
|
||||
*/
|
||||
dispose(): Promise<void>;
|
||||
}
|
||||
export declare namespace Disposable {
|
||||
function create(callback: () => Promise<void>): AsyncDisposable;
|
||||
function create(callback: () => void): Disposable;
|
||||
}
|
||||
//# sourceMappingURL=disposable.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"disposable.d.ts","sourceRoot":"","sources":["../../src/utils/disposable.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,MAAM,WAAW,UAAU;IACvB;;OAEG;IACH,OAAO,IAAI,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC5B;;OAEG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED,yBAAiB,UAAU,CAAC;IACxB,SAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC;IACvE,SAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,UAAU,CAAC;CAM5D"}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
export var Disposable;
|
||||
(function (Disposable) {
|
||||
function create(callback) {
|
||||
return {
|
||||
dispose: async () => await callback()
|
||||
};
|
||||
}
|
||||
Disposable.create = create;
|
||||
})(Disposable || (Disposable = {}));
|
||||
//# sourceMappingURL=disposable.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"disposable.js","sourceRoot":"","sources":["../../src/utils/disposable.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAgBhF,MAAM,KAAW,UAAU,CAQ1B;AARD,WAAiB,UAAU;IAGvB,SAAgB,MAAM,CAAC,QAAoC;QACvD,OAAO;YACH,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,MAAM,QAAQ,EAAE;SACxC,CAAC;IACN,CAAC;IAJe,iBAAM,SAIrB,CAAA;AACL,CAAC,EARgB,UAAU,KAAV,UAAU,QAQ1B"}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import type { CstNode } from '../syntax-tree.js';
|
||||
export declare class ErrorWithLocation extends Error {
|
||||
constructor(node: CstNode | undefined, message: string);
|
||||
}
|
||||
export declare function assertUnreachable(_: never, message?: string): never;
|
||||
export declare function assertCondition(condition: boolean, message?: string): asserts condition;
|
||||
//# sourceMappingURL=errors.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,qBAAa,iBAAkB,SAAQ,KAAK;gBAC5B,IAAI,EAAE,OAAO,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM;CAGzD;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,SAAiC,GAAG,KAAK,CAE3F;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,GAAE,MAAwC,GAAG,OAAO,CAAC,SAAS,CAIxH"}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
export class ErrorWithLocation extends Error {
|
||||
constructor(node, message) {
|
||||
super(node ? `${message} at ${node.range.start.line}:${node.range.start.character}` : message);
|
||||
}
|
||||
}
|
||||
export function assertUnreachable(_, message = 'Error: Got unexpected value.') {
|
||||
throw new Error(message);
|
||||
}
|
||||
export function assertCondition(condition, message = 'Error: Condition is violated.') {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=errors.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAIhF,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACxC,YAAY,IAAyB,EAAE,OAAe;QAClD,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,OAAO,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACnG,CAAC;CACJ;AAED,MAAM,UAAU,iBAAiB,CAAC,CAAQ,EAAE,OAAO,GAAG,8BAA8B;IAChF,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,SAAkB,EAAE,UAAkB,+BAA+B;IACjG,IAAI,CAAC,SAAS,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;AACL,CAAC"}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2024 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
export * from 'vscode-jsonrpc/lib/common/events.js';
|
||||
//# sourceMappingURL=event.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../src/utils/event.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAGhF,cAAc,qCAAqC,CAAC"}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2024 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
export * from 'vscode-jsonrpc/lib/common/events.js';
|
||||
//# sourceMappingURL=event.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"event.js","sourceRoot":"","sources":["../../src/utils/event.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,iDAAiD;AACjD,cAAc,qCAAqC,CAAC"}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2023 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import * as ast from '../languages/generated/ast.js';
|
||||
/**
|
||||
* Load a Langium grammar for your language from a JSON string. This is used by several services,
|
||||
* most notably the parser builder which interprets the grammar to create a parser.
|
||||
*/
|
||||
export declare function loadGrammarFromJson(json: string): ast.Grammar;
|
||||
//# sourceMappingURL=grammar-loader.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"grammar-loader.d.ts","sourceRoot":"","sources":["../../src/utils/grammar-loader.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAKhF,OAAO,KAAK,GAAG,MAAM,+BAA+B,CAAC;AAgCrD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,OAAO,CAK7D"}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2023 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import { createDefaultCoreModule, createDefaultSharedCoreModule } from '../default-module.js';
|
||||
import { inject } from '../dependency-injection.js';
|
||||
import * as ast from '../languages/generated/ast.js';
|
||||
import { EmptyFileSystem } from '../workspace/file-system-provider.js';
|
||||
import { URI } from './uri-utils.js';
|
||||
const minimalGrammarModule = {
|
||||
Grammar: () => undefined,
|
||||
LanguageMetaData: () => ({
|
||||
caseInsensitive: false,
|
||||
fileExtensions: ['.langium'],
|
||||
languageId: 'langium'
|
||||
})
|
||||
};
|
||||
const minimalSharedGrammarModule = {
|
||||
AstReflection: () => new ast.LangiumGrammarAstReflection()
|
||||
};
|
||||
function createMinimalGrammarServices() {
|
||||
const shared = inject(createDefaultSharedCoreModule(EmptyFileSystem), minimalSharedGrammarModule);
|
||||
const grammar = inject(createDefaultCoreModule({ shared }), minimalGrammarModule);
|
||||
shared.ServiceRegistry.register(grammar);
|
||||
return grammar;
|
||||
}
|
||||
/**
|
||||
* Load a Langium grammar for your language from a JSON string. This is used by several services,
|
||||
* most notably the parser builder which interprets the grammar to create a parser.
|
||||
*/
|
||||
export function loadGrammarFromJson(json) {
|
||||
const services = createMinimalGrammarServices();
|
||||
const astNode = services.serializer.JsonSerializer.deserialize(json);
|
||||
services.shared.workspace.LangiumDocumentFactory.fromModel(astNode, URI.parse(`memory:/${astNode.name ?? 'grammar'}.langium`));
|
||||
return astNode;
|
||||
}
|
||||
//# sourceMappingURL=grammar-loader.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"grammar-loader.js","sourceRoot":"","sources":["../../src/utils/grammar-loader.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,EAAE,uBAAuB,EAAE,6BAA6B,EAAE,MAAM,sBAAsB,CAAC;AAE9F,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACpD,OAAO,KAAK,GAAG,MAAM,+BAA+B,CAAC;AAGrD,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAErC,MAAM,oBAAoB,GAA4D;IAClF,OAAO,EAAE,GAAG,EAAE,CAAC,SAAmC;IAClD,gBAAgB,EAAE,GAAG,EAAE,CAAC,CAAC;QACrB,eAAe,EAAE,KAAK;QACtB,cAAc,EAAE,CAAC,UAAU,CAAC;QAC5B,UAAU,EAAE,SAAS;KACxB,CAAC;CACL,CAAC;AAEF,MAAM,0BAA0B,GAAwE;IACpG,aAAa,EAAE,GAAG,EAAE,CAAC,IAAI,GAAG,CAAC,2BAA2B,EAAE;CAC7D,CAAC;AAEF,SAAS,4BAA4B;IACjC,MAAM,MAAM,GAAG,MAAM,CACjB,6BAA6B,CAAC,eAAe,CAAC,EAC9C,0BAA0B,CAC7B,CAAC;IACF,MAAM,OAAO,GAAG,MAAM,CAClB,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC,EACnC,oBAAoB,CACvB,CAAC;IACF,MAAM,CAAC,eAAe,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACzC,OAAO,OAAO,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC5C,MAAM,QAAQ,GAAG,4BAA4B,EAAE,CAAC;IAChD,MAAM,OAAO,GAAG,QAAQ,CAAC,UAAU,CAAC,cAAc,CAAC,WAAW,CAAC,IAAI,CAAyB,CAAC;IAC7F,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,sBAAsB,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,WAAW,OAAO,CAAC,IAAI,IAAI,SAAS,UAAU,CAAC,CAAC,CAAC;IAC/H,OAAO,OAAO,CAAC;AACnB,CAAC"}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021-2022 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import * as ast from '../languages/generated/ast.js';
|
||||
import type { AstNode, CstNode } from '../syntax-tree.js';
|
||||
/**
|
||||
* Returns the entry rule of the given grammar, if any. If the grammar file does not contain an entry rule,
|
||||
* the result is `undefined`.
|
||||
*/
|
||||
export declare function getEntryRule(grammar: ast.Grammar): ast.ParserRule | undefined;
|
||||
/**
|
||||
* Returns all hidden terminal rules of the given grammar, if any.
|
||||
*/
|
||||
export declare function getHiddenRules(grammar: ast.Grammar): ast.AbstractRule[];
|
||||
/**
|
||||
* Returns all rules that can be reached from the topmost rules of the specified grammar (entry and hidden terminal rules).
|
||||
*
|
||||
* @param grammar The grammar that contains all rules
|
||||
* @param allTerminals Whether or not to include terminals that are referenced only by other terminals
|
||||
* @returns A list of referenced parser and terminal rules. If the grammar contains no entry rule,
|
||||
* this function returns all rules of the specified grammar.
|
||||
*/
|
||||
export declare function getAllReachableRules(grammar: ast.Grammar, allTerminals: boolean): Set<ast.AbstractRule>;
|
||||
/**
|
||||
* Returns all parser rules which provide types which are used in the grammar as type in cross-references.
|
||||
* @param grammar the grammar to investigate
|
||||
* @returns the set of parser rules whose contributed types are used as type in cross-references
|
||||
*/
|
||||
export declare function getAllRulesUsedForCrossReferences(grammar: ast.Grammar): Set<ast.ParserRule>;
|
||||
/**
|
||||
* Determines the grammar expression used to parse a cross-reference (usually a reference to a terminal rule).
|
||||
* A cross-reference can declare this expression explicitly in the form `[Type : Terminal]`, but if `Terminal`
|
||||
* is omitted, this function attempts to infer it from the name of the referenced `Type` (using `findNameAssignment`).
|
||||
*
|
||||
* Returns the grammar expression used to parse the given cross-reference, or `undefined` if it is not declared
|
||||
* and cannot be inferred.
|
||||
*/
|
||||
export declare function getCrossReferenceTerminal(crossRef: ast.CrossReference): ast.AbstractElement | undefined;
|
||||
/**
|
||||
* Determines whether the given terminal rule represents a comment. This is true if the rule is marked
|
||||
* as `hidden` and it does not match white space. This means every hidden token (i.e. excluded from the AST)
|
||||
* that contains visible characters is considered a comment.
|
||||
*/
|
||||
export declare function isCommentTerminal(terminalRule: ast.TerminalRule): boolean;
|
||||
/**
|
||||
* Find all CST nodes within the given node that contribute to the specified property.
|
||||
*
|
||||
* @param node A CST node in which to look for property assignments. If this is undefined, the result is an empty array.
|
||||
* @param property A property name of the constructed AST node. If this is undefined, the result is an empty array.
|
||||
*/
|
||||
export declare function findNodesForProperty(node: CstNode | undefined, property: string | undefined): CstNode[];
|
||||
/**
|
||||
* Find a single CST node within the given node that contributes to the specified property.
|
||||
*
|
||||
* @param node A CST node in which to look for property assignments. If this is undefined, the result is `undefined`.
|
||||
* @param property A property name of the constructed AST node. If this is undefined, the result is `undefined`.
|
||||
* @param index If no index is specified or the index is less than zero, the first found node is returned. If the
|
||||
* specified index exceeds the number of assignments to the property, the last found node is returned. Otherwise,
|
||||
* the node with the specified index is returned.
|
||||
*/
|
||||
export declare function findNodeForProperty(node: CstNode | undefined, property: string | undefined, index?: number): CstNode | undefined;
|
||||
/**
|
||||
* Find all CST nodes within the given node that correspond to the specified keyword.
|
||||
*
|
||||
* @param node A CST node in which to look for keywords. If this is undefined, the result is an empty array.
|
||||
* @param keyword A keyword as specified in the grammar.
|
||||
*/
|
||||
export declare function findNodesForKeyword(node: CstNode | undefined, keyword: string): CstNode[];
|
||||
/**
|
||||
* Find a single CST node within the given node that corresponds to the specified keyword.
|
||||
*
|
||||
* @param node A CST node in which to look for keywords. If this is undefined, the result is `undefined`.
|
||||
* @param keyword A keyword as specified in the grammar.
|
||||
* @param index If no index is specified or the index is less than zero, the first found node is returned. If the
|
||||
* specified index exceeds the number of keyword occurrences, the last found node is returned. Otherwise,
|
||||
* the node with the specified index is returned.
|
||||
*/
|
||||
export declare function findNodeForKeyword(node: CstNode | undefined, keyword: string, index?: number): CstNode | undefined;
|
||||
export declare function findNodesForKeywordInternal(node: CstNode, keyword: string, element: AstNode | undefined): CstNode[];
|
||||
/**
|
||||
* If the given CST node was parsed in the context of a property assignment, the respective `Assignment` grammar
|
||||
* node is returned. If no assignment is found, the result is `undefined`.
|
||||
*
|
||||
* @param cstNode A CST node for which to find a property assignment.
|
||||
*/
|
||||
export declare function findAssignment(cstNode: CstNode): ast.Assignment | undefined;
|
||||
/**
|
||||
* Find an assignment to the `name` property for the given grammar type. This requires the `type` to be inferred
|
||||
* from a parser rule, and that rule must contain an assignment to the `name` property. In all other cases,
|
||||
* this function returns `undefined`.
|
||||
*/
|
||||
export declare function findNameAssignment(type: ast.AbstractType): ast.Assignment | undefined;
|
||||
export declare function getActionAtElement(element: ast.AbstractElement): ast.Action | undefined;
|
||||
export type Cardinality = '?' | '*' | '+' | undefined;
|
||||
export type Operator = '=' | '+=' | '?=' | undefined;
|
||||
export declare function isOptionalCardinality(cardinality?: Cardinality, element?: ast.AbstractElement): boolean;
|
||||
export declare function isArrayCardinality(cardinality?: Cardinality): boolean;
|
||||
export declare function isArrayOperator(operator?: Operator): boolean;
|
||||
/**
|
||||
* Determines whether the given parser rule is a _data type rule_, meaning that it has a
|
||||
* primitive return type like `number`, `boolean`, etc.
|
||||
*/
|
||||
export declare function isDataTypeRule(rule: ast.ParserRule): boolean;
|
||||
export declare function isDataType(type: ast.Type): boolean;
|
||||
export declare function getExplicitRuleType(rule: ast.AbstractRule): string | undefined;
|
||||
export declare function getTypeName(type: ast.AbstractType | ast.Action): string;
|
||||
export declare function getActionType(action: ast.Action): string | undefined;
|
||||
/**
|
||||
* This function is used at development time (for code generation and the internal type system) to get the type of the AST node produced by the given rule.
|
||||
* For data type rules, the name of the rule is returned,
|
||||
* e.g. "INT_value returns number: MY_INT;" returns "INT_value".
|
||||
* @param rule the given rule
|
||||
* @returns the name of the AST node type of the rule
|
||||
*/
|
||||
export declare function getRuleTypeName(rule: ast.AbstractRule): string;
|
||||
/**
|
||||
* This function is used at runtime to get the actual type of the values produced by the given rule at runtime.
|
||||
* For data type rules, the name of the declared return type of the rule is returned (if any),
|
||||
* e.g. "INT_value returns number: MY_INT;" returns "number".
|
||||
* @param rule the given rule
|
||||
* @returns the name of the type of the produced values of the rule at runtime
|
||||
*/
|
||||
export declare function getRuleType(rule: ast.AbstractRule): string;
|
||||
export declare function terminalRegex(terminalRule: ast.TerminalRule): RegExp;
|
||||
//# sourceMappingURL=grammar-utils.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"grammar-utils.d.ts","sourceRoot":"","sources":["../../src/utils/grammar-utils.ts"],"names":[],"mappings":"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"}
|
||||
+606
@@ -0,0 +1,606 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021-2022 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import { assertUnreachable } from '../utils/errors.js';
|
||||
import * as ast from '../languages/generated/ast.js';
|
||||
import { isCompositeCstNode } from '../syntax-tree.js';
|
||||
import { getContainerOfType, streamAllContents } from './ast-utils.js';
|
||||
import { streamCst } from './cst-utils.js';
|
||||
import { escapeRegExp, isWhitespace } from './regexp-utils.js';
|
||||
/**
|
||||
* Returns the entry rule of the given grammar, if any. If the grammar file does not contain an entry rule,
|
||||
* the result is `undefined`.
|
||||
*/
|
||||
export function getEntryRule(grammar) {
|
||||
return grammar.rules.find(e => ast.isParserRule(e) && e.entry);
|
||||
}
|
||||
/**
|
||||
* Returns all hidden terminal rules of the given grammar, if any.
|
||||
*/
|
||||
export function getHiddenRules(grammar) {
|
||||
return grammar.rules.filter(e => ast.isTerminalRule(e) && e.hidden);
|
||||
}
|
||||
/**
|
||||
* Returns all rules that can be reached from the topmost rules of the specified grammar (entry and hidden terminal rules).
|
||||
*
|
||||
* @param grammar The grammar that contains all rules
|
||||
* @param allTerminals Whether or not to include terminals that are referenced only by other terminals
|
||||
* @returns A list of referenced parser and terminal rules. If the grammar contains no entry rule,
|
||||
* this function returns all rules of the specified grammar.
|
||||
*/
|
||||
export function getAllReachableRules(grammar, allTerminals) {
|
||||
const ruleNames = new Set();
|
||||
const entryRule = getEntryRule(grammar);
|
||||
if (!entryRule) {
|
||||
return new Set(grammar.rules);
|
||||
}
|
||||
const topMostRules = [entryRule].concat(getHiddenRules(grammar));
|
||||
for (const rule of topMostRules) {
|
||||
ruleDfs(rule, ruleNames, allTerminals);
|
||||
}
|
||||
const rules = new Set();
|
||||
for (const rule of grammar.rules) {
|
||||
if (ruleNames.has(rule.name) || (ast.isTerminalRule(rule) && rule.hidden)) {
|
||||
rules.add(rule);
|
||||
}
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
function ruleDfs(rule, visitedSet, allTerminals) {
|
||||
visitedSet.add(rule.name);
|
||||
streamAllContents(rule).forEach(node => {
|
||||
if (ast.isRuleCall(node) || (allTerminals && ast.isTerminalRuleCall(node))) {
|
||||
const refRule = node.rule.ref;
|
||||
if (refRule && !visitedSet.has(refRule.name)) {
|
||||
ruleDfs(refRule, visitedSet, allTerminals);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Returns all parser rules which provide types which are used in the grammar as type in cross-references.
|
||||
* @param grammar the grammar to investigate
|
||||
* @returns the set of parser rules whose contributed types are used as type in cross-references
|
||||
*/
|
||||
export function getAllRulesUsedForCrossReferences(grammar) {
|
||||
const result = new Set();
|
||||
streamAllContents(grammar).forEach(node => {
|
||||
if (ast.isCrossReference(node)) {
|
||||
// the cross-reference refers directly to a parser rule (without "returns", without "infers")
|
||||
if (ast.isParserRule(node.type.ref)) {
|
||||
result.add(node.type.ref);
|
||||
}
|
||||
// the cross-reference refers to the explicitly inferred type of a parser rule
|
||||
if (ast.isInferredType(node.type.ref) && ast.isParserRule(node.type.ref.$container)) {
|
||||
result.add(node.type.ref.$container);
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Determines the grammar expression used to parse a cross-reference (usually a reference to a terminal rule).
|
||||
* A cross-reference can declare this expression explicitly in the form `[Type : Terminal]`, but if `Terminal`
|
||||
* is omitted, this function attempts to infer it from the name of the referenced `Type` (using `findNameAssignment`).
|
||||
*
|
||||
* Returns the grammar expression used to parse the given cross-reference, or `undefined` if it is not declared
|
||||
* and cannot be inferred.
|
||||
*/
|
||||
export function getCrossReferenceTerminal(crossRef) {
|
||||
if (crossRef.terminal) {
|
||||
return crossRef.terminal;
|
||||
}
|
||||
else if (crossRef.type.ref) {
|
||||
const nameAssigment = findNameAssignment(crossRef.type.ref);
|
||||
return nameAssigment?.terminal;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Determines whether the given terminal rule represents a comment. This is true if the rule is marked
|
||||
* as `hidden` and it does not match white space. This means every hidden token (i.e. excluded from the AST)
|
||||
* that contains visible characters is considered a comment.
|
||||
*/
|
||||
export function isCommentTerminal(terminalRule) {
|
||||
return terminalRule.hidden && !isWhitespace(terminalRegex(terminalRule));
|
||||
}
|
||||
/**
|
||||
* Find all CST nodes within the given node that contribute to the specified property.
|
||||
*
|
||||
* @param node A CST node in which to look for property assignments. If this is undefined, the result is an empty array.
|
||||
* @param property A property name of the constructed AST node. If this is undefined, the result is an empty array.
|
||||
*/
|
||||
export function findNodesForProperty(node, property) {
|
||||
if (!node || !property) {
|
||||
return [];
|
||||
}
|
||||
return findNodesForPropertyInternal(node, property, node.astNode, true);
|
||||
}
|
||||
/**
|
||||
* Find a single CST node within the given node that contributes to the specified property.
|
||||
*
|
||||
* @param node A CST node in which to look for property assignments. If this is undefined, the result is `undefined`.
|
||||
* @param property A property name of the constructed AST node. If this is undefined, the result is `undefined`.
|
||||
* @param index If no index is specified or the index is less than zero, the first found node is returned. If the
|
||||
* specified index exceeds the number of assignments to the property, the last found node is returned. Otherwise,
|
||||
* the node with the specified index is returned.
|
||||
*/
|
||||
export function findNodeForProperty(node, property, index) {
|
||||
if (!node || !property) {
|
||||
return undefined;
|
||||
}
|
||||
const nodes = findNodesForPropertyInternal(node, property, node.astNode, true);
|
||||
if (nodes.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (index !== undefined) {
|
||||
index = Math.max(0, Math.min(index, nodes.length - 1));
|
||||
}
|
||||
else {
|
||||
index = 0;
|
||||
}
|
||||
return nodes[index];
|
||||
}
|
||||
function findNodesForPropertyInternal(node, property, element, first) {
|
||||
if (!first) {
|
||||
const nodeFeature = getContainerOfType(node.grammarSource, ast.isAssignment);
|
||||
if (nodeFeature && nodeFeature.feature === property) {
|
||||
return [node];
|
||||
}
|
||||
}
|
||||
if (isCompositeCstNode(node) && node.astNode === element) {
|
||||
return node.content.flatMap(e => findNodesForPropertyInternal(e, property, element, false));
|
||||
}
|
||||
return [];
|
||||
}
|
||||
/**
|
||||
* Find all CST nodes within the given node that correspond to the specified keyword.
|
||||
*
|
||||
* @param node A CST node in which to look for keywords. If this is undefined, the result is an empty array.
|
||||
* @param keyword A keyword as specified in the grammar.
|
||||
*/
|
||||
export function findNodesForKeyword(node, keyword) {
|
||||
if (!node) {
|
||||
return [];
|
||||
}
|
||||
return findNodesForKeywordInternal(node, keyword, node?.astNode);
|
||||
}
|
||||
/**
|
||||
* Find a single CST node within the given node that corresponds to the specified keyword.
|
||||
*
|
||||
* @param node A CST node in which to look for keywords. If this is undefined, the result is `undefined`.
|
||||
* @param keyword A keyword as specified in the grammar.
|
||||
* @param index If no index is specified or the index is less than zero, the first found node is returned. If the
|
||||
* specified index exceeds the number of keyword occurrences, the last found node is returned. Otherwise,
|
||||
* the node with the specified index is returned.
|
||||
*/
|
||||
export function findNodeForKeyword(node, keyword, index) {
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
const nodes = findNodesForKeywordInternal(node, keyword, node?.astNode);
|
||||
if (nodes.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (index !== undefined) {
|
||||
index = Math.max(0, Math.min(index, nodes.length - 1));
|
||||
}
|
||||
else {
|
||||
index = 0;
|
||||
}
|
||||
return nodes[index];
|
||||
}
|
||||
export function findNodesForKeywordInternal(node, keyword, element) {
|
||||
if (node.astNode !== element) {
|
||||
return [];
|
||||
}
|
||||
if (ast.isKeyword(node.grammarSource) && node.grammarSource.value === keyword) {
|
||||
return [node];
|
||||
}
|
||||
const treeIterator = streamCst(node).iterator();
|
||||
let result;
|
||||
const keywordNodes = [];
|
||||
do {
|
||||
result = treeIterator.next();
|
||||
if (!result.done) {
|
||||
const childNode = result.value;
|
||||
if (childNode.astNode === element) {
|
||||
if (ast.isKeyword(childNode.grammarSource) && childNode.grammarSource.value === keyword) {
|
||||
keywordNodes.push(childNode);
|
||||
}
|
||||
}
|
||||
else {
|
||||
treeIterator.prune();
|
||||
}
|
||||
}
|
||||
} while (!result.done);
|
||||
return keywordNodes;
|
||||
}
|
||||
/**
|
||||
* If the given CST node was parsed in the context of a property assignment, the respective `Assignment` grammar
|
||||
* node is returned. If no assignment is found, the result is `undefined`.
|
||||
*
|
||||
* @param cstNode A CST node for which to find a property assignment.
|
||||
*/
|
||||
export function findAssignment(cstNode) {
|
||||
const astNode = cstNode.astNode;
|
||||
// Only search until the ast node of the parent cst node is no longer the original ast node
|
||||
// This would make us jump to a preceding rule call, which contains only unrelated assignments
|
||||
while (astNode === cstNode.container?.astNode) {
|
||||
const assignment = getContainerOfType(cstNode.grammarSource, ast.isAssignment);
|
||||
if (assignment) {
|
||||
return assignment;
|
||||
}
|
||||
cstNode = cstNode.container;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Find an assignment to the `name` property for the given grammar type. This requires the `type` to be inferred
|
||||
* from a parser rule, and that rule must contain an assignment to the `name` property. In all other cases,
|
||||
* this function returns `undefined`.
|
||||
*/
|
||||
export function findNameAssignment(type) {
|
||||
let startNode = type;
|
||||
if (ast.isInferredType(startNode)) {
|
||||
// for inferred types, the location to start searching for the name-assignment is different
|
||||
if (ast.isAction(startNode.$container)) {
|
||||
// a type which is explicitly inferred by an action: investigate the sibling of the Action node, i.e. start searching at the Action's parent
|
||||
startNode = startNode.$container.$container;
|
||||
}
|
||||
else if (ast.isAbstractParserRule(startNode.$container)) {
|
||||
// investigate the parser rule with the explicitly inferred type
|
||||
startNode = startNode.$container;
|
||||
}
|
||||
else {
|
||||
assertUnreachable(startNode.$container);
|
||||
}
|
||||
}
|
||||
return findNameAssignmentInternal(type, startNode, new Map());
|
||||
}
|
||||
function findNameAssignmentInternal(type, startNode, cache) {
|
||||
// the cache is only required to prevent infinite loops
|
||||
function go(node, refType) {
|
||||
let childAssignment = undefined;
|
||||
const parentAssignment = getContainerOfType(node, ast.isAssignment);
|
||||
// No parent assignment implies unassigned rule call
|
||||
if (!parentAssignment) {
|
||||
childAssignment = findNameAssignmentInternal(refType, refType, cache);
|
||||
}
|
||||
cache.set(type, childAssignment);
|
||||
return childAssignment;
|
||||
}
|
||||
if (cache.has(type)) {
|
||||
return cache.get(type);
|
||||
}
|
||||
cache.set(type, undefined);
|
||||
for (const node of streamAllContents(startNode)) {
|
||||
if (ast.isAssignment(node) && node.feature.toLowerCase() === 'name') {
|
||||
cache.set(type, node);
|
||||
return node;
|
||||
}
|
||||
else if (ast.isRuleCall(node) && ast.isParserRule(node.rule.ref)) {
|
||||
return go(node, node.rule.ref);
|
||||
}
|
||||
else if (ast.isSimpleType(node) && node.typeRef?.ref) {
|
||||
return go(node, node.typeRef.ref);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
export function getActionAtElement(element) {
|
||||
const parent = element.$container;
|
||||
if (ast.isGroup(parent)) {
|
||||
const elements = parent.elements;
|
||||
const index = elements.indexOf(element);
|
||||
for (let i = index - 1; i >= 0; i--) {
|
||||
const item = elements[i];
|
||||
if (ast.isAction(item)) {
|
||||
return item;
|
||||
}
|
||||
else {
|
||||
const action = streamAllContents(elements[i]).find(ast.isAction);
|
||||
if (action) {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ast.isAbstractElement(parent)) {
|
||||
return getActionAtElement(parent);
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
export function isOptionalCardinality(cardinality, element) {
|
||||
return cardinality === '?' || cardinality === '*' || (ast.isGroup(element) && Boolean(element.guardCondition));
|
||||
}
|
||||
export function isArrayCardinality(cardinality) {
|
||||
return cardinality === '*' || cardinality === '+';
|
||||
}
|
||||
export function isArrayOperator(operator) {
|
||||
return operator === '+=';
|
||||
}
|
||||
/**
|
||||
* Determines whether the given parser rule is a _data type rule_, meaning that it has a
|
||||
* primitive return type like `number`, `boolean`, etc.
|
||||
*/
|
||||
export function isDataTypeRule(rule) {
|
||||
return isDataTypeRuleInternal(rule, new Set());
|
||||
}
|
||||
function isDataTypeRuleInternal(rule, visited) {
|
||||
if (visited.has(rule)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
visited.add(rule);
|
||||
}
|
||||
for (const node of streamAllContents(rule)) {
|
||||
if (ast.isRuleCall(node)) {
|
||||
if (!node.rule.ref) {
|
||||
// RuleCall to unresolved rule. Don't assume `rule` is a DataType rule.
|
||||
return false;
|
||||
}
|
||||
if (ast.isParserRule(node.rule.ref) && !isDataTypeRuleInternal(node.rule.ref, visited)) {
|
||||
return false;
|
||||
}
|
||||
if (ast.isInfixRule(node.rule.ref)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (ast.isAssignment(node)) {
|
||||
return false;
|
||||
}
|
||||
else if (ast.isAction(node)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return Boolean(rule.definition);
|
||||
}
|
||||
export function isDataType(type) {
|
||||
return isDataTypeInternal(type.type, new Set());
|
||||
}
|
||||
function isDataTypeInternal(type, visited) {
|
||||
if (visited.has(type)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
visited.add(type);
|
||||
}
|
||||
if (ast.isArrayType(type)) {
|
||||
return false;
|
||||
}
|
||||
else if (ast.isReferenceType(type)) {
|
||||
return false;
|
||||
}
|
||||
else if (ast.isUnionType(type)) {
|
||||
return type.types.every(e => isDataTypeInternal(e, visited));
|
||||
}
|
||||
else if (ast.isSimpleType(type)) {
|
||||
if (type.primitiveType !== undefined) {
|
||||
return true;
|
||||
}
|
||||
else if (type.stringType !== undefined) {
|
||||
return true;
|
||||
}
|
||||
else if (type.typeRef !== undefined) {
|
||||
const ref = type.typeRef.ref;
|
||||
if (ast.isType(ref)) {
|
||||
return isDataTypeInternal(ref.type, visited);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export function getExplicitRuleType(rule) {
|
||||
if (ast.isTerminalRule(rule)) {
|
||||
return undefined;
|
||||
}
|
||||
if (rule.inferredType) {
|
||||
return rule.inferredType.name;
|
||||
}
|
||||
else if (rule.dataType) {
|
||||
return rule.dataType;
|
||||
}
|
||||
else if (rule.returnType) {
|
||||
const refType = rule.returnType.ref;
|
||||
if (refType) {
|
||||
return refType.name;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
export function getTypeName(type) {
|
||||
if (ast.isAbstractParserRule(type)) {
|
||||
return ast.isParserRule(type) && isDataTypeRule(type) ? type.name : getExplicitRuleType(type) ?? type.name;
|
||||
}
|
||||
else if (ast.isInterface(type) || ast.isType(type) || ast.isReturnType(type)) {
|
||||
return type.name;
|
||||
}
|
||||
else if (ast.isAction(type)) {
|
||||
const actionType = getActionType(type);
|
||||
if (actionType) {
|
||||
return actionType;
|
||||
}
|
||||
}
|
||||
else if (ast.isInferredType(type)) {
|
||||
return type.name;
|
||||
}
|
||||
throw new Error('Cannot get name of Unknown Type');
|
||||
}
|
||||
export function getActionType(action) {
|
||||
if (action.inferredType) {
|
||||
return action.inferredType.name;
|
||||
}
|
||||
else if (action.type?.ref) {
|
||||
return getTypeName(action.type.ref);
|
||||
}
|
||||
return undefined; // not inferring and not referencing a valid type
|
||||
}
|
||||
/**
|
||||
* This function is used at development time (for code generation and the internal type system) to get the type of the AST node produced by the given rule.
|
||||
* For data type rules, the name of the rule is returned,
|
||||
* e.g. "INT_value returns number: MY_INT;" returns "INT_value".
|
||||
* @param rule the given rule
|
||||
* @returns the name of the AST node type of the rule
|
||||
*/
|
||||
export function getRuleTypeName(rule) {
|
||||
if (ast.isTerminalRule(rule)) {
|
||||
return rule.type?.name ?? 'string';
|
||||
}
|
||||
else {
|
||||
return ast.isParserRule(rule) && isDataTypeRule(rule) ? rule.name : getExplicitRuleType(rule) ?? rule.name;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This function is used at runtime to get the actual type of the values produced by the given rule at runtime.
|
||||
* For data type rules, the name of the declared return type of the rule is returned (if any),
|
||||
* e.g. "INT_value returns number: MY_INT;" returns "number".
|
||||
* @param rule the given rule
|
||||
* @returns the name of the type of the produced values of the rule at runtime
|
||||
*/
|
||||
export function getRuleType(rule) {
|
||||
if (ast.isTerminalRule(rule)) {
|
||||
return rule.type?.name ?? 'string';
|
||||
}
|
||||
else {
|
||||
return getExplicitRuleType(rule) ?? rule.name;
|
||||
}
|
||||
}
|
||||
export function terminalRegex(terminalRule) {
|
||||
const flags = {
|
||||
s: false,
|
||||
i: false,
|
||||
u: false
|
||||
};
|
||||
const source = abstractElementToRegex(terminalRule.definition, flags);
|
||||
const flagText = Object.entries(flags).filter(([, value]) => value).map(([name]) => name).join('');
|
||||
return new RegExp(source, flagText);
|
||||
}
|
||||
// Using [\s\S]* allows to match everything, compared to . which doesn't match line terminators
|
||||
const WILDCARD = /[\s\S]/.source;
|
||||
function abstractElementToRegex(element, flags) {
|
||||
if (ast.isTerminalAlternatives(element)) {
|
||||
return terminalAlternativesToRegex(element);
|
||||
}
|
||||
else if (ast.isTerminalGroup(element)) {
|
||||
return terminalGroupToRegex(element);
|
||||
}
|
||||
else if (ast.isCharacterRange(element)) {
|
||||
return characterRangeToRegex(element);
|
||||
}
|
||||
else if (ast.isTerminalRuleCall(element)) {
|
||||
const rule = element.rule.ref;
|
||||
if (!rule) {
|
||||
throw new Error('Missing rule reference.');
|
||||
}
|
||||
return withCardinality(abstractElementToRegex(rule.definition), {
|
||||
cardinality: element.cardinality,
|
||||
lookahead: element.lookahead,
|
||||
parenthesized: element.parenthesized
|
||||
});
|
||||
}
|
||||
else if (ast.isNegatedToken(element)) {
|
||||
return negateTokenToRegex(element);
|
||||
}
|
||||
else if (ast.isUntilToken(element)) {
|
||||
return untilTokenToRegex(element);
|
||||
}
|
||||
else if (ast.isRegexToken(element)) {
|
||||
const lastSlash = element.regex.lastIndexOf('/');
|
||||
const source = element.regex.substring(1, lastSlash);
|
||||
const regexFlags = element.regex.substring(lastSlash + 1);
|
||||
if (flags) {
|
||||
flags.i = regexFlags.includes('i');
|
||||
flags.s = regexFlags.includes('s');
|
||||
flags.u = regexFlags.includes('u');
|
||||
}
|
||||
return withCardinality(source, {
|
||||
cardinality: element.cardinality,
|
||||
lookahead: element.lookahead,
|
||||
parenthesized: element.parenthesized,
|
||||
wrap: false
|
||||
});
|
||||
}
|
||||
else if (ast.isWildcard(element)) {
|
||||
return withCardinality(WILDCARD, {
|
||||
cardinality: element.cardinality,
|
||||
lookahead: element.lookahead,
|
||||
parenthesized: element.parenthesized
|
||||
});
|
||||
}
|
||||
else {
|
||||
throw new Error(`Invalid terminal element: ${element?.$type}, ${element?.$cstNode?.text}`);
|
||||
}
|
||||
}
|
||||
function terminalAlternativesToRegex(alternatives) {
|
||||
return withCardinality(alternatives.elements.map(e => abstractElementToRegex(e)).join('|'), {
|
||||
cardinality: alternatives.cardinality,
|
||||
lookahead: alternatives.lookahead,
|
||||
parenthesized: alternatives.parenthesized,
|
||||
wrap: false // wrapping is not required for top level alternatives, and nested alternatives are already parenthesized according to the grammar
|
||||
});
|
||||
}
|
||||
function terminalGroupToRegex(group) {
|
||||
return withCardinality(group.elements.map(e => abstractElementToRegex(e)).join(''), {
|
||||
cardinality: group.cardinality,
|
||||
lookahead: group.lookahead,
|
||||
parenthesized: group.parenthesized,
|
||||
wrap: false // wrapping is not required for top level group, and nested group are already parenthesized according to the grammar
|
||||
});
|
||||
}
|
||||
function untilTokenToRegex(until) {
|
||||
return withCardinality(`${WILDCARD}*?${abstractElementToRegex(until.terminal)}`, {
|
||||
cardinality: until.cardinality,
|
||||
lookahead: until.lookahead,
|
||||
parenthesized: until.parenthesized
|
||||
});
|
||||
}
|
||||
function negateTokenToRegex(negate) {
|
||||
return withCardinality(`(?!${abstractElementToRegex(negate.terminal)})${WILDCARD}*?`, {
|
||||
cardinality: negate.cardinality,
|
||||
lookahead: negate.lookahead,
|
||||
parenthesized: negate.parenthesized
|
||||
});
|
||||
}
|
||||
function characterRangeToRegex(range) {
|
||||
if (range.right) {
|
||||
return withCardinality(`[${keywordToRegex(range.left)}-${keywordToRegex(range.right)}]`, {
|
||||
cardinality: range.cardinality,
|
||||
lookahead: range.lookahead,
|
||||
parenthesized: range.parenthesized,
|
||||
wrap: false
|
||||
});
|
||||
}
|
||||
return withCardinality(keywordToRegex(range.left), {
|
||||
cardinality: range.cardinality,
|
||||
lookahead: range.lookahead,
|
||||
parenthesized: range.parenthesized,
|
||||
wrap: false
|
||||
});
|
||||
}
|
||||
function keywordToRegex(keyword) {
|
||||
return escapeRegExp(keyword.value);
|
||||
}
|
||||
function withCardinality(regex, options) {
|
||||
if (options.parenthesized || options.lookahead || options.wrap !== false) {
|
||||
const groupConfig = options.lookahead ?? (options.parenthesized ? '' : '?:');
|
||||
regex = `(${groupConfig}${regex})`;
|
||||
}
|
||||
if (options.cardinality) {
|
||||
return `${regex}${options.cardinality}`;
|
||||
}
|
||||
return regex;
|
||||
}
|
||||
//# sourceMappingURL=grammar-utils.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+21
@@ -0,0 +1,21 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2023 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
export * from './caching.js';
|
||||
export * from './event.js';
|
||||
export * from './collections.js';
|
||||
export * from './disposable.js';
|
||||
export * from './errors.js';
|
||||
export * from './grammar-loader.js';
|
||||
export * from './promise-utils.js';
|
||||
export * from './stream.js';
|
||||
export * from './uri-utils.js';
|
||||
import * as AstUtils from './ast-utils.js';
|
||||
import * as Cancellation from './cancellation.js';
|
||||
import * as CstUtils from './cst-utils.js';
|
||||
import * as GrammarUtils from './grammar-utils.js';
|
||||
import * as RegExpUtils from './regexp-utils.js';
|
||||
export { AstUtils, Cancellation, CstUtils, GrammarUtils, RegExpUtils };
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAE/B,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC"}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2023 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
export * from './caching.js';
|
||||
export * from './event.js';
|
||||
export * from './collections.js';
|
||||
export * from './disposable.js';
|
||||
export * from './errors.js';
|
||||
export * from './grammar-loader.js';
|
||||
export * from './promise-utils.js';
|
||||
export * from './stream.js';
|
||||
export * from './uri-utils.js';
|
||||
import * as AstUtils from './ast-utils.js';
|
||||
import * as Cancellation from './cancellation.js';
|
||||
import * as CstUtils from './cst-utils.js';
|
||||
import * as GrammarUtils from './grammar-utils.js';
|
||||
import * as RegExpUtils from './regexp-utils.js';
|
||||
export { AstUtils, Cancellation, CstUtils, GrammarUtils, RegExpUtils };
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAE/B,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,YAAY,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC"}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import { CancellationToken, type AbstractCancellationTokenSource } from '../utils/cancellation.js';
|
||||
export type MaybePromise<T> = T | Promise<T>;
|
||||
/**
|
||||
* Delays the execution of the current code to the next tick of the event loop.
|
||||
* Don't call this method directly in a tight loop to prevent too many promises from being created.
|
||||
*/
|
||||
export declare function delayNextTick(): Promise<void>;
|
||||
/**
|
||||
* Reset the global interruption period and create a cancellation token source.
|
||||
*/
|
||||
export declare function startCancelableOperation(): AbstractCancellationTokenSource;
|
||||
/**
|
||||
* Change the period duration for `interruptAndCheck` to the given number of milliseconds.
|
||||
* The default value is 10ms.
|
||||
*/
|
||||
export declare function setInterruptionPeriod(period: number): void;
|
||||
/**
|
||||
* This symbol may be thrown in an asynchronous context by any Langium service that receives
|
||||
* a `CancellationToken`. This means that the promise returned by such a service is rejected with
|
||||
* this symbol as rejection reason.
|
||||
*/
|
||||
export declare const OperationCancelled: unique symbol;
|
||||
/**
|
||||
* Use this in a `catch` block to check whether the thrown object indicates that the operation
|
||||
* has been cancelled.
|
||||
*/
|
||||
export declare function isOperationCancelled(err: unknown): err is typeof OperationCancelled;
|
||||
/**
|
||||
* This function does two things:
|
||||
* 1. Check the elapsed time since the last call to this function or to `startCancelableOperation`. If the predefined
|
||||
* period (configured with `setInterruptionPeriod`) is exceeded, execution is delayed with `delayNextTick`.
|
||||
* 2. If the predefined period is not met yet or execution is resumed after an interruption, the given cancellation
|
||||
* token is checked, and if cancellation is requested, `OperationCanceled` is thrown.
|
||||
*
|
||||
* All services in Langium that receive a `CancellationToken` may potentially call this function, so the
|
||||
* `CancellationToken` must be caught (with an `async` try-catch block or a `catch` callback attached to
|
||||
* the promise) to avoid that event being exposed as an error.
|
||||
*/
|
||||
export declare function interruptAndCheck(token: CancellationToken): Promise<void>;
|
||||
/**
|
||||
* Simple implementation of the deferred pattern.
|
||||
* An object that exposes a promise and functions to resolve and reject it.
|
||||
*/
|
||||
export declare class Deferred<T = void> {
|
||||
resolve: (value: T) => this;
|
||||
reject: (err?: unknown) => this;
|
||||
promise: Promise<T>;
|
||||
}
|
||||
//# sourceMappingURL=promise-utils.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"promise-utils.d.ts","sourceRoot":"","sources":["../../src/utils/promise-utils.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,EAAE,iBAAiB,EAA2B,KAAK,+BAA+B,EAAE,MAAM,0BAA0B,CAAC;AAE5H,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;AAE5C;;;GAGG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAU7C;AAKD;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,+BAA+B,CAG1E;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAE1D;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,eAA+B,CAAC;AAE/D;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,OAAO,kBAAkB,CAEnF;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB/E;AAED;;;GAGG;AACH,qBAAa,QAAQ,CAAC,CAAC,GAAG,IAAI;IAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC5B,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAEhC,OAAO,aASJ;CACN"}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import { CancellationToken, CancellationTokenSource } from '../utils/cancellation.js';
|
||||
/**
|
||||
* Delays the execution of the current code to the next tick of the event loop.
|
||||
* Don't call this method directly in a tight loop to prevent too many promises from being created.
|
||||
*/
|
||||
export function delayNextTick() {
|
||||
return new Promise(resolve => {
|
||||
// In case we are running in a non-node environment, `setImmediate` isn't available.
|
||||
// Using `setTimeout` of the browser API accomplishes the same result.
|
||||
if (typeof setImmediate === 'undefined') {
|
||||
setTimeout(resolve, 0);
|
||||
}
|
||||
else {
|
||||
setImmediate(resolve);
|
||||
}
|
||||
});
|
||||
}
|
||||
let lastTick = 0;
|
||||
let globalInterruptionPeriod = 10;
|
||||
/**
|
||||
* Reset the global interruption period and create a cancellation token source.
|
||||
*/
|
||||
export function startCancelableOperation() {
|
||||
lastTick = performance.now();
|
||||
return new CancellationTokenSource();
|
||||
}
|
||||
/**
|
||||
* Change the period duration for `interruptAndCheck` to the given number of milliseconds.
|
||||
* The default value is 10ms.
|
||||
*/
|
||||
export function setInterruptionPeriod(period) {
|
||||
globalInterruptionPeriod = period;
|
||||
}
|
||||
/**
|
||||
* This symbol may be thrown in an asynchronous context by any Langium service that receives
|
||||
* a `CancellationToken`. This means that the promise returned by such a service is rejected with
|
||||
* this symbol as rejection reason.
|
||||
*/
|
||||
export const OperationCancelled = Symbol('OperationCancelled');
|
||||
/**
|
||||
* Use this in a `catch` block to check whether the thrown object indicates that the operation
|
||||
* has been cancelled.
|
||||
*/
|
||||
export function isOperationCancelled(err) {
|
||||
return err === OperationCancelled;
|
||||
}
|
||||
/**
|
||||
* This function does two things:
|
||||
* 1. Check the elapsed time since the last call to this function or to `startCancelableOperation`. If the predefined
|
||||
* period (configured with `setInterruptionPeriod`) is exceeded, execution is delayed with `delayNextTick`.
|
||||
* 2. If the predefined period is not met yet or execution is resumed after an interruption, the given cancellation
|
||||
* token is checked, and if cancellation is requested, `OperationCanceled` is thrown.
|
||||
*
|
||||
* All services in Langium that receive a `CancellationToken` may potentially call this function, so the
|
||||
* `CancellationToken` must be caught (with an `async` try-catch block or a `catch` callback attached to
|
||||
* the promise) to avoid that event being exposed as an error.
|
||||
*/
|
||||
export async function interruptAndCheck(token) {
|
||||
if (token === CancellationToken.None) {
|
||||
// Early exit in case cancellation was disabled by the caller
|
||||
return;
|
||||
}
|
||||
const current = performance.now();
|
||||
if (current - lastTick >= globalInterruptionPeriod) {
|
||||
lastTick = current;
|
||||
await delayNextTick();
|
||||
// prevent calling delayNextTick every iteration of loop
|
||||
// where delayNextTick takes up the majority or all of the
|
||||
// globalInterruptionPeriod itself
|
||||
lastTick = performance.now();
|
||||
}
|
||||
if (token.isCancellationRequested) {
|
||||
throw OperationCancelled;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Simple implementation of the deferred pattern.
|
||||
* An object that exposes a promise and functions to resolve and reject it.
|
||||
*/
|
||||
export class Deferred {
|
||||
constructor() {
|
||||
this.promise = new Promise((resolve, reject) => {
|
||||
this.resolve = (arg) => {
|
||||
resolve(arg);
|
||||
return this;
|
||||
};
|
||||
this.reject = (err) => {
|
||||
reject(err);
|
||||
return this;
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=promise-utils.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"promise-utils.js","sourceRoot":"","sources":["../../src/utils/promise-utils.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAwC,MAAM,0BAA0B,CAAC;AAI5H;;;GAGG;AACH,MAAM,UAAU,aAAa;IACzB,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;QACzB,oFAAoF;QACpF,sEAAsE;QACtE,IAAI,OAAO,YAAY,KAAK,WAAW,EAAE,CAAC;YACtC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAC3B,CAAC;aAAM,CAAC;YACJ,YAAY,CAAC,OAAO,CAAC,CAAC;QAC1B,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC;AAED,IAAI,QAAQ,GAAG,CAAC,CAAC;AACjB,IAAI,wBAAwB,GAAG,EAAE,CAAC;AAElC;;GAEG;AACH,MAAM,UAAU,wBAAwB;IACpC,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IAC7B,OAAO,IAAI,uBAAuB,EAAE,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAc;IAChD,wBAAwB,GAAG,MAAM,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAAC;AAE/D;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAC7C,OAAO,GAAG,KAAK,kBAAkB,CAAC;AACtC,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,KAAwB;IAC5D,IAAI,KAAK,KAAK,iBAAiB,CAAC,IAAI,EAAE,CAAC;QACnC,6DAA6D;QAC7D,OAAO;IACX,CAAC;IACD,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IAClC,IAAI,OAAO,GAAG,QAAQ,IAAI,wBAAwB,EAAE,CAAC;QACjD,QAAQ,GAAG,OAAO,CAAC;QACnB,MAAM,aAAa,EAAE,CAAC;QACtB,wDAAwD;QACxD,0DAA0D;QAC1D,kCAAkC;QAClC,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;IACjC,CAAC;IACD,IAAI,KAAK,CAAC,uBAAuB,EAAE,CAAC;QAChC,MAAM,kBAAkB,CAAC;IAC7B,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,OAAO,QAAQ;IAArB;QAII,YAAO,GAAG,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACzC,IAAI,CAAC,OAAO,GAAG,CAAC,GAAG,EAAE,EAAE;gBACnB,OAAO,CAAC,GAAG,CAAC,CAAC;gBACb,OAAO,IAAI,CAAC;YAChB,CAAC,CAAC;YACF,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,EAAE,EAAE;gBAClB,MAAM,CAAC,GAAG,CAAC,CAAC;gBACZ,OAAO,IAAI,CAAC;YAChB,CAAC,CAAC;QACN,CAAC,CAAC,CAAC;IACP,CAAC;CAAA"}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
export declare const NEWLINE_REGEXP: RegExp;
|
||||
export declare function getTerminalParts(regexp: RegExp | string): Array<{
|
||||
start: string;
|
||||
end: string;
|
||||
}>;
|
||||
export declare function isMultilineComment(regexp: RegExp | string): boolean;
|
||||
/**
|
||||
* A set of all characters that are considered whitespace by the '\s' RegExp character class.
|
||||
* Taken from [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Character_classes).
|
||||
*/
|
||||
export declare const whitespaceCharacters: string[];
|
||||
export declare function isWhitespace(value: RegExp | string): boolean;
|
||||
export declare function escapeRegExp(value: string): string;
|
||||
/**
|
||||
* Determines whether the given input has a partial match with the specified regex.
|
||||
* @param regex The regex to partially match against
|
||||
* @param input The input string
|
||||
* @returns Whether any match exists.
|
||||
*/
|
||||
export declare function partialMatches(regex: RegExp | string, input: string): boolean;
|
||||
/**
|
||||
* Builds a partial regex from the input regex. A partial regex is able to match incomplete input strings. E.g.
|
||||
* a partial regex constructed from `/ab/` is able to match the string `a` without needing a following `b` character. However it won't match `b` alone.
|
||||
* @param regex The input regex to be converted.
|
||||
* @returns A partial regex constructed from the input regex.
|
||||
*/
|
||||
export declare function partialRegExp(regex: RegExp | string): RegExp;
|
||||
//# sourceMappingURL=regexp-utils.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"regexp-utils.d.ts","sourceRoot":"","sources":["../../src/utils/regexp-utils.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAKhF,eAAO,MAAM,cAAc,QAAY,CAAC;AA8FxC,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAoB/F;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAanE;AAED;;;GAGG;AACH,eAAO,MAAM,oBAAoB,UAEsC,CAAC;AAExE,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAG5D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAI7E;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAyI5D"}
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import { RegExpParser, BaseRegExpVisitor } from '@chevrotain/regexp-to-ast';
|
||||
export const NEWLINE_REGEXP = /\r?\n/gm;
|
||||
const regexpParser = new RegExpParser();
|
||||
/**
|
||||
* This class is in charge of heuristically identifying start/end tokens of terminals.
|
||||
*
|
||||
* The way this works is by doing the following:
|
||||
* 1. Traverse the regular expression in the "start state"
|
||||
* 2. Add any encountered sets/single characters to the "start regexp"
|
||||
* 3. Once we encounter any variable-length content (i.e. with quantifiers such as +/?/*), we enter the "end state"
|
||||
* 4. In the end state, any sets/single characters are added to an "end stack".
|
||||
* 5. If we re-encounter any variable-length content we reset the end stack
|
||||
* 6. We continue visiting the regex until the end, reseting the end stack and rebuilding it as necessary
|
||||
*
|
||||
* After traversing a regular expression the `startRegexp/endRegexp` properties allow access to the stored start/end of the terminal
|
||||
*/
|
||||
class TerminalRegExpVisitor extends BaseRegExpVisitor {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.isStarting = true;
|
||||
this.endRegexpStack = [];
|
||||
this.multiline = false;
|
||||
}
|
||||
get endRegex() {
|
||||
return this.endRegexpStack.join('');
|
||||
}
|
||||
reset(regex) {
|
||||
this.multiline = false;
|
||||
this.regex = regex;
|
||||
this.startRegexp = '';
|
||||
this.isStarting = true;
|
||||
this.endRegexpStack = [];
|
||||
}
|
||||
visitGroup(node) {
|
||||
if (node.quantifier) {
|
||||
this.isStarting = false;
|
||||
this.endRegexpStack = [];
|
||||
}
|
||||
}
|
||||
visitCharacter(node) {
|
||||
const char = String.fromCharCode(node.value);
|
||||
if (!this.multiline && char === '\n') {
|
||||
this.multiline = true;
|
||||
}
|
||||
if (node.quantifier) {
|
||||
this.isStarting = false;
|
||||
this.endRegexpStack = [];
|
||||
}
|
||||
else {
|
||||
const escapedChar = escapeRegExp(char);
|
||||
this.endRegexpStack.push(escapedChar);
|
||||
if (this.isStarting) {
|
||||
this.startRegexp += escapedChar;
|
||||
}
|
||||
}
|
||||
}
|
||||
visitSet(node) {
|
||||
if (!this.multiline) {
|
||||
const set = this.regex.substring(node.loc.begin, node.loc.end);
|
||||
const regex = new RegExp(set);
|
||||
this.multiline = Boolean('\n'.match(regex));
|
||||
}
|
||||
if (node.quantifier) {
|
||||
this.isStarting = false;
|
||||
this.endRegexpStack = [];
|
||||
}
|
||||
else {
|
||||
const set = this.regex.substring(node.loc.begin, node.loc.end);
|
||||
this.endRegexpStack.push(set);
|
||||
if (this.isStarting) {
|
||||
this.startRegexp += set;
|
||||
}
|
||||
}
|
||||
}
|
||||
visitChildren(node) {
|
||||
if (node.type === 'Group') {
|
||||
// Ignore children of groups with quantifier (+/*/?)
|
||||
// These groups are unrelated to start/end tokens of terminals
|
||||
const group = node;
|
||||
if (group.quantifier) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
super.visitChildren(node);
|
||||
}
|
||||
}
|
||||
const visitor = new TerminalRegExpVisitor();
|
||||
export function getTerminalParts(regexp) {
|
||||
try {
|
||||
if (typeof regexp !== 'string') {
|
||||
regexp = regexp.source;
|
||||
}
|
||||
regexp = `/${regexp}/`;
|
||||
const pattern = regexpParser.pattern(regexp);
|
||||
const parts = [];
|
||||
for (const alternative of pattern.value.value) {
|
||||
visitor.reset(regexp);
|
||||
visitor.visit(alternative);
|
||||
parts.push({
|
||||
start: visitor.startRegexp,
|
||||
end: visitor.endRegex
|
||||
});
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
export function isMultilineComment(regexp) {
|
||||
try {
|
||||
if (typeof regexp === 'string') {
|
||||
regexp = new RegExp(regexp);
|
||||
}
|
||||
regexp = regexp.toString();
|
||||
visitor.reset(regexp);
|
||||
// Parsing the pattern might fail (since it's user code)
|
||||
visitor.visit(regexpParser.pattern(regexp));
|
||||
return visitor.multiline;
|
||||
}
|
||||
catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* A set of all characters that are considered whitespace by the '\s' RegExp character class.
|
||||
* Taken from [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Character_classes).
|
||||
*/
|
||||
export const whitespaceCharacters = ('\f\n\r\t\v\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007' +
|
||||
'\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000\ufeff').split('');
|
||||
export function isWhitespace(value) {
|
||||
const regexp = typeof value === 'string' ? new RegExp(value) : value;
|
||||
return whitespaceCharacters.some((ws) => regexp.test(ws));
|
||||
}
|
||||
export function escapeRegExp(value) {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
/**
|
||||
* Determines whether the given input has a partial match with the specified regex.
|
||||
* @param regex The regex to partially match against
|
||||
* @param input The input string
|
||||
* @returns Whether any match exists.
|
||||
*/
|
||||
export function partialMatches(regex, input) {
|
||||
const partial = partialRegExp(regex);
|
||||
const match = input.match(partial);
|
||||
return !!match && match[0].length > 0;
|
||||
}
|
||||
/**
|
||||
* Builds a partial regex from the input regex. A partial regex is able to match incomplete input strings. E.g.
|
||||
* a partial regex constructed from `/ab/` is able to match the string `a` without needing a following `b` character. However it won't match `b` alone.
|
||||
* @param regex The input regex to be converted.
|
||||
* @returns A partial regex constructed from the input regex.
|
||||
*/
|
||||
export function partialRegExp(regex) {
|
||||
if (typeof regex === 'string') {
|
||||
regex = new RegExp(regex);
|
||||
}
|
||||
const re = regex, source = regex.source;
|
||||
let i = 0;
|
||||
function process() {
|
||||
let result = '', tmp;
|
||||
function appendRaw(nbChars) {
|
||||
result += source.substr(i, nbChars);
|
||||
i += nbChars;
|
||||
}
|
||||
function appendOptional(nbChars) {
|
||||
result += '(?:' + source.substr(i, nbChars) + '|$)';
|
||||
i += nbChars;
|
||||
}
|
||||
while (i < source.length) {
|
||||
switch (source[i]) {
|
||||
case '\\':
|
||||
switch (source[i + 1]) {
|
||||
case 'c':
|
||||
appendOptional(3);
|
||||
break;
|
||||
case 'x':
|
||||
appendOptional(4);
|
||||
break;
|
||||
case 'u':
|
||||
if (re.unicode) {
|
||||
if (source[i + 2] === '{') {
|
||||
appendOptional(source.indexOf('}', i) - i + 1);
|
||||
}
|
||||
else {
|
||||
appendOptional(6);
|
||||
}
|
||||
}
|
||||
else {
|
||||
appendOptional(2);
|
||||
}
|
||||
break;
|
||||
case 'p':
|
||||
case 'P':
|
||||
if (re.unicode) {
|
||||
appendOptional(source.indexOf('}', i) - i + 1);
|
||||
}
|
||||
else {
|
||||
appendOptional(2);
|
||||
}
|
||||
break;
|
||||
case 'k':
|
||||
appendOptional(source.indexOf('>', i) - i + 1);
|
||||
break;
|
||||
default:
|
||||
appendOptional(2);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case '[':
|
||||
tmp = /\[(?:\\.|.)*?\]/g;
|
||||
tmp.lastIndex = i;
|
||||
tmp = tmp.exec(source) || [];
|
||||
appendOptional(tmp[0].length);
|
||||
break;
|
||||
case '|':
|
||||
case '^':
|
||||
case '$':
|
||||
case '*':
|
||||
case '+':
|
||||
case '?':
|
||||
appendRaw(1);
|
||||
break;
|
||||
case '{':
|
||||
tmp = /\{\d+,?\d*\}/g;
|
||||
tmp.lastIndex = i;
|
||||
tmp = tmp.exec(source);
|
||||
if (tmp) {
|
||||
appendRaw(tmp[0].length);
|
||||
}
|
||||
else {
|
||||
appendOptional(1);
|
||||
}
|
||||
break;
|
||||
case '(':
|
||||
if (source[i + 1] === '?') {
|
||||
switch (source[i + 2]) {
|
||||
case ':':
|
||||
result += '(?:';
|
||||
i += 3;
|
||||
result += process() + '|$)';
|
||||
break;
|
||||
case '=':
|
||||
result += '(?=';
|
||||
i += 3;
|
||||
result += process() + ')';
|
||||
break;
|
||||
case '!':
|
||||
tmp = i;
|
||||
i += 3;
|
||||
process();
|
||||
result += source.substr(tmp, i - tmp);
|
||||
break;
|
||||
case '<':
|
||||
switch (source[i + 3]) {
|
||||
case '=':
|
||||
case '!':
|
||||
tmp = i;
|
||||
i += 4;
|
||||
process();
|
||||
result += source.substr(tmp, i - tmp);
|
||||
break;
|
||||
default:
|
||||
appendRaw(source.indexOf('>', i) - i + 1);
|
||||
result += process() + '|$)';
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
appendRaw(1);
|
||||
result += process() + '|$)';
|
||||
}
|
||||
break;
|
||||
case ')':
|
||||
++i;
|
||||
return result;
|
||||
default:
|
||||
appendOptional(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return new RegExp(process(), regex.flags);
|
||||
}
|
||||
//# sourceMappingURL=regexp-utils.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+329
@@ -0,0 +1,329 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
/**
|
||||
* A stream is a read-only sequence of values. While the contents of an array can be accessed
|
||||
* both sequentially and randomly (via index), a stream allows only sequential access.
|
||||
*
|
||||
* The advantage of this is that a stream can be evaluated lazily, so it does not require
|
||||
* to store intermediate values. This can boost performance when a large sequence is
|
||||
* processed via filtering, mapping etc. and accessed at most once. However, lazy
|
||||
* evaluation means that all processing is repeated when you access the sequence multiple
|
||||
* times; in such a case, it may be better to store the resulting sequence into an array.
|
||||
*/
|
||||
export interface Stream<T> extends Iterable<T> {
|
||||
/**
|
||||
* Returns an iterator for this stream. This is the same as calling the `Symbol.iterator` function property.
|
||||
*/
|
||||
iterator(): IterableIterator<T>;
|
||||
/**
|
||||
* Determines whether this stream contains no elements.
|
||||
*/
|
||||
isEmpty(): boolean;
|
||||
/**
|
||||
* Determines the number of elements in this stream.
|
||||
*/
|
||||
count(): number;
|
||||
/**
|
||||
* Collects all elements of this stream into an array.
|
||||
*/
|
||||
toArray(): T[];
|
||||
/**
|
||||
* Collects all elements of this stream into a Set.
|
||||
*/
|
||||
toSet(): Set<T>;
|
||||
/**
|
||||
* Collects all elements of this stream into a Map, applying the provided functions to determine keys and values.
|
||||
*
|
||||
* @param keyFn The function to derive map keys. If omitted, the stream elements are used as keys.
|
||||
* @param valueFn The function to derive map values. If omitted, the stream elements are used as values.
|
||||
*/
|
||||
toMap<K = T, V = T>(keyFn?: (e: T) => K, valueFn?: (e: T) => V): Map<K, V>;
|
||||
/**
|
||||
* Returns a string representation of a stream.
|
||||
*/
|
||||
toString(): string;
|
||||
/**
|
||||
* Combines two streams by returning a new stream that yields all elements of this stream and the other stream.
|
||||
*
|
||||
* @param other Stream to be concatenated with this one.
|
||||
*/
|
||||
concat<T2>(other: Iterable<T2>): Stream<T | T2>;
|
||||
/**
|
||||
* Adds all elements of the stream into a string, separated by the specified separator string.
|
||||
*
|
||||
* @param separator A string used to separate one element of the stream from the next in the resulting string.
|
||||
* If omitted, the steam elements are separated with a comma.
|
||||
*/
|
||||
join(separator?: string): string;
|
||||
/**
|
||||
* Returns the index of the first occurrence of a value in the stream, or -1 if it is not present.
|
||||
*
|
||||
* @param searchElement The value to locate in the array.
|
||||
* @param fromIndex The stream index at which to begin the search. If fromIndex is omitted, the search
|
||||
* starts at index 0.
|
||||
*/
|
||||
indexOf(searchElement: T, fromIndex?: number): number;
|
||||
/**
|
||||
* Determines whether all members of the stream satisfy the specified test.
|
||||
*
|
||||
* @param predicate This method calls the predicate function for each element in the stream until the
|
||||
* predicate returns a value which is coercible to the Boolean value `false`, or until the end
|
||||
* of the stream.
|
||||
*/
|
||||
every<S extends T>(predicate: (value: T) => value is S): this is Stream<S>;
|
||||
every(predicate: (value: T) => unknown): boolean;
|
||||
/**
|
||||
* Determines whether any member of the stream satisfies the specified test.
|
||||
*
|
||||
* @param predicate This method calls the predicate function for each element in the stream until the
|
||||
* predicate returns a value which is coercible to the Boolean value `true`, or until the end
|
||||
* of the stream.
|
||||
*/
|
||||
some(predicate: (value: T) => unknown): boolean;
|
||||
/**
|
||||
* Performs the specified action for each element in the stream.
|
||||
*
|
||||
* @param callbackfn Function called once for each element in the stream.
|
||||
*/
|
||||
forEach(callbackfn: (value: T, index: number) => void): void;
|
||||
/**
|
||||
* Returns a stream that yields the results of calling the specified callback function on each element
|
||||
* of the stream. The function is called when the resulting stream elements are actually accessed, so
|
||||
* accessing the resulting stream multiple times means the function is also called multiple times for
|
||||
* each element of the stream.
|
||||
*
|
||||
* @param callbackfn Lazily evaluated function mapping stream elements.
|
||||
*/
|
||||
map<U>(callbackfn: (value: T) => U): Stream<U>;
|
||||
/**
|
||||
* Returns the elements of the stream that meet the condition specified in a callback function.
|
||||
* The function is called when the resulting stream elements are actually accessed, so accessing the
|
||||
* resulting stream multiple times means the function is also called multiple times for each element
|
||||
* of the stream.
|
||||
*
|
||||
* @param predicate Lazily evaluated function checking a condition on stream elements.
|
||||
*/
|
||||
filter<S extends T>(predicate: (value: T) => value is S): Stream<S>;
|
||||
filter(predicate: (value: T) => unknown): Stream<T>;
|
||||
/**
|
||||
* Returns the elements of the stream that are _non-nullable_, which means they are neither `undefined`
|
||||
* nor `null`.
|
||||
*/
|
||||
nonNullable(): Stream<NonNullable<T>>;
|
||||
/**
|
||||
* Calls the specified callback function for all elements in the stream. The return value of the
|
||||
* callback function is the accumulated result, and is provided as an argument in the next call to
|
||||
* the callback function.
|
||||
*
|
||||
* @param callbackfn This method calls the function once for each element in the stream, providing
|
||||
* the previous and current values of the reduction.
|
||||
* @param initialValue If specified, `initialValue` is used as the initial value to start the
|
||||
* accumulation. The first call to the function provides this value as an argument instead
|
||||
* of a stream value.
|
||||
*/
|
||||
reduce(callbackfn: (previousValue: T, currentValue: T) => T): T | undefined;
|
||||
reduce<U = T>(callbackfn: (previousValue: U, currentValue: T) => U, initialValue: U): U;
|
||||
/**
|
||||
* Calls the specified callback function for all elements in the stream, in descending order.
|
||||
* The return value of the callback function is the accumulated result, and is provided as an
|
||||
* argument in the next call to the callback function.
|
||||
*
|
||||
* @param callbackfn This method calls the function once for each element in the stream, providing
|
||||
* the previous and current values of the reduction.
|
||||
* @param initialValue If specified, `initialValue` is used as the initial value to start the
|
||||
* accumulation. The first call to the function provides this value as an argument instead
|
||||
* of an array value.
|
||||
*/
|
||||
reduceRight(callbackfn: (previousValue: T, currentValue: T) => T): T | undefined;
|
||||
reduceRight<U = T>(callbackfn: (previousValue: U, currentValue: T) => U, initialValue: U): U;
|
||||
/**
|
||||
* Returns the value of the first element in the stream that meets the condition, or `undefined`
|
||||
* if there is no such element.
|
||||
*
|
||||
* @param predicate This method calls `predicate` once for each element of the stream, in ascending
|
||||
* order, until it finds one where `predicate` returns a value which is coercible to the
|
||||
* Boolean value `true`.
|
||||
*/
|
||||
find<S extends T>(predicate: (value: T) => value is S): S | undefined;
|
||||
find(predicate: (value: T) => unknown): T | undefined;
|
||||
/**
|
||||
* Returns the index of the first element in the stream that meets the condition, or `-1`
|
||||
* if there is no such element.
|
||||
*
|
||||
* @param predicate This method calls `predicate` once for each element of the stream, in ascending
|
||||
* order, until it finds one where `predicate` returns a value which is coercible to the
|
||||
* Boolean value `true`.
|
||||
*/
|
||||
findIndex(predicate: (value: T) => unknown): number;
|
||||
/**
|
||||
* Determines whether the stream includes a certain element, returning `true` or `false` as appropriate.
|
||||
*
|
||||
* @param searchElement The element to search for.
|
||||
*/
|
||||
includes(searchElement: T): boolean;
|
||||
/**
|
||||
* Calls a defined callback function on each element of the stream and then flattens the result into
|
||||
* a new stream. This is identical to a `map` followed by `flat` with depth 1.
|
||||
*
|
||||
* @param callbackfn Lazily evaluated function mapping stream elements.
|
||||
*/
|
||||
flatMap<U>(callbackfn: (value: T) => U | Iterable<U>): Stream<U>;
|
||||
/**
|
||||
* Returns a new stream with all sub-stream or sub-array elements concatenated into it recursively up
|
||||
* to the specified depth.
|
||||
*
|
||||
* @param depth The maximum recursion depth. Defaults to 1.
|
||||
*/
|
||||
flat<D extends number = 1>(depth?: D): FlatStream<T, D>;
|
||||
/**
|
||||
* Returns the first element in the stream, or `undefined` if the stream is empty.
|
||||
*/
|
||||
head(): T | undefined;
|
||||
/**
|
||||
* Returns a stream that skips the first `skipCount` elements from this stream.
|
||||
*
|
||||
* @param skipCount The number of elements to skip. If this is larger than the number of elements in
|
||||
* the stream, an empty stream is returned. Defaults to 1.
|
||||
*/
|
||||
tail(skipCount?: number): Stream<T>;
|
||||
/**
|
||||
* Returns a stream consisting of the elements of this stream, truncated to be no longer than `maxSize`
|
||||
* in length.
|
||||
*
|
||||
* @param maxSize The number of elements the stream should be limited to
|
||||
*/
|
||||
limit(maxSize: number): Stream<T>;
|
||||
/**
|
||||
* Returns a stream containing only the distinct elements from this stream.
|
||||
* Equality is determined with the same rules as a standard `Set`.
|
||||
*
|
||||
* @param by A function returning the key used to check equality with a previous stream element.
|
||||
* If omitted, the stream elements themselves are used for comparison.
|
||||
*/
|
||||
distinct<Key = T>(by?: (element: T) => Key): Stream<T>;
|
||||
/**
|
||||
* Returns a stream that contains all elements that don't exist in the {@link other} iterable.
|
||||
* Equality is determined with the same rules as a standard `Set`.
|
||||
* @param other The elements that should be exluded from this stream.
|
||||
* @param key A function returning the key used to check quality.
|
||||
* If omitted, the stream elements themselves are used for comparison.
|
||||
*/
|
||||
exclude<Key = T>(other: Iterable<T>, key?: (element: T) => Key): Stream<T>;
|
||||
}
|
||||
export type FlatStream<T, Depth extends number> = {
|
||||
'done': Stream<T>;
|
||||
'recur': T extends Iterable<infer Content> ? FlatStream<Content, MinusOne<Depth>> : Stream<T>;
|
||||
}[Depth extends 0 ? 'done' : 'recur'];
|
||||
export type MinusOne<N extends number> = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20][N];
|
||||
/**
|
||||
* The default implementation of `Stream` works with two input functions:
|
||||
* - The first function creates the initial state of an iteration.
|
||||
* - The second function gets the current state as argument and returns an `IteratorResult`.
|
||||
*/
|
||||
export declare class StreamImpl<S, T> implements Stream<T> {
|
||||
protected readonly startFn: () => S;
|
||||
protected readonly nextFn: (state: S) => IteratorResult<T>;
|
||||
constructor(startFn: () => S, nextFn: (state: S) => IteratorResult<T, undefined>);
|
||||
iterator(): IterableIterator<T>;
|
||||
[Symbol.iterator](): Iterator<T>;
|
||||
isEmpty(): boolean;
|
||||
count(): number;
|
||||
toArray(): T[];
|
||||
toSet(): Set<T>;
|
||||
toMap<K = T, V = T>(keyFn?: (e: T) => K, valueFn?: (e: T) => V): Map<K, V>;
|
||||
toString(): string;
|
||||
concat<T2>(other: Iterable<T2>): Stream<T | T2>;
|
||||
join(separator?: string): string;
|
||||
indexOf(searchElement: T, fromIndex?: number): number;
|
||||
every<U extends T>(predicate: (value: T) => value is U): this is Stream<U> & this;
|
||||
every(predicate: (value: T) => unknown): boolean;
|
||||
some(predicate: (value: T) => unknown): boolean;
|
||||
forEach(callbackfn: (value: T, index: number) => void): void;
|
||||
map<U>(callbackfn: (value: T) => U): Stream<U>;
|
||||
filter<U extends T>(predicate: (value: T) => value is U): Stream<U> & this;
|
||||
filter(predicate: (value: T) => unknown): Stream<T> & this;
|
||||
nonNullable(): Stream<NonNullable<T>>;
|
||||
reduce(callbackfn: (previousValue: T, currentValue: T) => T): T | undefined;
|
||||
reduce<U = T>(callbackfn: (previousValue: U, currentValue: T) => U, initialValue: U): U;
|
||||
reduceRight(callbackfn: (previousValue: T, currentValue: T) => T): T | undefined;
|
||||
reduceRight<U = T>(callbackfn: (previousValue: U, currentValue: T) => U, initialValue: U): U;
|
||||
protected recursiveReduce<U>(iterator: Iterator<T>, callbackfn: (previousValue: U | T, currentValue: T) => U, initialValue?: U): U | T | undefined;
|
||||
find<S extends T>(predicate: (value: T) => value is S): S | undefined;
|
||||
find(predicate: (value: T) => unknown): T | undefined;
|
||||
findIndex(predicate: (value: T) => unknown): number;
|
||||
includes(searchElement: T): boolean;
|
||||
flatMap<U>(callbackfn: (value: T) => U | Iterable<U>): Stream<U>;
|
||||
flat<D extends number = 1>(depth?: D): FlatStream<T, D>;
|
||||
head(): T | undefined;
|
||||
tail(skipCount?: number): Stream<T>;
|
||||
limit(maxSize: number): Stream<T>;
|
||||
distinct<Key = T>(by?: (element: T) => Key): Stream<T>;
|
||||
exclude<Key = T>(other: Iterable<T>, key?: (element: T) => Key): Stream<T>;
|
||||
}
|
||||
/**
|
||||
* An empty stream of any type.
|
||||
*/
|
||||
export declare const EMPTY_STREAM: Stream<any>;
|
||||
/**
|
||||
* Use this `IteratorResult` when implementing a `StreamImpl` to indicate that there are no more elements in the stream.
|
||||
*/
|
||||
export declare const DONE_RESULT: IteratorReturnResult<undefined>;
|
||||
/**
|
||||
* Create a stream from one or more iterables or array-likes.
|
||||
*/
|
||||
export declare function stream<T>(...collections: Array<Iterable<T> | ArrayLike<T>>): Stream<T>;
|
||||
/**
|
||||
* A tree iterator adds the ability to prune the current iteration.
|
||||
*/
|
||||
export interface TreeIterator<T> extends IterableIterator<T> {
|
||||
/**
|
||||
* Skip the whole subtree below the last returned element. The iteration continues as if that
|
||||
* element had no children.
|
||||
*/
|
||||
prune(): void;
|
||||
}
|
||||
/**
|
||||
* A tree stream is used to stream the elements of a tree, for example an AST or CST.
|
||||
*/
|
||||
export interface TreeStream<T> extends Stream<T> {
|
||||
iterator(): TreeIterator<T>;
|
||||
}
|
||||
/**
|
||||
* The default implementation of `TreeStream` takes a root element and a function that computes the
|
||||
* children of its argument. Whether the root node included in the stream is controlled with the
|
||||
* `includeRoot` option, which defaults to `false`.
|
||||
*/
|
||||
export declare class TreeStreamImpl<T> extends StreamImpl<{
|
||||
iterators: Array<Iterator<T>>;
|
||||
pruned: boolean;
|
||||
}, T> implements TreeStream<T> {
|
||||
constructor(root: T, children: (node: T) => Iterable<T>, options?: {
|
||||
includeRoot?: boolean;
|
||||
});
|
||||
iterator(): TreeIterator<T>;
|
||||
}
|
||||
/**
|
||||
* A set of utility functions that reduce a stream to a single value.
|
||||
*/
|
||||
export declare namespace Reduction {
|
||||
/**
|
||||
* Compute the sum of a number stream.
|
||||
*/
|
||||
function sum(stream: Stream<number>): number;
|
||||
/**
|
||||
* Compute the product of a number stream.
|
||||
*/
|
||||
function product(stream: Stream<number>): number;
|
||||
/**
|
||||
* Compute the minimum of a number stream. Returns `undefined` if the stream is empty.
|
||||
*/
|
||||
function min(stream: Stream<number>): number | undefined;
|
||||
/**
|
||||
* Compute the maximum of a number stream. Returns `undefined` if the stream is empty.
|
||||
*/
|
||||
function max(stream: Stream<number>): number | undefined;
|
||||
}
|
||||
//# sourceMappingURL=stream.d.ts.map
|
||||
+1
File diff suppressed because one or more lines are too long
+510
@@ -0,0 +1,510 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2021 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
/**
|
||||
* The default implementation of `Stream` works with two input functions:
|
||||
* - The first function creates the initial state of an iteration.
|
||||
* - The second function gets the current state as argument and returns an `IteratorResult`.
|
||||
*/
|
||||
export class StreamImpl {
|
||||
constructor(startFn, nextFn) {
|
||||
this.startFn = startFn;
|
||||
this.nextFn = nextFn;
|
||||
}
|
||||
iterator() {
|
||||
const iterator = {
|
||||
state: this.startFn(),
|
||||
next: () => this.nextFn(iterator.state),
|
||||
[Symbol.iterator]: () => iterator
|
||||
};
|
||||
return iterator;
|
||||
}
|
||||
[Symbol.iterator]() {
|
||||
return this.iterator();
|
||||
}
|
||||
isEmpty() {
|
||||
const iterator = this.iterator();
|
||||
return Boolean(iterator.next().done);
|
||||
}
|
||||
count() {
|
||||
const iterator = this.iterator();
|
||||
let count = 0;
|
||||
let next = iterator.next();
|
||||
while (!next.done) {
|
||||
count++;
|
||||
next = iterator.next();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
toArray() {
|
||||
const result = [];
|
||||
const iterator = this.iterator();
|
||||
let next;
|
||||
do {
|
||||
next = iterator.next();
|
||||
if (next.value !== undefined) {
|
||||
result.push(next.value);
|
||||
}
|
||||
} while (!next.done);
|
||||
return result;
|
||||
}
|
||||
toSet() {
|
||||
return new Set(this);
|
||||
}
|
||||
toMap(keyFn, valueFn) {
|
||||
const entryStream = this.map(element => [
|
||||
keyFn ? keyFn(element) : element,
|
||||
valueFn ? valueFn(element) : element
|
||||
]);
|
||||
return new Map(entryStream);
|
||||
}
|
||||
toString() {
|
||||
return this.join();
|
||||
}
|
||||
concat(other) {
|
||||
return new StreamImpl(() => ({ first: this.startFn(), firstDone: false, iterator: other[Symbol.iterator]() }), state => {
|
||||
let result;
|
||||
if (!state.firstDone) {
|
||||
do {
|
||||
result = this.nextFn(state.first);
|
||||
if (!result.done) {
|
||||
return result;
|
||||
}
|
||||
} while (!result.done);
|
||||
state.firstDone = true;
|
||||
}
|
||||
do {
|
||||
result = state.iterator.next();
|
||||
if (!result.done) {
|
||||
return result;
|
||||
}
|
||||
} while (!result.done);
|
||||
return DONE_RESULT;
|
||||
});
|
||||
}
|
||||
join(separator = ',') {
|
||||
const iterator = this.iterator();
|
||||
let value = '';
|
||||
let result;
|
||||
let addSeparator = false;
|
||||
do {
|
||||
result = iterator.next();
|
||||
if (!result.done) {
|
||||
if (addSeparator) {
|
||||
value += separator;
|
||||
}
|
||||
value += toString(result.value);
|
||||
}
|
||||
addSeparator = true;
|
||||
} while (!result.done);
|
||||
return value;
|
||||
}
|
||||
indexOf(searchElement, fromIndex = 0) {
|
||||
const iterator = this.iterator();
|
||||
let index = 0;
|
||||
let next = iterator.next();
|
||||
while (!next.done) {
|
||||
if (index >= fromIndex && next.value === searchElement) {
|
||||
return index;
|
||||
}
|
||||
next = iterator.next();
|
||||
index++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
every(predicate) {
|
||||
const iterator = this.iterator();
|
||||
let next = iterator.next();
|
||||
while (!next.done) {
|
||||
if (!predicate(next.value)) {
|
||||
return false;
|
||||
}
|
||||
next = iterator.next();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
some(predicate) {
|
||||
const iterator = this.iterator();
|
||||
let next = iterator.next();
|
||||
while (!next.done) {
|
||||
if (predicate(next.value)) {
|
||||
return true;
|
||||
}
|
||||
next = iterator.next();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
forEach(callbackfn) {
|
||||
const iterator = this.iterator();
|
||||
let index = 0;
|
||||
let next = iterator.next();
|
||||
while (!next.done) {
|
||||
callbackfn(next.value, index);
|
||||
next = iterator.next();
|
||||
index++;
|
||||
}
|
||||
}
|
||||
map(callbackfn) {
|
||||
return new StreamImpl(this.startFn, (state) => {
|
||||
const { done, value } = this.nextFn(state);
|
||||
if (done) {
|
||||
return DONE_RESULT;
|
||||
}
|
||||
else {
|
||||
return { done: false, value: callbackfn(value) };
|
||||
}
|
||||
});
|
||||
}
|
||||
filter(predicate) {
|
||||
return new StreamImpl(this.startFn, state => {
|
||||
let result;
|
||||
do {
|
||||
result = this.nextFn(state);
|
||||
if (!result.done && predicate(result.value)) {
|
||||
return result;
|
||||
}
|
||||
} while (!result.done);
|
||||
return DONE_RESULT;
|
||||
});
|
||||
}
|
||||
nonNullable() {
|
||||
return this.filter(e => e !== undefined && e !== null);
|
||||
}
|
||||
reduce(callbackfn, initialValue) {
|
||||
const iterator = this.iterator();
|
||||
let previousValue = initialValue;
|
||||
let next = iterator.next();
|
||||
while (!next.done) {
|
||||
if (previousValue === undefined) {
|
||||
previousValue = next.value;
|
||||
}
|
||||
else {
|
||||
previousValue = callbackfn(previousValue, next.value);
|
||||
}
|
||||
next = iterator.next();
|
||||
}
|
||||
return previousValue;
|
||||
}
|
||||
reduceRight(callbackfn, initialValue) {
|
||||
return this.recursiveReduce(this.iterator(), callbackfn, initialValue);
|
||||
}
|
||||
recursiveReduce(iterator, callbackfn, initialValue) {
|
||||
const next = iterator.next();
|
||||
if (next.done) {
|
||||
return initialValue;
|
||||
}
|
||||
const previousValue = this.recursiveReduce(iterator, callbackfn, initialValue);
|
||||
if (previousValue === undefined) {
|
||||
return next.value;
|
||||
}
|
||||
return callbackfn(previousValue, next.value);
|
||||
}
|
||||
find(predicate) {
|
||||
const iterator = this.iterator();
|
||||
let next = iterator.next();
|
||||
while (!next.done) {
|
||||
if (predicate(next.value)) {
|
||||
return next.value;
|
||||
}
|
||||
next = iterator.next();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
findIndex(predicate) {
|
||||
const iterator = this.iterator();
|
||||
let index = 0;
|
||||
let next = iterator.next();
|
||||
while (!next.done) {
|
||||
if (predicate(next.value)) {
|
||||
return index;
|
||||
}
|
||||
next = iterator.next();
|
||||
index++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
includes(searchElement) {
|
||||
const iterator = this.iterator();
|
||||
let next = iterator.next();
|
||||
while (!next.done) {
|
||||
if (next.value === searchElement) {
|
||||
return true;
|
||||
}
|
||||
next = iterator.next();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
flatMap(callbackfn) {
|
||||
return new StreamImpl(() => ({ this: this.startFn() }), (state) => {
|
||||
do {
|
||||
if (state.iterator) {
|
||||
const next = state.iterator.next();
|
||||
if (next.done) {
|
||||
state.iterator = undefined;
|
||||
}
|
||||
else {
|
||||
return next;
|
||||
}
|
||||
}
|
||||
const { done, value } = this.nextFn(state.this);
|
||||
if (!done) {
|
||||
const mapped = callbackfn(value);
|
||||
if (isIterable(mapped)) {
|
||||
state.iterator = mapped[Symbol.iterator]();
|
||||
}
|
||||
else {
|
||||
return { done: false, value: mapped };
|
||||
}
|
||||
}
|
||||
} while (state.iterator);
|
||||
return DONE_RESULT;
|
||||
});
|
||||
}
|
||||
flat(depth) {
|
||||
if (depth === undefined) {
|
||||
depth = 1;
|
||||
}
|
||||
if (depth <= 0) {
|
||||
return this;
|
||||
}
|
||||
const stream = depth > 1 ? this.flat(depth - 1) : this;
|
||||
return new StreamImpl(() => ({ this: stream.startFn() }), (state) => {
|
||||
do {
|
||||
if (state.iterator) {
|
||||
const next = state.iterator.next();
|
||||
if (next.done) {
|
||||
state.iterator = undefined;
|
||||
}
|
||||
else {
|
||||
return next;
|
||||
}
|
||||
}
|
||||
const { done, value } = stream.nextFn(state.this);
|
||||
if (!done) {
|
||||
if (isIterable(value)) {
|
||||
state.iterator = value[Symbol.iterator]();
|
||||
}
|
||||
else {
|
||||
return { done: false, value: value };
|
||||
}
|
||||
}
|
||||
} while (state.iterator);
|
||||
return DONE_RESULT;
|
||||
});
|
||||
}
|
||||
head() {
|
||||
const iterator = this.iterator();
|
||||
const result = iterator.next();
|
||||
if (result.done) {
|
||||
return undefined;
|
||||
}
|
||||
return result.value;
|
||||
}
|
||||
tail(skipCount = 1) {
|
||||
return new StreamImpl(() => {
|
||||
const state = this.startFn();
|
||||
for (let i = 0; i < skipCount; i++) {
|
||||
const next = this.nextFn(state);
|
||||
if (next.done) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}, this.nextFn);
|
||||
}
|
||||
limit(maxSize) {
|
||||
return new StreamImpl(() => ({ size: 0, state: this.startFn() }), state => {
|
||||
state.size++;
|
||||
if (state.size > maxSize) {
|
||||
return DONE_RESULT;
|
||||
}
|
||||
return this.nextFn(state.state);
|
||||
});
|
||||
}
|
||||
distinct(by) {
|
||||
return new StreamImpl(() => ({ set: new Set(), internalState: this.startFn() }), state => {
|
||||
let result;
|
||||
do {
|
||||
result = this.nextFn(state.internalState);
|
||||
if (!result.done) {
|
||||
const value = by ? by(result.value) : result.value;
|
||||
if (!state.set.has(value)) {
|
||||
state.set.add(value);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} while (!result.done);
|
||||
return DONE_RESULT;
|
||||
});
|
||||
}
|
||||
exclude(other, key) {
|
||||
const otherKeySet = new Set();
|
||||
for (const item of other) {
|
||||
const value = key ? key(item) : item;
|
||||
otherKeySet.add(value);
|
||||
}
|
||||
return this.filter(e => {
|
||||
const ownKey = key ? key(e) : e;
|
||||
return !otherKeySet.has(ownKey);
|
||||
});
|
||||
}
|
||||
}
|
||||
function toString(item) {
|
||||
if (typeof item === 'string') {
|
||||
return item;
|
||||
}
|
||||
if (typeof item === 'undefined') {
|
||||
return 'undefined';
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (typeof item.toString === 'function') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return item.toString();
|
||||
}
|
||||
return Object.prototype.toString.call(item);
|
||||
}
|
||||
function isIterable(obj) {
|
||||
return !!obj && typeof obj[Symbol.iterator] === 'function';
|
||||
}
|
||||
/**
|
||||
* An empty stream of any type.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const EMPTY_STREAM = new StreamImpl(() => undefined, () => DONE_RESULT);
|
||||
/**
|
||||
* Use this `IteratorResult` when implementing a `StreamImpl` to indicate that there are no more elements in the stream.
|
||||
*/
|
||||
export const DONE_RESULT = Object.freeze({ done: true, value: undefined });
|
||||
/**
|
||||
* Create a stream from one or more iterables or array-likes.
|
||||
*/
|
||||
export function stream(...collections) {
|
||||
if (collections.length === 1) {
|
||||
const collection = collections[0];
|
||||
if (collection instanceof StreamImpl) {
|
||||
return collection;
|
||||
}
|
||||
if (isIterable(collection)) {
|
||||
return new StreamImpl(() => collection[Symbol.iterator](), (iterator) => iterator.next());
|
||||
}
|
||||
if (typeof collection.length === 'number') {
|
||||
return new StreamImpl(() => ({ index: 0 }), (state) => {
|
||||
if (state.index < collection.length) {
|
||||
return { done: false, value: collection[state.index++] };
|
||||
}
|
||||
else {
|
||||
return DONE_RESULT;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (collections.length > 1) {
|
||||
return new StreamImpl(() => ({ collIndex: 0, arrIndex: 0 }), (state) => {
|
||||
do {
|
||||
if (state.iterator) {
|
||||
const next = state.iterator.next();
|
||||
if (!next.done) {
|
||||
return next;
|
||||
}
|
||||
state.iterator = undefined;
|
||||
}
|
||||
if (state.array) {
|
||||
if (state.arrIndex < state.array.length) {
|
||||
return { done: false, value: state.array[state.arrIndex++] };
|
||||
}
|
||||
state.array = undefined;
|
||||
state.arrIndex = 0;
|
||||
}
|
||||
if (state.collIndex < collections.length) {
|
||||
const collection = collections[state.collIndex++];
|
||||
if (isIterable(collection)) {
|
||||
state.iterator = collection[Symbol.iterator]();
|
||||
}
|
||||
else if (collection && typeof collection.length === 'number') {
|
||||
state.array = collection;
|
||||
}
|
||||
}
|
||||
} while (state.iterator || state.array || state.collIndex < collections.length);
|
||||
return DONE_RESULT;
|
||||
});
|
||||
}
|
||||
return EMPTY_STREAM;
|
||||
}
|
||||
/**
|
||||
* The default implementation of `TreeStream` takes a root element and a function that computes the
|
||||
* children of its argument. Whether the root node included in the stream is controlled with the
|
||||
* `includeRoot` option, which defaults to `false`.
|
||||
*/
|
||||
export class TreeStreamImpl extends StreamImpl {
|
||||
constructor(root, children, options) {
|
||||
super(() => ({
|
||||
iterators: options?.includeRoot ? [[root][Symbol.iterator]()] : [children(root)[Symbol.iterator]()],
|
||||
pruned: false
|
||||
}), state => {
|
||||
if (state.pruned) {
|
||||
state.iterators.pop();
|
||||
state.pruned = false;
|
||||
}
|
||||
while (state.iterators.length > 0) {
|
||||
const iterator = state.iterators[state.iterators.length - 1];
|
||||
const next = iterator.next();
|
||||
if (next.done) {
|
||||
state.iterators.pop();
|
||||
}
|
||||
else {
|
||||
state.iterators.push(children(next.value)[Symbol.iterator]());
|
||||
return next;
|
||||
}
|
||||
}
|
||||
return DONE_RESULT;
|
||||
});
|
||||
}
|
||||
iterator() {
|
||||
const iterator = {
|
||||
state: this.startFn(),
|
||||
next: () => this.nextFn(iterator.state),
|
||||
prune: () => {
|
||||
iterator.state.pruned = true;
|
||||
},
|
||||
[Symbol.iterator]: () => iterator
|
||||
};
|
||||
return iterator;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* A set of utility functions that reduce a stream to a single value.
|
||||
*/
|
||||
export var Reduction;
|
||||
(function (Reduction) {
|
||||
/**
|
||||
* Compute the sum of a number stream.
|
||||
*/
|
||||
function sum(stream) {
|
||||
return stream.reduce((a, b) => a + b, 0);
|
||||
}
|
||||
Reduction.sum = sum;
|
||||
/**
|
||||
* Compute the product of a number stream.
|
||||
*/
|
||||
function product(stream) {
|
||||
return stream.reduce((a, b) => a * b, 0);
|
||||
}
|
||||
Reduction.product = product;
|
||||
/**
|
||||
* Compute the minimum of a number stream. Returns `undefined` if the stream is empty.
|
||||
*/
|
||||
function min(stream) {
|
||||
return stream.reduce((a, b) => Math.min(a, b));
|
||||
}
|
||||
Reduction.min = min;
|
||||
/**
|
||||
* Compute the maximum of a number stream. Returns `undefined` if the stream is empty.
|
||||
*/
|
||||
function max(stream) {
|
||||
return stream.reduce((a, b) => Math.max(a, b));
|
||||
}
|
||||
Reduction.max = max;
|
||||
})(Reduction || (Reduction = {}));
|
||||
//# sourceMappingURL=stream.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+55
@@ -0,0 +1,55 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2022 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import { URI, Utils } from 'vscode-uri';
|
||||
export { URI };
|
||||
export declare namespace UriUtils {
|
||||
const basename: typeof Utils.basename;
|
||||
const dirname: typeof Utils.dirname;
|
||||
const extname: typeof Utils.extname;
|
||||
const joinPath: typeof Utils.joinPath;
|
||||
const resolvePath: typeof Utils.resolvePath;
|
||||
function equals(a?: URI | string, b?: URI | string): boolean;
|
||||
function relative(from: URI | string, to: URI | string): string;
|
||||
function normalize(uri: URI | string): string;
|
||||
function contains(parent: URI | string, child: URI | string): boolean;
|
||||
}
|
||||
interface InternalUriTrieNode<T> {
|
||||
name: string;
|
||||
children: Map<string, InternalUriTrieNode<T>>;
|
||||
parent?: InternalUriTrieNode<T>;
|
||||
element?: T;
|
||||
}
|
||||
export interface UriTrieNode<T> {
|
||||
name: string;
|
||||
uri: string;
|
||||
element?: T;
|
||||
}
|
||||
/**
|
||||
* A trie structure for URIs. It allows to insert, delete and find elements by their URI.
|
||||
* More specifically, it allows to efficiently find all elements that are children of a given URI.
|
||||
*
|
||||
* Unlike a regular trie, this implementation uses the name of the URI segments as keys.
|
||||
*
|
||||
* @see {@link https://en.wikipedia.org/wiki/Trie}
|
||||
*/
|
||||
export declare class UriTrie<T> {
|
||||
protected readonly root: InternalUriTrieNode<T>;
|
||||
protected normalizeUri(uri: URI | string): string;
|
||||
clear(): void;
|
||||
insert(uri: URI | string, element: T): void;
|
||||
delete(uri: URI | string): void;
|
||||
has(uri: URI | string): boolean;
|
||||
hasNode(uri: URI | string): boolean;
|
||||
find(uri: URI | string): T | undefined;
|
||||
findNode(uri: URI | string): UriTrieNode<T> | undefined;
|
||||
findChildren(uri: URI | string): Array<UriTrieNode<T>>;
|
||||
all(): T[];
|
||||
findAll(prefix: URI | string): T[];
|
||||
protected getNode(uri: string, create: true): InternalUriTrieNode<T>;
|
||||
protected getNode(uri: string, create: false): InternalUriTrieNode<T> | undefined;
|
||||
protected collectValues(node: InternalUriTrieNode<T>): T[];
|
||||
}
|
||||
//# sourceMappingURL=uri-utils.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"uri-utils.d.ts","sourceRoot":"","sources":["../../src/utils/uri-utils.ts"],"names":[],"mappings":"AAAA;;;;gFAIgF;AAEhF,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,OAAO,EAAE,GAAG,EAAE,CAAC;AAEf,yBAAiB,QAAQ,CAAC;IAEf,MAAM,QAAQ,uBAAiB,CAAC;IAChC,MAAM,OAAO,sBAAgB,CAAC;IAC9B,MAAM,OAAO,sBAAgB,CAAC;IAC9B,MAAM,QAAQ,uBAAiB,CAAC;IAChC,MAAM,WAAW,0BAAoB,CAAC;IAI7C,SAAgB,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,EAAE,CAAC,CAAC,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO,CAElE;IAED,SAAgB,QAAQ,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,MAAM,GAAG,MAAM,CA6BrE;IAED,SAAgB,SAAS,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,MAAM,CAEnD;IAED,SAAgB,QAAQ,CAAC,MAAM,EAAE,GAAG,GAAG,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO,CAwB3E;CAEJ;AAED,UAAU,mBAAmB,CAAC,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,MAAM,CAAC,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC;IAEhC,OAAO,CAAC,EAAE,CAAC,CAAC;CACf;AAED,MAAM,WAAW,WAAW,CAAC,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,CAAC,CAAC;CACf;AAED;;;;;;;GAOG;AACH,qBAAa,OAAO,CAAC,CAAC;IAElB,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAqC;IAEpF,SAAS,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,MAAM;IAIjD,KAAK,IAAI,IAAI;IAIb,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI;IAK3C,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI;IAO/B,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO;IAI/B,OAAO,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO;IAInC,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,CAAC,GAAG,SAAS;IAItC,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,SAAS;IAavD,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAatD,GAAG,IAAI,CAAC,EAAE;IAIV,OAAO,CAAC,MAAM,EAAE,GAAG,GAAG,MAAM,GAAG,CAAC,EAAE;IAQlC,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,GAAG,mBAAmB,CAAC,CAAC,CAAC;IACpE,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,GAAG,mBAAmB,CAAC,CAAC,CAAC,GAAG,SAAS;IA2BjF,SAAS,CAAC,aAAa,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE;CAW7D"}
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
/******************************************************************************
|
||||
* Copyright 2022 TypeFox GmbH
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the MIT License, which is available in the project root.
|
||||
******************************************************************************/
|
||||
import { URI, Utils } from 'vscode-uri';
|
||||
export { URI };
|
||||
export var UriUtils;
|
||||
(function (UriUtils) {
|
||||
UriUtils.basename = Utils.basename;
|
||||
UriUtils.dirname = Utils.dirname;
|
||||
UriUtils.extname = Utils.extname;
|
||||
UriUtils.joinPath = Utils.joinPath;
|
||||
UriUtils.resolvePath = Utils.resolvePath;
|
||||
const isWindows = typeof process === 'object' && process?.platform === 'win32';
|
||||
function equals(a, b) {
|
||||
return a?.toString() === b?.toString();
|
||||
}
|
||||
UriUtils.equals = equals;
|
||||
function relative(from, to) {
|
||||
const fromPath = typeof from === 'string' ? URI.parse(from).path : from.path;
|
||||
const toPath = typeof to === 'string' ? URI.parse(to).path : to.path;
|
||||
const fromParts = fromPath.split('/').filter(e => e.length > 0);
|
||||
const toParts = toPath.split('/').filter(e => e.length > 0);
|
||||
if (isWindows) {
|
||||
const upperCaseDriveLetter = /^[A-Z]:$/;
|
||||
if (fromParts[0] && upperCaseDriveLetter.test(fromParts[0])) {
|
||||
fromParts[0] = fromParts[0].toLowerCase();
|
||||
}
|
||||
if (toParts[0] && upperCaseDriveLetter.test(toParts[0])) {
|
||||
toParts[0] = toParts[0].toLowerCase();
|
||||
}
|
||||
if (fromParts[0] !== toParts[0]) {
|
||||
// in case of different drive letters, we cannot compute a relative path, so...
|
||||
return toPath.substring(1); // fall back to full 'to' path, drop the leading '/', keep everything else as is for good comparability
|
||||
}
|
||||
}
|
||||
let i = 0;
|
||||
for (; i < fromParts.length; i++) {
|
||||
if (fromParts[i] !== toParts[i]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const backPart = '../'.repeat(fromParts.length - i);
|
||||
const toPart = toParts.slice(i).join('/');
|
||||
return backPart + toPart;
|
||||
}
|
||||
UriUtils.relative = relative;
|
||||
function normalize(uri) {
|
||||
return URI.parse(uri.toString()).toString();
|
||||
}
|
||||
UriUtils.normalize = normalize;
|
||||
function contains(parent, child) {
|
||||
let parentPath = typeof parent === 'string' ? parent : parent.path;
|
||||
let childPath = typeof child === 'string' ? child : child.path;
|
||||
// Trim trailing slashes
|
||||
if (childPath.charAt(childPath.length - 1) === '/') {
|
||||
childPath = childPath.slice(0, -1);
|
||||
}
|
||||
if (parentPath.charAt(parentPath.length - 1) === '/') {
|
||||
parentPath = parentPath.slice(0, -1);
|
||||
}
|
||||
// If the paths are equal, simply return true
|
||||
if (childPath === parentPath) {
|
||||
return true;
|
||||
}
|
||||
// If the child path is shorter than the parent path, it can't be a child
|
||||
if (childPath.length < parentPath.length) {
|
||||
return false;
|
||||
}
|
||||
// If the path does not feature a slash after the parent path, it can't be a child
|
||||
if (childPath.charAt(parentPath.length) !== '/') {
|
||||
return false;
|
||||
}
|
||||
// Check if the child path starts with the parent path
|
||||
return childPath.startsWith(parentPath);
|
||||
}
|
||||
UriUtils.contains = contains;
|
||||
})(UriUtils || (UriUtils = {}));
|
||||
/**
|
||||
* A trie structure for URIs. It allows to insert, delete and find elements by their URI.
|
||||
* More specifically, it allows to efficiently find all elements that are children of a given URI.
|
||||
*
|
||||
* Unlike a regular trie, this implementation uses the name of the URI segments as keys.
|
||||
*
|
||||
* @see {@link https://en.wikipedia.org/wiki/Trie}
|
||||
*/
|
||||
export class UriTrie {
|
||||
constructor() {
|
||||
this.root = { name: '', children: new Map() };
|
||||
}
|
||||
normalizeUri(uri) {
|
||||
return UriUtils.normalize(uri);
|
||||
}
|
||||
clear() {
|
||||
this.root.children.clear();
|
||||
}
|
||||
insert(uri, element) {
|
||||
const node = this.getNode(this.normalizeUri(uri), true);
|
||||
node.element = element;
|
||||
}
|
||||
delete(uri) {
|
||||
const nodeToDelete = this.getNode(this.normalizeUri(uri), false);
|
||||
if (nodeToDelete?.parent) {
|
||||
nodeToDelete.parent.children.delete(nodeToDelete.name);
|
||||
}
|
||||
}
|
||||
has(uri) {
|
||||
return this.getNode(this.normalizeUri(uri), false)?.element !== undefined;
|
||||
}
|
||||
hasNode(uri) {
|
||||
return this.getNode(this.normalizeUri(uri), false) !== undefined;
|
||||
}
|
||||
find(uri) {
|
||||
return this.getNode(this.normalizeUri(uri), false)?.element;
|
||||
}
|
||||
findNode(uri) {
|
||||
const uriString = this.normalizeUri(uri);
|
||||
const node = this.getNode(uriString, false);
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
name: node.name,
|
||||
uri: UriUtils.joinPath(URI.parse(uriString), node.name).toString(),
|
||||
element: node.element
|
||||
};
|
||||
}
|
||||
findChildren(uri) {
|
||||
const uriString = this.normalizeUri(uri);
|
||||
const node = this.getNode(uriString, false);
|
||||
if (!node) {
|
||||
return [];
|
||||
}
|
||||
return Array.from(node.children.values()).map(child => ({
|
||||
name: child.name,
|
||||
uri: UriUtils.joinPath(URI.parse(uriString), child.name).toString(),
|
||||
element: child.element
|
||||
}));
|
||||
}
|
||||
all() {
|
||||
return this.collectValues(this.root);
|
||||
}
|
||||
findAll(prefix) {
|
||||
const node = this.getNode(UriUtils.normalize(prefix), false);
|
||||
if (!node) {
|
||||
return [];
|
||||
}
|
||||
return this.collectValues(node);
|
||||
}
|
||||
getNode(uri, create) {
|
||||
const parts = uri.split('/');
|
||||
if (uri.charAt(uri.length - 1) === '/') {
|
||||
// Remove the last part if the URI ends with a slash
|
||||
parts.pop();
|
||||
}
|
||||
let current = this.root;
|
||||
for (const part of parts) {
|
||||
let child = current.children.get(part);
|
||||
if (!child) {
|
||||
if (create) {
|
||||
child = {
|
||||
name: part,
|
||||
children: new Map(),
|
||||
parent: current
|
||||
};
|
||||
current.children.set(part, child);
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
current = child;
|
||||
}
|
||||
return current;
|
||||
}
|
||||
collectValues(node) {
|
||||
const result = [];
|
||||
if (node.element) {
|
||||
result.push(node.element);
|
||||
}
|
||||
for (const child of node.children.values()) {
|
||||
result.push(...this.collectValues(child));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=uri-utils.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user