Описание тега rust-compiler-plugin
Компилятор Rust (rustc
) может загружать различные динамические библиотеки для расширения существующего синтаксиса языка - например, линты, выражения проверки времени компиляции и т. д.
Плагины компилятора - это динамические библиотеки, которые загружаются путем вызова следующих атрибутов контейнера Rust:
#![feature(plugin)] // Enable plugin feature game
#![plugin(my_compiler_plugin)] //Instantiate my_compiler_plugin
В большинстве случаев плагин следует использовать только через #![my_compiler_plugin]
а не через внешний ящик. Связывание плагина потребует всехlibsyntax
а также librustc
как зависимости вашего ящика. Обычно это нежелательно, если вы не создаете другой плагин.
Процедурные макросы
Один из способов расширения подключаемых модулей компилятора - использование процедурных макросов. Процедурные макросы похожи на обычные макросы, за исключением того, что они могут изменять дерево синтаксиса во время компиляции. Используя этот подход, можно создать макрос, который будет синтаксически проверен во время компиляции.
Например, мы хотели добавить макрос, который преобразует римские числа в десятичные и проверяет правильность чисел во время компиляции, мы бы написали что-то вроде roman_numerals.rs:
#![crate_type="dylib"]
#![feature(plugin_registrar, rustc_private)]
#![feature(slice_patterns)]
extern crate syntax;
extern crate rustc;
use syntax::codemap::Span;
use syntax::ast::{TokenTree, TtToken};
use syntax::parse::token;
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)];
let text = match args {
[TtToken(_, 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);
}
Обратите внимание #[plugin_registar]
функции, которые добавляют плагин как зарегистрированный макрос. Теперь мы можем использовать процедурный макрос римских цифр (rn!
) написав:
#![feature(plugin)]
#![plugin(roman_numerals)]
fn main() {
assert_eq!(rn!(MMXV), 2015);
}
Линты
Еще одно поле, в котором требуются расширения синтаксиса, - это дополнительные ссылки для rustc
. Например, предположим, что вы хотите определить простой линт, который замечает функции, называемыеlintme
и выдает предупреждение, если встречает их.
Его базовая структура выглядит примерно так (полный пример см. Здесь):
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 == "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() { }
выдает предупреждение компилятора:
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; - структура, содержащая любое состояние, необходимое для прохода lint (здесь таких состояний нет);
- реализация LintPass, определяющая, как проверять каждый элемент синтаксиса. Один LintPass может вызывать span_lint для нескольких разных Lints, но должен регистрировать их все через
get_lints
метод.
Нестабильность
Все ящики, использующие плагины (из-за зависимости от rustc
API, который является нестабильным) ограничен запуском только на ржавчине каждую ночь. Это может быть компенсировано использованием пользовательской замены libsyntax (например, syntex).