Rustのasm!マクロが生成するもの
Rustのasm!
はインラインアセンブリを挿入するための組み込みマクロである。さて、このマクロは何を生成しているのか。
実は、RustのASTには、対応する具象構文を持たない特殊な抽象構文 InlineAsm
が定義されている。
pub enum ExprKind { Box(P<Expr>), ... /// Output of the `asm!()` macro InlineAsm(P<InlineAsm>), ... }
asm!
の実装である expand_asm
の末尾 でこれが生成されているのがわかる。
具象構文を持たない抽象構文をマクロが生成できるのはなぜか。これは expand_asm
の戻り値 MacEager
と戻り値型 MacResult
を調べるとわかる。
まず MacResult
は以下のように複数のメソッドを持っている。
pub trait MacResult { /// Create an expression. fn make_expr(self: Box<Self>) -> Option<P<ast::Expr>> { None } /// Create zero or more items. fn make_items(self: Box<Self>) -> Option<SmallVector<P<ast::Item>>> { None } ... }
つまり、 MacResult
は文脈に応じて異なる種類のASTにキャストできる値ということになる。
MacResult
を実装しているのは、 (DummyResult
を除くと) ParserAnyMacro
と MacEager
のみである。
通常の macro_rules!
マクロは ParserAnyMacro
を返す。 ParserAnyMacro
のメンバは以下のようになっている。
pub struct ParserAnyMacro<'a> { parser: Parser<'a>, ... }
つまりパーサーの状態そのものを持っている。このパーサーは以下のように生成されている。
... match TokenTree::parse(cx, lhs_tt, arg) { Success(named_matches) => { ... let tts = transcribe(&cx.parse_sess.span_diagnostic, Some(named_matches), rhs); ... let mut p = Parser::new(cx.parse_sess(), tts, Some(directory), false); ... return Box::new(ParserAnyMacro { parser: p, ... }) } ... }
つまり、マクロ実引数が規則にマッチしたら、マッチ結果からトークン列を生成する。このトークン列を初期状態としてパーサーを生成している。
ParserAnyMacro
は MacResult
を以下のように実装している。
macro_rules! expansions { ($($kind:ident: $ty:ty [$($vec:ident, $ty_elt:ty)*], $kind_name:expr, .$make:ident, $(.$fold:ident)* $(lift .$fold_elt:ident)*, $(.$visit:ident)* $(lift .$visit_elt:ident)*;)*) => { ... impl<'a> MacResult for ::ext::tt::macro_rules::ParserAnyMacro<'a> { $(fn $make(self: Box<::ext::tt::macro_rules::ParserAnyMacro<'a>>) -> Option<$ty> { Some(self.make(ExpansionKind::$kind).$make()) })* } } } expansions! { Expr: P<ast::Expr> [], "expression", .make_expr, .fold_expr, .visit_expr; Pat: P<ast::Pat> [], "pattern", .make_pat, .fold_pat, .visit_pat; ... }
つまり、 ParserAnyMacro
は、 make_*
が呼び出された時点ではじめてトークン列を構文解析し、ASTを返す。
一方、 MacEager
は以下のように定義されている。
macro_rules! make_MacEager { ( $( $fld:ident: $t:ty, )* ) => { ... pub struct MacEager { $( pub $fld: Option<$t>, )* } ... } } make_MacEager! { expr: P<ast::Expr>, pat: P<ast::Pat>, ... }
つまり、 MacEager
はトークン列をもたず、構文解析済みのASTをそのまま持っている。
ここで expand_asm
に戻ると、これは InlineAsm
をのせた MacEager
を生成しているため、具象構文を経由せずに直接 InlineAsm
を生成できていることになる。
... MacEager::expr(P(ast::Expr { id: ast::DUMMY_NODE_ID, node: ast::ExprKind::InlineAsm(P(ast::InlineAsm { asm: asm, asm_str_style: asm_str_style.unwrap(), outputs: outputs, inputs: inputs, clobbers: clobs, volatile: volatile, alignstack: alignstack, dialect: dialect, expn_id: expn_id, })), span: sp, attrs: ast::ThinVec::new(), }))
なお、 InlineAsm
を含む構文木を stringify!
したときは、 pprust
内の処理で asm!(...)
の形に復元される。