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,
};
};