Markdown/CommonMark 探訪(1):laziness

Markdown パーサを書くにあたってちょっと苦労したのが Laziness という考え方です。

Markdown では、引用ブロック (blockquotes) を書く際、引用符を付けるのは最初の行だけでよいと定められています。

> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.

> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
id sem consectetuer libero luctus adipiscing.

https://daringfireball.net/projects/markdown/syntax#blockquote

引用符がある行から段落が終わるまでは、すべて引用ブロックの中の段落として扱われます。
たしかにこれは書きやすいだろうし、読めなくはないので、記法として理解できます。

しかし、さまざまな実装の共通項を探る CommonMark では、こんな風に整理されています。

>>> foo
> bar
>>baz

http://spec.commonmark.org/0.28/#example-214

このテキストを処理系に渡すと、3重にネストされた引用ブロックの中に「foo, bar, baz という 3行の段落」がある、と解釈されます。
えっ。
テキストとしての見た目と、あまりに違う解釈結果ですね。

laziness はリストでも有効

この laziness の考え方はリストでも適用されています。

*   Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
viverra nec, fringilla in, laoreet vitae, risus.
*   Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
Suspendisse id sem consectetuer libero luctus adipiscing.

印刷物とかでもこういうレイアウトが使われていることもあり、理解はできます。

では、こちらはどうでしょう。

> 1. > Blockquote
> continued here.

http://spec.commonmark.org/0.28/#example-256

2行目の「continued here」は内側の引用ブロックの内部の段落の一部です。
えっ。
またですか。

laziness の範囲

徐々に Markdown の世界観がつかめてきましたか? それではこれはどうでしょう。

> - foo
- bar

引用ブロックの中に箇条書きが記述されていますね。
さて、これはどう解釈されるのでしょうか。

実はこれは

  • 「foo」は引用ブロックの内側のリストとして扱われる
  • 「bar」は引用ブロックの外側の (別の) リストとして扱われる

と解釈されます。

というのは、laziness が適用される対象は「段落」だけです。その他の記法には laziness は適用されません。
楽しいですね。

面倒くさい例としては

> lorem ipsum
> ---

は引用ブロックの中に <h2> の見出しが記述されると判定されるのに対して

> lorem ipsum
---

は引用ブロックの後ろに区切り線 (themantic break) がある、と解釈されるものがあります。

pycmark ではどうしているのか

段落用のプロセッサーで、次のような処理をしています。
1. 同じインデントレベル (引用符やリストアイテムなどのレベル) の中で読み進める
2. その状態で空行や区切り線などの割り込み要素がきた場合は、段落の処理は終了
3. インデントレベルの最後まで段落が続いた場合は、LazyLineReader という laziness に対応した読み込みモジュールを使って読み進める
4. 割り込み要素が来るまで、段落の一部として解釈を続ける


ということで、pycmark でも無事に laziness な段落は正しく扱えます。
多用すると読みづらくなりそうですが、つかってみてください。