README.md
cl-commonmark
A CommonMark 0.31.2 parser and HTML renderer for Common Lisp.
Current conformance against the official spec suite: 100% (652 / 652 examples).
Status
Fully conformant with CommonMark 0.31.2.
- Block phase (container + leaf): blockquote, lists (bullet + ordered), list-item, paragraph, ATX/setext heading, indented + fenced code, thematic break, HTML blocks (all 7 types) — implemented.
- Inline phase: backslash escapes, entity references, code spans, emphasis + strong (delimiter-stack algorithm), links (inline + full/collapsed/shortcut reference + autolink), images, raw HTML, hard / soft breaks — implemented.
- List tightness and lazy continuation across nested containers, paragraph interruption, and tab-aware indentation — all spec corner cases pass.
- Unicode-aware emphasis flanking (P/S general categories) and link-label case folding via
sb-unicode(with portable fallbacks).
Usage
(asdf:load-system :cl-commonmark)
(cl-commonmark:markdown-to-html "# Hello
A paragraph with *emphasis* and a [link](https://example.com).")
;; => "<h1>Hello</h1>
;; <p>A paragraph with <em>emphasis</em> and a <a href=\"https://example.com\">link</a>.</p>
;; "
Get the AST instead of HTML:
(cl-commonmark:parse-to-ast "...")
Extensions
Non-CommonMark features are opt-in via the :extensions keyword, so the
default output stays 100% CommonMark-conformant. The full GitHub Flavored
Markdown extension set is supported:
| Keyword | Feature |
|---|---|
:tables |
GFM tables (table / table-row / table-cell nodes) |
:strikethrough |
~~text~~ / ~text~ → <del> (strikethrough node) |
:tasklists |
- [ ] / - [x] list items rendered with a disabled checkbox |
:autolinks |
bare www., http(s)://, and email addresses become links |
:tagfilter |
escape disallowed raw-HTML tags (script/style/iframe/…) |
(cl-commonmark:markdown-to-html
"| foo | bar |
| :-- | --: |
| baz | bim |
~~done~~ — see www.example.com
- [x] ship it"
:extensions '(:tables :strikethrough :autolinks :tasklists))
Pass any subset; each feature is fully inert unless its keyword is present.
parse-to-ast takes the same keyword.
Architecture
src/blocks.lisp— hand-written block-phase state machine (CommonMark is not context-free)src/inlines.lisp— left-to-right tokenizer + doubly-linked cell list + delimiter-stack algorithm for emphasis / links / imagessrc/links.lisp— link destination (pointy + bare with balanced parens), title, label parsers; reference-definition extractorsrc/entities.lisp— HTML5 named character references (2125 entries, generated from WHATWG entities.json)src/renderer-html.lisp— generic-function renderer over the AST- GFM tables (extension) are threaded through these same files, gated on
*extensions*
Running the conformance suite
sbcl --eval "(asdf:load-system :cl-commonmark/test)" \
--eval "(cl-commonmark/test:run-spec)"
Per-section detail:
(cl-commonmark/test:run-spec-section "Emphasis and strong emphasis")
GFM extension examples (tables, strikethrough, task lists, autolinks, tagfilter):
(cl-commonmark/test:run-gfm-extensions)
Author and License
Anthony Green <green@moxielogic.com>. MIT.