Packages
The Markanto reference implementation — a tolerant parser, a canonical formatter, an AST validator, and the markanto CLI, published for TypeScript and JavaScript.
Markanto’s specification is written to be implementation-language-agnostic. TypeScript is the reference implementation: it defines the AST, the canonical serialisation, and the conformance corpus any other implementation would be checked against. A Rust port is a deliberately kept-open option, not a current commitment.
Install
npm install @markantolang/parser
Zero runtime dependencies. ES modules with type declarations, Node ≥ 18.20 and Bun. The markanto command-line tool is bundled.
Quickstart
import { parse, format } from '@markantolang/parser'
const parsed = parse('# Notes\n\nWith _loose_ and __mixed__ markers.\n')
if (parsed.status === 'ok') {
const formatted = format(parsed.document)
if (formatted.status === 'ok') {
formatted.source
// '# Notes\n\nWith *loose* and **mixed** markers.\n'
}
}
Every operation returns a tagged result — branch on status before reading a payload, on format and validate as well as parse. There is no thrown error, and no inline error node, for ordinary invalid input.
API
parse(source, options?)— the tolerant parser.statusisok,invalid, orresource; onokyou getdocument(the strict AST) and anannotationssidecar for source positions.strict: trueaccepts only the canonical surface;errorRecovery: trueadds aRecoveryDocumenton invalid input without changing the AST of valid input.format(document, budget?)— the canonical serialiser.statusokyieldssource, the one canonical string for that AST.validate(document, options?)— a pure function over an already-built AST: undefined footnote references, ID uniqueness, resource-attribute allowlists, quote nesting, table shape, and the inline structural invariants. Independent of the parser.checkCanonical(source)— is this surface already canonical?statuscanonicalornoncanonical(carrying the correctedcanonicalstring),invalid, orresource— without changing the document’s meaning.adoptDocument(document, createId?)— returns a copy with missing block IDs filled in.
parse(format(document)) is semantically equal to document, and format
is stable on its own output. Source positions are never fields of AST nodes; read them from the
annotations sidecar when you need them.
CLI
markanto check <file> # is the file a valid Markanto document?
markanto check <file> --strict # …and is its surface exactly canonical?
markanto format <file> --stdout # print the canonical form
markanto format <file> # rewrite the file in place
markanto adopt <file> --stdout # attach durable block identity
check exits non-zero when the file fails the requested check.
CommonMark / GFM import
Converting existing CommonMark or GFM into Markanto is an explicit, lossy operation behind a separate entry
point,
@markantolang/parser/commonmark, 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. diagnostics is an ordered loss-policy ladder — normalised, resolved, source-structure-loss, semantic-degradation, content-dropped, unrepresentable — where the last two are hard-loss conditions suitable for failing an unattended import.
Status
- Specification — 0.1.0, frozen. The contract does not change within that language line.
- TypeScript reference implementation — parser, canonical formatter, validator,
adopt, and CLI, verified against a 583-case conformance corpus (each case run in normal and strict mode), a compatibility report over the vendored CommonMark 0.31.2 / GFM 0.29-gfm suites, hand-curated fixtures, a 100 000-document-per-stream property soak, and an independent whole-repo review that returned a SHIP / FREEZE verdict.
The implementation, the corpus, and the design record are in github.com/markantolang/markanto-js.