core/converters/algorithm.index.converter.ts

core/converters/algorithm.index.converter.ts is a file in GovLab Context. 63 lines of code and 15 definitions.

import {
    ALTERNATIVE_SEPARATOR,
    MIN_QUOTED_LENGTH,
    NONTERMINAL_OPEN,
    QUOTE,
    QUOTE_PAIR_COUNT,
    SYMBOL_NAME_SEPARATOR,
} from "#configuration/constants/algorithm.constants";
import type { ContractSymbolView, SymbolEntry } from "#types/algorithm.types";

interface SymbolAccumulator {
    grammar: string;
    records: Set<string>;
    rhsSet: Set<string>;
}

const isSingleQuotedLiteral = function isSingleQuotedLiteral(part: string): boolean {
    const trimmed = part.trim();
    if (trimmed.length < MIN_QUOTED_LENGTH || !trimmed.startsWith(QUOTE) || !trimmed.endsWith(QUOTE)) {
        return false;
    }
    let quotes = 0;
    for (const ch of trimmed) {
        if (ch === QUOTE) {
            quotes += 1;
        }
    }
    return quotes === QUOTE_PAIR_COUNT;
};

const enumValues = function enumValues(rhs: string): string[] | null {
    const parts = rhs.split(ALTERNATIVE_SEPARATOR);
    return parts.every(isSingleQuotedLiteral) ? parts.map((part) => part.trim().slice(1, -1)) : null;
};

const accumulateSymbols = function accumulateSymbols(
    contracts: readonly ContractSymbolView[],
): Map<string, SymbolAccumulator> {
    const accumulators = new Map<string, SymbolAccumulator>();
    for (const contract of contracts) {
        for (const production of contract.productions) {
            const key = `${contract.domain}${SYMBOL_NAME_SEPARATOR}${production.lhs}`;
            const entry = accumulators.get(key) ?? {
                grammar: contract.domain,
                records: new Set<string>(),
                rhsSet: new Set<string>(),
            };
            entry.records.add(contract.id);
            entry.rhsSet.add(production.rhs);
            accumulators.set(key, entry);
        }
    }
    return accumulators;
};

const toSymbolEntry = function toSymbolEntry(name: string, accumulator: SymbolAccumulator): SymbolEntry {
    const [rhs = ""] = [...accumulator.rhsSet].toSorted((a, b) => a.localeCompare(b));
    const definedIn = [...accumulator.records].toSorted((a, b) => a.localeCompare(b));
    const values = rhs.includes(NONTERMINAL_OPEN) ? null : enumValues(rhs);
    return values
        ? { definedIn, grammar: accumulator.grammar, kind: "enum", name, values }
        : { definedIn, grammar: accumulator.grammar, kind: "composite", name, rhs };
};

export const buildSymbolIndex = function buildSymbolIndex(contracts: readonly ContractSymbolView[]): SymbolEntry[] {
    return [...accumulateSymbols(contracts)]
        .map(([key, accumulator]) => toSymbolEntry(key.slice(key.indexOf(SYMBOL_NAME_SEPARATOR) + 1), accumulator))
        .toSorted((left, right) => left.grammar.localeCompare(right.grammar) || left.name.localeCompare(right.name));
};