feat(web): document the API

This commit is contained in:
Amaan Qureshi 2025-01-20 02:43:52 -05:00
parent a4b20c1c56
commit 09cb4c5729
17 changed files with 1155 additions and 198 deletions

View file

@ -2,7 +2,7 @@ import { C, INTERNAL, Internal, assertInternal, SIZE_OF_INT, SIZE_OF_SHORT } fro
import { LookaheadIterator } from './lookahead_iterator';
import { Node } from './node';
import { TRANSFER_BUFFER } from './parser';
import { CaptureQuantifier, Predicate, PredicateStep, Properties, Query, TextPredicate } from './query';
import { CaptureQuantifier, QueryPredicate, PredicateStep, QueryProperties, Query, TextPredicate } from './query';
const PREDICATE_STEP_TYPE_CAPTURE = 1;
const PREDICATE_STEP_TYPE_STRING = 2;
@ -10,14 +10,27 @@ const PREDICATE_STEP_TYPE_STRING = 2;
const QUERY_WORD_REGEX = /[\w-]+/g;
const LANGUAGE_FUNCTION_REGEX = /^tree_sitter_\w+$/;
/**
* An opaque object that defines how to parse a particular language.
* The code for each `Language` is generated by the Tree-sitter CLI.
*/
export class Language {
/** @internal */
private [0] = 0; // Internal handle for WASM
/**
* A list of all node types in the language. The index of each type in this
* array is its node type id.
*/
types: string[];
/**
* A list of all field names in the language. The index of each field name in
* this array is its field id.
*/
fields: (string | null)[];
/** @internal */
constructor(internal: Internal, address: number) {
assertInternal(internal);
this[0] = address;
@ -38,33 +51,54 @@ export class Language {
}
}
/**
* Gets the name of the language.
*/
get name(): string | null {
const ptr = C._ts_language_name(this[0]);
if (ptr === 0) return null;
return C.UTF8ToString(ptr);
}
/**
* Gets the version of the language.
*/
get version(): number {
return C._ts_language_version(this[0]);
}
/**
* Gets the number of fields in the language.
*/
get fieldCount(): number {
return this.fields.length - 1;
}
/**
* Gets the number of states in the language.
*/
get stateCount(): number {
return C._ts_language_state_count(this[0]);
}
/**
* Get the field id for a field name.
*/
fieldIdForName(fieldName: string): number | null {
const result = this.fields.indexOf(fieldName);
return result !== -1 ? result : null;
}
/**
* Get the field name for a field id.
*/
fieldNameForId(fieldId: number): string | null {
return this.fields[fieldId] ?? null;
}
/**
* Get the node type id for a node type name.
*/
idForNodeType(type: string, named: boolean): number | null {
const typeLength = C.lengthBytesUTF8(type);
const typeAddress = C._malloc(typeLength + 1);
@ -74,23 +108,42 @@ export class Language {
return result || null;
}
/**
* Gets the number of node types in the language.
*/
get nodeTypeCount(): number {
return C._ts_language_symbol_count(this[0]);
}
/**
* Get the node type name for a node type id.
*/
nodeTypeForId(typeId: number): string | null {
const name = C._ts_language_symbol_name(this[0], typeId);
return name ? C.UTF8ToString(name) : null;
}
/**
* Check if a node type is named.
*
* @see {@link https://tree-sitter.github.io/tree-sitter/using-parsers/2-basic-parsing.html#named-vs-anonymous-nodes}
*/
nodeTypeIsNamed(typeId: number): boolean {
return C._ts_language_type_is_named_wasm(this[0], typeId) ? true : false;
}
/**
* Check if a node type is visible.
*/
nodeTypeIsVisible(typeId: number): boolean {
return C._ts_language_type_is_visible_wasm(this[0], typeId) ? true : false;
}
/**
* Get the supertypes ids of this language.
*
* @see {@link https://tree-sitter.github.io/tree-sitter/using-parsers/6-static-node-types.html?highlight=supertype#supertype-nodes}
*/
get supertypes(): number[] {
C._ts_language_supertypes_wasm(this[0]);
const count = C.getValue(TRANSFER_BUFFER, 'i32');
@ -108,6 +161,9 @@ export class Language {
return result;
}
/**
* Get the subtype ids for a given supertype node id.
*/
subtypes(supertype: number): number[] {
C._ts_language_subtypes_wasm(this[0], supertype);
const count = C.getValue(TRANSFER_BUFFER, 'i32');
@ -125,16 +181,44 @@ export class Language {
return result;
}
/**
* Get the next state id for a given state id and node type id.
*/
nextState(stateId: number, typeId: number): number {
return C._ts_language_next_state(this[0], stateId, typeId);
}
/**
* Create a new lookahead iterator for this language and parse state.
*
* This returns `null` if state is invalid for this language.
*
* Iterating {@link LookaheadIterator} will yield valid symbols in the given
* parse state. Newly created lookahead iterators will return the `ERROR`
* symbol from {@link LookaheadIterator#currentType}.
*
* Lookahead iterators can be useful for generating suggestions and improving
* syntax error diagnostics. To get symbols valid in an `ERROR` node, use the
* lookahead iterator on its first leaf node state. For `MISSING` nodes, a
* lookahead iterator created on the previous non-extra leaf node may be
* appropriate.
*/
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;
}
/**
* Create a new query from a string containing one or more S-expression
* patterns.
*
* The query is associated with a particular language, and can only be run
* on syntax nodes parsed with that language. References to Queries can be
* shared between multiple threads.
*
* @link {@see https://tree-sitter.github.io/tree-sitter/using-parsers/queries}
*/
query(source: string): Query {
const sourceLength = C.lengthBytesUTF8(source);
const sourceAddress = C._malloc(sourceLength + 1);
@ -219,10 +303,10 @@ export class Language {
stringValues[i] = C.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<Predicate[]>(patternCount);
const setProperties = new Array<QueryProperties>(patternCount);
const assertedProperties = new Array<QueryProperties>(patternCount);
const refutedProperties = new Array<QueryProperties>(patternCount);
const predicates = new Array<QueryPredicate[]>(patternCount);
const textPredicates = new Array<TextPredicate[]>(patternCount);
for (let i = 0; i < patternCount; i++) {
@ -236,7 +320,11 @@ export class Language {
predicates[i] = [];
textPredicates[i] = [];
const steps: PredicateStep[] = [];
const steps = new Array<PredicateStep>();
const isStringStep = (step: PredicateStep): step is { type: 'string', value: string } => {
return step.type === 'string';
}
let stepAddress = predicatesAddress;
for (let j = 0; j < stepCount; j++) {
const stepType = C.getValue(stepAddress, 'i32');
@ -329,7 +417,7 @@ export class Language {
}
if (steps[2].type !== 'string') {
throw new Error(
`Second argument of \`#${operator}\` predicate must be a string. Got @${steps[2].value}.`,
`Second argument of \`#${operator}\` predicate must be a string. Got @${steps[2].name}.`,
);
}
captureName = steps[1].name;
@ -359,13 +447,13 @@ export class Language {
`Wrong number of arguments to \`#set!\` predicate. Expected 1 or 2. Got ${steps.length - 1}.`,
);
}
if (steps.some((s) => s.type !== 'string')) {
if (!steps.every(isStringStep)) {
throw new Error(
`Arguments to \`#set!\` predicate must be a strings.".`,
`Arguments to \`#set!\` predicate must be strings.".`,
);
}
if (!setProperties[i]) setProperties[i] = {};
setProperties[i][steps[1].value!] = steps[2]?.value ?? null;
setProperties[i][steps[1].value] = steps[2]?.value ?? null;
break;
}
@ -376,14 +464,14 @@ export class Language {
`Wrong number of arguments to \`#${operator}\` predicate. Expected 1 or 2. Got ${steps.length - 1}.`,
);
}
if (steps.some((s) => s.type !== 'string')) {
if (!steps.every(isStringStep)) {
throw new Error(
`Arguments to \`#${operator}\` predicate must be a strings.".`,
`Arguments to \`#${operator}\` predicate must be strings.".`,
);
}
const properties = operator === 'is?' ? assertedProperties : refutedProperties;
if (!properties[i]) properties[i] = {};
properties[i][steps[1].value!] = steps[2]?.value ?? null;
properties[i][steps[1].value] = steps[2]?.value ?? null;
break;
}
@ -400,15 +488,16 @@ export class Language {
`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);
const stringSteps = steps.slice(2);
if (!stringSteps.every(isStringStep)) {
throw new Error(
`Arguments to \`#${operator}\` predicate must be strings.".`,
);
}
const values = stringSteps.map((s) => s.value);
textPredicates[i].push((captures) => {
const nodes = [];
for (const c of captures) {
@ -447,6 +536,10 @@ export class Language {
);
}
/**
* Load a language from a WebAssembly module.
* The module can be provided as a path to a file or as a buffer.
*/
static async load(input: string | Uint8Array): Promise<Language> {
let bytes: Promise<Uint8Array>;
if (input instanceof Uint8Array) {