注意: 最新版のドキュメントをご覧ください。この第1版ドキュメントは古くなっており、最新情報が反映されていません。リンク先のドキュメントが現在の Rust の最新のドキュメントです。
rustc
はコンパイラプラグイン、ユーザの提供する構文拡張や構文チェックなどのコンパイラの振舞を拡張するライブラリをロード出来ます。
プラグインとは rustc
に拡張を登録するための、指定された 登録用 関数を持った動的ライブラリのクレートです。
他のクレートはこれらのプラグインを #![plugin(...)]
クレートアトリビュートでロード出来ます。
プラグインの定義、ロードの仕組みについて詳しくはrustc_plugin
を参照して下さい。
#![plugin(foo(... args ...))]
のように渡された引数があるなら、それらはrustc自身によっては解釈されません。
Registry
のargs
メソッドを通じてプラグインに渡されます。
ほとんどの場合で、プラグインは #![plugin]
を通じて のみ 使われるべきで、 extern crate
を通じて使われるべきではありません。
プラグインをリンクするとlibsyntaxとlibrustcの全てをクレートの依存に引き込んでしまいます。
これは別のプラグインを作っているのでもない限り一般的には望まぬ挙動です。
plugin_as_library
チェッカによってこのガイドラインは検査されます。
普通の慣行ではコンパイラプラグインはそれ専用のクレートに置かれて、 macro_rules!
マクロやコンシューマが使うライブラリのコードとは分けられます。
プラグインはRustの構文を様々な方法で拡張出来ます。構文拡張の1つに手続的マクロがあります。 これらは普通のマクロと同じように実行されますが展開は任意の構文木をコンパイル時に操作するRustのコードが行います。
ローマ数字リテラルを実装するroman_numerals.rs
を書いてみましょう。
#![crate_type="dylib"] #![feature(plugin_registrar, rustc_private)] extern crate syntax; extern crate rustc; extern crate rustc_plugin; use syntax::codemap::Span; use syntax::parse::token; use syntax::ast::TokenTree; use syntax::ext::base::{ExtCtxt, MacResult, DummyResult, MacEager}; use syntax::ext::build::AstBuilder; // expr_usizeのトレイト use rustc_plugin::Registry; fn expand_rn(cx: &mut ExtCtxt, sp: Span, args: &[TokenTree]) -> Box<MacResult + 'static> { static NUMERALS: &'static [(&'static str, usize)] = &[ ("M", 1000), ("CM", 900), ("D", 500), ("CD", 400), ("C", 100), ("XC", 90), ("L", 50), ("XL", 40), ("X", 10), ("IX", 9), ("V", 5), ("IV", 4), ("I", 1)]; if args.len() != 1 { cx.span_err( sp, &format!("argument should be a single identifier, but got {} arguments", args.len())); return DummyResult::any(sp); } let text = match args[0] { TokenTree::Token(_, token::Ident(s, _)) => s.to_string(), _ => { cx.span_err(sp, "argument should be a single identifier"); return DummyResult::any(sp); } }; let mut text = &*text; let mut total = 0; while !text.is_empty() { match NUMERALS.iter().find(|&&(rn, _)| text.starts_with(rn)) { Some(&(rn, val)) => { total += val; text = &text[rn.len()..]; } None => { cx.span_err(sp, "invalid Roman numeral"); return DummyResult::any(sp); } } } MacEager::expr(cx.expr_usize(sp, total)) } #[plugin_registrar] pub fn plugin_registrar(reg: &mut Registry) { reg.register_macro("rn", expand_rn); }
rn!()
マクロを他の任意のマクロと同じように使えます。
#![feature(plugin)] #![plugin(roman_numerals)] fn main() { assert_eq!(rn!(MMXV), 2015); }
単純な fn(&str) -> u32
に対する利点は
手続き的マクロに加えてderive
ライクなアトリビュートや他の拡張を書けます。
Registry::register_syntax_extension
やSyntaxExtension
列挙型を参照して下さい。
もっと複雑なマクロの例はregex_macros
を参照して下さい。
マクロデバッグのヒントのいくつかが使えます。
syntax::parse
を使うことでトークン木を式などの高レベルな構文要素に変換出来ます。
fn expand_foo(cx: &mut ExtCtxt, sp: Span, args: &[TokenTree]) -> Box<MacResult+'static> { let mut parser = cx.new_parser_from_tts(args); let expr: P<Expr> = parser.parse_expr();
libsyntax
のパーサのコードを見るとパーサの基盤がどのように機能しているかを感られるでしょう。
パースしたもののSpan
は良いエラー報告のために保持しておきましょう。
自分で作ったデータ構造に対してもSpanned
でラップ出来ます。
ExtCtxt::span_fatal
を呼ぶとコンパイルは即座に中断されます。
ExtCtxt::span_err
を呼んでDummyResult
を返せばコンパイラはさらなるエラーを発見できるのでその方が良いでしょう。
構文の断片を表示するにはspan_note
とsyntax::print::pprust::*_to_string
を使えば出来ます。
上記の例ではAstBuilder::expr_usize
を使って整数リテラルを作りました。
AstBuilder
トレイトの代替として libsyntax
は準クォート マクロを提供しています。
ドキュメントがない上に荒削りです。しかしながらその実装は改良版の普通のプラグインライブラリのとっかかりにはほど良いでしょう。
プラグインによってRustの構文チェック基盤を拡張してコーディングスタイル、安全性などを検査するようにできます。ではlint_plugin_test.rs
プラグインを書いてみましょう。
lintme
という名前のアイテムについて警告を出すものです。
#![feature(plugin_registrar)] #![feature(box_syntax, rustc_private)] extern crate syntax; // macroを使うためにrustcをプラグインとして読み込む #[macro_use] extern crate rustc; extern crate rustc_plugin; use rustc::lint::{EarlyContext, LintContext, LintPass, EarlyLintPass, EarlyLintPassObject, LintArray}; use rustc_plugin::Registry; use syntax::ast; declare_lint!(TEST_LINT, Warn, "Warn about items named 'lintme'"); struct Pass; impl LintPass for Pass { fn get_lints(&self) -> LintArray { lint_array!(TEST_LINT) } } impl EarlyLintPass for Pass { fn check_item(&mut self, cx: &EarlyContext, it: &ast::Item) { if it.ident.name.as_str() == "lintme" { cx.span_lint(TEST_LINT, it.span, "item is named 'lintme'"); } } } #[plugin_registrar] pub fn plugin_registrar(reg: &mut Registry) { reg.register_early_lint_pass(box Pass as EarlyLintPassObject); }
そしたらこのようなコードは
fn main() { #![plugin(lint_plugin_test)] fn lintme() { } }#![plugin(lint_plugin_test)] fn lintme() { }
コンパイラの警告を発生させます。
foo.rs:4:1: 4:16 warning: item is named 'lintme', #[warn(test_lint)] on by default
foo.rs:4 fn lintme() { }
^~~~~~~~~~~~~~~
構文チェックプラグインのコンポーネントは
declare_lint!
の実行。それによってLint
構造体が定義されます。LintPass
の実装。
単一の LintPass
は複数回 span_lint
をいくつかの異なる Lint
に対して呼ぶかもしれませんが、全て get_lints
を通じて登録すべきです。構文チェックパスは構文巡回ですが、型情報が得られる、コンパイルの終盤で走ります。 rustc
の組み込み構文チェックは殆どプラグインと同じ基盤を使っており、どうやって型情報にアクセスするかの例になっています。
プラグインによって定義されたLintは普通のアトリビュートとコンパイラフラグ例えば #[allow(test_lint)]
や -A test-lint
によってコントロールされます。
これらの識別子は declare_lint!
の第一引数に由来しており、適切な名前に変換されます。
rustc -W help foo.rs
を走らせることで rustc
の知っている、及び foo.rs
内で定義されたコンパイラ構文チェックをロード出来ます。