Подтвердить что ты не робот

Как написать настраиваемый атрибут, который вводит код в функцию

Я дошел до того, что вызвал настраиваемый атрибут:

#[plugin_registrar]
pub fn registrar(reg: &mut rustc::plugin::Registry) {
  use syntax::parse::token::intern;
  use syntax::ext::base;

  // Register the `#[dummy]` attribute.
  reg.register_syntax_extension(intern("dummy"),
  base::ItemDecorator(dummy_expand));
}

// Decorator for `dummy` attribute
pub fn dummy_expand(context: &mut ext::base::ExtCtxt, span: codemap::Span, meta_item: Gc<ast::MetaItem>, item: Gc<ast::Item>, push: |Gc<ast::Item>|) {
  match item.node {
    ast::ItemFn(decl, ref style, ref abi, ref generics, block) => {
      trace!("{}", decl);
      // ...? Add something here.
    }
    _ => {
      context.span_err(span, "dummy is only permissiable on functions");
    }
  }
}

Вызывается через:

#![feature(phase)]

#[phase(plugin)]
extern crate dummy_ext;

#[test]
#[dummy]
fn hello() {
  println!("Put something above this...");
}

... и я видел несколько примеров вокруг вещей, которые используют quote_expr!( ... ) для этого, но я их действительно не понимаю.

Скажем, я хочу добавить этот оператор (или это выражение?) в начало любой функции, помеченной #[dummy]:

println!("dummy");

Как мне это достичь?

4b9b3361

Ответ 1

Здесь две задачи:

  • создание AST, которое вы хотите вставить
  • преобразование АСТ некоторой функции (например, вставка другой части)

Примечания:

  • Когда я говорю "элемент" в этом ответе, я специально имел в виду элемент AST node, например. fn, struct, impl.
  • когда вы делаете что-либо с макросами, rustc --pretty expanded foo.rs - ваш лучший друг (лучше всего работает на самых маленьких примерах, например, избегая #[deriving] и println!, если вы не пытаетесь отлаживать их специально).

Создание АСТ

Есть 3 основных способа создания кусков AST с нуля:

  • вручную выписывая структуры и перечисления,
  • используя методы AstBuilder, чтобы сократить это, и
  • используя цитату, чтобы избежать этого.

В этом случае мы можем использовать цитирование, поэтому я не буду тратить время на других. Макросы quote принимают ExtCtxt ( "контекст расширения" ) и выражение или элемент и т.д. И создают значение AST, которое представляет этот элемент, например

let x: Gc<ast::Expr> = quote_expr!(cx, 1 + 2);

создает Expr_ со значением ExprBinary, который содержит два ExprLit (для 1 и 2 литералов).

Следовательно, чтобы создать желаемое выражение, quote_expr!(cx, println!("dummy")) должен работать. Котировка более мощная, чем просто: вы можете использовать $ для сращивания переменной, хранящей AST, в выражение, например, если у нас есть x, как указано выше, тогда

let y = quote_expr!(cx, if $x > 0 { println!("dummy") });

создаст рецепт AST if 1 + 2 > 0 { println!("dummy") }.

Все это очень неустойчиво, и макросы имеют функцию gated. Полный "рабочий" пример:

#![feature(quote)]
#![crate_type = "dylib"]

extern crate syntax;

use syntax::ext::base::ExtCtxt;
use syntax::ast;

use std::gc::Gc;

fn basic_print(cx: &mut ExtCtxt) -> Gc<ast::Expr> {
    quote_expr!(cx, println!("dummy"))
}

fn quoted_print(cx: &mut ExtCtxt) -> Gc<ast::Expr> {
    let p = basic_print(cx);
    quote_expr!(cx, if true { $p })
}

По состоянию на 2014-08-29 список цитирующих макросов: quote_tokens, quote_expr, quote_ty, quote_method, quote_item, quote_pat, quote_arm, quote_stmt. (Каждый по существу создает аналогично названный тип в syntax::ast.)

(Будьте осторожны: они реализованы в очень хакерском ключе на данный момент, просто строя их аргумент и репарацию, поэтому относительно легко встретить запутанное поведение.)

Преобразование AST

Теперь мы знаем, как сделать изолированные куски AST, но как мы можем вернуть их в основной код?

Ну, точный метод зависит от того, что вы пытаетесь сделать. Там множество различных типов расширений синтаксиса.

  • Если вы просто хотите расширить какое-то выражение на месте (например, println!), NormalTT верен,
  • если вы хотите создавать новые элементы на основе существующего, не изменяя ничего, используйте ItemDecorator (например, #[deriving] создает некоторые impl блоки на основе на элементах struct и enum, к которым он прикреплен)
  • Если вы хотите взять элемент и на самом деле его изменить, используйте ItemModifier

Таким образом, в этом случае мы хотим ItemModifier, так что мы можем изменить #[dummy] fn foo() { ... } на #[dummy] fn foo() { println!("dummy"); .... }. Пусть объявляет функцию с правильной сигнатурой:

fn dummy_expand(cx: &mut ExtCtxt, sp: Span, _: Gc<ast::MetaItem>, item: Gc<ast::Item>) -> Gc<Item>

Это зарегистрировано с помощью

reg.register_syntax_extension(intern("dummy"), base::ItemModifier(dummy_expand));

У нас есть настройка шаблона, нам просто нужно написать реализацию. Есть два подхода. Мы могли бы просто добавить println! в начало содержимого функции, или мы могли бы изменить содержимое от foo(); bar(); ... до println!("dummy"); { foo(); bar(); ... }, просто создав два новых выражения.

Как вы нашли, ItemFn можно сопоставить с

ast::ItemFn(decl, ref style, ref abi, ref generics, block)

где block - это фактическое содержимое. Второй подход, о котором я упоминал выше, проще всего, просто

let new_contents = quote_expr!(cx, 
    println!("dummy");
    $block
);

а затем, чтобы сохранить старую информацию, мы построим новый ItemFn и завершим его с помощью правильного метода на AstBuilder. Всего:

#![feature(quote, plugin_registrar)]
#![crate_type = "dylib"]

// general boilerplate
extern crate syntax;
extern crate rustc;

use syntax::ast;
use syntax::codemap::Span;
use syntax::ext::base::{ExtCtxt, ItemModifier};
// NB. this is important or the method calls don't work
use syntax::ext::build::AstBuilder;
use syntax::parse::token;

use std::gc::Gc;

#[plugin_registrar]
pub fn registrar(reg: &mut rustc::plugin::Registry) {
  // Register the `#[dummy]` attribute.
  reg.register_syntax_extension(token::intern("dummy"),
                                ItemModifier(dummy_expand));
}

fn dummy_expand(cx: &mut ExtCtxt, sp: Span, _: Gc<ast::MetaItem>, 
                item: Gc<ast::Item>) -> Gc<ast::Item> {
    match item.node {
        ast::ItemFn(decl, ref style, ref abi, ref generics, block) => {
            let new_contents = quote_expr!(&mut *cx,
                println!("dummy");
                $block
            );
            let new_item_ = ast::ItemFn(decl, style.clone(), 
                                        abi.clone(), generics.clone(),
                                        // AstBuilder to create block from expr
                                        cx.block_expr(new_contents));
            // copying info from old to new
            cx.item(item.span, item.ident, item.attrs.clone(), new_item_)
        }
        _ => {
            cx.span_err(sp, "dummy is only permissible on functions");
            item
        }
    }
}