如何编写将代码注入函数的自定义属性 [英] How to write a custom attribute that injects code into a function

查看:24
本文介绍了如何编写将代码注入函数的自定义属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I've gotten as far as having the custom attribute invoked:

#[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!( ... ) 来做到这一点的例子,但我并不真正理解它们.

...and I've seen a few examples around of things that use quote_expr!( ... ) to do this, but I don't really understand them.

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

Let's say I want to add this statement (or is it expression?) to the top of any function tagged #[dummy]:

println!("dummy");

我如何做到这一点?

推荐答案

这里有两个任务:

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

注意事项:

  • 当我在这个答案中说item"时,我特意指的是 item AST 节点,例如fnstructimpl.
  • 当用宏做任何事情时,rustc --pretty Expanded foo.rs 是你最好的朋友(在最小的例子上效果最好,例如避免 #[deriving]println!,除非你想专门调试那些).
  • when I say "item" in this answer, I specifically meant the item AST node, e.g. fn, struct, impl.
  • when doing anything with macros, rustc --pretty expanded foo.rs is your best friend (works best on smallest examples, e.g. avoiding #[deriving] and println!, unless you're trying to debug those specifically).

有 3 种基本方法可以从头开始创建 AST 块:

There's 3 basic ways to create chunks of AST from scratch:

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

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

In this case, we can use quoting, so I won't waste time on the others. The quote macros take an ExtCtxt ("extension context") and an expression or item etc. and create an AST value that represents that item, e.g.

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

创建一个 Expr_ExprBinary,包含两个 ExprLit(用于 12 文字).

creates an Expr_ with value ExprBinary, that contains two ExprLits (for the 1 and 2 literals).

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

Hence, to create the desired expression, quote_expr!(cx, println!("dummy")) should work. Quotation is more powerful than just this: you can use $ it to splice a variable storing AST into an expression, e.g., if we have the x as above, then

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

将创建一个 AST 表示 如果 1 + 2 >0 { println!("dummy") }.

will create an AST reprsenting if 1 + 2 > 0 { println!("dummy") }.

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

This is all very unstable, and the macros are feature gated. A full "working" example:

#![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 日,.rs#L366-L3引用宏的列表是:quote_tokensquote_exprquote_tyquote_methodquote_itemquote_patquote_armquote_stmt.(每个本质上都在 syntax::ast 中创建了名称相似的类型.)

As of 2014-08-29, the list of quoting macros is: quote_tokens, quote_expr, quote_ty, quote_method, quote_item, quote_pat, quote_arm, quote_stmt. (Each essentially creates the similarly-named type in syntax::ast.)

(请注意:它们目前以一种非常hacky的方式实现,只是将它们的参数字符串化并重新解析,因此相对容易遇到令人困惑的行为.)

(Be warned: they are implemented in a very hacky way at the moment, by just stringifying their argument and reparsing, so it's relatively easy to encounter confusing behaviour.)

我们现在知道如何制作隔离的 AST 块,但是我们如何将它们反馈到主代码中呢?

We now know how to make isolated chunks of AST, but how can we feed them back into the main code?

嗯,确切的方法取决于您要尝试做什么.有各种不同类型的语法扩展.

Well, the exact method depends on what you are trying to do. There's a variety of different types of syntax extensions.

  • 如果你只是想扩展到某个地方的表达式(比如 println!),NormalTT 是正确的,
  • 如果您想基于现有项目创建新项目,而无需修改任何内容,请使用 ItemDecorator(例如 #[deriving] 基于 struct 创建一些 impl 块和 enum 附加到的项目)
  • 如果您想获取一个项目并实际更改它,请使用ItemModifier
  • If you just wanted to expand to some expression in place (like println!), NormalTT is correct,
  • if you want to create new items based on an existing one, without modifying anything, use ItemDecorator (e.g. #[deriving] creates some impl blocks based on the struct and enum items to which it is attached)
  • if you want to take an item and actually change it, use ItemModifier

因此,在这种情况下,我们需要一个 ItemModifier,以便我们可以将 #[dummy] fn foo() { ... } 更改为 #[dummy] fn foo() { println!("dummy");.... }.让我们声明一个具有正确签名的函数:

Thus, in this case, we want an ItemModifier, so that we can change #[dummy] fn foo() { ... } into #[dummy] fn foo() { println!("dummy"); .... }. Let's declare a function with the right signature:

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(); 中的内容.酒吧();... to println!("dummy");{ foo();酒吧();... } 只需创建两个新表达式即可.

We've got the boilerplate set-up, we just need to write the implementation. There's two approaches. We could just add the println! to the start of the function's contents, or we could change the contents from foo(); bar(); ... to println!("dummy"); { foo(); bar(); ... } by just creating two new expressions.

如您所见,ItemFn 可以与

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

其中 block 是实际内容.我上面提到的第二种方法最简单,只需

where block is the actual contents. The second approach I mention above is easiest, just

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

然后为了保留旧信息,我们将构造一个新的 ItemFn 并用 正确的方法.总计:

and then to preserve the old information, we'll construct a new ItemFn and wrap it back up with the right method on AstBuilder. In total:

#![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
        }
    }
}

这篇关于如何编写将代码注入函数的自定义属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆