如何编写将代码注入函数的自定义属性

Dou*_*oug 14 rust

我已经调用了自定义属性:

#[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");
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

通过以下方式调用

#![feature(phase)]

#[phase(plugin)]
extern crate dummy_ext;

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

......我已经看到了一些quote_expr!( ... )用来做这件事的例子,但我并不真正理解它们.

假设我想将此语句(或表达式?)添加到任何标记的函数的顶部#[dummy]:

println!("dummy");
Run Code Online (Sandbox Code Playgroud)

我如何实现这一目标?

huo*_*uon 27

这里有两个任务:

  • 创建您想要插入的AST
  • 转换某个函数的AST(例如插入另一个函数)

笔记:

  • 当我在这个答案说,"项目",我专门指物品AST节点,例如fn,struct,impl.
  • 做与宏任何时候,rustc --pretty expanded foo.rs就是你最好的朋友(最适用于最小的例子,例如避免#[deriving]println!,除非你要特别调试的).

AST创建

从头开始创建AST块有三种基本方法:

  • 手动写出结构和枚举,
  • 使用缩写的方法AstBuilder,和
  • 使用引用来完全避免这种情况.

在这种情况下,我们可以使用引用,所以我不会浪费时间在其他人身上.所述quote宏采取ExtCtxt("扩展上下文")和一个表达式或项目等,并创建代表该项目AST值,例如

let x: Gc<ast::Expr> = quote_expr!(cx, 1 + 2);
Run Code Online (Sandbox Code Playgroud)

创建一个Expr_带有值的值ExprBinary,它包含两个ExprLits(用于12文字).

因此,要创建所需的表达式,quote_expr!(cx, println!("dummy"))应该可行.报价比这更强大:您可以使用$它将存储AST的变量拼接成表达式,例如,如果我们有x如上所述,那么

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

将创建一个AST reprsenting if 1 + 2 > 0 { println!("dummy") }.

这一切都非常不稳定,并且宏是特征门控的.一个完整的"工作"示例:

#![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 })
}
Run Code Online (Sandbox Code Playgroud)

截至2014年8月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块)structenum
  • 如果您想拍摄物品并实际更改它,请使用 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>
Run Code Online (Sandbox Code Playgroud)

这是注册的

reg.register_syntax_extension(intern("dummy"), base::ItemModifier(dummy_expand));
Run Code Online (Sandbox Code Playgroud)

我们已经设置了样板,我们只需要编写实现.有两种方法.我们可以只添加println!到的函数的内容开始,或者我们可以将内容从改变foo(); bar(); ...println!("dummy"); { foo(); bar(); ... }的只是创造了两个新的表达式.

正如您所发现的,ItemFn可以与之匹配

ast::ItemFn(decl, ref style, ref abi, ref generics, block)
Run Code Online (Sandbox Code Playgroud)

block实际内容在哪里.我上面提到的第二种方法是最简单的

let new_contents = quote_expr!(cx, 
    println!("dummy");
    $block
);
Run Code Online (Sandbox Code Playgroud)

然后保存旧的信息,我们将建立一个新的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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)