core/converters/syntax.converter.ts

core/converters/syntax.converter.ts is a file in Code Parse. 108 lines of code and 27 definitions.

import type { CodeParseOptions, CstNode, ParseAttempt, ParseLogger } from "#types/syntax.types";
import { Language, Parser, type Tree } from "web-tree-sitter";
import {
    NO_TREE,
    PARSE_FAILED,
    PARSE_INCOMPLETE,
    noGrammar,
    notPreloaded,
} from "#configuration/strings/syntax.strings";
import { grammarPath, resolveGrammarDir, runtimeWasm } from "#core/resolvers/grammar.resolver";
import { PARSE_ATTEMPTS } from "#configuration/constants/syntax.constants";
import { adaptNode } from "#core/adapters/syntax.adapter";
import { existsSync } from "node:fs";

const languageCache = new Map<string, Language>();
const inFlight = new Map<string, Promise<Language>>();
let initPromise: Promise<void> | null = null;
let loadChain: Promise<unknown> = Promise.resolve();

const ensureInit = async function ensureInit(): Promise<void> {
    initPromise ??= Parser.init({ locateFile: runtimeWasm });
    return initPromise;
};

const wasmFor = function wasmFor(language: string): string {
    return grammarPath(resolveGrammarDir(), language);
};

const loadLanguage = async function loadLanguage(wasm: string): Promise<Language> {
    const held = inFlight.get(wasm);
    if (held !== undefined) {
        return held;
    }
    const next = loadChain.then(async () => Language.load(wasm));
    loadChain = Promise.allSettled([next]);
    inFlight.set(wasm, next);
    return next;
};

const loadMissing = async function loadMissing(wasms: string[]): Promise<void> {
    const pending = wasms.filter((wasm) => !languageCache.has(wasm) && existsSync(wasm));
    const loaded = await Promise.all(pending.map(loadLanguage));
    pending.forEach((wasm, index) => {
        const language = loaded.at(index);
        if (language) {
            languageCache.set(wasm, language);
        }
    });
};

export const ensureLanguages = async function ensureLanguages(languages: Iterable<string>): Promise<void> {
    await ensureInit();
    await loadMissing([...new Set(languages)].map(wasmFor));
};

const adaptTree = function adaptTree(tree: Tree | null, sourceLength: number, logger?: ParseLogger): ParseAttempt {
    if (tree === null) {
        logger?.warn(NO_TREE);
        return { complete: false, root: null };
    }
    const node = tree.rootNode;
    const complete = node.startIndex === 0 && node.endIndex === sourceLength;
    const root = adaptNode(node);
    tree.delete();
    return { complete, root };
};

const parseOnce = function parseOnce(loaded: Language, source: string, logger?: ParseLogger): ParseAttempt {
    const parser = new Parser();
    try {
        parser.setLanguage(loaded);
        return adaptTree(parser.parse(source), source.length, logger);
    } finally {
        parser.delete();
    }
};

const runParser = function runParser(loaded: Language, source: string, logger?: ParseLogger): CstNode | null {
    let last: CstNode | null = null;
    for (let attempt = 0; attempt < PARSE_ATTEMPTS; attempt += 1) {
        try {
            const result = parseOnce(loaded, source, logger);
            last = result.root;
            if (result.complete) {
                return result.root;
            }
        } catch (error) {
            logger?.warn(PARSE_FAILED, error);
        }
    }
    logger?.warn(PARSE_INCOMPLETE);
    return last;
};

export const parseCodeSync = function parseCodeSync(
    source: string,
    language: string,
    options: CodeParseOptions = {},
): CstNode | null {
    const loaded = languageCache.get(wasmFor(language));
    if (!loaded) {
        throw new TypeError(notPreloaded(language));
    }
    return runParser(loaded, source, options.logger);
};

export const parseCode = async function parseCode(
    source: string,
    language: string,
    options: CodeParseOptions = {},
): Promise<CstNode | null> {
    const wasm = wasmFor(language);
    if (!existsSync(wasm)) {
        options.logger?.warn(noGrammar(language), wasm);
        return null;
    }
    await ensureLanguages([language]);
    return parseCodeSync(source, language, options);
};