macros - هل هناك طريقة لحساب مع وحدات الماكرو؟




rust rust-macros (2)

أريد إنشاء ماكرو يقوم بطباعة "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 ، ولكن يتم تغذية كتعبير جديد في الماكرو.


بقدر ما أعرف ، لا. تعتمد لغة الماكرو على مطابقة الأنماط والاستبدال المتغير ، وتقوم بتقييم وحدات الماكرو فقط.

الآن ، يمكنك تنفيذ عملية العد باستخدام التقييم: إنه ممل فقط ... انظر playpen

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

بناءً على ذلك ، أنا متأكد من أنه يمكن لأحد أن يخترع مجموعة من الماكرو "لحساب" واستدعاء عمليات مختلفة في كل خطوة (مع العد).


بينما لا يمكّنك نظام الماكرو العادي من تكرار توسيع الماكرو عدة مرات ، لا توجد مشكلة في استخدام حلقة 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

لاحظ أنه إذا لم نرغب حقًا في تنفيذ حلقات ، ولكننا نتوسع في وقت الترجمة ، يتعين علينا القيام بأشياء مثل طلب الأرقام الحرفية. بعد كل شيء ، لسنا قادرين على تقييم المتغيرات ومكالمات الوظائف التي تشير إلى أجزاء أخرى من البرنامج في وقت الترجمة.