2025-01-13 01:48:42 -05:00
|
|
|
import { Internal, assertInternal, Point, ZERO_POINT, SIZE_OF_INT, C } from './constants';
|
|
|
|
|
import { Node } from './node';
|
|
|
|
|
import { marshalNode, unmarshalCaptures } from './marshal';
|
|
|
|
|
import { TRANSFER_BUFFER } from './parser';
|
|
|
|
|
|
2025-01-16 01:10:54 -05:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
2025-01-13 01:48:42 -05:00
|
|
|
let currentQueryProgressCallback: ((percent: number) => void) | null = null;
|
|
|
|
|
|
|
|
|
|
interface QueryOptions {
|
|
|
|
|
startPosition?: Point;
|
|
|
|
|
endPosition?: Point;
|
|
|
|
|
startIndex?: number;
|
|
|
|
|
endIndex?: number;
|
|
|
|
|
matchLimit?: number;
|
|
|
|
|
maxStartDepth?: number;
|
|
|
|
|
timeoutMicros?: number;
|
|
|
|
|
progressCallback?: (percent: number) => void;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-16 01:10:54 -05:00
|
|
|
export type Properties = Record<string, string | null>;
|
2025-01-13 01:48:42 -05:00
|
|
|
|
|
|
|
|
export interface Predicate {
|
|
|
|
|
operator: string;
|
|
|
|
|
operands: PredicateStep[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface Capture {
|
|
|
|
|
name: string;
|
|
|
|
|
node: Node;
|
|
|
|
|
setProperties?: Properties;
|
|
|
|
|
assertedProperties?: Properties;
|
|
|
|
|
refutedProperties?: Properties;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const CaptureQuantifier = {
|
|
|
|
|
Zero: 0,
|
|
|
|
|
ZeroOrOne: 1,
|
|
|
|
|
ZeroOrMore: 2,
|
|
|
|
|
One: 3,
|
|
|
|
|
OneOrMore: 4
|
|
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
export type CaptureQuantifier = typeof CaptureQuantifier[keyof typeof CaptureQuantifier];
|
|
|
|
|
|
|
|
|
|
export interface QueryMatch {
|
|
|
|
|
pattern: number;
|
|
|
|
|
captures: Capture[];
|
|
|
|
|
setProperties?: Properties;
|
|
|
|
|
assertedProperties?: Properties;
|
|
|
|
|
refutedProperties?: Properties;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type PredicateStep =
|
|
|
|
|
| { type: 'string'; value: string }
|
|
|
|
|
| { type: 'capture'; value?: string, name: string };
|
|
|
|
|
|
2025-01-16 01:10:54 -05:00
|
|
|
export type TextPredicate = (captures: Capture[]) => boolean;
|
2025-01-13 01:48:42 -05:00
|
|
|
|
|
|
|
|
export class Query {
|
|
|
|
|
private [0]: number; // Internal handle for WASM
|
|
|
|
|
private exceededMatchLimit: boolean;
|
|
|
|
|
private textPredicates: TextPredicate[][];
|
|
|
|
|
|
|
|
|
|
readonly captureNames: string[];
|
2025-01-16 01:10:54 -05:00
|
|
|
readonly captureQuantifiers: CaptureQuantifier[][];
|
2025-01-13 01:48:42 -05:00
|
|
|
readonly predicates: Predicate[][];
|
|
|
|
|
readonly setProperties: Properties[];
|
|
|
|
|
readonly assertedProperties: Properties[];
|
|
|
|
|
readonly refutedProperties: Properties[];
|
|
|
|
|
matchLimit?: number;
|
|
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
|
internal: Internal,
|
|
|
|
|
address: number,
|
|
|
|
|
captureNames: string[],
|
2025-01-16 01:10:54 -05:00
|
|
|
captureQuantifiers: CaptureQuantifier[][],
|
2025-01-13 01:48:42 -05:00
|
|
|
textPredicates: TextPredicate[][],
|
|
|
|
|
predicates: Predicate[][],
|
|
|
|
|
setProperties: Properties[],
|
|
|
|
|
assertedProperties: Properties[],
|
|
|
|
|
refutedProperties: Properties[],
|
|
|
|
|
) {
|
|
|
|
|
assertInternal(internal);
|
|
|
|
|
this[0] = address;
|
|
|
|
|
this.captureNames = captureNames;
|
|
|
|
|
this.captureQuantifiers = captureQuantifiers;
|
|
|
|
|
this.textPredicates = textPredicates;
|
|
|
|
|
this.predicates = predicates;
|
|
|
|
|
this.setProperties = setProperties;
|
|
|
|
|
this.assertedProperties = assertedProperties;
|
|
|
|
|
this.refutedProperties = refutedProperties;
|
|
|
|
|
this.exceededMatchLimit = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delete(): void {
|
|
|
|
|
C._ts_query_delete(this[0]);
|
|
|
|
|
this[0] = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
matches(
|
|
|
|
|
node: Node,
|
|
|
|
|
options: QueryOptions = {}
|
|
|
|
|
): QueryMatch[] {
|
2025-01-16 01:10:54 -05:00
|
|
|
const startPosition = options.startPosition ?? ZERO_POINT;
|
|
|
|
|
const endPosition = options.endPosition ?? ZERO_POINT;
|
|
|
|
|
const startIndex = options.startIndex ?? 0;
|
|
|
|
|
const endIndex = options.endIndex ?? 0;
|
|
|
|
|
const matchLimit = options.matchLimit ?? 0xFFFFFFFF;
|
|
|
|
|
const maxStartDepth = options.maxStartDepth ?? 0xFFFFFFFF;
|
|
|
|
|
const timeoutMicros = options.timeoutMicros ?? 0;
|
2025-01-13 01:48:42 -05:00
|
|
|
const progressCallback = options.progressCallback;
|
|
|
|
|
|
|
|
|
|
if (typeof matchLimit !== 'number') {
|
|
|
|
|
throw new Error('Arguments must be numbers');
|
|
|
|
|
}
|
|
|
|
|
this.matchLimit = matchLimit;
|
|
|
|
|
|
|
|
|
|
if (endIndex !== 0 && startIndex > endIndex) {
|
|
|
|
|
throw new Error('`startIndex` cannot be greater than `endIndex`');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (endPosition !== ZERO_POINT && (
|
|
|
|
|
startPosition.row > endPosition.row ||
|
|
|
|
|
(startPosition.row === endPosition.row && startPosition.column > endPosition.column)
|
|
|
|
|
)) {
|
|
|
|
|
throw new Error('`startPosition` cannot be greater than `endPosition`');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (progressCallback) {
|
|
|
|
|
currentQueryProgressCallback = progressCallback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
marshalNode(node);
|
|
|
|
|
|
|
|
|
|
C._ts_query_matches_wasm(
|
|
|
|
|
this[0],
|
|
|
|
|
node.tree[0],
|
|
|
|
|
startPosition.row,
|
|
|
|
|
startPosition.column,
|
|
|
|
|
endPosition.row,
|
|
|
|
|
endPosition.column,
|
|
|
|
|
startIndex,
|
|
|
|
|
endIndex,
|
|
|
|
|
matchLimit,
|
|
|
|
|
maxStartDepth,
|
|
|
|
|
timeoutMicros,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const rawCount = getValue(TRANSFER_BUFFER, 'i32');
|
|
|
|
|
const startAddress = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32');
|
|
|
|
|
const didExceedMatchLimit = getValue(TRANSFER_BUFFER + 2 * SIZE_OF_INT, 'i32');
|
2025-01-16 01:10:54 -05:00
|
|
|
const result = new Array<QueryMatch>(rawCount);
|
2025-01-13 01:48:42 -05:00
|
|
|
this.exceededMatchLimit = Boolean(didExceedMatchLimit);
|
|
|
|
|
|
|
|
|
|
let filteredCount = 0;
|
|
|
|
|
let address = startAddress;
|
|
|
|
|
for (let i = 0; i < rawCount; i++) {
|
|
|
|
|
const pattern = getValue(address, 'i32');
|
|
|
|
|
address += SIZE_OF_INT;
|
|
|
|
|
const captureCount = getValue(address, 'i32');
|
|
|
|
|
address += SIZE_OF_INT;
|
|
|
|
|
|
2025-01-16 01:10:54 -05:00
|
|
|
const captures = new Array<Capture>(captureCount);
|
2025-01-13 01:48:42 -05:00
|
|
|
address = unmarshalCaptures(this, node.tree, address, captures);
|
|
|
|
|
|
|
|
|
|
if (this.textPredicates[pattern].every((p) => p(captures))) {
|
|
|
|
|
result[filteredCount] = { pattern, captures };
|
|
|
|
|
const setProperties = this.setProperties[pattern];
|
2025-01-16 01:10:54 -05:00
|
|
|
result[filteredCount].setProperties = setProperties;
|
2025-01-13 01:48:42 -05:00
|
|
|
const assertedProperties = this.assertedProperties[pattern];
|
2025-01-16 01:10:54 -05:00
|
|
|
result[filteredCount].assertedProperties = assertedProperties;
|
2025-01-13 01:48:42 -05:00
|
|
|
const refutedProperties = this.refutedProperties[pattern];
|
2025-01-16 01:10:54 -05:00
|
|
|
result[filteredCount].refutedProperties = refutedProperties;
|
2025-01-13 01:48:42 -05:00
|
|
|
filteredCount++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
result.length = filteredCount;
|
|
|
|
|
|
|
|
|
|
C._free(startAddress);
|
|
|
|
|
currentQueryProgressCallback = null;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
captures(
|
|
|
|
|
node: Node,
|
|
|
|
|
options: QueryOptions = {}
|
|
|
|
|
): Capture[] {
|
2025-01-16 01:10:54 -05:00
|
|
|
const startPosition = options.startPosition ?? ZERO_POINT;
|
|
|
|
|
const endPosition = options.endPosition ?? ZERO_POINT;
|
|
|
|
|
const startIndex = options.startIndex ?? 0;
|
|
|
|
|
const endIndex = options.endIndex ?? 0;
|
|
|
|
|
const matchLimit = options.matchLimit ?? 0xFFFFFFFF;
|
|
|
|
|
const maxStartDepth = options.maxStartDepth ?? 0xFFFFFFFF;
|
|
|
|
|
const timeoutMicros = options.timeoutMicros ?? 0;
|
2025-01-13 01:48:42 -05:00
|
|
|
const progressCallback = options.progressCallback;
|
|
|
|
|
|
|
|
|
|
if (typeof matchLimit !== 'number') {
|
|
|
|
|
throw new Error('Arguments must be numbers');
|
|
|
|
|
}
|
|
|
|
|
this.matchLimit = matchLimit;
|
|
|
|
|
|
|
|
|
|
if (endIndex !== 0 && startIndex > endIndex) {
|
|
|
|
|
throw new Error('`startIndex` cannot be greater than `endIndex`');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (endPosition !== ZERO_POINT && (
|
|
|
|
|
startPosition.row > endPosition.row ||
|
|
|
|
|
(startPosition.row === endPosition.row && startPosition.column > endPosition.column)
|
|
|
|
|
)) {
|
|
|
|
|
throw new Error('`startPosition` cannot be greater than `endPosition`');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (progressCallback) {
|
|
|
|
|
currentQueryProgressCallback = progressCallback;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
marshalNode(node);
|
|
|
|
|
|
|
|
|
|
C._ts_query_captures_wasm(
|
|
|
|
|
this[0],
|
|
|
|
|
node.tree[0],
|
|
|
|
|
startPosition.row,
|
|
|
|
|
startPosition.column,
|
|
|
|
|
endPosition.row,
|
|
|
|
|
endPosition.column,
|
|
|
|
|
startIndex,
|
|
|
|
|
endIndex,
|
|
|
|
|
matchLimit,
|
|
|
|
|
maxStartDepth,
|
|
|
|
|
timeoutMicros,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const count = getValue(TRANSFER_BUFFER, 'i32');
|
|
|
|
|
const startAddress = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32');
|
|
|
|
|
const didExceedMatchLimit = getValue(TRANSFER_BUFFER + 2 * SIZE_OF_INT, 'i32');
|
|
|
|
|
const result: Capture[] = [];
|
|
|
|
|
this.exceededMatchLimit = Boolean(didExceedMatchLimit);
|
|
|
|
|
|
|
|
|
|
const captures: Capture[] = [];
|
|
|
|
|
let address = startAddress;
|
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
|
|
|
const pattern = getValue(address, 'i32');
|
|
|
|
|
address += SIZE_OF_INT;
|
|
|
|
|
const captureCount = getValue(address, 'i32');
|
|
|
|
|
address += SIZE_OF_INT;
|
|
|
|
|
const captureIndex = getValue(address, 'i32');
|
|
|
|
|
address += SIZE_OF_INT;
|
|
|
|
|
|
|
|
|
|
captures.length = captureCount;
|
|
|
|
|
address = unmarshalCaptures(this, node.tree, address, captures);
|
|
|
|
|
|
|
|
|
|
if (this.textPredicates[pattern].every((p) => p(captures))) {
|
|
|
|
|
const capture = captures[captureIndex];
|
|
|
|
|
const setProperties = this.setProperties[pattern];
|
2025-01-16 01:10:54 -05:00
|
|
|
capture.setProperties = setProperties;
|
2025-01-13 01:48:42 -05:00
|
|
|
const assertedProperties = this.assertedProperties[pattern];
|
2025-01-16 01:10:54 -05:00
|
|
|
capture.assertedProperties = assertedProperties;
|
2025-01-13 01:48:42 -05:00
|
|
|
const refutedProperties = this.refutedProperties[pattern];
|
2025-01-16 01:10:54 -05:00
|
|
|
capture.refutedProperties = refutedProperties;
|
2025-01-13 01:48:42 -05:00
|
|
|
result.push(capture);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
C._free(startAddress);
|
|
|
|
|
currentQueryProgressCallback = null;
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
predicatesForPattern(patternIndex: number): Predicate[] {
|
|
|
|
|
return this.predicates[patternIndex];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
disableCapture(captureName: string): void {
|
|
|
|
|
const captureNameLength = lengthBytesUTF8(captureName);
|
|
|
|
|
const captureNameAddress = C._malloc(captureNameLength + 1);
|
|
|
|
|
stringToUTF8(captureName, captureNameAddress, captureNameLength + 1);
|
|
|
|
|
C._ts_query_disable_capture(this[0], captureNameAddress, captureNameLength);
|
|
|
|
|
C._free(captureNameAddress);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
disablePattern(patternIndex: number): void {
|
|
|
|
|
if (patternIndex >= this.predicates.length) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Pattern index is ${patternIndex} but the pattern count is ${this.predicates.length}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
C._ts_query_disable_pattern(this[0], patternIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
didExceedMatchLimit(): boolean {
|
|
|
|
|
return this.exceededMatchLimit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
startIndexForPattern(patternIndex: number): number {
|
|
|
|
|
if (patternIndex >= this.predicates.length) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Pattern index is ${patternIndex} but the pattern count is ${this.predicates.length}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return C._ts_query_start_byte_for_pattern(this[0], patternIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
endIndexForPattern(patternIndex: number): number {
|
|
|
|
|
if (patternIndex >= this.predicates.length) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Pattern index is ${patternIndex} but the pattern count is ${this.predicates.length}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return C._ts_query_end_byte_for_pattern(this[0], patternIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isPatternNonLocal(patternIndex: number): boolean {
|
|
|
|
|
return C._ts_query_is_pattern_non_local(this[0], patternIndex) === 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isPatternRooted(patternIndex: number): boolean {
|
|
|
|
|
return C._ts_query_is_pattern_rooted(this[0], patternIndex) === 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isPatternGuaranteedAtStep(patternIndex: number, stepIndex: number): boolean {
|
|
|
|
|
return C._ts_query_is_pattern_guaranteed_at_step(
|
|
|
|
|
this[0],
|
|
|
|
|
patternIndex,
|
|
|
|
|
stepIndex
|
|
|
|
|
) === 1;
|
|
|
|
|
}
|
|
|
|
|
}
|