1 branch

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 / images
  • src/links.lisp — link destination (pointy + bare with balanced parens), title, label parsers; reference-definition extractor
  • src/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.