Подтвердить что ты не робот

Есть ли способ подсчета с помощью макросов?

Я хочу создать макрос, который печатает "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 не вычисляется, а вводится как новое выражение в макрос.

4b9b3361

Ответ 1

Хотя обычная система макросов не позволяет многократно повторять раскрытие макроса, нет проблем с использованием цикла for в макросе:

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

Если вам действительно нужно повторить макрос, вы должны изучить процедурные макросы /плагины компилятора (которые по состоянию на 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

Обратите внимание, что если мы действительно не хотим делать циклы, а расширяемся во время компиляции, мы должны делать такие вещи, как требование литеральных чисел. В конце концов, мы не можем оценивать переменные и вызовы функций, которые ссылаются на другие части программы во время компиляции.

Ответ 2

Насколько я знаю, нет. Язык макросов основан на сопоставлении шаблонов и подстановке переменных и оценивает только макросы.

Теперь вы можете выполнить подсчет с оценкой: это просто скучно... см. манекен

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

Исходя из этого, я уверен, что можно было бы придумать набор макросов для "подсчета" и вызвать различные операции на каждом шаге (со счетчиком).

Ответ 3

Как уже говорили другие ответы: нет, вы не можете так считать с декларативными макросами (macro_rules!).


Но вы можете реализовать много many_greetings! пример как процедурный макрос. процедурные макросы были стабилизированы некоторое время назад, поэтому определение работает на стабильной. Тем не менее, мы пока не можем расширить макросы в операторы стабильного - вот для чего #![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 отпечатков трех "Привет".