Markdown stays nice, and becomes reliable.
The reference implementation of Markanto 0.2.0 — a parser, a semantic-AST validator, and a canonical formatter for a Markdown variant.
Read tolerantly, model strictly, serialise canonically. CommonMark standardises how Markdown is read; Markanto also standardises how it is written. The parser accepts the loose Markdown people actually write, models it in a strict semantic AST, and the formatter serialises exactly one canonical form per meaning. Markanto-specific syntax is designed to degrade gracefully — in a plain Markdown renderer it should lose its meaning, never its readability.
Parsing, validation, canonical formatting, and diagnostics are separate operations with separate result types; resource exhaustion is always distinct from language invalidity.
- Zero production dependencies.
npm install @markantolang/parserpulls nothing else. - Deterministic. Forward-only scanning, bounded lookahead, no backtracking over competing interpretations, RE2-compatible expressions only.
- Runtime-neutral. Node ≥ 18.20 and Bun. The semantic core uses no runtime-specific APIs.
Specification: https://markanto.org/spec/
(markantolang/spec) · Homepage:
https://markanto.org
npm install @markantolang/parserimport { parse, format, validate } from '@markantolang/parser';
const source = '# Title\n\nSome **bold** text and a [link](https://example.org).\n';
const parsed = parse(source);
if (parsed.status !== 'ok') {
// 'invalid' — not a valid Markanto document (see parsed.diagnostics)
// 'resource' — a resource budget was exceeded
throw new Error(`parse: ${parsed.status}`);
}
// parsed.document is the semantic AST (see `src/ast.ts` for its shape).
const formatted = format(parsed.document);
if (formatted.status === 'ok') {
console.log(formatted.source === source); // true — this input was already canonical
}
console.log(validate(parsed.document).status); // 'ok'Every operation returns a tagged union — branch on status before reading any
payload. There is no thrown error and no inline error node for ordinary
failures.
| Operation | Call | status |
Payload on success |
|---|---|---|---|
| Parse surface → AST | parse(source, options?) |
ok | invalid | resource |
document, annotations |
| AST → canonical surface | format(document, budget?) |
ok | invalid | resource |
source |
| Validate an AST | validate(document, options?) |
ok | invalid | resource |
— |
| Check surface canonicality | checkCanonical(source) |
canonical | noncanonical | invalid | resource |
canonical (the corrected surface) |
parse(source, { strict: true }) accepts only the canonical surface and
returns invalid with a noncanonical diagnostic otherwise. Default
(strict: false) accepts the documented tolerant surface.
parse(source, { errorRecovery: true }) additionally returns a
RecoveryDocument (which may hold ErrorBlocks) on invalid input — it never
changes the AST of valid input.
Source positions are not fields of AST nodes; when you need them, read the
per-result annotations sidecar (parsed.annotations, only present on ok
and on recovery results). See
docs/PARSER_CONTRACT.md.
Converting existing CommonMark or GFM into Markanto is an explicit, lossy operation behind a separate entry point, so the core parser never depends on a Markdown library:
import { importCommonMark } from '@markantolang/parser/commonmark';
const { markanto, diagnostics } = importCommonMark('# Hi\n\nsome _text_\n');The micromark / mdast packages this subpath needs are optional peer
dependencies — install them alongside @markantolang/parser only if you use
the subpath. diagnostics is an ordered loss-policy ladder: normalised,
resolved, source-structure-loss, semantic-degradation, content-dropped,
unrepresentable. The last two are hard-loss conditions suitable for failing an
unattended import.
npx markanto check <file> # is the file a valid Markanto document?
npx markanto check <file> --strict # …and is its surface exactly canonical?
npx markanto format <file> --stdout # print the canonical form
npx markanto format <file> # rewrite the file in place
npx markanto adopt <file> --stdout # attach durable identity (see docs/PARSER_CONTRACT.md)check exits non-zero when the file fails the requested check.
- One meaning ⇒ one surface.
format(parse(s).document)is idempotent, and two surfaces with the same meaning format to the same bytes. - Semantic round-trip.
parse(format(ast))reproduces the same semantic AST. - Linear time. Target O(n) parsing with a monotonic cursor; the property
gate (
bun run soak) includes a parser-linearity probe. - Portability. The architecture is written to be ported mechanically to
safe, idiomatic Rust (
docs/PORTABILITY.md).
markantolang/spec— the normative language specification, one tag per version (the specification wins over any implementation note);vendor/spec/holds the copies the tests readsrc/ast.ts— normative semantic-AST shapedocs/PARSER_CONTRACT.md— result shapes, resource budgets, source-annotation sidecardocs/IMPLEMENTATION.md— compact implementation contractdocs/DESIGN_DECISIONS.md— the load-bearing language and API decisions, with rationaledocs/COMMONMARK_DIVERGENCE.md— every intentional CommonMark / GFM deviationdocs/MIGRATION.md— cutover from the 0.5.3-era buildsdocs/EVOLUTION_V053_TO_010.md— every v0.5.3 → 0.1.0 language delta and its rationaledocs/DESIGN_PROCESS.md— how 0.1.0 was built and reviewedCHANGELOG.md
bun run verify # type check + boundary checks + conformance corpus + full test suite
bun run verify:node # the portable subset under the supported Node baseline
bun run compat:report # classify the 672 vendored CommonMark 0.31.2 / GFM cases
bun run soak # 100k seeded documents per stream through the §7.2 laws (heavier gate)
bun run bench # parse throughput vs marked.lexer (Node; bench:bun for Bun)Bun is the primary test/runtime environment; Node is the secondary
compatibility check. The conformance corpus is JSON and is validated by the
same .mjs script under both.
No added syntax, no compatibility aliases, no parser profiles that alter meaning, no raw HTML, no plugin semantics, no heuristic media typing, no generalized recursive containers.
ISC © Michael Kortstiege