Dev Study
← Rust で小さなブラウザを作る コース

10. 状態機械という読み方 — 1 文字読んで、状態を変えるか、トークンを出すか

HTML のは、正規表現でも、タグを探す split でもなく、で書きます。HTML Standard 13.2.5 は「実装は次の状態機械を使って HTML をトークン化するかのように振る舞わなければならない。状態機械は data state から始まる。ほとんどのは 1 文字を消費し、新しい状態へ切り替えて今の文字をするか、新しい状態へ切り替えて次の文字を消費するか、同じ状態にとどまって次の文字を消費する」と定めています。

状態機械で書く理由は、同じ文字の意味が場所で変わるからです。"<" はタグの外では「タグの始まり」ですが、"1 < 2" のように後ろが英字でなければただの文字です。script の中では常に文字です。「今どこにいるか」を状態として持てば、1 文字ごとの判断は 1 つで済みます。

下のコードは、仕様の 84 の状態のうち Data / TagOpen / EndTagOpen / TagName の 4 つだけを持つ最小の字句解析器です。 を実装し、next() が呼ばれるたびに文字を消費して、が完成したら返します。トークンは StartTag / EndTag / Char / Eof の 4 種類で、文字は 1 文字ずつ Char として出すのが仕様どおりです(後で木を作るときにまとめます)。

読みどころは reconsume です。TagOpen で英字を読んだとき、その英字はタグ名の最初の文字なので、「TagName に切り替えて、今の文字をもう一度読む」必要があります。参照実装は reconsume フラグを立てて、次の consume() が直前の文字を返すようにしています。仕様の「reconsume in the X state」はすべてこの形で書けます。

本物のブラウザでは: の字句解析器は third_party/blink/renderer/core/html/parser/html_tokenizer.cc で、状態は kDataState・kTagOpenState・kScriptDataState・kBogusCommentState のように仕様の節名そのままの です。Firefox の parser/html/nsHtml5Tokenizer.cpp は先頭に「THIS IS A GENERATED FILE. PLEASE DO NOT EDIT. Please edit Tokenizer.java instead」とあり、Java で書かれた の HTML を機械翻訳した C++ です(parser/html/java/README.txt の make translate)。どちらも仕様の状態機械を『そのまま』書いていて、この回の enum State と match の形と読み方は同じです。

開発のどこに活かすか: 状態機械は、プロトコルの受信処理、ログの複数行まとまりの解析、設定ファイルやテンプレートの読み込み、UI の入力モードなど、「文脈で同じ入力の意味が変わる」ところに全部出てきます。enum State と match で書く形を 1 回手で書いておくと、正規表現で無理をして壊れるコードを避けられます。

点線の付いた語はタップすると説明が出ます。用語集を開く →

① 4 つの状態と遷移

矢印のラベルは「読んだ文字」。括弧は副作用
Dataタグの外
'<' → TagOpen / それ以外 → Char を出して Data のまま
TagOpen'<' を読んだ直後
'/' → EndTagOpen / 英字 → (StartTag を作って) TagName で再消費 / 他 → ('<' を Char で出して) Data で再消費
EndTagOpen'</' を読んだ直後
TagNameタグ名の途中
TagName: '>' → (タグを出して) Data / 他 → 小文字にして名前に足す
Data に戻る
  • ・どの状態でも入力が尽きたら Eof を 1 回出して終わる
  • ・仕様の節番号: Data 13.2.5.1、Tag open 13.2.5.6、End tag open 13.2.5.7、Tag name 13.2.5.8

状態は enum、遷移は match の腕 1 つ。仕様の節と 1 対 1 で対応するので、仕様を読みながら足していけます。

▶ サンプルコードを実行してみよう(自由に書き換えてOK)

Rust

手元で動かす(配布物)

図やコードに出てくる用語(タップで説明)

サンプルコードで身につける(3本) →

学んだ概念を、素朴な例から実務寄りの例まで3つの具体例で確認します。

公式ドキュメントで詳しく ↗