rust - 如何编写将代码注入(inject)函数的自定义属性

标签 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");
    }
  }
}

通过以下方式调用:
#![feature(phase)]

#[phase(plugin)]
extern crate dummy_ext;

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

...我已经看到了一些使用 quote_expr!( ... ) 的例子。这样做,但我真的不明白他们。

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

我该如何做到这一点?

最佳答案

这里有两个任务:

  • 创建要插入的 AST
  • 转换某些函数的 AST(例如插入另一 block )

  • 笔记:
  • 当我在这个答案中说“项目”时,我的意思是 the item AST node ,例如fn , struct , impl .
  • 使用宏做任何事情时,rustc --pretty expanded foo.rs是你最好的 friend (在最小的例子上效果最好,例如避免 #[deriving]println! ,除非你试图专门调试它们)。

  • AST 创建

    有 3 种创建 AST block 的基本方法从头开始:
  • 手动写出结构和枚举,
  • 使用 methods of AstBuilder 缩写,和
  • 使用引用来完全避免这种情况。

  • 在这种情况下,我们可以使用引用,所以我不会在其他方面浪费时间。 quote宏采用 ExtCtxt (“扩展上下文”)和一个表达式或项目等,并创建一个表示该项目的 AST 值,例如
    let x: Gc<ast::Expr> = quote_expr!(cx, 1 + 2);
    

    创建一个 Expr_ 有值 ExprBinary ,其中包含两个 ExprLit s(对于 12 文字)。

    因此,要创建所需的表达式,quote_expr!(cx, println!("dummy"))应该管用。报价比这更强大:您可以使用 $它将存储 AST 的变量拼接到表达式中,例如,如果我们有 x如上,那么
    let y = quote_expr!(cx, if $x > 0 { println!("dummy") });
    

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

    这一切都非常不稳定,并且宏是功能门控的。一个完整的“工作”示例:
    #![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 年 8 月 29 日,the list of quoting macros是:quote_tokens , quote_expr , quote_ty , quote_method , quote_item , quote_pat , quote_arm , quote_stmt . (每个本质上都在 syntax::ast 中创建了类似名称的类型。)

    (请注意:它们目前以一种非常老套的方式实现,只是将它们的论点串起来并重新解析,因此相对容易遇到令人困惑的行为。)

    AST转换

    我们现在知道如何制作独立的 AST block ,但我们如何将它们反馈到主代码中呢?

    好吧,确切的方法取决于您要执行的操作。有a variety of different types of syntax extensions .
  • 如果您只是想扩展为某个适当的表达式(如 println! ),NormalTT是正确的,
  • 如果您想在现有项目的基础上创建新项目,而不进行任何修改,请使用 ItemDecorator (例如 #[deriving] 根据所附加的 implstruct 项目创建一些 enum block )
  • 如果你想拿一个项目并实际改变它,使用 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并用 the right method 将其包裹起来在 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
            }
        }
    }
    

    关于rust - 如何编写将代码注入(inject)函数的自定义属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25561137/

    相关文章:

    rust - 包装 AsyncRead

    generics - 为什么我不能在带有类型参数的特征上添加一揽子暗示?

    iterator - 如何在 String 中使用 contains() 和 retain()?

    rust - Rust 中的可变借用

    rust - 用结构满足 Rust 借用检查器

    rust - 如何使用默认值为 HashMap 编写安全包装

    rust - 如何在不使用不安全的情况下读取自定义文件描述符?

    rust - std中的async/await功能是否可以替代tokio?

    rust - 父<->子关系所有权问题

    rust - 在用户不使用N + 1查询的情况下登录时将数据添加到Diesel结果中