GoでCommonMarkのパーサを実装しました。
分かりやすいASTに変換+拡張が容易、そこそこ速い実装になっています。
めちゃくちゃしんどかったです。
経緯
Go言語のMarkdownパーサといえばblackfridayですが、 拡張するための機構がないのでさくっと自前でMarkdownパーサを書くか、と思い立ちました。
そこで「そういえばCommonMarkなんてもんがあったな」と思い出しせっかくならCommonMark準拠にするかとおもってCommonMarkの仕様を読み始めました。
え、なにこれは…
Markdownで出来ることなんてrestructuredTextなどほかのマークアップ言語に比べればわずかなものです。しかし、たかがそれだけを実装するために凄まじく複雑な仕様が定義されているのでした。
以下、CommonMarkに寄せられた声です。
commonmarkのlistの仕様複雑すぎて死んでいる
— tkr (@kgtkr) 2019年3月28日
う〜ん、CommonMark難しすぎる!w
— らぃと (@lightnet328) 2019年2月23日
質問:
— ZR-TeXnobabbler🤔 (@zr_tex8r) 2018年11月1日
CommonMarkのパーザの実装を試みていますが、仕様が絶望的です。どうしたらいいでしょうか?
回答:
もっとテキトーにパーザを実装して、そのパーザの動作を以て“新しいMarkdown方言”であると定めましょう!#Markdown #うわぁ #うわぁ #うわぁ
皆さんには是非 CommonMark Spec を読んで絶望してほしい.
— 画力・博士号・油田 (@bd_gfngfn) 2018年10月31日
しまいには、CommonMarkの中心人物自体が「えらいもん作ってしまった・・・」となっています。
There are 17 principles governing emphasis , for example, and these rules still leave cases undecided. The rules for list items and HTML blocks are also very complex. All of these rules lead to unexpected results sometimes, and they make writing a parser for CommonMark a complex affair. I despair, at times, of getting to a spec that is worth calling 1.0.
私も仕様を読んだ段階で「これはヤバい」と感じたのですがここで引くのも悔しかったので作り切りました。
CommonMarkについて思うところ
正直なところ、これが世に広まるってどうなの、と思います。
そもそもなんでMarkdownが軽量マークアップ言語の中でこれだけ広く使われるようになったのかというと
- 書き手としての書きやすさ
- 実装者としての実装しやすさ
つまり、「書き手の適当さ」と「実装側の適当さ」がいい具合に噛み合ったからだと思っています。 書き手はそれなりに適当に書けるし、実装するにしても仕様が適当なので、適当に実装してもMarkdown 対応であると言うことができました。
それによってあらゆる言語、さまざまなタイプの実装が生まれそれが至るところで使われることになり Markdownは広まっていったのです。
それに対して、CommonMarkはあまりに実装するのがしんどすぎます。さらにはそれだけしんどい思いをしても出来ること自体は少ないのです。テーブルさえ使えないのです。
その実装の難しさからCommonMark対応のパーサはいわゆるMarkdownパーサと比べるとわずかしかありません。 CommonMarkにかかわっている人の中には「それが何の問題ですか?」という人さえいます。 「CとJSで参照実装提供してるじゃん。ブラウザはC動かないからJSも用意してある。それ以外の言語は C実装のバインディング作ればいいだけでしょ。実装なんて1個あればいいんだよ」なんて感じです。
CommonMarkはオリジナルのMarkdown作者から「Markdownの名前は使うな」と言われてCommonMarkという名前になったという経緯があります。
Markdownを冠していないのであれば、perlの神正規表現で実装されたオリジナルのMarkdown.plの動きにできるだけ寄せようとするのではなく、もっと仕様をシンプルにする方向に動いて欲しかったです。
CommonMarkパーサを実装したい人に向けて
とにかく、CommonMarkに準拠するのは難しい。ということでいるかいないかわからない、CommonMarkパーサをこれから書こうと言う人に気づきを共有しておきます。
- いわゆるプログラミング言語やXML, JSONなどのパースとは全く別物です。そういうものはもともと 「パースしやすいように」という視点で文法が作られてますが、CommonMarkはそんなことはありません。 pegベースのパーサもあるので無理じゃないですがいわゆるLL,LRやLALRのような方法では厳しいです。
- Markdownはいうなれば「行志向」なので業単位を基本としましょう。
- 「lazy continuation」はかなり曲者です。
- 仕様策定者自身が Beyond Markdown で述べている部分は間違いなくしんどいです。特に強調は自分で考えるのは諦めましょう。素直に参照実装と同じアルゴリズムで実装するより他にspec testを通すのは難しいです。
- タブが本来のタブの意味でつかわれる点に注意。つまりタブ文字の位置によりタブは 1文字分、2文字分、3文字分、4文字分、いずれの文字幅にもなりえます。
- とにかく折れない心が大事です。CommonMarkとMarkdownは別物です。覚悟してかかりましょう。