2

I want to write a declarative macros:

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

parameter is printed as "" if parameter is not add "public" or add "private" macros,

my code:

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);
}

but i do not know how do I set a 'private' property for the 3th default parameter,

i tried to use Function-like macro to parse the parameter in log macro, but it is very complex, can you help me

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
bu haha
  • 21
  • 1

1 Answers1

0

Macro design

You can use a recursive macro-by-example to build the format! string argument list by individually processing each argument.

The recursive algorithm for the macro needs to keep of the following:

  • A list of arguments yet to be processed
  • A list of arguments already processed

In each step, the macro removes the first arguments from the list of arguments yet to be processed, processes it, and adds the processed argument to the end of the list of arguments already processed. It then recursively invokes itself with these modified lists. The base case is called when there is only one unprocessed argument remaining, and the recursive case is called when there are more than one unprocessed arguments remaining.

When processing an argument, we have three cases to consider:

  1. private!(expr): Private argument given by expression expr that must be replaced with "<private>"
  2. public!(expr): Public argument given by expression expr that must be printed as-is
  3. expr: Unmarked argument given by expression expr that must be assumed private by default

The macro has one recursive case and one base case for each of the three types of arguments listed above. However, each base and recursive case follows the same pattern - the only difference is how the argument is processed.

Implementation

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)*); );
    };
}

The recursive @rec rules have the following argument specification:

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

The public API rule of the macro simply builds the appropriate argument lists and forwards them to the recursive @rec rules.

Note that the $(,) matcher fragment present in both the base case rules and the @call rule is used to capture trailing commas that are produced by the very first recursive case invocation (when the processed_args list is empty).

Usage

This macro is used exactly as specified in your question:

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

Use private!(arg) to denote private arguments and public!(arg) to denote public arguments (even though the macros do not technically exist). Given that public! and private! don't actually exist as standalone macros, you may choose to opt for a less confusing syntax such as @private arg and @public arg instead.

Playground

EvilTak
  • 7,091
  • 27
  • 36
  • Don't use prefix `@` for macros, it will break internal rules. – Chayim Friedman Feb 20 '23 at 04:09
  • 1
    @ChayimFriedman there is nothing special about `@` in macros, it's just a convention for internal rules but it can be used anywhere if your macro is designed that way. – Jmb Feb 20 '23 at 08:13
  • @Jmb I know, but using it is a recipe for troubles. – Chayim Friedman Feb 20 '23 at 08:39
  • Thank you for providing such a detailed answer, but there's another question, if i want to call the log! macor from another crate, private and public macro undefined will be displayed, @EvilTak – bu haha Apr 05 '23 at 15:10
  • @buhaha In that case, refer to the last paragraph in my answer and use a different notation to represent private and public arguments. – EvilTak Apr 05 '23 at 21:11
  • @EvilTak sorry, i didn't take a closer look at your answer, it can now run normally, thanks : ) – bu haha Apr 08 '23 at 08:07