Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: Allow tt at format string position #15

Closed
d4h0 opened this issue Sep 24, 2020 · 3 comments · Fixed by #17
Closed

Feature request: Allow tt at format string position #15

d4h0 opened this issue Sep 24, 2020 · 3 comments · Fixed by #17

Comments

@d4h0
Copy link

d4h0 commented Sep 24, 2020

Hi,

At the moment bunt only allows literals at the format string position. std:println, however, allows tt.

Unfortunately, this prevents bunt users from generating format strings via macros.

For example, I tried the following:

macro_rules! println_with_colored_prefix {
    ($color:literal, $id:expr, $fmt_str:literal $(, $arg:expr)*) => {
        bunt::println!(
            concat!("{$", $color, "}[{}][{}]{/$} ", $fmt_str),
            BIN.get().expect("Usage before static variable `BIN` set"),
            $id
            $(, $arg)*
        )
    };
}

macro_rules! say {
    ($($arg:expr),+) => {
        println_with_colored_prefix!("blue", $($arg),+)
    };
}

macro_rules! err {
    ($($arg:expr),+) => {
        println_with_colored_prefix!("red", $($arg),+)
    };
}

fn main() {
    let name = "foo";
    say!(name, "my message.");
    err!(name, "my error.");
}

Because concat!("{$", $color, "}[{}][{}]{/$} ", $fmt_str) isn't a literal, this code doesn't compile.

std allows this, however (Playground:

macro_rules! my_println {
    ($fmt_str:literal $(, $arg:expr)*) => {
        println!(concat!("my prefix: ", $fmt_str), $(, $arg)*)
    };
}

fn main(){
    my_println!("foo")
}

From looking at the source code (see links above), it seems bunt would just have to replace literal in $format_str:literal with tt to support this.

Maybe the tt would have to be converted to a literal somehow, I'm not sure. The macros above are the only macros I have created so far :)

@d4h0
Copy link
Author

d4h0 commented Sep 24, 2020

For now, I use the following workaround:

macro_rules! say{
    ($id:expr, $fmt_str:literal $(, $arg:expr)*) => {{
        bunt::print!(
            "{$blue}[{}][{}]{/$} ",
            BIN.get().expect("Usage before static variable `BIN` set"),
            $id
        );
        bunt::println!($fmt_str $(, $arg)*)
    }};
}

macro_rules! err{
    ($id:expr, $fmt_str:literal $(, $arg:expr)*) => {{
        bunt::print!(
            "{$red}[{}][{}]{/$} ",
            BIN.get().expect("Usage before static variable `BIN` set"),
            $id
        );
        bunt::println!($fmt_str $(, $arg)*)
    }};
}

But I think tt in format string position would be a useful addition if this is easy to implement (probably also a bit more efficient. However, I'm not sure if it would be significant).

@LukasKalbertodt
Copy link
Owner

This is actually something I already thought about, precisely for the same reason: I have another wrapper macro that wants to pass a concat! invocation as format string. Unfortunately, this is tricky.

Maybe the tt would have to be converted to a literal somehow,

Exactly and that's the problem. bunt has to actually parse the string literal, i.e. needs access to the complete string literal data. Macros are lazily expanded by default. That means if you pass concat!("a", "b") to bunt::println, then bunt receives the tokens concat, !, (, "a", ,, "b" and ) and NOT the result of concat. Of course bunt could try to interpret the macro invocation and concat the strings. But that only works for concat! and even that is fishy, if there is another concat macro in scope that works differently.

There is a relevant RFC that's not merged yet: rust-lang/rfcs#2320
This would allow bunt to ask the compiler to expand that concat please. But yeah, that won't be stable anytime soon.

But in the meantime, we could allow multiple string literals in that position. I.e. to allow bunt::println!("foo" "bar" "{}x", 3). bunt could then easily concat all those literals. I think that would already be sufficient for many use cases. What do you think about that solution?

@d4h0
Copy link
Author

d4h0 commented Sep 25, 2020

Macros are lazily expanded by default.

That's unfortunate, eager expansion would make more sense, IMO (but there are most likely good reasons, why it was implemented this way initially).

But in the meantime, we could allow multiple string literals in that position. I.e. to allow bunt::println!("foo" "bar" "{}x", 3). bunt could then easily concat all those literals. I think that would already be sufficient for many use cases. What do you think about that solution?

Yes, that would be a good workaround, that probably covers most use cases.

What do you think about bunt::println!(("foo", "bar", "{}x"), 3)?

With a match arm that matches a tuple in the first position, it should be easy to collect the values and pass them to concat!, and this variant has the advantage of being valid Rust (maybe that is better for syntax highlighting).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants