kestrel.markets
Version:
A typed, token-efficient language + runtime for agentic trading: agents author bounded plans, the runtime fires them at the tick. CLI + typed library + MCP server.
121 lines (113 loc) • 5.12 kB
text/typescript
/**
* # format — the canonical formatter (text → text), built on print ∘ parse
*
* ADR-0004 makes the round-trip the definition of done: `print(parse(text))` is the byte-
* stable canonical form, so **formatting simply IS `print ∘ parse`** — there is no separate
* formatting logic to drift from the grammar. Because the canonical form is a fixed point,
* {@link format} is idempotent (`format(format(x)) === format(x)`).
*
* Fail closed (ARCHITECTURE §6): text that does not parse is returned UNCHANGED rather than
* mangled. A formatter that ate an in-progress buffer — or rewrote a deliberately-invalid
* `reject` fixture in the docs — would be worse than useless, so a {@link KestrelParseError}
* degrades to "leave it exactly as the author wrote it". Non-parse errors (genuine bugs)
* still propagate loudly.
*
* Portability: imports ONLY `src/lang` (the parse/print barrel) and pure TS. No `node:*`, no
* DOM. The Markdown fence walk is a self-contained line scanner (it adapts the gsh.1 fence
* pattern from tests/support/fences.ts, but that module is node-bound, so it is not imported).
*/
import { KestrelParseError, parse, print } from "../lang/index.ts";
/**
* Canonicalize a whole `.kestrel` document. Returns `print(parse(text))` — byte-stable and
* idempotent. Fails closed: if the text does not parse, it is returned unchanged.
*/
export function format(text: string): string {
try {
return print(parse(text));
} catch (e) {
if (e instanceof KestrelParseError) return text; // fail closed — never mangle
throw e;
}
}
/** One ` ```kestrel ` fenced block located in a Markdown source, with the absolute offsets of
* its body so a caller can splice a replacement or offset highlight tokens into it. */
export interface KestrelBlock {
/** 0-based offset of the first character of the body (start of the line after the fence). */
readonly bodyStart: number;
/** 0-based offset one past the last body character (`bodyStart + body.length`). */
readonly bodyEnd: number;
/** The body exactly as written (the fence lines excluded, no surrounding newlines). */
readonly body: string;
}
const FENCE_OPEN = /^(\s*)(`{3,})(.*)$/;
const FENCE_CLOSE = /^(\s*)(`{3,})\s*$/;
/**
* Locate every ` ```kestrel ` fenced block in a Markdown/MDX source, in order. Only fences
* whose info-string language token is exactly `kestrel` are returned — a `text`/`kdl`/… fence
* (how the docs mark `reject`/`proposed` examples, per tests/support/fences.ts) is skipped, so
* deliberately-invalid syntax is never touched. An unterminated fence is skipped (fail closed).
*
* Shared by {@link formatMarkdown} (to splice canonical bodies) and the editor semantic-token
* provider (to offset highlight tokens into a Markdown document).
*/
export function kestrelBlocks(text: string): KestrelBlock[] {
const lines = text.split("\n");
const starts: number[] = [];
let off = 0;
for (const l of lines) {
starts.push(off);
off += l.length + 1; // + newline (the final line's phantom newline is never read)
}
const blocks: KestrelBlock[] = [];
let i = 0;
while (i < lines.length) {
const open = FENCE_OPEN.exec(lines[i]!);
// A backtick fence's info string may not contain backticks (CommonMark §4.5): a line with
// an inline code span is not a fence opener. Skip it (matches tests/support/fences.ts).
if (open !== null && !open[3]!.includes("`")) {
const lang = /^\S*/.exec(open[3]!.trim())![0];
if (lang === "kestrel") {
const ticks = open[2]!;
let c = i + 1;
let closed = false;
for (; c < lines.length; c++) {
const close = FENCE_CLOSE.exec(lines[c]!);
if (close !== null && close[2]!.length >= ticks.length) {
closed = true;
break;
}
}
if (closed) {
const body = lines.slice(i + 1, c).join("\n");
const bodyStart = starts[i + 1]!; // i+1 ≤ c ≤ lines.length-1 ⇒ always in range
blocks.push({ bodyStart, bodyEnd: bodyStart + body.length, body });
i = c + 1;
continue;
}
}
}
i++;
}
return blocks;
}
/**
* Format every parseable ` ```kestrel ` fence in a Markdown/MDX document in place, leaving the
* surrounding prose, the fence markers, and every un-parseable or non-`kestrel` fence byte-for-
* byte untouched (fail closed). Because {@link format} returns its input unchanged on a parse
* error, a `reject`/`proposed`/typo fence is preserved exactly — this is a pure canonicaliza-
* tion of the executable examples the docs assert (ADR-0001).
*/
export function formatMarkdown(text: string): string {
const blocks = kestrelBlocks(text);
let out = "";
let cursor = 0;
for (const b of blocks) {
out += text.slice(cursor, b.bodyStart);
const formatted = format(b.body);
// format() already fails closed to `b.body`; comparing keeps an unchanged body byte-exact.
out += formatted === b.body ? b.body : formatted;
cursor = b.bodyEnd;
}
out += text.slice(cursor);
return out;
}