Parser
Hand-written recursive descent. Tracks fence depth by counting leading colons. Never throws on malformed input — produces a best-effort AST and lets the validator complain.
The noma CLI parses, renders, and validates .noma source files. This page is itself written in Noma — the same source produces the rendered HTML you're reading and the deterministic LLM context an agent would consume.
npm install -g @ferax564/noma-cli
# or one-off
npx @ferax564/noma-cli render path/to/file.noma --to html
noma parse <file>Print the parsed AST as JSON. Useful for debugging the parser or building tools that consume Noma documents.
noma parse examples/thesis.noma
noma parse examples/thesis.noma --out ast.json
Returns the full typed AST defined in src/ast.ts. Block IDs are stable across re-parses of unchanged content, so AST diffs map cleanly to source diffs.
noma render <file> [--to <target>]Render a .noma file to one of the supported targets.
| Target | Output |
|---|---|
html | Standalone HTML document with the default theme |
llm | Deterministic plain-text context for LLM consumption |
json | The parsed AST (alias of noma export) |
noma render docs/spec.noma --to html --out dist/spec.html
noma render docs/spec.noma --to llm
noma render docs/spec.noma --to json --out dist/spec.json
Use --no-standalone to emit just the HTML body (for embedding inside an existing page). Use --title "..." to override the document title.
noma check <file>Validate a Noma document. Exits 1 if any errors are present, 0 otherwise.
noma check examples/thesis.noma
Catches: duplicate block IDs, broken for= references on evidence blocks, broken internal links, invalid frontmatter, plot blocks missing a dataset or data= attribute, and (in v0.2) claims missing supporting evidence.
noma export <file>Alias for noma render <file> --to json. Kept as a separate command because it's the most common scripted use case (CI pipelines, agent context, RAG indexing).
Hand-written recursive descent. Tracks fence depth by counting leading colons. Never throws on malformed input — produces a best-effort AST and lets the validator complain.
Discriminated union in src/ast.ts. Single source of truth. Adding a node type is a one-line change that the TypeScript compiler propagates to every renderer's switch.
Pure functions. AST → string. No I/O, no globals. Three in core today (HTML, LLM, JSON); PDF is a wrapper around the HTML renderer + Puppeteer.
.noma source
│
▼
┌──────────┐
│ Parser │ src/parser.ts (line-based, recursive descent)
└─────┬────┘
│ typed AST (src/ast.ts)
▼
┌──────────────────────────────────┐
│ Renderers (pure) │
│ ├── renderer-html.ts → HTML │
│ ├── renderer-llm.ts → LLM ctx │
│ └── renderer-json.ts → JSON │
└──────────┬───────────────────────┘
│
▼
artifact
import { parse, renderHtml, validate } from "@ferax564/noma-cli";
const source = await fs.readFile("doc.noma", "utf8");
const ast = parse(source, { filename: "doc.noma" });
const diagnostics = validate(ast);
if (diagnostics.some(d => d.severity === "error")) {
throw new Error("invalid Noma document");
}
const html = renderHtml(ast, { standalone: true });
Agents should not rewrite full files. They propose block-level operations that the CLI applies safely.
{
"op": "replace_block",
"id": "asml-euv-moat",
"content": "ASML's moat rests on EUV optics, mechanics, and supply chain — not just exclusivity."
}
Operations: add_block, replace_block, delete_block, move_block, update_attribute, add_evidence, add_comment, resolve_comment, rename_id. See docs/agent-protocol.noma for the full schema.
flowchart LR A[.noma source] --> B[parser] B --> C[AST] C --> D[validator] C --> E[renderer-html] C --> F[renderer-llm] C --> G[renderer-noma] G --> H[noma fmt / patch]
docs/spec.noma for the full block reference.docs/agent-protocol.noma for the patch protocol.docs/direction.noma for the product positioning.examples/research-thesis.noma for a reasoning-heavy demo.