194 lines
5.2 KiB
TypeScript
194 lines
5.2 KiB
TypeScript
|
|
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;
|
||
|
|
}
|
||
|
|
}
|