# core/converters/syntax.converter.ts

> 108 lines of code and 27 definitions.

Tree: Code Parse
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/code-parse#file-code-parse-core-converters-syntax-converter-ts
Source text: https://banes-lab.com/source/code-parse/core/converters/syntax.converter.ts.txt

Listed in [core/converters](https://banes-lab.com/api/source/code-parse/core/converters.md), after [core/converters/grammar.converter.ts](https://banes-lab.com/source/code-parse/core/converters/grammar.converter.ts.md).

## Definitions

- `ensureLanguages` (lexical_declaration, line 51, exported)
- `parseCodeSync` (lexical_declaration, line 95, exported)
- `ensureInit` (lexical_declaration, line 20)
- `loadMissing` (lexical_declaration, line 40)
- `adaptTree` (lexical_declaration, line 56)
- `runParser` (lexical_declaration, line 78)
- `parseCode` (lexical_declaration, line 107, exported)
- `wasmFor` (lexical_declaration, line 25)
- `parseOnce` (lexical_declaration, line 68)
- `languageCache` (lexical_declaration, line 15)
- `inFlight` (lexical_declaration, line 16)
- `initPromise` (lexical_declaration, line 17)
- `loadChain` (lexical_declaration, line 18)
- `loadLanguage` (lexical_declaration, line 29)
- `held` (lexical_declaration, line 30)
- `next` (lexical_declaration, line 34)
- `pending` (lexical_declaration, line 41)
- `language` (lexical_declaration, line 44)
- `node` (lexical_declaration, line 61)
- `complete` (lexical_declaration, line 62)
- `root` (lexical_declaration, line 63)
- `parser` (lexical_declaration, line 69)
- `last` (lexical_declaration, line 79)
- `attempt` (lexical_declaration, line 80)
- `result` (lexical_declaration, line 82)
- `loaded` (lexical_declaration, line 100, exported)
- `wasm` (lexical_declaration, line 112, exported)

## Contained in

- [core/converters](https://banes-lab.com/anatomy/code-parse/folder-code-parse-core-converters.md)

## Uses

- [configuration/strings/syntax.strings.ts](https://banes-lab.com/source/code-parse/configuration/strings/syntax.strings.ts.md)
- [core/resolvers/grammar.resolver.ts](https://banes-lab.com/source/code-parse/core/resolvers/grammar.resolver.ts.md)

## Linked from

- [configuration/strings](https://banes-lab.com/anatomy/code-parse/folder-code-parse-configuration-strings.md)
- [core/converters](https://banes-lab.com/anatomy/code-parse/folder-code-parse-core-converters.md)
- [core/resolvers](https://banes-lab.com/anatomy/code-parse/folder-code-parse-core-resolvers.md)

## Source

```typescript
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);
};
```
