¿Hay alguna manera de contar con macros?
rust rust-macros (3)
Quiero crear una macro que imprima "Hola" un número específico de veces. Se usa como:
many_greetings!(3); // expands to three `println!("Hello");` statements
La forma ingenua de crear esa macro es:
macro_rules! many_greetings {
($times:expr) => {{
println!("Hello");
many_greetings!($times - 1);
}};
(0) => ();
}
Sin embargo, esto no funciona porque el compilador no evalúa las expresiones;
$times - 1
no se calcula, pero se introduce como una nueva expresión en la macro.
Como las otras respuestas ya dijeron:
no, no puedes contar así con macros declarativas (
macro_rules!
)
.
¡Pero puedes implementar el
many_greetings!
ejemplo
como una macro de procedimiento
.
Las macros de procedimiento se estabilizaron hace un tiempo, por lo que la definición funciona en estable.
Sin embargo, todavía no podemos expandir las macros en declaraciones en estable; para eso está la función
#![feature(proc_macro_hygiene)]
.
Esto parece una gran cantidad de código, pero la mayoría del código es solo manejo de errores, ¡así que no es tan complicado!
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()
}
Ejecución de
cargo run --example main
impresiones
cargo run --example main
tres "Hola".
Que yo sepa, no. El lenguaje macro se basa en la coincidencia de patrones y la sustitución de variables, y solo evalúa macros.
Ahora, puedes implementar el conteo con evaluación: simplemente es aburrido ... mira el corralito
macro_rules! many_greetings {
(3) => {{
println!("Hello");
many_greetings!(2);
}};
(2) => {{
println!("Hello");
many_greetings!(1);
}};
(1) => {{
println!("Hello");
many_greetings!(0);
}};
(0) => ();
}
Basado en esto, estoy bastante seguro de que uno podría inventar un conjunto de macros para "contar" e invocar varias operaciones en cada paso (con el conteo).
Si bien el sistema macro ordinario no le permite repetir la expansión de la macro muchas veces, no hay ningún problema con el uso de un bucle for en la macro:
macro_rules! many_greetings {
($times:expr) => {{
for _ in 0..$times {
println!("Hello");
}
}};
}
Si realmente necesita repetir la macro, debe buscar complementos de compilación / macros de procedimiento (que a partir de 1.4 son inestables y un poco más difíciles de escribir).
Editar: Probablemente hay mejores formas de implementar esto, pero he pasado suficiente tiempo en esto por hoy, así que aquí va.
repeat!
, una macro que realmente duplica un bloque de código varias veces:
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);
}
agregado a Cargo.toml
[lib]
name = "repeat"
plugin = true
Tenga en cuenta que si realmente no queremos hacer bucles, sino expandirnos en tiempo de compilación, tenemos que hacer cosas como requerir números literales. Después de todo, no podemos evaluar variables y llamadas a funciones que hacen referencia a otras partes del programa en tiempo de compilación.