tools/core/readers/rule.reader.ts

tools/core/readers/rule.reader.ts is a file in Coordination Surface. 118 lines of code and 37 definitions.

import type { DeclaredRule } from "../types/rule.types.ts";
import { unfencedLines } from "./document.reader.ts";

interface HeadingState {
    readonly slug: string | null;
    readonly locked: boolean;
}

const GATE_MARKER = "gate:";

const LOCKED_MARKER = "(LOCKED)";

const LIST_SLUG = "- `";

const SEPARATOR_DOT = "·";

const backtickedSlug = function backtickedSlug(line: string): string | null {
    const open = line.indexOf("`");
    const close = open === -1 ? -1 : line.indexOf("`", open + 1);
    const slug = close === -1 ? "" : line.slice(open + 1, close);
    return slug.length > 0 ? slug : null;
};

const isGateChar = function isGateChar(char: string): boolean {
    return (char >= "a" && char <= "z") || char === "-";
};

const spacesAfter = function spacesAfter(line: string, from: number): number {
    let cursor = from;
    while (line.charAt(cursor) === " ") {
        cursor += 1;
    }
    return cursor;
};

const spacesBefore = function spacesBefore(line: string, from: number): number {
    let cursor = from;
    while (cursor > 0 && line.charAt(cursor - 1) === " ") {
        cursor -= 1;
    }
    return cursor;
};

const gateRunEnd = function gateRunEnd(line: string, from: number): number {
    let cursor = from;
    while (cursor < line.length && isGateChar(line.charAt(cursor))) {
        cursor += 1;
    }
    return cursor;
};

const gateOf = function gateOf(line: string): string | null {
    const marker = line.lastIndexOf(GATE_MARKER);
    if (marker === -1) {
        return null;
    }
    const start = spacesAfter(line, marker + GATE_MARKER.length);
    const value = line.slice(start, gateRunEnd(line, start));
    return value.length > 0 ? value : null;
};

const hashRun = function hashRun(line: string): number {
    let cursor = 0;
    while (line.charAt(cursor) === "#") {
        cursor += 1;
    }
    return cursor;
};

const slugAfter = function slugAfter(line: string, open: number): string | null {
    const close = line.indexOf("`", open + 1);
    const slug = close === -1 ? "" : line.slice(open + 1, close);
    const tail = line.slice(close + 1).trim();
    return slug.length > 0 && (tail.length === 0 || tail === LOCKED_MARKER) ? slug : null;
};

const headingSlug = function headingSlug(line: string): string | null {
    const hashes = hashRun(line);
    const open = spacesAfter(line, hashes);
    return hashes > 0 && line.charAt(open) === "`" ? slugAfter(line, open) : null;
};

const listedRule = function listedRule(line: string, number: number): DeclaredRule[] {
    const slug = backtickedSlug(line);
    const gate = slug === null ? null : gateOf(line);
    return slug === null || gate === null ? [] : [{ gate, line: number, locked: line.includes(LOCKED_MARKER), slug }];
};

const headedRule = function headedRule(heading: HeadingState, line: string, number: number): DeclaredRule | null {
    const gate = heading.slug === null ? null : gateOf(line);
    return heading.slug === null || gate === null
        ? null
        : { gate, line: number, locked: heading.locked, slug: heading.slug };
};

export const readDeclaredRules = function readDeclaredRules(source: string): DeclaredRule[] {
    const out: DeclaredRule[] = [];
    let heading: HeadingState = { locked: false, slug: null };

    for (const { text: line, number } of unfencedLines(source)) {
        if (line.startsWith("#")) {
            heading = { locked: line.includes(LOCKED_MARKER), slug: headingSlug(line) };
        } else if (line.startsWith(LIST_SLUG)) {
            out.push(...listedRule(line, number));
        } else {
            const rule = headedRule(heading, line, number);
            out.push(...(rule === null ? [] : [rule]));
            heading = rule === null ? heading : { ...heading, slug: null };
        }
    }

    return out;
};

export const readExpandedSlugs = function readExpandedSlugs(source: string): { slug: string; line: number }[] {
    return unfencedLines(source)
        .filter(({ text }) => text.startsWith("#"))
        .flatMap(({ text, number }) => {
            const slug = headingSlug(text);
            return slug === null ? [] : [{ line: number, slug }];
        });
};

const declarationStart = function declarationStart(line: string, marker: number): number {
    const before = spacesBefore(line, marker);
    const dotted = before > 0 && line.charAt(before - 1) === SEPARATOR_DOT ? before - 1 : before;
    return spacesBefore(line, dotted);
};

const withoutGate = function withoutGate(line: string): string {
    const marker = line.lastIndexOf(GATE_MARKER);
    if (marker === -1 || gateOf(line) === null) {
        return line;
    }
    const end = gateRunEnd(line, spacesAfter(line, marker + GATE_MARKER.length));
    return line.slice(0, declarationStart(line, marker)) + line.slice(end);
};

export const stripGateDeclarations = function stripGateDeclarations(source: string): string {
    return source.split("\n").map(withoutGate).join("\n");
};