鍍金池/ 教程/ Java/ 編譯器插件
哲學(xué)家就餐問題
鏈接進(jìn)階
名詞中英文對照
測試
引用和借用
泛型
方法語法
函數(shù)
不安全代碼
并發(fā)
裝箱語法和模式
注釋
棧和堆
運算符與重載
語法索引
文檔
固有功能
所有權(quán)
循環(huán)
通用函數(shù)調(diào)用語法
不定長類型
<code>const</code> 和 <code>static</code>
迭代器
其他語言中的 Rust
枚舉
詞匯表
If語句
猜猜看
錯誤處理
生命周期
編譯器插件
發(fā)布途徑
閉包
trait 對象
不使用標(biāo)準(zhǔn)庫
關(guān)聯(lián)常量
外部函數(shù)接口(FFI)
類型轉(zhuǎn)換
原生類型
匹配
參考文獻(xiàn)
Rust 編程語言
內(nèi)聯(lián)匯編
條件編譯
選擇你的保證
學(xué)習(xí) Rust
`type`別名
自定義內(nèi)存分配器
屬性
if let
高效 Rust
可變性
語法和語義
模式
基準(zhǔn)測試
結(jié)構(gòu)體
變量綁定
語言項
切片模式
<code>Deref</code> 強制多態(tài)
關(guān)聯(lián)類型
裸指針
<code>Borrow</code> 和 <code>AsRef</code>
準(zhǔn)備
Rust 開發(fā)版
字符串

編譯器插件

compiler-plugins.md
commit 1430a3500076ad504a0b30be77fd2ad4468ea769

介紹

rustc可以加載編譯器插件,它是由用戶提供的庫用來擴充編譯器的行為,例如新的語法擴展,lint檢查等。

一個插件是帶有設(shè)計好的用來在rustc中注冊擴展的注冊registrar)函數(shù)的一個動態(tài)庫包裝箱。其它包裝箱可以使用#![plugin(...)]屬性來裝載這個擴展。查看rustc::plugin文檔來獲取更多關(guān)于定義和裝載插件的機制。

如果屬性存在的話,#![plugin(foo(... args ...))]傳遞的參數(shù)并不由rustc自身解釋。它們被傳遞給插件的Registryargs方法。

在絕大多數(shù)情況中,一個插件應(yīng)該通過#![plugin]而不通過extern crate來使用。鏈接一個插件會將libsyntaxlibrustc加入到你的包裝箱的依賴中?;旧夏悴粫M绱顺悄阍跇?gòu)建另一個插件。plugin_as_librarylint會檢查這些原則。

通常的做法是將插件放到它們自己的包裝箱中,與任何那些會被庫的調(diào)用者使用的macro_rules!宏或 Rust 代碼分開。

語法擴展

插件可以有多種方法來擴展 Rust 的語法。一種語法擴展是宏過程。它們與[普通宏](5.35.Macros 宏.md)的調(diào)用方法一樣,不過擴展是通過執(zhí)行任意Rust代碼在編譯時操作語法樹進(jìn)行的。

讓我們寫一個實現(xiàn)了羅馬數(shù)字的插件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;  // 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, 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函數(shù)相比的優(yōu)勢有:

  • (任意復(fù)雜程度的)轉(zhuǎn)換都發(fā)生在編譯時
  • 輸入驗證也在編譯時進(jìn)行
  • 可以擴展并允許在模式中使用,它可以有效的為任何數(shù)據(jù)類型定義新語法。

除了宏過程,你可以定義新的類derive屬性和其它類型的擴展。查看Registry::register_syntax_extensionSyntaxExtension enum。對于更復(fù)雜的宏例子,查看regex_macros。

提示與技巧

這里提供一些[宏調(diào)試的提示](5.35.Macros 宏.md#debugging-macro-code)。

你可以使用syntax::parse來將記號樹轉(zhuǎn)換為像表達(dá)式這樣的更高級的語法元素:

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解析器代碼會給你一個解析基礎(chǔ)設(shè)施如何工作的感覺。

保留你解析所有的Span,以便更好的報告錯誤。你可以用Spanned包圍你的自定數(shù)據(jù)結(jié)構(gòu)。

調(diào)用ExtCtxt::span_fatal將會立即終止編譯。相反最好調(diào)用ExtCtxt::span_err并返回DummyResult,這樣編譯器可以繼續(xù)并找到更多錯誤。

為了打印用于調(diào)試的語法段,你可以同時使用span_notesyntax::print::pprust::*_to_string。

上面的例子使用AstBuilder::expr_usize產(chǎn)生了一個普通整數(shù)。作為一個AstBuilder特性的額外選擇,libsyntax提供了一個準(zhǔn)引用宏的集合。它們并沒有文檔并且非常邊緣化。然而,這些將會是實現(xiàn)一個作為一個普通插件庫的改進(jìn)準(zhǔn)引用的好的出發(fā)點。

Lint插件

插件可以擴展Rust Lint基礎(chǔ)設(shè)施來添加額外的代碼風(fēng)格,安全檢查等。你可以查看src/test/auxiliary/lint_plugin_test.rs來了解一個完整的例子,我們在這里重現(xiàn)它的核心部分:

#![feature(plugin_registrar)]
#![feature(box_syntax, rustc_private)]

extern crate syntax;

// Load rustc as a plugin to get macros
#[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);
}

那么像這樣的代碼:

#![plugin(lint_plugin_test)]

fn lintme() { }

將產(chǎn)生一個編譯警告:

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!調(diào)用,它定義了Lint結(jié)構(gòu)
  • 一個用來存放lint檢查所需的所有狀態(tài)(在我們的例子中,沒有)
  • 一個定義了如何檢查每個語法元素的LintPass實現(xiàn)。一個單獨的LintPass可能會對多個不同的Lint調(diào)用span_lint,不過它們都需要用get_lints方法進(jìn)行注冊。

Lint過程是語法遍歷,不過它們運行在編譯的晚期,這時類型信息時可用的。rustc內(nèi)建lint與lint插件使用相同的基礎(chǔ)構(gòu)架,并提供了如何訪問類型信息的例子。

由插件定義的語法通常通過屬性和插件標(biāo)識控制,例如,[#[allow(test_lint)]],-A test-lint。這些標(biāo)識符來自于declare_lint!的第一個參數(shù),經(jīng)過合適的大小寫和標(biāo)點轉(zhuǎn)換。

你可以運行rustc -W help foo.rs來見檢查lint列表是否為rustc所知,包括由foo.rs加載的插件。

上一篇:注釋下一篇:Rust 開發(fā)版