青空文庫記法を解析する Rust パーサ aozora

Share

aozora は青空文庫記法のパーサ。CommonMark や Markdown は扱わず、青空文庫が配布している .txt ファイルに現れる注釈記法だけを対象にする。実装は Rust で、CLI バイナリ・Rust ライブラリ・WASM・C ABI・Python バインディングの 5 種類で配布する。

use aozora::Document;

let source = "|青梅《おうめ》".to_owned();
let doc = Document::new(source);
let tree = doc.parse();

let html: String = tree.to_html();
let canonical: String = tree.serialize();
let diagnostics = tree.diagnostics();

assert_eq!(canonical, "|青梅《おうめ》");

青空文庫記法とは何か

青空文庫 はパブリックドメインの日本文学を電子化して公開しているボランティアの電子図書館で、1997 年から続いている。各作品は Shift_JIS でエンコードされた .txt ファイルに、本文と独自の注釈記法が混在した形で配布される。

注釈記法には次のような種類がある。

  • ルビ: |青梅《おうめ》 で「青梅」に「おうめ」と振り仮名を付ける
  • 傍点: [#「ここに傍点」に傍点] で強調用の点を打つ
  • 縦中横: [#「23」は縦中横] で縦書き中の数字を横向きに組む
  • 外字: ※[#「魚+師」、第3水準1-94-37] で JIS X 0213 や Unicode 不在字を参照
  • 訓点・返り点: 漢文の読み下し用記号
  • 字下げコンテナ: [#ここから2字下げ]… [#ここで字下げ終わり] の範囲指定
  • 改ページ・改段: 章区切りを示す

aozora はこれらを全て認識し、構造化された AST と診断情報に変換する。

インストールと使い方

ビルド済みの CLI バイナリは GitHub Releases に Linux x86_64 / macOS arm64 / Windows x86_64 の 3 種類が aozora-vX.Y.Z-<target>.{tar.gz,zip} 形式で置いてある (SHA256SUMS 同梱)。

ソースから入れるなら cargo install:

cargo install --git <https://github.com/P4suta/aozora> --locked aozora-cli

CLI のサブコマンドはこの 4 つを覚えればだいたい用が足りる。

aozora check FILE.txt           # 字句解析と診断
aozora fmt --check FILE.txt     # parse → serialize の往復チェック
aozora render FILE.txt          # HTML を標準出力へ
aozora check -E sjis FILE.txt   # 青空文庫が配布する Shift_JIS をそのまま読む

すべて - (またはパス省略) で標準入力から読める。

Rust ライブラリとしての使い方は冒頭のコード例の通り。Document が bumpalo アリーナを所有し、tree がそこから borrow するライフタイム設計になっている。WASM / C ABI / Python から呼ぶ場合は handbook の Bindings 章にそれぞれの最小例がある。

実装の独自な部分

設計判断として目立つのは 3 点。詳細は handbook の Architecture 章にそれぞれ独立した節がある。

Markdown を扱わない。青空文庫記法は CommonMark とも GFM とも違う独自の文法体系で、aozora は青空文庫記法だけに責務を絞っている。Markdown と組み合わせて使いたい場合は別プロジェクトの afm を使う (afm は aozora を内部に取り込んだ Markdown 方言)。

SIMD マルチパターンスキャナ。青空文庫の本文は UTF-8 で 1 文字 3 バイトが大半を占め、トリガーバイト (|《》※[] および全角空白) の出現密度は 1KB あたり 1 個程度しかない。この希薄な探索を回すために Intel Hyperscan 由来の Teddy アルゴリズムを採用している。AVX2 環境のコーパス計測で 12 GB/s。AVX2 が無い環境では Hoehrmann 系 DFA に runtime dispatch でフォールバックする (3.5 GB/s)。wasm32 ターゲットは memchr 多重スキャンで 1.2 GB/s。

借用アリーナ AST。1 ドキュメントは数万ノードに展開されるため、Box<Node> を素直に並べると malloc がボトルネックになる。aozora は bumpalo 単一アリーナに全ノードを置き、ドキュメント drop 時に 1 回の Bump::reset で全解放する。青空文庫全作品を回すコーパスベンチで Box<Node> 版比 6.4 倍速、ピーク RSS は 30% 減を測っている。

さらに知りたい場合

  • handbook: https://p4suta.github.io/aozora/ — 記法の網羅リファレンス、内部設計 (借用アリーナ、SIMD スキャナ、外字解決)、性能チューニング (PGO、samply、コーパススイープ) まで一通り入っている
  • API リファレンス: https://p4suta.github.io/aozora/api/aozora/ — rustdoc を自動デプロイ
  • 関連プロジェクト
    • P4suta/afm — CommonMark + GFM + 青空文庫記法を統合した Markdown 方言
    • P4suta/aozora-tools — フォーマッタ、LSP サーバ、tree-sitter 文法、VS Code 拡張

crates.io への公開は v1.0 API 確定後の予定。それまでは tagged commit に依存する形で利用する。

Read more

Dependabotの対応が面倒なのでRenovateを導入する

Dependabotくんはある程度の依存関係の更新を通知してくれるが、山のようにprojectを抱えているとなんとも痒いところに手が届かない。 ということで、Renovateを使ってみる。 Build software better, togetherGitHub is where people build software. More than 150 million people use GitHub to discover, fork, and contribute to over 420 million projects.GitHub ここからGitHub Appを入れる。 あとは、指示に従ってポチポチするだけで、超かんたんだった。 感想 自動化最高。

By Sakashita Yasunobu

nodejsのバグを見つけたのでPRを投げてみた

概要 要するに、node --testで特定のバイナリを標準出力に書くと、テストランナーが固まったり、異常終了したりするバグです。 バグ自体は既知のものだったようで、修正がされていました。 test_runner: infinite loop in FileTest#drainRawBuffer when child stdout contains FF 0F followed by large size bytes · Issue #62693 · nodejs/nodeVersion v25.9.0 (also reproduced on v24.14.1 LTS, v22.21.0 LTS, v23.4.0) Platform Darwin 25.

By Sakashita Yasunobu
青空文庫のParser

青空文庫のParser

青空文庫のParserを作ってます。 GitHub - P4suta/aozora: Pure-functional Rust parser for 青空文庫記法 (Aozora Bunko notation): ルビ, 傍点, 縦中横, 外字, 返り点, indent containers, page breaks.Pure-functional Rust parser for 青空文庫記法 (Aozora Bunko notation): ルビ, 傍点, 縦中横, 外字, 返り点, indent containers, page breaks. - P4suta/aozoraGitHubP4suta Rustで作ってますが、配布物はcargoのほかにnpm/pypiでも入手できます Documentation * Playground — try it in

By Sakashita Yasunobu