## 介紹
`rustc`可以加載編譯器插件,它是由用戶提供的庫用來擴充編譯器的行為,例如新的語法擴展,lint檢查等。
一個插件是帶有設計好的用來在`rustc`中注冊擴展的_注冊_(_registrar_)函數的一個動態庫包裝箱。其它包裝箱可以使用`#![plugin(...)]`屬性來裝載這個擴展。查看[rustc::plugin](http://doc.rust-lang.org/rustc/plugin/)文檔來獲取更多關于定義和裝載插件的機制。
如果屬性存在的話,`#![plugin(foo(... args ...))]`傳遞的參數并不由`rustc`自身解釋。它們被傳遞給插件的`Registry`[args方法](http://doc.rust-lang.org/rustc/plugin/registry/struct.Registry.html#method.args)。
在絕大多數情況中,一個插件應該_只_通過`#![plugin]`而不通過`extern crate`來使用。鏈接一個插件會將`libsyntax`和`librustc`加入到你的包裝箱的依賴中。基本上你不會希望如此除非你在構建另一個插件。`plugin_as_library`lint會檢查這些原則。
通常的做法是將插件放到它們自己的包裝箱中,與任何那些會被庫的調用者使用的`macro_rules!`宏或Rust代碼分開。
## 語法擴展
插件可以有多種方法來擴展Rust的語法。一種語法擴展是宏過程。它們與[普通宏](http://kaisery.gitbooks.io/rust-book-chinese/content/content/5.35.Macros%20%E5%AE%8F.md)的調用方法一樣,不過擴展是通過執行任意Rust代碼在編譯時操作[語法樹](http://doc.rust-lang.org/syntax/ast/)進行的。
讓我們寫一個實現了羅馬數字的插件[roman_numerals.rs](https://github.com/rust-lang/rust/blob/master/src/test/auxiliary/roman_numerals.rs)。
~~~
#![crate_type="dylib"]
#![feature(plugin_registrar, rustc_private)]
extern crate syntax;
extern crate rustc;
use syntax::codemap::Span;
use syntax::parse::token;
use syntax::ast::{TokenTree, TtToken};
use syntax::ext::base::{ExtCtxt, MacResult, DummyResult, MacEager};
use syntax::ext::build::AstBuilder; // trait for expr_usize
use rustc::plugin::Registry;
fn expand_rn(cx: &mut ExtCtxt, sp: Span, args: &[TokenTree])
-> Box<MacResult + 'static> {
static NUMERALS: &'static [(&'static str, u32)] = &[
("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)];
let text = match args {
[TtToken(_, token::Ident(s, _))] => token::get_ident(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_u32(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](http://doc.rust-lang.org/reference.html#derive)屬性和其它類型的擴展。查看[Registry::register_syntax_extension](http://doc.rust-lang.org/rustc/plugin/registry/struct.Registry.html#method.register_syntax_extension)和[SyntaxExtension enum](http://doc.rust-lang.org/syntax/ext/base/enum.SyntaxExtension.html)。對于更復雜的宏例子,查看[regex_macros](https://github.com/rust-lang/regex/blob/master/regex_macros/src/lib.rs)。
## 提示與技巧
這里提供一些[宏調試的提示](http://kaisery.gitbooks.io/rust-book-chinese/content/content/5.35.Macros%20%E5%AE%8F.md#debugging-macro-code)。
你可以使用[syntax::parse](http://doc.rust-lang.org/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解析器代碼](https://github.com/rust-lang/rust/blob/master/src/libsyntax/parse/parser.rs)會給你一個解析基礎設施如何工作的感覺。
保留你解析所有的[Span](http://doc.rust-lang.org/syntax/codemap/struct.Span.html),以便更好的報告錯誤。你可以用[Spanned](http://doc.rust-lang.org/syntax/codemap/struct.Spanned.html)包圍你的自定數據結構。
調用[ExtCtxt::span_fatal](http://doc.rust-lang.org/syntax/ext/base/struct.ExtCtxt.html#method.span_fatal)將會立即終止編譯。相反最好調用[ExtCtxt::span_err](http://doc.rust-lang.org/syntax/ext/base/struct.ExtCtxt.html#method.span_err)并返回[DummyResult](http://doc.rust-lang.org/syntax/ext/base/struct.DummyResult.html),這樣編譯器可以繼續并找到更多錯誤。
為了打印用于調試的語法段,你可以同時使用[span_note](http://doc.rust-lang.org/syntax/ext/base/struct.ExtCtxt.html#method.span_note)和[syntax::print::pprust::*_to_string](http://doc.rust-lang.org/syntax/print/pprust/#functions)。
上面的例子使用[AstBuilder::expr_usize](http://doc.rust-lang.org/syntax/ext/build/trait.AstBuilder.html#tymethod.expr_usize)產生了一個普通整數。作為一個`AstBuilder`特性的額外選擇,`libsyntax`提供了一個[準引用宏](http://doc.rust-lang.org/syntax/ext/quote/)的集合。它們并沒有文檔并且非常邊緣化。然而,這些將會是實現一個作為一個普通插件庫的改進準引用的好的出發點。
## Lint插件
插件可以擴展[Rust Lint基礎設施](http://doc.rust-lang.org/reference.html#lint-check-attributes)來添加額外的代碼風格,安全檢查等。你可以查看[src/test/auxiliary/lint_plugin_test.rs](https://github.com/rust-lang/rust/blob/master/src/test/auxiliary/lint_plugin_test.rs)來了解一個完整的例子,我們在這里重現它的核心部分:
~~~
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)
}
fn check_item(&mut self, cx: &Context, it: &ast::Item) {
let name = token::get_ident(it.ident);
if name.get() == "lintme" {
cx.span_lint(TEST_LINT, it.span, "item is named 'lintme'");
}
}
}
#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
reg.register_lint_pass(box Pass as LintPassObject);
}
~~~
那么像這樣的代碼:
~~~
#![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() { }
^~~~~~~~~~~~~~~
~~~
Lint插件的組件有:
* 一個或多個`declare_lint!`調用,它定義了[Lint](http://doc.rust-lang.org/rustc/lint/struct.Lint.html)結構
* 一個用來存放lint檢查所需的所有狀態(在我們的例子中,沒有)
* 一個定義了如何檢查每個語法元素的[LintPass](http://doc.rust-lang.org/rustc/lint/trait.LintPass.html)實現。一個單獨的`LintPass`可能會對多個不同的`Lint`調用`span_lint`,不過它們都需要用`get_lints`方法進行注冊。
Lint過程是語法遍歷,不過它們運行在編譯的晚期,這時類型信息時可用的。`rustc`的[內建lint](https://github.com/rust-lang/rust/blob/master/src/librustc/lint/builtin.rs)與lint插件使用相同的基礎構架,并提供了如何訪問類型信息的例子。
由插件定義的語法通常通過[屬性和插件標識](http://doc.rust-lang.org/reference.html#lint-check-attributes)控制,例如,`[#[allow(test_lint)]]`,`-A test-lint`。這些標識符來自于`declare_lint!`的第一個參數,經過合適的大小寫和標點轉換。
你可以運行`rustc -W help foo.rs`來見檢查lint列表是否為`rustc`所知,包括由`foo.rs`加載的插件。
- 前言
- 1.介紹
- 2.準備
- 2.1.安裝Rust
- 2.2.Hello, world!
- 2.3.Hello, Cargo!
- 3.學習Rust
- 3.1.猜猜看
- 3.2.哲學家就餐問題
- 3.3.其它語言中的Rust
- 4.高效Rust
- 4.1.棧和堆
- 4.2.測試
- 4.3.條件編譯
- 4.4.文檔
- 4.5.迭代器
- 4.6.并發
- 4.7.錯誤處理
- 4.8.外部語言接口
- 4.9.Borrow 和 AsRef
- 4.10.發布途徑
- 5.語法和語義
- 5.1.變量綁定
- 5.2.函數
- 5.3.原生類型
- 5.4.注釋
- 5.5.If語句
- 5.6.for循環
- 5.7.while循環
- 5.8.所有權
- 5.9.引用和借用
- 5.10.生命周期
- 5.11.可變性
- 5.12.結構體
- 5.13.枚舉
- 5.14.匹配
- 5.15.模式
- 5.16.方法語法
- 5.17.Vectors
- 5.18.字符串
- 5.19.泛型
- 5.20.Traits
- 5.21.Drop
- 5.22.if let
- 5.23.trait對象
- 5.24.閉包
- 5.25.通用函數調用語法
- 5.26.包裝箱和模塊
- 5.27.`const`和`static`
- 5.28.屬性
- 5.29.`type`別名
- 5.30.類型轉換
- 5.31.關聯類型
- 5.32.不定長類型
- 5.33.運算符和重載
- 5.34.`Deref`強制多態
- 5.35.宏
- 5.36.裸指針
- 6.Rust開發版
- 6.1.編譯器插件
- 6.2.內聯匯編
- 6.3.不使用標準庫
- 6.4.固有功能
- 6.5.語言項
- 6.6.鏈接參數
- 6.7.基準測試
- 6.8.裝箱語法和模式
- 6.9.切片模式
- 6.10.關聯常量
- 7.詞匯表
- 8.學院派研究
- 勘誤