UNPKG

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
/** * # 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; }