Ответ 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
}
}
}