提案: AST版compilerとrendererの導入について
突然ですが、次世代Re:VIEWとしてAST(抽象構文木)ベースの新しいcompilerとrendererシステムを試験的に実装しています(Claude Codeを全面的に使って書いています。ちなみにこの文書もClaude Codeに書かせたものをベースに手を入れています)。
背景
現状のRe:VIEWはCompiler+Builderの構成になっています。が、これには以下のような課題があります:
- 複雑な構成: パーサーと出力形式が密結合しており、機能追加や改修が困難
- 保守性: コードが複雑化していることと、逐次的に出力する仕様のため、保守性が低い
- 拡張性: 中間表現がないため、他の入力フォーマットへの対応や他のプロセッサとの協調ができない
ASTベースのアプローチは、これらの問題の解決を狙うものです。
新アーキテクチャ
1. AST(抽象構文木)ベースの処理フロー
入力文書 → AST::Compiler → AST → Renderer → 出力
従来のように直接変換するのではなく、中間表現としてASTを生成し、それを各出力形式に変換します。 Rendererは従来のBuilderとは異なり、基本的にAST::Compilerを参照しないため、compilerとの依存関係がありません。
なお、以前のPEG版も同様な課題の解決を計画していましたが、今回はparse自体は現状を踏まえた手書きパーサを流用しつつ改変した上で、中間形式としてASTを導入するものです。 また、Builderは完全に別物のRendererとして再実装するため、挙動の維持は図りますがAPI自体の互換性はありません。
なおASTはRe:VIEWに合わせて独自に設計しています。
2. 主要コンポーネント
ReVIEW::AST::Compiler
- ソーステキストの解析
- 統一された専用AST表現への変換
- エラー処理と位置情報の保持
ReVIEW::AST::Node
- 文書構造を表現するノードクラス
- DocumentNode, HeadlineNode, ParagraphNode, ColumnNode等
- Visitorパターンによる処理の分離(ReVIEW::AST::Visitorクラスを導入)
ReVIEW::Renderer::Base
- ReVIEW::AST::Visitorを拡張したrenderer基底クラス
- 各出力形式(HTML, LaTeX等)に対応するサブクラスをそれぞれ実装(Renderer::HtmlRenderer、Renderer::LatexRenderer等)
- 出力は既存Builderとの互換性を極力維持
なお、Book/Chapter/Configure等は今のところそのまま使用しています(FootnoteIndexのみ都合により変更)。 PDFMakerやEPUBMakerは継承して使っています。
3. 新機能
Markdown対応
入力文書にRe:VIEW記法だけではなく、Markdown記法で書かれた文書がそのまま利用できます。 Markdownを使いたい場合はcatalog.ymlに「ch01.md」と書くだけで、拡張子で判別されます。 とはいえMarkdownにはない機能は使えませんが、コラム機能は見出しオプションとHTMLコメント記法により利用可能です。
# 普通の見出し
## [column] コラムタイトル
Markdown記法が全て使える**コラム**内容:
- リスト項目
- コードブロック
- インライン記法
> 引用文
> 引用文
> 引用文
<!-- /column -->
Markdownのparserはgithub/cmark-gfmをベースにしたmarklyを使用してします。 markly(cmark-gfm)のASTをRe:VIEW ASTに変換するMarkdownAdapterを導入します。
実装状況
プロトタイプ実装が完了し、以下の機能が一応動作しています。
✅ 実装済み機能
-
AST
- DocumentNode, HeadlineNode, ParagraphNode等の基本ノード
- AST::Compiler(Re:VIEW形式対応)
- AST::MarkdownCompiler(Markdown形式対応)
- Visitorパターンによる処理基盤
-
Renderer
- HtmlRenderer(HTMLBuilder互換)
- LatexRenderer(LATEXBuilder互換)
- 既存テンプレートシステムとの統合
- (他のRendererは未着手です)
-
Markdown拡張
- MarkdownAdapter (marklyベースの解析)
- HTMLコメント形式のコラム記法
📊 テスト結果
- LaTeX出力互換性: そこそこありそう(samples/sample-book文書のPDFにて確認)
- HTML出力互換性: 一応出る程度(動作確認はこれから)
- テストカバレッジ: テストケースを追加中
- パフォーマンス: 目立った低下はなし
利点
1. アーキテクチャ面
- 分離されたアーキテクチャ: パーサー、AST、rendererの明確な分離
- 拡張性: 新しい入力形式・出力形式の追加が可能の予定(ただし複雑な記法の追加はASTからの対応となるのでやや困難になる可能性があります)
- 再利用性: AST表現により複数の出力形式で同じロジックを共有
- テスタビリティ: 各段階でのユニットテスト実装が可能
2. 機能面
- Markdown対応: 本格的なMarkdown文書サポート
- 統一API: 入力形式に関わらず統一された処理API
3. 開発・保守面
- 段階的移行: 既存システムとの併用が可能
- 機能追加の容易さ: 新機能をASTレベルで実装すれば全出力形式に対応
- デバッグ支援: AST構造の可視化によりデバッグが容易
互換性
既存システムとの関係
- 後方互換: 一部古い機能(version2対応等)を除いて既存のRe:VIEW文書はそのまま対応可能予定、Builderやreview-ext.rbの互換性はなし
- Builder併用: 従来のコマンドを残したまま新コマンドを追加することで従来のBuilderシステムと併用はしばらく可能
新しいコマンド
# AST版コマンド(新規)
review-ast-compile # AST方式でのコンパイル
review-ast-pdfmaker # AST+LatexRendererでのPDF生成
review-ast-epubmaker # AST+HtmlRendererでのEPUB生成
# 従来コマンド(継続)
review-compile # 従来方式(Builder使用)
review-pdfmaker # 従来方式
review-epubmaker # 従来方式
実装詳細
追加ファイル構成(一部抜粋)
lib/review/ast/
├── compiler.rb # AST compiler(Re:VIEWフォーマット版)
├── markdown_compiler.rb # AST compiler(Markdown版)
├── node.rb # ノード基底クラス
├── document_node.rb # 文書ノード
├── headline_node.rb # 見出しノード
├── paragraph_node.rb # 段落ノード
├── column_node.rb # コラムノード
├── markdown_html_node.rb # Markdown HTML ノード
└── visitor.rb # Visitor パターン
lib/review/renderer/
├── base.rb # renderer基底クラス
├── html_renderer.rb # HTML renderer
└── latex_renderer.rb # LaTeX renderer
導入にあたり議論するべきポイント
- Builderを全く別物のRendererに置き換えることの是非
- 記法の拡張性はどのくらい必要かどうか
- インライン記法は普通に追加できそう
- ブロック記法は単純なものは追加できるかも?
- 古い互換性は維持するべきかどうか(このタイミングで整理するのはどうか)
- Markdown入力を正式に対応するかどうか
え、すごい
Builderを全く別物のRendererに置き換えることの是非
私は気にしないです。メジャーバージョンは変える必要はありそう
記法の拡張性はどのくらい必要かどうか
ブロックはまぁまぁ拡張することがありましたが、公式化していけばいいのかもしれない? extによるモンキーパッチはどのくらい大変でしょう?
古い互換性は維持するべきかどうか(このタイミングで整理するのはどうか)
メジャーバージョン変えるなら無理に維持しなくても。
Markdown入力を正式に対応するかどうか
pandoc2reviewでなんとかする案件もあったりしたので、marklyがたいていの拡張Markdownに対応できるのであれば、正式にRe:VIEWでサポートされるのはよさそう。
記法の拡張ですが、どうもpluginフレームワークを導入してそれで一定の拡張を行う設計を(Claude Codeが)計画しているようでした。ほんとかいな…。 とはいえ一部parserに組み込まれているような記法は変更できませんが。
ちなみにこのタイミングでリストもMarkdownっぽいくネストしたいとかありますか?
Markdownとは違って、//ol{と//li{とを使ってブロック込みでネストさせるのはできそうでした。むしろ//beginchild・//endchildをサポートするよりこちらを導入した方が確実そうな雰囲気でした。
うーん、なんかS式構文っぽくなるんだろうか。雰囲気的にはこういうことでしょうか。
//ol{
//li{
liの段落
* 箇条書き
* 箇条書き
liの段落2
//} # li閉じ
//li{
//ul{
//li{
箇条書き段落1
//image[][]{
//}
箇条書き段落2
//} # li閉じ
//} # ul閉じ
//} # li閉じ
//} # ol閉じ
何かからの変換にはよさそうだけど、自分で書くときには見通しがだいぶ悪くなりそうなのが心配ですね…。