有没有办法用宏来计数?

Posted

技术标签:

【中文标题】有没有办法用宏来计数?【英文标题】:Is there a way to count with macros? 【发布时间】:2016-02-18 12:29:27 【问题描述】:

我想创建一个打印“Hello”指定次数的宏。它的用法如下:

many_greetings!(3);  // expands to three `println!("Hello");` statements

创建该宏的简单方法是:

macro_rules! many_greetings 
    ($times:expr) => 
        println!("Hello");
        many_greetings!($times - 1);
    ;
    (0) => ();

但是,这不起作用,因为编译器不计算表达式; $times - 1 不是计算出来的,而是作为新表达式输入到宏中。

【问题讨论】:

你能得到的最接近的是递归,利用事实匹配被重新评估:is.gd/3QfTr9 虽然它很丑。 如果您能说明为什么在宏中使用 for 循环不是一个好的解决方案(因为这似乎是一个显而易见的答案),那就太好了。 @ideasman42 这是一个人为的例子。我对这个特定的用例并不感兴趣;问题是关于“用宏计数”的一般情况。 好的,在这种情况下,很难知道什么是好的答案,因为在示例中你很明显你会使用迭代。请注意,这个问题的标题与另一个问题非常相似:***.com/questions/30152800(我在寻找什么以及为什么我偶然发现了这个页面)。 AFAICS 是关于宏扩展,而不是计数。 @ideasman42 为非Copy 元素(例如,String)或任何类型的 n > 32 个元素构建一个数组初始化器是一个用例,其中for 是不合适。例如:static FOO: [AtomicUsize; 100] = arr_init![AtomicUsize::new(0); 100];arr_init! 宏应在编译时发出 [AtomicUsize::new(0), AtomicUsize::new(0), ... , AtomicUsize::new(0) ](n = 100 个元素)。 【参考方案1】:

对于那些正在寻找方法的人,还有seq_macro crate。

它相当容易使用,并且与稳定的 Rust 一起开箱即用。

use seq_macro::seq;

macro_rules! many_greetings 
    ($times:literal) => 
        seq! N in 0..$times 
            println!("Hello");
        
    ;


fn main() 
    many_greetings!(3);
    many_greetings!(12);

【讨论】:

【参考方案2】:

正如其他答案已经说过的那样:不,你不能像这样使用声明性宏 (macro_rules!)


但您可以将many_greetings! 示例实现为过程宏。程序宏在前一段时间是稳定的,因此该定义适用于稳定。但是,我们还不能将宏扩展为 stable 上的语句——这就是 #![feature(proc_macro_hygiene)] 的用途。

这看起来代码很多,但大部分代码只是错误处理,所以没那么复杂!

examples/main.rs

#![feature(proc_macro_hygiene)]

use count_proc_macro::many_greetings;

fn main() 
    many_greetings!(3);

Cargo.toml

[package]
name = "count-proc-macro"
version = "0.1.0"
authors = ["me"]
edition = "2018"

[lib]
proc-macro = true

[dependencies]
quote = "0.6"

src/lib.rs

extern crate proc_macro;

use std::iter;
use proc_macro::Span, TokenStream, TokenTree;
use quote::quote, quote_spanned;


/// Expands into multiple `println!("Hello");` statements. E.g.
/// `many_greetings!(3);` will expand into three `println`s.
#[proc_macro]
pub fn many_greetings(input: TokenStream) -> TokenStream 
    let tokens = input.into_iter().collect::<Vec<_>>();

    // Make sure at least one token is provided.
    if tokens.is_empty() 
        return err(Span::call_site(), "expected integer, found no input");
    

    // Make sure we don't have too many tokens.
    if tokens.len() > 1 
        return err(tokens[1].span(), "unexpected second token");
    

    // Get the number from our token.
    let count = match &tokens[0] 
        TokenTree::Literal(lit) => 
            // Unfortunately, `Literal` doesn't have nice methods right now, so
            // the easiest way for us to get an integer out of it is to convert
            // it into string and parse it again.
            if let Ok(count) = lit.to_string().parse::<usize>() 
                count
             else 
                let msg = format!("expected unsigned integer, found ``", lit);
                return err(lit.span(), msg);
            
        
        other => 
            let msg = format!("expected integer literal, found ``", other);
            return err(other.span(), msg);
        
    ;

    // Return multiple `println` statements.
    iter::repeat(quote!  println!("Hello"); )
        .map(TokenStream::from)
        .take(count)
        .collect()


/// Report an error with the given `span` and message.
fn err(span: Span, msg: impl Into<String>) -> TokenStream 
    let msg = msg.into();
    quote_spanned!(span.into()=> 
        compile_error!(#msg);
    ).into()

运行cargo run --example main 会打印三个“Hello”。

【讨论】:

【参考方案3】:

虽然普通的宏系统不能让你多次重复宏展开,但是在宏中使用for循环是没有问题的:

macro_rules! many_greetings 
    ($times:expr) => 
        for _ in 0..$times 
            println!("Hello");
        
    ;

如果你真的需要重复宏,你必须查看过程宏/compiler plugins(从 1.4 开始不稳定,并且有点难以编写)。

编辑:可能有更好的方法来实现这一点,但我今天在这方面花了足够长的时间,所以就这样吧。 repeat!,一个实际上多次复制一段代码的宏:

main.rs

#![feature(plugin)]
#![plugin(repeat)]

fn main() 
    let mut n = 0;
    repeat! 4 
        println!("hello ", n);
        n += 1;
    ;

lib.rs

#![feature(plugin_registrar, rustc_private)]

extern crate syntax;
extern crate rustc;

use syntax::codemap::Span;
use syntax::ast::TokenTree;
use syntax::ext::base::ExtCtxt, MacResult, MacEager, DummyResult;
use rustc::plugin::Registry;
use syntax::util::small_vector::SmallVector;
use syntax::ast::Lit_;
use std::error::Error;

fn expand_repeat(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> 
    let mut parser = cx.new_parser_from_tts(tts);
    let times = match parser.parse_lit() 
        Ok(lit) => match lit.node 
            Lit_::LitInt(n, _) => n,
            _ => 
                cx.span_err(lit.span, "Expected literal integer");
                return DummyResult::any(sp);
            
        ,
        Err(e) => 
            cx.span_err(sp, e.description());
            return DummyResult::any(sp);
        
    ;
    let res = parser.parse_block();

    match res 
        Ok(block) => 
            let mut stmts = SmallVector::many(block.stmts.clone());
            for _ in 1..times 
                let rep_stmts = SmallVector::many(block.stmts.clone());
                stmts.push_all(rep_stmts);
            
            MacEager::stmts(stmts)
        
        Err(e) => 
            cx.span_err(sp, e.description());
            DummyResult::any(sp)
        
    


#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) 
    reg.register_macro("repeat", expand_repeat);

添加到 Cargo.toml

[lib]
name = "repeat"
plugin = true

请注意,如果我们真的不想进行循环,而是在编译时进行扩展,我们必须做一些事情,比如要求文字数字。毕竟,我们无法在编译时评估引用程序其他部分的变量和函数调用。

【讨论】:

当然,循环总是有效的,但它不需要是宏。无论如何,我的例子很愚蠢。您能否发布一些代码,如何使用编译器插件来做到这一点?如果你有时间……那就太棒了:) 我添加了一个可以多次重复任意代码的过程宏示例。我想不出使用这个确切宏的任何充分理由,但正如您所见,这是可能的。 这个答案还有效吗? Rust 的递归宏能处理这个吗?【参考方案4】:

据我所知,没有。宏语言基于模式匹配和变量替换,并且只计算宏。

现在,您可以通过评估来实现计数:这很无聊...见the playpen

macro_rules! many_greetings 
    (3) => 
        println!("Hello");
        many_greetings!(2);
    ;
    (2) => 
        println!("Hello");
        many_greetings!(1);
    ;
    (1) => 
        println!("Hello");
        many_greetings!(0);
    ;
    (0) => ();

基于此,我很确定可以发明一组宏来“计数”并在每一步调用各种操作(使用计数)。

【讨论】:

以上是关于有没有办法用宏来计数?的主要内容,如果未能解决你的问题,请参考以下文章

如何把Excel中选定的一列数据复制粘贴到另一列相同的列中,要用宏来自动复制粘贴。

excel中如何进行语句查询

Source Insight中的多行注释

template Instantiate的一个坑

DELPHI中IDE宏录制小用

FreeRTOS系列第13篇---FreeRTOS内核控制