Rustコンパイラの処理の流れ(2)
Rustコンパイラの処理の流れ(1) の続きとして、マクロの展開について書いた。マクロの使用法については https://doc.rust-lang.org/book/second-edition/appendix-04-macros.html を読むとよい。
Rustコンパイラは2つのパーサを持っている。1つは通常のパーサで、もう1つはマクロ用のパーサである。通常のパーサがパースした後で、マクロ用のパーサがマクロを展開し、その後で名前解決するという流れになっている。また、マクロ用のパーサはマクロ定義とマクロ呼び出しの両方をパースしている。
マクロ用のパーサは src/libsyntax/ext/tt/macro_parser.rs で定義されており、Earley parser とよく似たアルゴリズムが使用されている。
input
マクロ用のパーサのパース部分のインターフェースは以下のようになっている。
pub fn parse(
sess: &ParseSess,
tts: TokenStream,
ms: &[TokenTree],
directory: Option<Directory>,
recurse_into_modules: bool,
) -> NamedParseResult {
sess
はパースに必要ないくつかのメタデータを保持している。発生したエラー情報をユーザに返すのが主な用途である。tts
はパース対象の入力である。マクロ用のパーサはトークンのストリーム(※1)からms
とマッチしたメタ変数(※2)の束縛を得る。ms
は Tokenと区切り文字から構成されており、tss
とマッチさせるためのパターンとして使用されている。directory
はファイルの位置に関する情報である。recurse_into_modules
は再帰呼び出しするかどうかを示す。
output
パース結果の NamedParseResult
は Success
, Failure
, Error
の3パターンの結果を返す。
- Sucess:
tss
がms
とマッチしたことを示す。つまり、トークンと一致したメタ変数の束縛を生成できたということである。 - Failure:
tss
がms
とマッチしないことを示す。 "No rule expected token blah"のようなエラーが発生する。 - Error: パーサ内でなんらかの致命的なエラーが発生したことを示す。マクロの定義が曖昧で、マッチしたものが複数ある場合などに発生する。
マクロ用のパーサは正規表現のパーサとほとんど同じであるが、メタ変数がどのフラグメント指定子(※3)にマッチしているかが考慮されている。
処理の具体的な流れは src/libsyntax/ext/tt/macro_parser.rs のコメントに書かれている。具体例は以下のとおりである。
Start parsing a a a a b against [· a $( a )* a b].Remaining input: a a a a b
next: [· a $( a )* a b]- - - Advance over an a. - - -Remaining input: a a a b
cur: [a · $( a )* a b]
Descend/Skip (first item).
next: [a $( · a )* a b] [a $( a )* · a b].- - - Advance over an a. - - -Remaining input: a a b
cur: [a $( a · )* a b] [a $( a )* a · b]
Follow epsilon transition: Finish/Repeat (first item)
next: [a $( a )* · a b] [a $( · a )* a b] [a $( a )* a · b]- - - Advance over an a. - - - (this looks exactly like the last step)Remaining input: a b
cur: [a $( a · )* a b] [a $( a )* a · b]
Follow epsilon transition: Finish/Repeat (first item)
next: [a $( a )* · a b] [a $( · a )* a b] [a $( a )* a · b]- - - Advance over an a. - - - (this looks exactly like the last step)Remaining input: b
cur: [a $( a · )* a b] [a $( a )* a · b]
Follow epsilon transition: Finish/Repeat (first item)
next: [a $( a )* · a b] [a $( · a )* a b] [a $( a )* a · b]- - - Advance over a b. - - -Remaining input: ''
eof: [a $( a )* a b ·]
注釈
※1 TokenStreamはTokenTreeのシーケンスであり、TokenTreeはTokenと区切り文字で構成されている。
※2 メタ変数とは下のようなマクロの定義があった場合、 $x
のことを指す
macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
※3 ident
、 block
、 expr
、 pat
など