feat(web)!: rewrite the library in TypeScript
This commit is contained in:
parent
07a86b1729
commit
2cae67892e
39 changed files with 7856 additions and 3629 deletions
238
lib/binding_web/src/constants.ts
Normal file
238
lib/binding_web/src/constants.ts
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
import { CaptureQuantifier } from "./query";
|
||||
|
||||
export interface Point {
|
||||
row: number;
|
||||
column: number;
|
||||
}
|
||||
|
||||
export interface Range {
|
||||
startPosition: Point;
|
||||
endPosition: Point;
|
||||
startIndex: number;
|
||||
endIndex: number;
|
||||
}
|
||||
|
||||
export interface Edit {
|
||||
startPosition: Point;
|
||||
oldEndPosition: Point;
|
||||
newEndPosition: Point;
|
||||
startIndex: number;
|
||||
oldEndIndex: number;
|
||||
newEndIndex: number;
|
||||
}
|
||||
|
||||
export interface ParserOptions {
|
||||
includedRanges?: Range[];
|
||||
progressCallback?: (progress: { currentOffset: number }) => boolean;
|
||||
}
|
||||
|
||||
export const SIZE_OF_SHORT = 2;
|
||||
export const SIZE_OF_INT = 4;
|
||||
export const SIZE_OF_CURSOR = 4 * SIZE_OF_INT;
|
||||
export const SIZE_OF_NODE = 5 * SIZE_OF_INT;
|
||||
export const SIZE_OF_POINT = 2 * SIZE_OF_INT;
|
||||
export const SIZE_OF_RANGE = 2 * SIZE_OF_INT + 2 * SIZE_OF_POINT;
|
||||
export const ZERO_POINT: Point = { row: 0, column: 0 };
|
||||
|
||||
// Types for callbacks
|
||||
export type ParseCallback = (index: number, position: Point) => string | null;
|
||||
export type ProgressCallback = (progress: { currentOffset: number }) => boolean;
|
||||
export type LogCallback = (message: string, isLex: boolean) => void;
|
||||
|
||||
// Helper type for internal use
|
||||
export const INTERNAL = Symbol('INTERNAL');
|
||||
export type Internal = typeof INTERNAL;
|
||||
|
||||
// Helper functions for type checking
|
||||
export function assertInternal(x: unknown): asserts x is Internal {
|
||||
if (x !== INTERNAL) throw new Error('Illegal constructor');
|
||||
}
|
||||
|
||||
export function isPoint(point: Point): point is Point {
|
||||
return (
|
||||
!!point &&
|
||||
typeof (point as Point).row === 'number' &&
|
||||
typeof (point as Point).column === 'number'
|
||||
);
|
||||
}
|
||||
|
||||
export const C: EmscriptenModule & {
|
||||
// Global
|
||||
_ts_init(): number;
|
||||
|
||||
// Libc
|
||||
_malloc(size: number): number;
|
||||
_calloc(count: number, size: number): number;
|
||||
_free(ptr: number): void;
|
||||
|
||||
// Parser
|
||||
_ts_parser_new_wasm(): void;
|
||||
_ts_parser_delete(address: number): void;
|
||||
_ts_parser_set_language(parserAddress: number, languageAddress: number): void;
|
||||
_ts_language_version(address: number): number;
|
||||
_ts_parser_enable_logger_wasm(address: number, enabled: number): void;
|
||||
_ts_parser_parse_wasm(
|
||||
address: number,
|
||||
payload: number,
|
||||
oldTreeAddress: number,
|
||||
rangeAddress: number,
|
||||
rangeCount: number
|
||||
): number;
|
||||
_ts_parser_reset(address: number): void;
|
||||
_ts_parser_timeout_micros(address: number): number;
|
||||
_ts_parser_set_timeout_micros(address: number, timeout: number): void;
|
||||
_ts_parser_included_ranges_wasm(address: number): void;
|
||||
|
||||
// Language
|
||||
_ts_language_symbol_count(address: number): number;
|
||||
_ts_language_symbol_name(address: number, index: number): number;
|
||||
_ts_language_symbol_type(address: number, index: number): number;
|
||||
_ts_language_field_count(address: number): number;
|
||||
_ts_language_field_name_for_id(address: number, id: number): number;
|
||||
_ts_language_name(address: number): number;
|
||||
_ts_language_version(address: number): number;
|
||||
_ts_language_state_count(address: number): number;
|
||||
_ts_language_symbol_for_name(address: number, typeAddress: number, typeLength: number, named: boolean): number;
|
||||
_ts_language_type_is_named_wasm(address: number, typeId: number): number;
|
||||
_ts_language_type_is_visible_wasm(address: number, typeId: number): number;
|
||||
_ts_language_next_state(address: number, stateId: number, typeId: number): number;
|
||||
_ts_language_supertypes_wasm(address: number): void;
|
||||
_ts_language_subtypes_wasm(address: number, supertype: number): void;
|
||||
|
||||
// Tree
|
||||
_ts_tree_copy(tree: number): number;
|
||||
_ts_tree_delete(tree: number): void;
|
||||
_ts_tree_edit_wasm(tree: number): void;
|
||||
_ts_tree_root_node_wasm(tree: number): void;
|
||||
_ts_tree_root_node_with_offset_wasm(tree: number): void;
|
||||
_ts_tree_get_changed_ranges_wasm(self: number, other: number): void;
|
||||
_ts_tree_included_ranges_wasm(self: number): void;
|
||||
|
||||
// Node
|
||||
_ts_node_symbol_wasm(tree: number): number;
|
||||
_ts_node_grammar_symbol_wasm(tree: number): number;
|
||||
_ts_node_end_point_wasm(tree: number): void;
|
||||
_ts_node_end_index_wasm(tree: number): number;
|
||||
_ts_node_parse_state_wasm(tree: number): number;
|
||||
_ts_node_next_parse_state_wasm(tree: number): number;
|
||||
_ts_node_is_named_wasm(tree: number): number;
|
||||
_ts_node_has_error_wasm(tree: number): number;
|
||||
_ts_node_has_changes_wasm(tree: number): number;
|
||||
_ts_node_is_error_wasm(tree: number): number;
|
||||
_ts_node_is_missing_wasm(tree: number): number;
|
||||
_ts_node_is_extra_wasm(tree: number): number;
|
||||
_ts_node_child_wasm(tree: number, index: number): void;
|
||||
_ts_node_named_child_wasm(tree: number, index: number): void;
|
||||
_ts_node_child_by_field_id_wasm(tree: number, fieldId: number): void;
|
||||
_ts_node_field_name_for_child_wasm(tree: number, index: number): number;
|
||||
_ts_node_field_name_for_named_child_wasm(tree: number, index: number): number;
|
||||
_ts_node_children_by_field_id_wasm(tree: number, fieldId: number): void;
|
||||
_ts_node_first_child_for_byte_wasm(tree: number): void;
|
||||
_ts_node_first_named_child_for_byte_wasm(tree: number): void;
|
||||
_ts_node_child_count_wasm(tree: number): number;
|
||||
_ts_node_named_child_count_wasm(tree: number): number;
|
||||
_ts_node_children_wasm(tree: number): void;
|
||||
_ts_node_named_children_wasm(tree: number): void;
|
||||
_ts_node_descendants_of_type_wasm(
|
||||
tree: number,
|
||||
symbolsAddress: number,
|
||||
symbolCount: number,
|
||||
startRow: number,
|
||||
startColumn: number,
|
||||
endRow: number,
|
||||
endColumn: number
|
||||
): void;
|
||||
_ts_node_next_sibling_wasm(tree: number): void;
|
||||
_ts_node_prev_sibling_wasm(tree: number): void;
|
||||
_ts_node_next_named_sibling_wasm(tree: number): void;
|
||||
_ts_node_prev_named_sibling_wasm(tree: number): void;
|
||||
_ts_node_descendant_count_wasm(tree: number): number;
|
||||
_ts_node_parent_wasm(tree: number): void;
|
||||
_ts_node_descendant_for_index_wasm(tree: number): void;
|
||||
_ts_node_named_descendant_for_index_wasm(tree: number): void;
|
||||
_ts_node_descendant_for_position_wasm(tree: number): void;
|
||||
_ts_node_named_descendant_for_position_wasm(tree: number): void;
|
||||
_ts_tree_cursor_new_wasm(tree: number): void;
|
||||
_ts_node_to_string_wasm(tree: number): number;
|
||||
|
||||
// TreeCursor
|
||||
_ts_tree_cursor_copy_wasm(cursor: number): void;
|
||||
_ts_tree_cursor_delete_wasm(cursor: number): void;
|
||||
_ts_tree_cursor_reset_wasm(cursor: number): void;
|
||||
_ts_tree_cursor_reset_to_wasm(cursor: number, other: number): void;
|
||||
_ts_tree_cursor_current_node_type_id_wasm(cursor: number): number;
|
||||
_ts_tree_cursor_current_node_state_id_wasm(cursor: number): number;
|
||||
_ts_tree_cursor_current_node_id_wasm(cursor: number): number;
|
||||
_ts_tree_cursor_current_node_is_named_wasm(cursor: number): number;
|
||||
_ts_tree_cursor_current_node_is_missing_wasm(cursor: number): number;
|
||||
_ts_tree_cursor_start_index_wasm(cursor: number): number;
|
||||
_ts_tree_cursor_end_index_wasm(cursor: number): number;
|
||||
_ts_tree_cursor_start_position_wasm(cursor: number): void;
|
||||
_ts_tree_cursor_end_position_wasm(cursor: number): void;
|
||||
_ts_tree_cursor_current_node_wasm(cursor: number): void;
|
||||
_ts_tree_cursor_current_field_id_wasm(cursor: number): number;
|
||||
_ts_tree_cursor_current_depth_wasm(cursor: number): number;
|
||||
_ts_tree_cursor_current_descendant_index_wasm(cursor: number): number;
|
||||
_ts_tree_cursor_goto_first_child_wasm(cursor: number): number;
|
||||
_ts_tree_cursor_goto_last_child_wasm(cursor: number): number;
|
||||
_ts_tree_cursor_goto_first_child_for_index_wasm(cursor: number): number;
|
||||
_ts_tree_cursor_goto_first_child_for_position_wasm(cursor: number): number;
|
||||
_ts_tree_cursor_goto_next_sibling_wasm(cursor: number): number;
|
||||
_ts_tree_cursor_goto_previous_sibling_wasm(cursor: number): number;
|
||||
_ts_tree_cursor_goto_descendant_wasm(cursor: number, index: number): void;
|
||||
_ts_tree_cursor_goto_parent_wasm(cursor: number): number;
|
||||
|
||||
// Query
|
||||
_ts_query_new(languageAddress: number, sourceAddress: number, sourceLength: number, errorOffset: number, errorType: number): number;
|
||||
_ts_query_string_count(address: number): number;
|
||||
_ts_query_capture_count(address: number): number;
|
||||
_ts_query_pattern_count(address: number): number;
|
||||
_ts_query_capture_name_for_id(address: number, id: number, buffer: number): number;
|
||||
_ts_query_capture_quantifier_for_id(address: number, patternId: number, captureId: number): CaptureQuantifier;
|
||||
_ts_query_string_value_for_id(address: number, id: number, buffer: number): number;
|
||||
_ts_query_predicates_for_pattern(address: number, patternId: number, buffer: number): number;
|
||||
_ts_query_delete(address: number): void;
|
||||
_ts_query_matches_wasm(
|
||||
address: number,
|
||||
treeAddress: number,
|
||||
startRow: number,
|
||||
startColumn: number,
|
||||
endRow: number,
|
||||
endColumn: number,
|
||||
startIndex: number,
|
||||
endIndex: number,
|
||||
matchLimit: number,
|
||||
maxStartDepth: number,
|
||||
timeoutMicros: number
|
||||
): void;
|
||||
_ts_query_captures_wasm(
|
||||
address: number,
|
||||
treeAddress: number,
|
||||
startRow: number,
|
||||
startColumn: number,
|
||||
endRow: number,
|
||||
endColumn: number,
|
||||
startIndex: number,
|
||||
endIndex: number,
|
||||
matchLimit: number,
|
||||
maxStartDepth: number,
|
||||
timeoutMicros: number
|
||||
): void;
|
||||
_ts_query_disable_capture(address: number, nameAddress: number, nameLength: number): void;
|
||||
_ts_query_disable_pattern(address: number, patternIndex: number): void;
|
||||
_ts_query_start_byte_for_pattern(address: number, patternIndex: number): number;
|
||||
_ts_query_end_byte_for_pattern(address: number, patternIndex: number): number;
|
||||
_ts_query_is_pattern_non_local(address: number, patternIndex: number): number;
|
||||
_ts_query_is_pattern_rooted(address: number, patternIndex: number): number;
|
||||
_ts_query_is_pattern_guaranteed_at_step(address: number, patternIndex: number, stepIndex: number): number;
|
||||
|
||||
// LookaheadIterator
|
||||
_ts_lookahead_iterator_new(address: number, stateId: number): number;
|
||||
_ts_lookahead_iterator_current_symbol(address: number): number;
|
||||
_ts_lookahead_iterator_delete(address: number): void;
|
||||
_ts_lookahead_iterator_reset_state(address: number, stateId: number): boolean;
|
||||
_ts_lookahead_iterator_reset(address: number, languageAddress: number, stateId: number): boolean;
|
||||
_ts_lookahead_iterator_next(address: number): boolean;
|
||||
|
||||
// @ts-ignore
|
||||
} = Module;
|
||||
9
lib/binding_web/src/index.ts
Normal file
9
lib/binding_web/src/index.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export * from './constants';
|
||||
export * from './marshal';
|
||||
export * from './node';
|
||||
export * from './tree';
|
||||
export * from './tree_cursor';
|
||||
export * from './lookahead_iterator';
|
||||
export * from './query';
|
||||
export * from './language';
|
||||
export * from './parser';
|
||||
491
lib/binding_web/src/language.ts
Normal file
491
lib/binding_web/src/language.ts
Normal file
|
|
@ -0,0 +1,491 @@
|
|||
import { INTERNAL, Internal, assertInternal, SIZE_OF_INT, SIZE_OF_SHORT, C } from './constants';
|
||||
import { LookaheadIterator } from './lookahead_iterator';
|
||||
import { Node } from './node';
|
||||
import { TRANSFER_BUFFER } from './parser';
|
||||
import { CaptureQuantifier, Predicate, PredicateStep, Properties, Query, TextPredicate } from './query';
|
||||
|
||||
declare const UTF8ToString: (ptr: number, maxBytesToRead?: number) => string;
|
||||
declare const lengthBytesUTF8: (str: string) => number;
|
||||
declare const stringToUTF8: (str: string, outPtr: number, maxBytesToRead: number) => void;
|
||||
declare const getValue: (ptr: number, type: string) => any;
|
||||
declare const loadWebAssemblyModule: (bytes: Uint8Array, options: { loadAsync: boolean }) => Promise<any>;
|
||||
|
||||
const PREDICATE_STEP_TYPE_CAPTURE = 1;
|
||||
const PREDICATE_STEP_TYPE_STRING = 2;
|
||||
|
||||
const QUERY_WORD_REGEX = /[\w-]+/g;
|
||||
const LANGUAGE_FUNCTION_REGEX = /^tree_sitter_\w+$/;
|
||||
|
||||
export class Language {
|
||||
private [0]: number; // Internal handle for WASM
|
||||
public types: string[];
|
||||
public fields: (string | null)[];
|
||||
|
||||
constructor(internal: Internal, address: number) {
|
||||
assertInternal(internal);
|
||||
this[0] = address;
|
||||
this.types = new Array(C._ts_language_symbol_count(this[0]));
|
||||
for (let i = 0, n = this.types.length; i < n; i++) {
|
||||
if (C._ts_language_symbol_type(this[0], i) < 2) {
|
||||
this.types[i] = UTF8ToString(C._ts_language_symbol_name(this[0], i));
|
||||
}
|
||||
}
|
||||
this.fields = new Array(C._ts_language_field_count(this[0]) + 1);
|
||||
for (let i = 0, n = this.fields.length; i < n; i++) {
|
||||
const fieldName = C._ts_language_field_name_for_id(this[0], i);
|
||||
if (fieldName !== 0) {
|
||||
this.fields[i] = UTF8ToString(fieldName);
|
||||
} else {
|
||||
this.fields[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get name(): string | null {
|
||||
const ptr = C._ts_language_name(this[0]);
|
||||
if (ptr === 0) return null;
|
||||
return UTF8ToString(ptr);
|
||||
}
|
||||
|
||||
get version(): number {
|
||||
return C._ts_language_version(this[0]);
|
||||
}
|
||||
|
||||
get fieldCount(): number {
|
||||
return this.fields.length - 1;
|
||||
}
|
||||
|
||||
get stateCount(): number {
|
||||
return C._ts_language_state_count(this[0]);
|
||||
}
|
||||
|
||||
fieldIdForName(fieldName: string): number | null {
|
||||
const result = this.fields.indexOf(fieldName);
|
||||
return result !== -1 ? result : null;
|
||||
}
|
||||
|
||||
fieldNameForId(fieldId: number): string | null {
|
||||
return this.fields[fieldId] || null;
|
||||
}
|
||||
|
||||
idForNodeType(type: string, named: boolean): number | null {
|
||||
const typeLength = lengthBytesUTF8(type);
|
||||
const typeAddress = C._malloc(typeLength + 1);
|
||||
stringToUTF8(type, typeAddress, typeLength + 1);
|
||||
const result = C._ts_language_symbol_for_name(this[0], typeAddress, typeLength, named);
|
||||
C._free(typeAddress);
|
||||
return result || null;
|
||||
}
|
||||
|
||||
get nodeTypeCount(): number {
|
||||
return C._ts_language_symbol_count(this[0]);
|
||||
}
|
||||
|
||||
nodeTypeForId(typeId: number): string | null {
|
||||
const name = C._ts_language_symbol_name(this[0], typeId);
|
||||
return name ? UTF8ToString(name) : null;
|
||||
}
|
||||
|
||||
nodeTypeIsNamed(typeId: number): boolean {
|
||||
return C._ts_language_type_is_named_wasm(this[0], typeId) ? true : false;
|
||||
}
|
||||
|
||||
nodeTypeIsVisible(typeId: number): boolean {
|
||||
return C._ts_language_type_is_visible_wasm(this[0], typeId) ? true : false;
|
||||
}
|
||||
|
||||
get supertypes(): number[] {
|
||||
C._ts_language_supertypes_wasm(this[0]);
|
||||
const count = getValue(TRANSFER_BUFFER, 'i32');
|
||||
const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32');
|
||||
const result = new Array(count);
|
||||
|
||||
if (count > 0) {
|
||||
let address = buffer;
|
||||
for (let i = 0; i < count; i++) {
|
||||
result[i] = getValue(address, 'i16');
|
||||
address += SIZE_OF_SHORT;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
subtypes(supertype: number): number[] {
|
||||
C._ts_language_subtypes_wasm(this[0], supertype);
|
||||
const count = getValue(TRANSFER_BUFFER, 'i32');
|
||||
const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32');
|
||||
const result = new Array(count);
|
||||
|
||||
if (count > 0) {
|
||||
let address = buffer;
|
||||
for (let i = 0; i < count; i++) {
|
||||
result[i] = getValue(address, 'i16');
|
||||
address += SIZE_OF_SHORT;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
nextState(stateId: number, typeId: number): number {
|
||||
return C._ts_language_next_state(this[0], stateId, typeId);
|
||||
}
|
||||
|
||||
lookaheadIterator(stateId: number): LookaheadIterator | null {
|
||||
const address = C._ts_lookahead_iterator_new(this[0], stateId);
|
||||
if (address) return new LookaheadIterator(INTERNAL, address, this);
|
||||
return null;
|
||||
}
|
||||
|
||||
query(source: string): Query {
|
||||
const sourceLength = lengthBytesUTF8(source);
|
||||
const sourceAddress = C._malloc(sourceLength + 1);
|
||||
stringToUTF8(source, sourceAddress, sourceLength + 1);
|
||||
const address = C._ts_query_new(
|
||||
this[0],
|
||||
sourceAddress,
|
||||
sourceLength,
|
||||
TRANSFER_BUFFER,
|
||||
TRANSFER_BUFFER + SIZE_OF_INT
|
||||
);
|
||||
|
||||
if (!address) {
|
||||
const errorId = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32');
|
||||
const errorByte = getValue(TRANSFER_BUFFER, 'i32');
|
||||
const errorIndex = UTF8ToString(sourceAddress, errorByte).length;
|
||||
const suffix = source.slice(errorIndex, errorIndex + 100).split('\n')[0];
|
||||
let word = suffix.match(QUERY_WORD_REGEX)?.[0] || '';
|
||||
let error: Error;
|
||||
|
||||
switch (errorId) {
|
||||
case 2:
|
||||
error = new RangeError(`Bad node name '${word}'`);
|
||||
break;
|
||||
case 3:
|
||||
error = new RangeError(`Bad field name '${word}'`);
|
||||
break;
|
||||
case 4:
|
||||
error = new RangeError(`Bad capture name @${word}`);
|
||||
break;
|
||||
case 5:
|
||||
error = new TypeError(`Bad pattern structure at offset ${errorIndex}: '${suffix}'...`);
|
||||
word = '';
|
||||
break;
|
||||
default:
|
||||
error = new SyntaxError(`Bad syntax at offset ${errorIndex}: '${suffix}'...`);
|
||||
word = '';
|
||||
break;
|
||||
}
|
||||
|
||||
(error as any).index = errorIndex;
|
||||
(error as any).length = word.length;
|
||||
C._free(sourceAddress);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const stringCount = C._ts_query_string_count(address);
|
||||
const captureCount = C._ts_query_capture_count(address);
|
||||
const patternCount = C._ts_query_pattern_count(address);
|
||||
const captureNames = new Array<string>(captureCount);
|
||||
const captureQuantifiers = new Array<Array<CaptureQuantifier>>(patternCount);
|
||||
const stringValues = new Array<string>(stringCount);
|
||||
|
||||
for (let i = 0; i < captureCount; i++) {
|
||||
const nameAddress = C._ts_query_capture_name_for_id(
|
||||
address,
|
||||
i,
|
||||
TRANSFER_BUFFER
|
||||
);
|
||||
const nameLength = getValue(TRANSFER_BUFFER, 'i32');
|
||||
captureNames[i] = UTF8ToString(nameAddress, nameLength);
|
||||
}
|
||||
|
||||
for (let i = 0; i < patternCount; i++) {
|
||||
const captureQuantifiersArray = new Array<CaptureQuantifier>(captureCount);
|
||||
for (let j = 0; j < captureCount; j++) {
|
||||
const quantifier = C._ts_query_capture_quantifier_for_id(address, i, j);
|
||||
captureQuantifiersArray[j] = quantifier;
|
||||
}
|
||||
captureQuantifiers[i] = captureQuantifiersArray;
|
||||
}
|
||||
|
||||
for (let i = 0; i < stringCount; i++) {
|
||||
const valueAddress = C._ts_query_string_value_for_id(
|
||||
address,
|
||||
i,
|
||||
TRANSFER_BUFFER
|
||||
);
|
||||
const nameLength = getValue(TRANSFER_BUFFER, 'i32');
|
||||
stringValues[i] = UTF8ToString(valueAddress, nameLength);
|
||||
}
|
||||
|
||||
const setProperties = new Array<Properties>(patternCount);
|
||||
const assertedProperties = new Array<Properties>(patternCount);
|
||||
const refutedProperties = new Array<Properties>(patternCount);
|
||||
const predicates = new Array<Array<Predicate>>(patternCount);
|
||||
const textPredicates = new Array<Array<TextPredicate>>(patternCount);
|
||||
|
||||
for (let i = 0; i < patternCount; i++) {
|
||||
const predicatesAddress = C._ts_query_predicates_for_pattern(
|
||||
address,
|
||||
i,
|
||||
TRANSFER_BUFFER
|
||||
);
|
||||
const stepCount = getValue(TRANSFER_BUFFER, 'i32');
|
||||
|
||||
predicates[i] = [];
|
||||
textPredicates[i] = [];
|
||||
|
||||
const steps: PredicateStep[] = [];
|
||||
let stepAddress = predicatesAddress;
|
||||
for (let j = 0; j < stepCount; j++) {
|
||||
const stepType = getValue(stepAddress, 'i32');
|
||||
stepAddress += SIZE_OF_INT;
|
||||
const stepValueId: number = getValue(stepAddress, 'i32');
|
||||
stepAddress += SIZE_OF_INT;
|
||||
|
||||
if (stepType === PREDICATE_STEP_TYPE_CAPTURE) {
|
||||
const name = captureNames[stepValueId];
|
||||
steps.push({ type: 'capture', name });
|
||||
} else if (stepType === PREDICATE_STEP_TYPE_STRING) {
|
||||
steps.push({ type: 'string', value: stringValues[stepValueId] });
|
||||
} else if (steps.length > 0) {
|
||||
if (steps[0].type !== 'string') {
|
||||
throw new Error('Predicates must begin with a literal value');
|
||||
}
|
||||
|
||||
const operator = steps[0].value!;
|
||||
let isPositive = true;
|
||||
let matchAll = true;
|
||||
let captureName: string | undefined;
|
||||
|
||||
switch (operator) {
|
||||
case 'any-not-eq?':
|
||||
case 'not-eq?':
|
||||
isPositive = false;
|
||||
case 'any-eq?':
|
||||
case 'eq?': {
|
||||
if (steps.length !== 3) {
|
||||
throw new Error(
|
||||
`Wrong number of arguments to \`#${operator}\` predicate. Expected 2, got ${steps.length - 1}`
|
||||
);
|
||||
}
|
||||
if (steps[1].type !== 'capture') {
|
||||
throw new Error(
|
||||
`First argument of \`#${operator}\` predicate must be a capture. Got "${steps[1].value}"`
|
||||
);
|
||||
}
|
||||
matchAll = !operator.startsWith('any-');
|
||||
if (steps[2].type === 'capture') {
|
||||
const captureName1 = steps[1].name!;
|
||||
const captureName2 = steps[2].name!;
|
||||
textPredicates[i].push((captures) => {
|
||||
const nodes1: Node[] = [];
|
||||
const nodes2: Node[] = [];
|
||||
for (const c of captures) {
|
||||
if (c.name === captureName1) nodes1.push(c.node);
|
||||
if (c.name === captureName2) nodes2.push(c.node);
|
||||
}
|
||||
const compare = (n1: { text: string }, n2: { text: string }, positive: boolean) => {
|
||||
return positive ? n1.text === n2.text : n1.text !== n2.text;
|
||||
};
|
||||
return matchAll
|
||||
? nodes1.every((n1) => nodes2.some((n2) => compare(n1, n2, isPositive)))
|
||||
: nodes1.some((n1) => nodes2.some((n2) => compare(n1, n2, isPositive)));
|
||||
});
|
||||
} else {
|
||||
captureName = steps[1].name;
|
||||
const stringValue = steps[2].value;
|
||||
const matches = (n: Node) => n.text === stringValue;
|
||||
const doesNotMatch = (n: Node) => n.text !== stringValue;
|
||||
textPredicates[i].push((captures) => {
|
||||
const nodes = [];
|
||||
for (const c of captures) {
|
||||
if (c.name === captureName) nodes.push(c.node);
|
||||
}
|
||||
const test = isPositive ? matches : doesNotMatch;
|
||||
return matchAll ?
|
||||
nodes.every(test) :
|
||||
nodes.some(test);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'any-not-match?':
|
||||
case 'not-match?':
|
||||
isPositive = false;
|
||||
case 'any-match?':
|
||||
case 'match?': {
|
||||
if (steps.length !== 3) {
|
||||
throw new Error(
|
||||
`Wrong number of arguments to \`#${operator}\` predicate. Expected 2, got ${steps.length - 1}.`,
|
||||
);
|
||||
}
|
||||
if (steps[1].type !== 'capture') {
|
||||
throw new Error(
|
||||
`First argument of \`#${operator}\` predicate must be a capture. Got "${steps[1].value}".`,
|
||||
);
|
||||
}
|
||||
if (steps[2].type !== 'string') {
|
||||
throw new Error(
|
||||
`Second argument of \`#${operator}\` predicate must be a string. Got @${steps[2].value}.`,
|
||||
);
|
||||
}
|
||||
captureName = steps[1].name;
|
||||
const regex = new RegExp(steps[2].value);
|
||||
matchAll = !operator.startsWith('any-');
|
||||
textPredicates[i].push((captures) => {
|
||||
const nodes = [];
|
||||
for (const c of captures) {
|
||||
if (c.name === captureName) nodes.push(c.node.text);
|
||||
}
|
||||
const test = (text: string, positive: boolean) => {
|
||||
return positive ?
|
||||
regex.test(text) :
|
||||
!regex.test(text);
|
||||
};
|
||||
if (nodes.length === 0) return !isPositive;
|
||||
return matchAll ?
|
||||
nodes.every((text) => test(text, isPositive)) :
|
||||
nodes.some((text) => test(text, isPositive));
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'set!': {
|
||||
if (steps.length < 2 || steps.length > 3) {
|
||||
throw new Error(
|
||||
`Wrong number of arguments to \`#set!\` predicate. Expected 1 or 2. Got ${steps.length - 1}.`,
|
||||
);
|
||||
}
|
||||
if (steps.some((s) => s.type !== 'string')) {
|
||||
throw new Error(
|
||||
`Arguments to \`#set!\` predicate must be a strings.".`,
|
||||
);
|
||||
}
|
||||
if (!setProperties[i]) setProperties[i] = {};
|
||||
setProperties[i][steps[1].value!] = steps[2]?.value || null;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'is?':
|
||||
case 'is-not?': {
|
||||
if (steps.length < 2 || steps.length > 3) {
|
||||
throw new Error(
|
||||
`Wrong number of arguments to \`#${operator}\` predicate. Expected 1 or 2. Got ${steps.length - 1}.`,
|
||||
);
|
||||
}
|
||||
if (steps.some((s) => s.type !== 'string')) {
|
||||
throw new Error(
|
||||
`Arguments to \`#${operator}\` predicate must be a strings.".`,
|
||||
);
|
||||
}
|
||||
const properties = operator === 'is?' ? assertedProperties : refutedProperties;
|
||||
if (!properties[i]) properties[i] = {};
|
||||
properties[i][steps[1].value!] = steps[2]?.value || null;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'not-any-of?':
|
||||
isPositive = false;
|
||||
case 'any-of?': {
|
||||
if (steps.length < 2) {
|
||||
throw new Error(
|
||||
`Wrong number of arguments to \`#${operator}\` predicate. Expected at least 1. Got ${steps.length - 1}.`,
|
||||
);
|
||||
}
|
||||
if (steps[1].type !== 'capture') {
|
||||
throw new Error(
|
||||
`First argument of \`#${operator}\` predicate must be a capture. Got "${steps[1].value}".`,
|
||||
);
|
||||
}
|
||||
for (let i = 2; i < steps.length; i++) {
|
||||
if (steps[i].type !== 'string') {
|
||||
throw new Error(
|
||||
`Arguments to \`#${operator}\` predicate must be a strings.".`,
|
||||
);
|
||||
}
|
||||
}
|
||||
captureName = steps[1].name;
|
||||
const values = steps.slice(2).map((s) => s.value);
|
||||
textPredicates[i].push((captures) => {
|
||||
const nodes = [];
|
||||
for (const c of captures) {
|
||||
if (c.name === captureName) nodes.push(c.node.text);
|
||||
}
|
||||
if (nodes.length === 0) return !isPositive;
|
||||
return nodes.every((text) => values.includes(text)) === isPositive;
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
predicates[i].push({ operator, operands: steps.slice(1) });
|
||||
}
|
||||
|
||||
steps.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Object.freeze(setProperties[i]);
|
||||
Object.freeze(assertedProperties[i]);
|
||||
Object.freeze(refutedProperties[i]);
|
||||
}
|
||||
|
||||
C._free(sourceAddress);
|
||||
return new Query(
|
||||
INTERNAL,
|
||||
address,
|
||||
captureNames,
|
||||
captureQuantifiers,
|
||||
textPredicates,
|
||||
predicates,
|
||||
setProperties,
|
||||
assertedProperties,
|
||||
refutedProperties,
|
||||
);
|
||||
}
|
||||
|
||||
static load(input: string | Uint8Array): Promise<Language> {
|
||||
let bytes: Promise<Uint8Array>;
|
||||
if (input instanceof Uint8Array) {
|
||||
bytes = Promise.resolve(input);
|
||||
} else {
|
||||
// @ts-ignore
|
||||
if (globalThis.process?.versions?.node) {
|
||||
// @ts-ignore
|
||||
const fs = require('fs/promises');
|
||||
bytes = fs.readFile(input);
|
||||
} else {
|
||||
bytes = fetch(input)
|
||||
.then((response) => response.arrayBuffer()
|
||||
.then((buffer) => {
|
||||
if (response.ok) {
|
||||
return new Uint8Array(buffer);
|
||||
} else {
|
||||
const body = new TextDecoder('utf-8').decode(buffer);
|
||||
throw new Error(`Language.load failed with status ${response.status}.\n\n${body}`);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return bytes
|
||||
.then((bytes) => loadWebAssemblyModule(bytes, { loadAsync: true }))
|
||||
.then((mod) => {
|
||||
const symbolNames = Object.keys(mod);
|
||||
const functionName = symbolNames.find((key) =>
|
||||
LANGUAGE_FUNCTION_REGEX.test(key) &&
|
||||
!key.includes('external_scanner_'),
|
||||
);
|
||||
if (!functionName) {
|
||||
console.log(`Couldn't find language function in WASM file. Symbols:\n${JSON.stringify(symbolNames, null, 2)}`);
|
||||
throw new Error('Language.load failed: no language function found in WASM file');
|
||||
}
|
||||
const languageAddress = mod[functionName]();
|
||||
return new Language(INTERNAL, languageAddress);
|
||||
});
|
||||
}
|
||||
}
|
||||
50
lib/binding_web/src/lookahead_iterator.ts
Normal file
50
lib/binding_web/src/lookahead_iterator.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { C, Internal, assertInternal } from './constants';
|
||||
import { Language } from './language';
|
||||
|
||||
export class LookaheadIterator implements Iterable<string> {
|
||||
private [0]: number; // Internal handle for WASM
|
||||
private language: Language;
|
||||
|
||||
constructor(internal: Internal, address: number, language: Language) {
|
||||
assertInternal(internal);
|
||||
this[0] = address;
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
get currentTypeId(): number {
|
||||
return C._ts_lookahead_iterator_current_symbol(this[0]);
|
||||
}
|
||||
|
||||
get currentType(): string {
|
||||
return this.language.types[this.currentTypeId] || 'ERROR';
|
||||
}
|
||||
|
||||
delete(): void {
|
||||
C._ts_lookahead_iterator_delete(this[0]);
|
||||
this[0] = 0;
|
||||
}
|
||||
|
||||
resetState(stateId: number): boolean {
|
||||
return Boolean(C._ts_lookahead_iterator_reset_state(this[0], stateId));
|
||||
}
|
||||
|
||||
reset(language: Language, stateId: number): boolean {
|
||||
if (C._ts_lookahead_iterator_reset(this[0], language[0], stateId)) {
|
||||
this.language = language;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[Symbol.iterator](): Iterator<string> {
|
||||
const self = this;
|
||||
return {
|
||||
next(): IteratorResult<string> {
|
||||
if (C._ts_lookahead_iterator_next(self[0])) {
|
||||
return { done: false, value: self.currentType };
|
||||
}
|
||||
return { done: true, value: '' };
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
106
lib/binding_web/src/marshal.ts
Normal file
106
lib/binding_web/src/marshal.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import { Edit, INTERNAL, Point, Range, SIZE_OF_INT, SIZE_OF_NODE, SIZE_OF_POINT } from "./constants";
|
||||
import { Node } from "./node";
|
||||
import { Tree } from "./tree";
|
||||
import { Query } from "./query";
|
||||
import { TreeCursor } from "./tree_cursor";
|
||||
import { TRANSFER_BUFFER } from "./parser";
|
||||
|
||||
export function unmarshalCaptures(query: Query, tree: Tree, address: number, result: Array<{name: string, node: Node}>) {
|
||||
for (let i = 0, n = result.length; i < n; i++) {
|
||||
const captureIndex = getValue(address, 'i32');
|
||||
address += SIZE_OF_INT;
|
||||
const node = unmarshalNode(tree, address)!;
|
||||
address += SIZE_OF_NODE;
|
||||
result[i] = {name: query.captureNames[captureIndex], node};
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
export function marshalNode(node: Node) {
|
||||
let address = TRANSFER_BUFFER;
|
||||
setValue(address, node.id, 'i32');
|
||||
address += SIZE_OF_INT;
|
||||
setValue(address, node.startIndex, 'i32');
|
||||
address += SIZE_OF_INT;
|
||||
setValue(address, node.startPosition.row, 'i32');
|
||||
address += SIZE_OF_INT;
|
||||
setValue(address, node.startPosition.column, 'i32');
|
||||
address += SIZE_OF_INT;
|
||||
setValue(address, node[0], 'i32');
|
||||
}
|
||||
|
||||
export function unmarshalNode(tree: Tree, address = TRANSFER_BUFFER): Node | null {
|
||||
const id = getValue(address, 'i32');
|
||||
address += SIZE_OF_INT;
|
||||
if (id === 0) return null;
|
||||
|
||||
const index = getValue(address, 'i32');
|
||||
address += SIZE_OF_INT;
|
||||
const row = getValue(address, 'i32');
|
||||
address += SIZE_OF_INT;
|
||||
const column = getValue(address, 'i32');
|
||||
address += SIZE_OF_INT;
|
||||
const other = getValue(address, 'i32');
|
||||
|
||||
const result = new Node(INTERNAL, {
|
||||
id,
|
||||
tree,
|
||||
startIndex: index,
|
||||
startPosition: {row, column},
|
||||
other,
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function marshalTreeCursor(cursor: TreeCursor, address = TRANSFER_BUFFER) {
|
||||
setValue(address + 0 * SIZE_OF_INT, cursor[0], 'i32');
|
||||
setValue(address + 1 * SIZE_OF_INT, cursor[1], 'i32');
|
||||
setValue(address + 2 * SIZE_OF_INT, cursor[2], 'i32');
|
||||
setValue(address + 3 * SIZE_OF_INT, cursor[3], 'i32');
|
||||
}
|
||||
|
||||
export function unmarshalTreeCursor(cursor: TreeCursor) {
|
||||
cursor[0] = getValue(TRANSFER_BUFFER + 0 * SIZE_OF_INT, 'i32');
|
||||
cursor[1] = getValue(TRANSFER_BUFFER + 1 * SIZE_OF_INT, 'i32');
|
||||
cursor[2] = getValue(TRANSFER_BUFFER + 2 * SIZE_OF_INT, 'i32');
|
||||
cursor[3] = getValue(TRANSFER_BUFFER + 3 * SIZE_OF_INT, 'i32');
|
||||
}
|
||||
|
||||
export function marshalPoint(address: number, point: Point): void {
|
||||
setValue(address, point.row, 'i32');
|
||||
setValue(address + SIZE_OF_INT, point.column, 'i32');
|
||||
}
|
||||
|
||||
export function unmarshalPoint(address: number): Point {
|
||||
const result = {
|
||||
row: getValue(address, 'i32') >>> 0,
|
||||
column: getValue(address + SIZE_OF_INT, 'i32') >>> 0,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
export function marshalRange(address: number, range: Range): void {
|
||||
marshalPoint(address, range.startPosition); address += SIZE_OF_POINT;
|
||||
marshalPoint(address, range.endPosition); address += SIZE_OF_POINT;
|
||||
setValue(address, range.startIndex, 'i32'); address += SIZE_OF_INT;
|
||||
setValue(address, range.endIndex, 'i32'); address += SIZE_OF_INT;
|
||||
}
|
||||
|
||||
export function unmarshalRange(address: number): Range {
|
||||
const result = {} as Range;
|
||||
result.startPosition = unmarshalPoint(address); address += SIZE_OF_POINT;
|
||||
result.endPosition = unmarshalPoint(address); address += SIZE_OF_POINT;
|
||||
result.startIndex = getValue(address, 'i32') >>> 0; address += SIZE_OF_INT;
|
||||
result.endIndex = getValue(address, 'i32') >>> 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
export function marshalEdit(edit: Edit, address = TRANSFER_BUFFER) {
|
||||
marshalPoint(address, edit.startPosition); address += SIZE_OF_POINT;
|
||||
marshalPoint(address, edit.oldEndPosition); address += SIZE_OF_POINT;
|
||||
marshalPoint(address, edit.newEndPosition); address += SIZE_OF_POINT;
|
||||
setValue(address, edit.startIndex, 'i32'); address += SIZE_OF_INT;
|
||||
setValue(address, edit.oldEndIndex, 'i32'); address += SIZE_OF_INT;
|
||||
setValue(address, edit.newEndIndex, 'i32'); address += SIZE_OF_INT;
|
||||
}
|
||||
445
lib/binding_web/src/node.ts
Normal file
445
lib/binding_web/src/node.ts
Normal file
|
|
@ -0,0 +1,445 @@
|
|||
import { INTERNAL, Internal, assertInternal, Point, Edit, SIZE_OF_INT, SIZE_OF_NODE, SIZE_OF_POINT, ZERO_POINT, isPoint, C } from './constants';
|
||||
import { getText, Tree } from './tree';
|
||||
import { TreeCursor } from './tree_cursor';
|
||||
import { marshalNode, marshalPoint, unmarshalNode, unmarshalPoint } from './marshal';
|
||||
import { TRANSFER_BUFFER } from './parser';
|
||||
|
||||
declare const AsciiToString: (ptr: number) => string;
|
||||
|
||||
export class Node {
|
||||
// @ts-ignore
|
||||
private [0]: number; // Internal handle for WASM
|
||||
private _children?: (Node | null)[];
|
||||
private _namedChildren?: (Node | null)[];
|
||||
|
||||
id!: number;
|
||||
startIndex!: number;
|
||||
startPosition!: Point;
|
||||
tree: Tree;
|
||||
|
||||
constructor(
|
||||
internal: Internal,
|
||||
{
|
||||
id,
|
||||
tree,
|
||||
startIndex,
|
||||
startPosition,
|
||||
other,
|
||||
}: {
|
||||
id: number;
|
||||
tree: Tree;
|
||||
startIndex: number;
|
||||
startPosition: Point;
|
||||
other: number;
|
||||
}
|
||||
) {
|
||||
assertInternal(internal);
|
||||
this[0] = other;
|
||||
this.id = id;
|
||||
this.tree = tree;
|
||||
this.startIndex = startIndex;
|
||||
this.startPosition = startPosition;
|
||||
}
|
||||
|
||||
get typeId(): number {
|
||||
marshalNode(this);
|
||||
return C._ts_node_symbol_wasm(this.tree[0]);
|
||||
}
|
||||
|
||||
get grammarId(): number {
|
||||
marshalNode(this);
|
||||
return C._ts_node_grammar_symbol_wasm(this.tree[0]);
|
||||
}
|
||||
|
||||
get type(): string {
|
||||
return this.tree.language.types[this.typeId] || 'ERROR';
|
||||
}
|
||||
|
||||
get grammarType(): string {
|
||||
return this.tree.language.types[this.grammarId] || 'ERROR';
|
||||
}
|
||||
|
||||
get endPosition(): Point {
|
||||
marshalNode(this);
|
||||
C._ts_node_end_point_wasm(this.tree[0]);
|
||||
return unmarshalPoint(TRANSFER_BUFFER);
|
||||
}
|
||||
|
||||
get endIndex(): number {
|
||||
marshalNode(this);
|
||||
return C._ts_node_end_index_wasm(this.tree[0]);
|
||||
}
|
||||
|
||||
get text(): string {
|
||||
return getText(this.tree, this.startIndex, this.endIndex, this.startPosition);
|
||||
}
|
||||
|
||||
get parseState(): number {
|
||||
marshalNode(this);
|
||||
return C._ts_node_parse_state_wasm(this.tree[0]);
|
||||
}
|
||||
|
||||
get nextParseState(): number {
|
||||
marshalNode(this);
|
||||
return C._ts_node_next_parse_state_wasm(this.tree[0]);
|
||||
}
|
||||
|
||||
get isNamed(): boolean {
|
||||
marshalNode(this);
|
||||
return C._ts_node_is_named_wasm(this.tree[0]) === 1;
|
||||
}
|
||||
|
||||
get hasError(): boolean {
|
||||
marshalNode(this);
|
||||
return C._ts_node_has_error_wasm(this.tree[0]) === 1;
|
||||
}
|
||||
|
||||
get hasChanges(): boolean {
|
||||
marshalNode(this);
|
||||
return C._ts_node_has_changes_wasm(this.tree[0]) === 1;
|
||||
}
|
||||
|
||||
get isError(): boolean {
|
||||
marshalNode(this);
|
||||
return C._ts_node_is_error_wasm(this.tree[0]) === 1;
|
||||
}
|
||||
|
||||
get isMissing(): boolean {
|
||||
marshalNode(this);
|
||||
return C._ts_node_is_missing_wasm(this.tree[0]) === 1;
|
||||
}
|
||||
|
||||
get isExtra(): boolean {
|
||||
marshalNode(this);
|
||||
return C._ts_node_is_extra_wasm(this.tree[0]) === 1;
|
||||
}
|
||||
|
||||
equals(other: Node): boolean {
|
||||
return this.tree === other.tree && this.id === other.id;
|
||||
}
|
||||
|
||||
child(index: number): Node | null {
|
||||
marshalNode(this);
|
||||
C._ts_node_child_wasm(this.tree[0], index);
|
||||
return unmarshalNode(this.tree);
|
||||
}
|
||||
|
||||
namedChild(index: number): Node | null {
|
||||
marshalNode(this);
|
||||
C._ts_node_named_child_wasm(this.tree[0], index);
|
||||
return unmarshalNode(this.tree);
|
||||
}
|
||||
|
||||
childForFieldId(fieldId: number): Node | null {
|
||||
marshalNode(this);
|
||||
C._ts_node_child_by_field_id_wasm(this.tree[0], fieldId);
|
||||
return unmarshalNode(this.tree);
|
||||
}
|
||||
|
||||
childForFieldName(fieldName: string): Node | null {
|
||||
const fieldId = this.tree.language.fields.indexOf(fieldName);
|
||||
if (fieldId !== -1) return this.childForFieldId(fieldId);
|
||||
return null;
|
||||
}
|
||||
|
||||
fieldNameForChild(index: number): string | null {
|
||||
marshalNode(this);
|
||||
const address = C._ts_node_field_name_for_child_wasm(this.tree[0], index);
|
||||
if (!address) return null;
|
||||
return AsciiToString(address);
|
||||
}
|
||||
|
||||
fieldNameForNamedChild(index: number): string | null {
|
||||
marshalNode(this);
|
||||
const address = C._ts_node_field_name_for_named_child_wasm(this.tree[0], index);
|
||||
if (!address) return null;
|
||||
return AsciiToString(address);
|
||||
}
|
||||
|
||||
childrenForFieldName(fieldName: string): (Node | null)[] {
|
||||
const fieldId = this.tree.language.fields.indexOf(fieldName);
|
||||
if (fieldId !== -1 && fieldId !== 0) return this.childrenForFieldId(fieldId);
|
||||
return [];
|
||||
}
|
||||
|
||||
childrenForFieldId(fieldId: number): (Node | null)[] {
|
||||
marshalNode(this);
|
||||
C._ts_node_children_by_field_id_wasm(this.tree[0], fieldId);
|
||||
const count = getValue(TRANSFER_BUFFER, 'i32');
|
||||
const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32');
|
||||
const result = new Array<Node | null>(count);
|
||||
|
||||
if (count > 0) {
|
||||
let address = buffer;
|
||||
for (let i = 0; i < count; i++) {
|
||||
result[i] = unmarshalNode(this.tree, address);
|
||||
address += SIZE_OF_NODE;
|
||||
}
|
||||
C._free(buffer);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
firstChildForIndex(index: number): Node | null {
|
||||
marshalNode(this);
|
||||
const address = TRANSFER_BUFFER + SIZE_OF_NODE;
|
||||
setValue(address, index, 'i32');
|
||||
C._ts_node_first_child_for_byte_wasm(this.tree[0]);
|
||||
return unmarshalNode(this.tree);
|
||||
}
|
||||
|
||||
firstNamedChildForIndex(index: number): Node | null {
|
||||
marshalNode(this);
|
||||
const address = TRANSFER_BUFFER + SIZE_OF_NODE;
|
||||
setValue(address, index, 'i32');
|
||||
C._ts_node_first_named_child_for_byte_wasm(this.tree[0]);
|
||||
return unmarshalNode(this.tree);
|
||||
}
|
||||
|
||||
get childCount(): number {
|
||||
marshalNode(this);
|
||||
return C._ts_node_child_count_wasm(this.tree[0]);
|
||||
}
|
||||
|
||||
get namedChildCount(): number {
|
||||
marshalNode(this);
|
||||
return C._ts_node_named_child_count_wasm(this.tree[0]);
|
||||
}
|
||||
|
||||
get firstChild(): Node | null {
|
||||
return this.child(0);
|
||||
}
|
||||
|
||||
get firstNamedChild(): Node | null {
|
||||
return this.namedChild(0);
|
||||
}
|
||||
|
||||
get lastChild(): Node | null {
|
||||
return this.child(this.childCount - 1);
|
||||
}
|
||||
|
||||
get lastNamedChild(): Node | null {
|
||||
return this.namedChild(this.namedChildCount - 1);
|
||||
}
|
||||
|
||||
get children(): (Node | null)[] {
|
||||
if (!this._children) {
|
||||
marshalNode(this);
|
||||
C._ts_node_children_wasm(this.tree[0]);
|
||||
const count = getValue(TRANSFER_BUFFER, 'i32');
|
||||
const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32');
|
||||
this._children = new Array<Node>(count);
|
||||
if (count > 0) {
|
||||
let address = buffer;
|
||||
for (let i = 0; i < count; i++) {
|
||||
this._children[i] = unmarshalNode(this.tree, address);
|
||||
address += SIZE_OF_NODE;
|
||||
}
|
||||
C._free(buffer);
|
||||
}
|
||||
}
|
||||
return this._children;
|
||||
}
|
||||
|
||||
get namedChildren(): (Node | null)[] {
|
||||
if (!this._namedChildren) {
|
||||
marshalNode(this);
|
||||
C._ts_node_named_children_wasm(this.tree[0]);
|
||||
const count = getValue(TRANSFER_BUFFER, 'i32');
|
||||
const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32');
|
||||
this._namedChildren = new Array<Node>(count);
|
||||
if (count > 0) {
|
||||
let address = buffer;
|
||||
for (let i = 0; i < count; i++) {
|
||||
this._namedChildren[i] = unmarshalNode(this.tree, address);
|
||||
address += SIZE_OF_NODE;
|
||||
}
|
||||
C._free(buffer);
|
||||
}
|
||||
}
|
||||
return this._namedChildren;
|
||||
}
|
||||
|
||||
descendantsOfType(
|
||||
types: string | string[],
|
||||
startPosition: Point = ZERO_POINT,
|
||||
endPosition: Point = ZERO_POINT
|
||||
): (Node | null)[] {
|
||||
if (!Array.isArray(types)) types = [types];
|
||||
|
||||
// Convert the type strings to numeric type symbols
|
||||
const symbols: number[] = [];
|
||||
const typesBySymbol = this.tree.language.types;
|
||||
for (let i = 0, n = typesBySymbol.length; i < n; i++) {
|
||||
if (types.includes(typesBySymbol[i])) {
|
||||
symbols.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the array of symbols to the WASM heap
|
||||
const symbolsAddress = C._malloc(SIZE_OF_INT * symbols.length);
|
||||
for (let i = 0, n = symbols.length; i < n; i++) {
|
||||
setValue(symbolsAddress + i * SIZE_OF_INT, symbols[i], 'i32');
|
||||
}
|
||||
|
||||
// Call the C API to compute the descendants
|
||||
marshalNode(this);
|
||||
C._ts_node_descendants_of_type_wasm(
|
||||
this.tree[0],
|
||||
symbolsAddress,
|
||||
symbols.length,
|
||||
startPosition.row,
|
||||
startPosition.column,
|
||||
endPosition.row,
|
||||
endPosition.column
|
||||
);
|
||||
|
||||
// Instantiate the nodes based on the data returned
|
||||
const descendantCount = getValue(TRANSFER_BUFFER, 'i32');
|
||||
const descendantAddress = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32');
|
||||
const result = new Array<Node | null>(descendantCount);
|
||||
if (descendantCount > 0) {
|
||||
let address = descendantAddress;
|
||||
for (let i = 0; i < descendantCount; i++) {
|
||||
result[i] = unmarshalNode(this.tree, address);
|
||||
address += SIZE_OF_NODE;
|
||||
}
|
||||
}
|
||||
|
||||
// Free the intermediate buffers
|
||||
C._free(descendantAddress);
|
||||
C._free(symbolsAddress);
|
||||
return result;
|
||||
}
|
||||
|
||||
get nextSibling(): Node | null {
|
||||
marshalNode(this);
|
||||
C._ts_node_next_sibling_wasm(this.tree[0]);
|
||||
return unmarshalNode(this.tree);
|
||||
}
|
||||
|
||||
get previousSibling(): Node | null {
|
||||
marshalNode(this);
|
||||
C._ts_node_prev_sibling_wasm(this.tree[0]);
|
||||
return unmarshalNode(this.tree);
|
||||
}
|
||||
|
||||
get nextNamedSibling(): Node | null {
|
||||
marshalNode(this);
|
||||
C._ts_node_next_named_sibling_wasm(this.tree[0]);
|
||||
return unmarshalNode(this.tree);
|
||||
}
|
||||
|
||||
get previousNamedSibling(): Node | null {
|
||||
marshalNode(this);
|
||||
C._ts_node_prev_named_sibling_wasm(this.tree[0]);
|
||||
return unmarshalNode(this.tree);
|
||||
}
|
||||
|
||||
get descendantCount(): number {
|
||||
marshalNode(this);
|
||||
return C._ts_node_descendant_count_wasm(this.tree[0]);
|
||||
}
|
||||
|
||||
get parent(): Node | null {
|
||||
marshalNode(this);
|
||||
C._ts_node_parent_wasm(this.tree[0]);
|
||||
return unmarshalNode(this.tree);
|
||||
}
|
||||
|
||||
descendantForIndex(start: number, end: number = start): Node | null {
|
||||
if (typeof start !== 'number' || typeof end !== 'number') {
|
||||
throw new Error('Arguments must be numbers');
|
||||
}
|
||||
|
||||
marshalNode(this);
|
||||
const address = TRANSFER_BUFFER + SIZE_OF_NODE;
|
||||
setValue(address, start, 'i32');
|
||||
setValue(address + SIZE_OF_INT, end, 'i32');
|
||||
C._ts_node_descendant_for_index_wasm(this.tree[0]);
|
||||
return unmarshalNode(this.tree);
|
||||
}
|
||||
|
||||
namedDescendantForIndex(start: number, end: number = start): Node | null {
|
||||
if (typeof start !== 'number' || typeof end !== 'number') {
|
||||
throw new Error('Arguments must be numbers');
|
||||
}
|
||||
|
||||
marshalNode(this);
|
||||
const address = TRANSFER_BUFFER + SIZE_OF_NODE;
|
||||
setValue(address, start, 'i32');
|
||||
setValue(address + SIZE_OF_INT, end, 'i32');
|
||||
C._ts_node_named_descendant_for_index_wasm(this.tree[0]);
|
||||
return unmarshalNode(this.tree);
|
||||
}
|
||||
|
||||
descendantForPosition(start: Point, end: Point = start) {
|
||||
if (!isPoint(start) || !isPoint(end)) {
|
||||
throw new Error('Arguments must be {row, column} objects');
|
||||
}
|
||||
|
||||
marshalNode(this);
|
||||
const address = TRANSFER_BUFFER + SIZE_OF_NODE;
|
||||
marshalPoint(address, start);
|
||||
marshalPoint(address + SIZE_OF_POINT, end);
|
||||
C._ts_node_descendant_for_position_wasm(this.tree[0]);
|
||||
return unmarshalNode(this.tree);
|
||||
}
|
||||
|
||||
namedDescendantForPosition(start: Point, end: Point = start) {
|
||||
if (!isPoint(start) || !isPoint(end)) {
|
||||
throw new Error('Arguments must be {row, column} objects');
|
||||
}
|
||||
|
||||
marshalNode(this);
|
||||
const address = TRANSFER_BUFFER + SIZE_OF_NODE;
|
||||
marshalPoint(address, start);
|
||||
marshalPoint(address + SIZE_OF_POINT, end);
|
||||
C._ts_node_named_descendant_for_position_wasm(this.tree[0]);
|
||||
return unmarshalNode(this.tree);
|
||||
}
|
||||
|
||||
walk(): TreeCursor {
|
||||
marshalNode(this);
|
||||
C._ts_tree_cursor_new_wasm(this.tree[0]);
|
||||
return new TreeCursor(INTERNAL, this.tree);
|
||||
}
|
||||
|
||||
edit(edit: Edit) {
|
||||
if (this.startIndex >= edit.oldEndIndex) {
|
||||
this.startIndex = edit.newEndIndex + (this.startIndex - edit.oldEndIndex);
|
||||
let subbedPointRow;
|
||||
let subbedPointColumn;
|
||||
if (this.startPosition.row > edit.oldEndPosition.row) {
|
||||
subbedPointRow = this.startPosition.row - edit.oldEndPosition.row;
|
||||
subbedPointColumn = this.startPosition.column;
|
||||
} else {
|
||||
subbedPointRow = 0;
|
||||
subbedPointColumn = this.startPosition.column;
|
||||
if (this.startPosition.column >= edit.oldEndPosition.column) {
|
||||
subbedPointColumn =
|
||||
this.startPosition.column - edit.oldEndPosition.column;
|
||||
}
|
||||
}
|
||||
|
||||
if (subbedPointRow > 0) {
|
||||
this.startPosition.row += subbedPointRow;
|
||||
this.startPosition.column = subbedPointColumn;
|
||||
} else {
|
||||
this.startPosition.column += subbedPointColumn;
|
||||
}
|
||||
} else if (this.startIndex > edit.startIndex) {
|
||||
this.startIndex = edit.newEndIndex;
|
||||
this.startPosition.row = edit.newEndPosition.row;
|
||||
this.startPosition.column = edit.newEndPosition.column;
|
||||
}
|
||||
}
|
||||
|
||||
toString() {
|
||||
marshalNode(this);
|
||||
const address = C._ts_node_to_string_wasm(this.tree[0]);
|
||||
const result = AsciiToString(address);
|
||||
C._free(address);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
193
lib/binding_web/src/parser.ts
Normal file
193
lib/binding_web/src/parser.ts
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
import { C, INTERNAL, Point, Range, SIZE_OF_INT, SIZE_OF_RANGE } from './constants';
|
||||
import { Language } from './language';
|
||||
import { marshalRange, unmarshalRange } from './marshal';
|
||||
import { Tree } from './tree';
|
||||
|
||||
declare const getValue: (ptr: number, type: string) => any;
|
||||
declare const Module: { [key: string]: any };
|
||||
declare let initPromise: Promise<void>;
|
||||
|
||||
interface ParseOptions {
|
||||
includedRanges?: Range[];
|
||||
progressCallback?: (percent: number) => void;
|
||||
}
|
||||
|
||||
type ParseCallback = ((index: number, position: Point) => string) | string;
|
||||
type LogCallback = ((message: string, type: number, row: number, column: number) => void) | null;
|
||||
|
||||
// Global variable for transferring data across the FFI boundary
|
||||
export let TRANSFER_BUFFER: number;
|
||||
|
||||
let VERSION: number;
|
||||
let MIN_COMPATIBLE_VERSION: number;
|
||||
|
||||
// @ts-ignore
|
||||
let currentParseCallback: ((index: number, position: Point) => string) | null = null;
|
||||
// @ts-ignore
|
||||
let currentLogCallback: LogCallback = null;
|
||||
// @ts-ignore
|
||||
let currentProgressCallback: ((percent: number) => void) | null = null;
|
||||
|
||||
export class ParserImpl {
|
||||
protected [0]: number = 0;
|
||||
protected [1]: number = 0;
|
||||
protected language: Language | null = null;
|
||||
protected logCallback: LogCallback = null;
|
||||
static Language: typeof Language;
|
||||
|
||||
static init() {
|
||||
TRANSFER_BUFFER = C._ts_init();
|
||||
VERSION = getValue(TRANSFER_BUFFER, 'i32');
|
||||
MIN_COMPATIBLE_VERSION = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32');
|
||||
}
|
||||
|
||||
initialize() {
|
||||
C._ts_parser_new_wasm();
|
||||
this[0] = getValue(TRANSFER_BUFFER, 'i32');
|
||||
this[1] = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32');
|
||||
}
|
||||
|
||||
delete() {
|
||||
C._ts_parser_delete(this[0]);
|
||||
C._free(this[1]);
|
||||
this[0] = 0;
|
||||
this[1] = 0;
|
||||
}
|
||||
|
||||
setLanguage(language: Language | null): this {
|
||||
let address: number;
|
||||
if (!language) {
|
||||
address = 0;
|
||||
this.language = null;
|
||||
} else if (language.constructor === Language) {
|
||||
address = language[0];
|
||||
const version = C._ts_language_version(address);
|
||||
if (version < MIN_COMPATIBLE_VERSION || VERSION < version) {
|
||||
throw new Error(
|
||||
`Incompatible language version ${version}. ` +
|
||||
`Compatibility range ${MIN_COMPATIBLE_VERSION} through ${VERSION}.`
|
||||
);
|
||||
}
|
||||
this.language = language;
|
||||
} else {
|
||||
throw new Error('Argument must be a Language');
|
||||
}
|
||||
|
||||
C._ts_parser_set_language(this[0], address);
|
||||
return this;
|
||||
}
|
||||
|
||||
getLanguage(): Language | null {
|
||||
return this.language;
|
||||
}
|
||||
|
||||
parse(
|
||||
callback: ParseCallback,
|
||||
oldTree?: Tree | null,
|
||||
options: ParseOptions = {}
|
||||
): Tree {
|
||||
if (typeof callback === 'string') {
|
||||
currentParseCallback = (index: number) => callback.slice(index);
|
||||
} else if (typeof callback === 'function') {
|
||||
currentParseCallback = callback;
|
||||
} else {
|
||||
throw new Error('Argument must be a string or a function');
|
||||
}
|
||||
|
||||
if (options?.progressCallback) {
|
||||
currentProgressCallback = options.progressCallback;
|
||||
} else {
|
||||
currentProgressCallback = null;
|
||||
}
|
||||
|
||||
if (this.logCallback) {
|
||||
currentLogCallback = this.logCallback;
|
||||
C._ts_parser_enable_logger_wasm(this[0], 1);
|
||||
} else {
|
||||
currentLogCallback = null;
|
||||
C._ts_parser_enable_logger_wasm(this[0], 0);
|
||||
}
|
||||
|
||||
let rangeCount = 0;
|
||||
let rangeAddress = 0;
|
||||
if (options?.includedRanges) {
|
||||
rangeCount = options.includedRanges.length;
|
||||
rangeAddress = C._calloc(rangeCount, SIZE_OF_RANGE);
|
||||
let address = rangeAddress;
|
||||
for (let i = 0; i < rangeCount; i++) {
|
||||
marshalRange(address, options.includedRanges[i]);
|
||||
address += SIZE_OF_RANGE;
|
||||
}
|
||||
}
|
||||
|
||||
const treeAddress = C._ts_parser_parse_wasm(
|
||||
this[0],
|
||||
this[1],
|
||||
oldTree ? oldTree[0] : 0,
|
||||
rangeAddress,
|
||||
rangeCount
|
||||
);
|
||||
|
||||
if (!treeAddress) {
|
||||
currentParseCallback = null;
|
||||
currentLogCallback = null;
|
||||
currentProgressCallback = null;
|
||||
throw new Error('Parsing failed');
|
||||
}
|
||||
|
||||
if (!this.language) {
|
||||
throw new Error('Parser must have a language to parse');
|
||||
}
|
||||
|
||||
const result = new Tree(INTERNAL, treeAddress, this.language, currentParseCallback);
|
||||
currentParseCallback = null;
|
||||
currentLogCallback = null;
|
||||
currentProgressCallback = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
C._ts_parser_reset(this[0]);
|
||||
}
|
||||
|
||||
getIncludedRanges(): Range[] {
|
||||
C._ts_parser_included_ranges_wasm(this[0]);
|
||||
const count = getValue(TRANSFER_BUFFER, 'i32');
|
||||
const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32');
|
||||
const result: Range[] = new Array(count);
|
||||
|
||||
if (count > 0) {
|
||||
let address = buffer;
|
||||
for (let i = 0; i < count; i++) {
|
||||
result[i] = unmarshalRange(address);
|
||||
address += SIZE_OF_RANGE;
|
||||
}
|
||||
C._free(buffer);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
getTimeoutMicros(): number {
|
||||
return C._ts_parser_timeout_micros(this[0]);
|
||||
}
|
||||
|
||||
setTimeoutMicros(timeout: number): void {
|
||||
C._ts_parser_set_timeout_micros(this[0], timeout);
|
||||
}
|
||||
|
||||
setLogger(callback: LogCallback): this {
|
||||
if (!callback) {
|
||||
this.logCallback = null;
|
||||
} else if (typeof callback !== 'function') {
|
||||
throw new Error('Logger callback must be a function');
|
||||
} else {
|
||||
this.logCallback = callback;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
getLogger(): LogCallback {
|
||||
return this.logCallback;
|
||||
}
|
||||
}
|
||||
329
lib/binding_web/src/query.ts
Normal file
329
lib/binding_web/src/query.ts
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
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';
|
||||
|
||||
// @ts-ignore
|
||||
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;
|
||||
}
|
||||
|
||||
export interface Properties {
|
||||
[key: string]: string | null;
|
||||
}
|
||||
|
||||
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 };
|
||||
|
||||
export type TextPredicate = (captures: Array<Capture>) => boolean;
|
||||
|
||||
export class Query {
|
||||
private [0]: number; // Internal handle for WASM
|
||||
private exceededMatchLimit: boolean;
|
||||
private textPredicates: TextPredicate[][];
|
||||
|
||||
readonly captureNames: string[];
|
||||
readonly captureQuantifiers: number[][];
|
||||
readonly predicates: Predicate[][];
|
||||
readonly setProperties: Properties[];
|
||||
readonly assertedProperties: Properties[];
|
||||
readonly refutedProperties: Properties[];
|
||||
matchLimit?: number;
|
||||
|
||||
constructor(
|
||||
internal: Internal,
|
||||
address: number,
|
||||
captureNames: string[],
|
||||
captureQuantifiers: number[][],
|
||||
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[] {
|
||||
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;
|
||||
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');
|
||||
const result = new Array(rawCount);
|
||||
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;
|
||||
|
||||
const captures: Capture[] = new Array(captureCount);
|
||||
address = unmarshalCaptures(this, node.tree, address, captures);
|
||||
|
||||
if (this.textPredicates[pattern].every((p) => p(captures))) {
|
||||
result[filteredCount] = { pattern, captures };
|
||||
const setProperties = this.setProperties[pattern];
|
||||
if (setProperties) result[filteredCount].setProperties = setProperties;
|
||||
const assertedProperties = this.assertedProperties[pattern];
|
||||
if (assertedProperties) result[filteredCount].assertedProperties = assertedProperties;
|
||||
const refutedProperties = this.refutedProperties[pattern];
|
||||
if (refutedProperties) result[filteredCount].refutedProperties = refutedProperties;
|
||||
filteredCount++;
|
||||
}
|
||||
}
|
||||
result.length = filteredCount;
|
||||
|
||||
C._free(startAddress);
|
||||
currentQueryProgressCallback = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
captures(
|
||||
node: Node,
|
||||
options: QueryOptions = {}
|
||||
): Capture[] {
|
||||
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;
|
||||
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];
|
||||
if (setProperties) capture.setProperties = setProperties;
|
||||
const assertedProperties = this.assertedProperties[pattern];
|
||||
if (assertedProperties) capture.assertedProperties = assertedProperties;
|
||||
const refutedProperties = this.refutedProperties[pattern];
|
||||
if (refutedProperties) capture.refutedProperties = refutedProperties;
|
||||
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;
|
||||
}
|
||||
}
|
||||
115
lib/binding_web/src/tree.ts
Normal file
115
lib/binding_web/src/tree.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import { INTERNAL, Internal, assertInternal, ParseCallback, Point, Range, Edit, SIZE_OF_NODE, SIZE_OF_INT, SIZE_OF_RANGE, C } from './constants';
|
||||
import { Language } from './language';
|
||||
import { Node } from './node';
|
||||
import { TreeCursor } from './tree_cursor';
|
||||
import { marshalEdit, marshalPoint, unmarshalNode, unmarshalRange } from './marshal';
|
||||
import { TRANSFER_BUFFER } from './parser';
|
||||
|
||||
export function getText(tree: Tree, startIndex: number, endIndex: number, startPosition: Point): string {
|
||||
const length = endIndex - startIndex;
|
||||
let result = tree.textCallback(startIndex, startPosition);
|
||||
if (result) {
|
||||
startIndex += result.length;
|
||||
while (startIndex < endIndex) {
|
||||
const string = tree.textCallback(startIndex, startPosition);
|
||||
if (string && string.length > 0) {
|
||||
startIndex += string.length;
|
||||
result += string;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (startIndex > endIndex) {
|
||||
result = result.slice(0, length);
|
||||
}
|
||||
}
|
||||
return result || '';
|
||||
}
|
||||
|
||||
export class Tree {
|
||||
private [0]: number; // Internal handle for WASM
|
||||
|
||||
textCallback: ParseCallback;
|
||||
language: Language;
|
||||
|
||||
constructor(internal: Internal, address: number, language: Language, textCallback: ParseCallback) {
|
||||
assertInternal(internal);
|
||||
this[0] = address;
|
||||
this.language = language;
|
||||
this.textCallback = textCallback;
|
||||
}
|
||||
|
||||
copy(): Tree {
|
||||
const address = C._ts_tree_copy(this[0]);
|
||||
return new Tree(INTERNAL, address, this.language, this.textCallback);
|
||||
}
|
||||
|
||||
delete(): void {
|
||||
C._ts_tree_delete(this[0]);
|
||||
this[0] = 0;
|
||||
}
|
||||
|
||||
edit(edit: Edit): void {
|
||||
marshalEdit(edit);
|
||||
C._ts_tree_edit_wasm(this[0]);
|
||||
}
|
||||
|
||||
get rootNode(): Node {
|
||||
C._ts_tree_root_node_wasm(this[0]);
|
||||
return unmarshalNode(this)!;
|
||||
}
|
||||
|
||||
rootNodeWithOffset(offsetBytes: number, offsetExtent: Point): Node {
|
||||
const address = TRANSFER_BUFFER + SIZE_OF_NODE;
|
||||
setValue(address, offsetBytes, 'i32');
|
||||
marshalPoint(address + SIZE_OF_INT, offsetExtent);
|
||||
C._ts_tree_root_node_with_offset_wasm(this[0]);
|
||||
return unmarshalNode(this)!;
|
||||
}
|
||||
|
||||
getLanguage(): Language {
|
||||
return this.language;
|
||||
}
|
||||
|
||||
walk(): TreeCursor {
|
||||
return this.rootNode.walk();
|
||||
}
|
||||
|
||||
getChangedRanges(other: Tree): Range[] {
|
||||
if (!(other instanceof Tree)) {
|
||||
throw new TypeError('Argument must be a Tree');
|
||||
}
|
||||
|
||||
C._ts_tree_get_changed_ranges_wasm(this[0], other[0]);
|
||||
const count = getValue(TRANSFER_BUFFER, 'i32');
|
||||
const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32');
|
||||
const result = new Array(count);
|
||||
|
||||
if (count > 0) {
|
||||
let address = buffer;
|
||||
for (let i = 0; i < count; i++) {
|
||||
result[i] = unmarshalRange(address);
|
||||
address += SIZE_OF_RANGE;
|
||||
}
|
||||
C._free(buffer);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
getIncludedRanges(): Range[] {
|
||||
C._ts_tree_included_ranges_wasm(this[0]);
|
||||
const count = getValue(TRANSFER_BUFFER, 'i32');
|
||||
const buffer = getValue(TRANSFER_BUFFER + SIZE_OF_INT, 'i32');
|
||||
const result = new Array(count);
|
||||
|
||||
if (count > 0) {
|
||||
let address = buffer;
|
||||
for (let i = 0; i < count; i++) {
|
||||
result[i] = unmarshalRange(address);
|
||||
address += SIZE_OF_RANGE;
|
||||
}
|
||||
C._free(buffer);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
193
lib/binding_web/src/tree_cursor.ts
Normal file
193
lib/binding_web/src/tree_cursor.ts
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
import { INTERNAL, Internal, assertInternal, Point, SIZE_OF_NODE, SIZE_OF_CURSOR, C } from './constants';
|
||||
import { marshalNode, marshalPoint, marshalTreeCursor, unmarshalNode, unmarshalPoint, unmarshalTreeCursor } from './marshal';
|
||||
import { Node } from './node';
|
||||
import { TRANSFER_BUFFER } from './parser';
|
||||
import { getText, Tree } from './tree';
|
||||
|
||||
export class TreeCursor {
|
||||
// @ts-ignore
|
||||
private [0]: number; // Internal handle for WASM
|
||||
// @ts-ignore
|
||||
private [1]: number; // Internal handle for WASM
|
||||
// @ts-ignore
|
||||
private [2]: number; // Internal handle for WASM
|
||||
// @ts-ignore
|
||||
private [3]: number; // Internal handle for WASM
|
||||
|
||||
private tree: Tree;
|
||||
|
||||
constructor(internal: Internal, tree: Tree) {
|
||||
assertInternal(internal);
|
||||
this.tree = tree;
|
||||
unmarshalTreeCursor(this);
|
||||
}
|
||||
|
||||
copy(): TreeCursor {
|
||||
const copy = new TreeCursor(INTERNAL, this.tree);
|
||||
C._ts_tree_cursor_copy_wasm(this.tree[0]);
|
||||
unmarshalTreeCursor(copy);
|
||||
return copy;
|
||||
}
|
||||
|
||||
delete(): void {
|
||||
marshalTreeCursor(this);
|
||||
C._ts_tree_cursor_delete_wasm(this.tree[0]);
|
||||
this[0] = this[1] = this[2] = 0;
|
||||
}
|
||||
|
||||
reset(node: Node): void {
|
||||
marshalNode(node);
|
||||
marshalTreeCursor(this, TRANSFER_BUFFER + SIZE_OF_NODE);
|
||||
C._ts_tree_cursor_reset_wasm(this.tree[0]);
|
||||
unmarshalTreeCursor(this);
|
||||
}
|
||||
|
||||
resetTo(cursor: TreeCursor): void {
|
||||
marshalTreeCursor(this, TRANSFER_BUFFER);
|
||||
marshalTreeCursor(cursor, TRANSFER_BUFFER + SIZE_OF_CURSOR);
|
||||
C._ts_tree_cursor_reset_to_wasm(this.tree[0], cursor.tree[0]);
|
||||
unmarshalTreeCursor(this);
|
||||
}
|
||||
|
||||
get nodeType(): string {
|
||||
return this.tree.language.types[this.nodeTypeId] || 'ERROR';
|
||||
}
|
||||
|
||||
get nodeTypeId(): number {
|
||||
marshalTreeCursor(this);
|
||||
return C._ts_tree_cursor_current_node_type_id_wasm(this.tree[0]);
|
||||
}
|
||||
|
||||
get nodeStateId(): number {
|
||||
marshalTreeCursor(this);
|
||||
return C._ts_tree_cursor_current_node_state_id_wasm(this.tree[0]);
|
||||
}
|
||||
|
||||
get nodeId(): number {
|
||||
marshalTreeCursor(this);
|
||||
return C._ts_tree_cursor_current_node_id_wasm(this.tree[0]);
|
||||
}
|
||||
|
||||
get nodeIsNamed(): boolean {
|
||||
marshalTreeCursor(this);
|
||||
return C._ts_tree_cursor_current_node_is_named_wasm(this.tree[0]) === 1;
|
||||
}
|
||||
|
||||
get nodeIsMissing(): boolean {
|
||||
marshalTreeCursor(this);
|
||||
return C._ts_tree_cursor_current_node_is_missing_wasm(this.tree[0]) === 1;
|
||||
}
|
||||
|
||||
get nodeText(): string {
|
||||
marshalTreeCursor(this);
|
||||
const startIndex = C._ts_tree_cursor_start_index_wasm(this.tree[0]);
|
||||
const endIndex = C._ts_tree_cursor_end_index_wasm(this.tree[0]);
|
||||
C._ts_tree_cursor_start_position_wasm(this.tree[0]);
|
||||
const startPosition = unmarshalPoint(TRANSFER_BUFFER);
|
||||
return getText(this.tree, startIndex, endIndex, startPosition);
|
||||
}
|
||||
|
||||
get startPosition(): Point {
|
||||
marshalTreeCursor(this);
|
||||
C._ts_tree_cursor_start_position_wasm(this.tree[0]);
|
||||
return unmarshalPoint(TRANSFER_BUFFER);
|
||||
}
|
||||
|
||||
get endPosition(): Point {
|
||||
marshalTreeCursor(this);
|
||||
C._ts_tree_cursor_end_position_wasm(this.tree[0]);
|
||||
return unmarshalPoint(TRANSFER_BUFFER);
|
||||
}
|
||||
|
||||
get startIndex(): number {
|
||||
marshalTreeCursor(this);
|
||||
return C._ts_tree_cursor_start_index_wasm(this.tree[0]);
|
||||
}
|
||||
|
||||
get endIndex(): number {
|
||||
marshalTreeCursor(this);
|
||||
return C._ts_tree_cursor_end_index_wasm(this.tree[0]);
|
||||
}
|
||||
|
||||
get currentNode(): Node | null {
|
||||
marshalTreeCursor(this);
|
||||
C._ts_tree_cursor_current_node_wasm(this.tree[0]);
|
||||
return unmarshalNode(this.tree);
|
||||
}
|
||||
|
||||
get currentFieldId(): number {
|
||||
marshalTreeCursor(this);
|
||||
return C._ts_tree_cursor_current_field_id_wasm(this.tree[0]);
|
||||
}
|
||||
|
||||
get currentFieldName(): string | null {
|
||||
return this.tree.language.fields[this.currentFieldId];
|
||||
}
|
||||
|
||||
get currentDepth(): number {
|
||||
marshalTreeCursor(this);
|
||||
return C._ts_tree_cursor_current_depth_wasm(this.tree[0]);
|
||||
}
|
||||
|
||||
get currentDescendantIndex(): number {
|
||||
marshalTreeCursor(this);
|
||||
return C._ts_tree_cursor_current_descendant_index_wasm(this.tree[0]);
|
||||
}
|
||||
|
||||
gotoFirstChild(): boolean {
|
||||
marshalTreeCursor(this);
|
||||
const result = C._ts_tree_cursor_goto_first_child_wasm(this.tree[0]);
|
||||
unmarshalTreeCursor(this);
|
||||
return result === 1;
|
||||
}
|
||||
|
||||
gotoLastChild(): boolean {
|
||||
marshalTreeCursor(this);
|
||||
const result = C._ts_tree_cursor_goto_last_child_wasm(this.tree[0]);
|
||||
unmarshalTreeCursor(this);
|
||||
return result === 1;
|
||||
}
|
||||
|
||||
gotoFirstChildForIndex(goalIndex: number): boolean {
|
||||
marshalTreeCursor(this);
|
||||
setValue(TRANSFER_BUFFER + SIZE_OF_CURSOR, goalIndex, 'i32');
|
||||
const result = C._ts_tree_cursor_goto_first_child_for_index_wasm(this.tree[0]);
|
||||
unmarshalTreeCursor(this);
|
||||
return result === 1;
|
||||
}
|
||||
|
||||
gotoFirstChildForPosition(goalPosition: Point): boolean {
|
||||
marshalTreeCursor(this);
|
||||
marshalPoint(TRANSFER_BUFFER + SIZE_OF_CURSOR, goalPosition);
|
||||
const result = C._ts_tree_cursor_goto_first_child_for_position_wasm(this.tree[0]);
|
||||
unmarshalTreeCursor(this);
|
||||
return result === 1;
|
||||
}
|
||||
|
||||
gotoNextSibling(): boolean {
|
||||
marshalTreeCursor(this);
|
||||
const result = C._ts_tree_cursor_goto_next_sibling_wasm(this.tree[0]);
|
||||
unmarshalTreeCursor(this);
|
||||
return result === 1;
|
||||
}
|
||||
|
||||
gotoPreviousSibling(): boolean {
|
||||
marshalTreeCursor(this);
|
||||
const result = C._ts_tree_cursor_goto_previous_sibling_wasm(this.tree[0]);
|
||||
unmarshalTreeCursor(this);
|
||||
return result === 1;
|
||||
}
|
||||
|
||||
gotoDescendant(goalDescendantIndex: number): void {
|
||||
marshalTreeCursor(this);
|
||||
C._ts_tree_cursor_goto_descendant_wasm(this.tree[0], goalDescendantIndex);
|
||||
unmarshalTreeCursor(this);
|
||||
}
|
||||
|
||||
gotoParent(): boolean {
|
||||
marshalTreeCursor(this);
|
||||
const result = C._ts_tree_cursor_goto_parent_wasm(this.tree[0]);
|
||||
unmarshalTreeCursor(this);
|
||||
return result === 1;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue