core/parsers/node.parser.ts

core/parsers/node.parser.ts is a file in GovLab Context. 119 lines of code and 27 definitions.

import {
    CONTRACT_FRESHNESS,
    CONTRACT_INPUT,
    CONTRACT_TRANSFORM,
    EM_DASH,
    HEAD_TOKEN_MIN,
    INVARIANT_BLOCK_HEAD,
    NODE_HEAD,
    NODE_TAG_SLOTS,
    REPAIR_EDGE_HEAD,
    RETIRED_UNIT_HEADS,
    TAG_CLOSE,
    TAG_CUE,
    TAG_GENESIS,
    TAG_OPEN,
    TAG_PURPOSE,
    TAG_SEPARATOR,
    TAG_YIELDS,
} from "#configuration/constants/grammar.constants";
import type { PagNode, PagNodeField, PagNodeFieldValue, PagNodeTag, ParsedHeader } from "#types/grammar.document.types";
import { valueAfter } from "#core/converters/text.converter";

const COMMENT: ParsedHeader = { head: "", kind: "comment", number: null, tag: null, title: "" };
const WORD_SEPARATOR = " ";
const COLON = ":";

const slotsOf = function slotsOf(inner: string): string[] {
    return inner
        .split(TAG_SEPARATOR)
        .map((slot) => slot.trim())
        .filter((slot) => slot !== "");
};

export const parseTag = function parseTag(text: string): PagNodeTag | null {
    const open = text.indexOf(TAG_OPEN);
    const close = text.lastIndexOf(TAG_CLOSE);
    if (open === -1 || close === -1 || close < open) {
        return null;
    }
    const slots = slotsOf(text.slice(open + 1, close));
    const yields = slots[NODE_TAG_SLOTS - 1] ?? "";
    if (slots.length !== NODE_TAG_SLOTS || !yields.startsWith(TAG_YIELDS)) {
        return null;
    }
    return {
        axis: slots[1] ?? "",
        layer: slots[0] ?? "",
        mathType: slots[2] ?? "",
        yields: valueAfter(yields, TAG_YIELDS),
    };
};

const titleOf = function titleOf(afterHead: string): string {
    const dash = afterHead.indexOf(EM_DASH);
    const body = dash === -1 ? afterHead : afterHead.slice(dash + EM_DASH.length);
    const open = body.indexOf(TAG_OPEN);
    return (open === -1 ? body : body.slice(0, open)).trim();
};

const parseNodeHeader = function parseNodeHeader(afterHash: string, tokens: string[]): ParsedHeader {
    const second = tokens[1] ?? "";
    const number = second === "" || second === EM_DASH ? null : second;
    const afterHead = afterHash.slice(NODE_HEAD.length + 1 + (number ?? "").length);
    return { head: NODE_HEAD, kind: "node", number, tag: parseTag(afterHash), title: titleOf(afterHead) };
};

const parseRetiredHeader = function parseRetiredHeader(afterHash: string, tokens: string[]): ParsedHeader {
    const colon = afterHash.indexOf(COLON);
    const rawNumber = tokens[1] ?? "";
    return {
        head: tokens[0] ?? "",
        kind: "retired-unit",
        number: rawNumber === "" ? null : (rawNumber.split(COLON)[0] ?? null),
        tag: null,
        title: colon === -1 ? afterHash.trim() : afterHash.slice(colon + 1).trim(),
    };
};

export const parseHeader = function parseHeader(afterHash: string): ParsedHeader {
    const trimmed = afterHash.trim();
    if (trimmed.startsWith(INVARIANT_BLOCK_HEAD)) {
        return { ...COMMENT, head: INVARIANT_BLOCK_HEAD, kind: "invariants" };
    }
    if (trimmed.startsWith(REPAIR_EDGE_HEAD)) {
        return { ...COMMENT, head: REPAIR_EDGE_HEAD, kind: "repair" };
    }
    const tokens = trimmed.split(WORD_SEPARATOR).filter(Boolean);
    const head = tokens[0] ?? "";
    if (tokens.length < HEAD_TOKEN_MIN) {
        return COMMENT;
    }
    if (head === NODE_HEAD) {
        return parseNodeHeader(trimmed, tokens);
    }
    return RETIRED_UNIT_HEADS.has(head) ? parseRetiredHeader(trimmed, tokens) : COMMENT;
};

const NODE_FIELDS: readonly [string, PagNodeField][] = [
    [TAG_PURPOSE, "purpose"],
    [TAG_CUE, "cue"],
    [TAG_GENESIS, "genesis"],
    [CONTRACT_INPUT, "input"],
    [CONTRACT_TRANSFORM, "transform"],
    [CONTRACT_FRESHNESS, "freshness"],
];

export const nodeFieldOf = function nodeFieldOf(trimmed: string): PagNodeFieldValue | null {
    const entry = NODE_FIELDS.find(([prefix]) => trimmed.startsWith(prefix));
    return entry === undefined ? null : { field: entry[1], value: valueAfter(trimmed, entry[0]) };
};

export const nodeOf = function nodeOf(header: ParsedHeader, raw: string, line: number): PagNode {
    return {
        cue: null,
        directives: [],
        freshness: null,
        gate: null,
        genesis: null,
        head: header.head,
        header: raw,
        input: null,
        line,
        number: header.number,
        purpose: null,
        tag: header.tag,
        title: header.title,
        transform: null,
    };
};