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

时间:2014-08-29 03:09:50

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

我如何实现这一目标?

1 个答案:

答案 0 :(得分:27)

这里有两个任务:

  • 创建您想要插入的AST
  • 转换某些功能的AST(例如插入另一部分)

注意:

  • 当我说&#34; item&#34;在这个答案中,我特别指的是the item AST node,例如fnstructimpl
  • 在使用宏执行任何操作时,rustc --pretty expanded foo.rs是您最好的朋友(最适合最小的示例,例如避免#[deriving]println!,除非您尝试专门调试这些)。

AST创建

从头开始创建AST块的3种基本方法:

  • 手动写出结构&amp;枚举,
  • 使用methods of AstBuilder缩写,
  • 使用引文来完全避免这种情况。

在这种情况下,我们可以使用引用,所以我不会浪费时间在其他人身上。 quote宏采用ExtCtxt(&#34;扩展上下文&#34;)和表达式或项目等,并创建表示该项的AST值,例如

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

创建一个值为ExprBinary的{​​{3}},其中包含两个ExprLit s(适用于12文字。)

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

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

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

这一切都非常不稳定,并且宏是特征门控的。一个完整的&#34;工作&#34;例如:

#![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,Expr_为:quote_tokensquote_exprquote_tyquote_methodquote_item,{{ 1}},quote_patquote_arm。 (每个基本上都在quote_stmt中创建类似命名的类型。)

(请注意:目前,他们以非常黑客的方式实施,只是将他们的论点和解析进行字符串化,因此相对容易遇到令人困惑的行为。)

AST转换

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

嗯,确切的方法取决于你想要做什么。那里有the list of quoting macros

  • 如果您只想扩展到某个表达式(例如syntax::ast),println!是正确的,
  • 如果您想基于现有项目创建新项目而不修改任何内容,请使用a variety of different types of syntax extensions(例如NormalTT根据#[deriving]创建一些implstruct附加到的项目
  • 如果您想拍摄物品并进行实际更改,请使用ItemDecorator

因此,在这种情况下,我们需要enum,以便我们可以将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上使用ItemModifier将其重新包装起来。总计:

AstBuilder