tools/core/analyzers/vocabulary.analyzer.ts

tools/core/analyzers/vocabulary.analyzer.ts is a file in Coordination Surface. 142 lines of code and 36 definitions.

import type { Declared } from "../types/vocabulary.types.ts";
import { lifetime } from "../../../config/surface.config.ts";

const CELL = "|";

export const CLOSED: Readonly<Record<string, readonly string[]>> = {
    mutability: lifetime.values.mutability,
    removal: lifetime.values.removal,
    retention: lifetime.values.retention,
};

const MARKUP = new Set(["`", "*", "_", " "]);

const SEPARATORS = new Set(["·", ",", "/"]);

const WORD_BREAK = new Set([" ", "\t", "-"]);

const SPACING = new Set([" ", "\t"]);

const AXIS_COLUMN = lifetime.columns.axis;

const VALUE_COLUMN = lifetime.columns.value;

interface Columns {
    readonly axis: number;
    readonly value: number;
}

const bare = function bare(cell: string): string {
    let start = 0;
    let end = cell.length;

    while (start < end && MARKUP.has(cell[start] ?? "")) {
        start += 1;
    }
    while (end > start && MARKUP.has(cell[end - 1] ?? "")) {
        end -= 1;
    }

    return cell.slice(start, end).toLowerCase();
};

const tokens = function tokens(value: string): string[] {
    const out: string[] = [];
    let held = "";

    for (const char of value) {
        if (!SEPARATORS.has(char)) {
            held += char;
            continue;
        }
        const token = bare(held);
        if (token.length > 0) {
            out.push(token);
        }
        held = "";
    }

    const last = bare(held);
    if (last.length > 0) {
        out.push(last);
    }
    return out;
};

const splitOn = function splitOn(text: string, breaks: ReadonlySet<string>): string[] {
    const out: string[] = [];
    let held = "";

    for (const char of text) {
        if (!breaks.has(char)) {
            held += char;
            continue;
        }
        if (held.length > 0) {
            out.push(held);
        }
        held = "";
    }

    if (held.length > 0) {
        out.push(held);
    }
    return out;
};

const axisNamedBy = function axisNamedBy(cell: string): string | null {
    for (const word of splitOn(cell, WORD_BREAK)) {
        if (CLOSED[word] !== undefined) {
            return word;
        }
    }
    return null;
};

const columnsOf = function columnsOf(row: readonly string[]): Columns | null {
    let axis = -1;
    let value = -1;

    for (let at = 0; at < row.length; at += 1) {
        const label = bare(row[at] ?? "");
        if (label === AXIS_COLUMN) {
            axis = at;
        }
        if (label === VALUE_COLUMN) {
            value = at;
        }
    }

    return axis === -1 || value === -1 ? null : { axis, value };
};

const statesTheSet = function statesTheSet(axis: string, value: string): boolean {
    const closed = CLOSED[axis] ?? [];
    const parts = tokens(value);
    if (parts.length < 2) {
        return false;
    }

    for (const part of parts) {
        if (!closed.includes(part)) {
            return false;
        }
    }
    return true;
};

const definesTheAxis = function definesTheAxis(value: string): boolean {
    return splitOn(value, SPACING).length > 1;
};

const cells = function cells(line: string): string[] {
    const trimmed = line.trim();
    if (!trimmed.startsWith(CELL)) {
        return [];
    }

    const out: string[] = [];
    for (const part of trimmed.split(CELL)) {
        const cell = part.trim();
        if (cell.length > 0) {
            out.push(cell);
        }
    }
    return out;
};

const declaredRow = function declaredRow(row: readonly string[], columns: Columns, line: number): Declared[] {
    const axis = axisNamedBy(bare(row[columns.axis] ?? ""));
    const value = bare(row[columns.value] ?? "");
    if (axis === null || value.length === 0 || statesTheSet(axis, value) || definesTheAxis(value)) {
        return [];
    }
    return [{ axis, line, value }];
};

export const declaredIn = function declaredIn(source: string): Declared[] {
    const found: Declared[] = [];
    let columns: Columns | null = null;

    for (const [index, line] of source.split("\n").entries()) {
        const row = cells(line);
        if (row.length === 0) {
            columns = null;
        } else if (columns === null) {
            columns = columnsOf(row);
        } else {
            found.push(...declaredRow(row, columns, index + 1));
        }
    }

    return found;
};