我们可以在过程宏属性中获取调用者的源代码位置吗? [英] Can we get the source code location of the caller in a procedural macro attribute?

查看:50
本文介绍了我们可以在过程宏属性中获取调用者的源代码位置吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要获取每个方法的调用者的源位置.我正在尝试创建一个 proc_macro_attribute 来捕获位置并打印它.

I have requirement to get the source location of the caller of every method. I am trying to create a proc_macro_attribute to capture the location and print it.

#[proc_macro_attribute]
pub fn get_location(attr: TokenStream, item: TokenStream) -> TokenStream {
    // Get and print file!(), line!() of source
    // Should print line no. 11
    item
}

#[get_location]
fn add(x: u32, y: u32) -> u32 {
    x + y
}

fn main() {
    add(1, 5); // Line No. 11
}

推荐答案

可以使用现成的解决方案(请参阅 @timotree 的评论).如果你想自己做这件事,有更多的灵活性或学习,你可以编写一个程序宏来解析回溯(从被调用的函数内部获得)并打印你需要的信息.这是 lib.rs 中的一个过程宏:

Ready to use solutions are available (see @timotree 's comment). If you want to do this yourself, have more flexibility or learn, you can write a procedural macro that will parse a backtrace (obtained from inside the function that is called) and print the information that you need. Here is a procedural macro inside a lib.rs:

extern crate proc_macro;
use proc_macro::{TokenStream, TokenTree};

#[proc_macro_attribute]
pub fn get_location(_attr: TokenStream, item: TokenStream) -> TokenStream {

    // prefix code to be added to the function's body
    let mut prefix: TokenStream = "
        // find earliest symbol in source file using backtrace
        let ps = Backtrace::new().frames().iter()
            .flat_map(BacktraceFrame::symbols)
            .skip_while(|s| s.filename()
                .map(|p|!p.ends_with(file!())).unwrap_or(true))
            .nth(1 as usize).unwrap();

        println!(\"Called from {:?} at line {:?}\",
            ps.filename().unwrap(), ps.lineno().unwrap());
    ".parse().unwrap(); // parse string into TokenStream

    item.into_iter().map(|tt| { // edit input TokenStream
        match tt { 
            TokenTree::Group(ref g) // match the function's body
                if g.delimiter() == proc_macro::Delimiter::Brace => { 

                    prefix.extend(g.stream()); // add parsed string

                    TokenTree::Group(proc_macro::Group::new(
                        proc_macro::Delimiter::Brace, prefix.clone()))
            },
            other => other, // else just forward TokenTree
        }
    }).collect()
} 

分析回溯以找到源文件中最早的符号(使用另一个宏 file!() 检索).我们需要添加到函数中的代码定义在一个字符串中,然后将其解析为 TokenStream 并添加到函数体的开头.我们本可以在最后添加这个逻辑,但是返回一个没有分号的值将不再起作用.然后,您可以在 main.rs 中使用过程宏,如下所示:

The backtrace is parsed to find the earliest symbol inside the source file (retrieved using file!(), another macro). The code we need to add to the function is defined in a string, that is then parsed as a TokenStream and added at the beginning of the function's body. We could have added this logic at the end, but then returning a value without a semicolon wouldn't work anymore. You can then use the procedural macro in your main.rs as follow:

extern crate backtrace;
use backtrace::{Backtrace, BacktraceFrame};
use mylib::get_location;

#[get_location]
fn add(x: u32, y: u32) -> u32 { x + y }

fn main() { 
    add(1, 41);
    add(41, 1);
}

输出为:

> Called from "src/main.rs" at line 10
> Called from "src/main.rs" at line 11

不要忘记通过将这两行添加到您的 Cargo.toml 来指定您的 lib crate 提供程序宏:

Don't forget to specify that your lib crate is providing procedural macros by adding these two lines to your Cargo.toml:

[lib]
proc-macro = true

这篇关于我们可以在过程宏属性中获取调用者的源代码位置吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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