Rustコンパイラの処理の流れ(2)

Takanori Ishibashi
5 min readMay 27, 2018

--

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

パース結果の NamedParseResultSuccess , Failure, Error の3パターンの結果を返す。

  • Sucess: tssms とマッチしたことを示す。つまり、トークンと一致したメタ変数の束縛を生成できたということである。
  • Failure: tssms とマッチしないことを示す。 "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 identblockexprpat など

参考

--

--