如何为rust声明性宏中的默认参数设置'private'属性

ikfrs5lh  于 2023-02-23  发布在  其他
关注(0)|答案(1)|浏览(286)

我想写一个声明性宏:

log!("{} {} {}", private!(1), public!(2), 3);
out : <private>2<private>

如果参数不是add "public"或add "private"宏,则将参数打印为"",
我的代码:

macro_rules! log {
    ($($arg:tt)*) => {
        let log_str = format!($($arg)*);
        println!("{}", log_str);    
    };
}

macro_rules! private {
    ($arg:tt) => {
        "<private>"
    };
}

macro_rules! public {
    ($arg:tt) => {
        $arg
    };
}

fn main() {
    log!("{} {} {}", private!(1), public!(2), 3);
}

但是我不知道如何为第三默认参数设置"私有"属性,
我尝试使用类似函数的宏来解析日志宏中的参数,但它非常复杂,您能帮助我吗

hmae6n7t

hmae6n7t1#

宏观设计

您可以使用递归宏by-example,通过单独处理每个参数来构建format!字符串参数列表。
宏的递归算法需要保留以下内容:

  • 尚未处理的参数列表
  • 已处理的参数列表

在每一步中,宏从尚未处理的参数列表中删除第一个参数,处理它,并将已处理的参数添加到已处理的参数列表的末尾。然后,它使用这些修改后的列表递归地调用自身。当只剩下一个未处理的参数时,调用基本情况;当剩下多个未处理的参数时,调用递归情况。
在处理一个论点时,我们需要考虑三种情况:

  1. private!(expr):表达式expr给定的私有参数,必须替换为"<private>"
  2. public!(expr):表达式expr给定的公共参数,必须按原样打印
  3. expr:表达式expr给定的未标记参数,默认情况下必须假定为私有
    对于上面列出的三种类型的参数,宏都有一个递归大小写和一个基本大小写。但是,每个基本大小写和递归大小写遵循相同的模式-唯一的区别是如何处理参数。

实施

macro_rules! log {
    // Log message with processed arguments
    (@call $fmt:literal, $(,)? $($processed_args:expr),*) => {
        let log_str = format!($fmt, $($processed_args),*);
        println!("{}", log_str);
    };

    // Base case - single unpexplictly private argument
    (@rec $fmt:literal; (private!($arg:expr)); $(,)? $($processed_args:expr),*) => {
        log!(@call $fmt, $($processed_args),*, "<private>");
    };

    // Base case - single explictly public argument
    (@rec $fmt:literal; (public!($arg:expr)); $(,)? $($processed_args:expr),*) => {
        log!(@call $fmt, $($processed_args),*, $arg);
    };

    // Base case - single unmarked argument
    (@rec $fmt:literal; ($arg:expr); $(,)? $($processed_args:expr),*) => {
        // Assume private
        log!(@call $fmt, $($processed_args),*, "<private>");
    };

    // Recursive case - process one explictly private argument
    (@rec $fmt:literal; (private!($arg:expr), $($unprocessed_args:tt)*); $($processed_args:tt)*) => {
        log!(@rec $fmt; ($($unprocessed_args)*); $($processed_args)*, "<private>");
    };

    // Recursive case - process one explictly public argument
    (@rec $fmt:literal; (public!($arg:expr), $($unprocessed_args:tt)*); $($processed_args:tt)*) => {
        log!(@rec $fmt; ($($unprocessed_args)*); $($processed_args)*, $arg);
    };

    // Recursive case - process one unmarked argument
    (@rec $fmt:literal; ($arg:expr, $($unprocessed_args:tt)*); $($processed_args:tt)*) => {
        log!(@rec $fmt; ($($unprocessed_args)*); $($processed_args)*, "<private>");
    };

    // Public API
    ($fmt:literal, $($processed_args:tt)*) => {
        log!(@rec $fmt; ($($processed_args)*); );
    };
}

递归@rec规则具有以下参数规范:

log!(@rec <format-string>; (<comma-separated-unprocessed-args>); <comma-separated-processed-args>);

宏的公共API规则只是构建适当的参数列表,并将它们转发给递归@rec规则。
注意,基本case规则和@call规则中的$(,)匹配器片段用于捕获第一次递归case调用(当processed_args列表为空时)产生的尾随逗号。

用法

此宏完全按照您的问题中指定的方式使用:

log!("{} {} {}", private!(1), public!(2), 3);

使用private!(arg)来表示私有参数,使用public!(arg)来表示公共参数(即使这些宏在技术上并不存在)。由于public!private!实际上并不作为独立宏存在,因此您可以选择使用不那么容易混淆的语法,例如@private arg@public arg
Playground

相关问题