Compose-idents: a new `macro_rules!` alternative

compose-idents - an advanced token-manipulation macro library and a simpler alternative to macro_rules!.

It could be seen as a merger between paste and duplicate crates. The long-term plan for the project is to turn it into a fully fledged codegen-specific template engine.

Current features:

  • Identifier generation

    • Making new identifiers from parts using built-in DSL , e.g. concat(lower(FOO), _, to_ident("bar"))foo_bar.
    • Turning non-ident-ish tokens into idents: normalize(&'static str)static_str.
  • Code repetition

    Making multiple different variations of the code (e.g., many tests for different types, bindings to C APIs, etc.).

  • String formatting

    It is possible to format string literals, including in doc-attributes: #[doc = "This is a docstring for % my_fn %"].

  • Casing manipulation

    Conversions between camelCase, snake_case, etc. are supported.

  • Unique/temporary identifier generation

    Useful for globals whose names must not collide.

  • IDE friendliness

    A major feature of the library is that it allows to do templating on Rust code without adding any new syntax (things like [< ... >] or $my_var), and therefore avoids confusing the IDE (syntax highlighting, formatting, etc.).

Here is a short example:

use compose_idents::compose_item;

#[compose_item(
    for (suffix, (interjection, noun)) in [
        (BAR, (Hello, "world")),
        (baz, ("Hallo", "WELT")),
    ]

    my_fn = concat(foo, _, lower(suffix)),
    greeting = concat(to_str(interjection), ", ", lower(noun), "!"),
)]
#[doc = "Makes a greeting: % greeting %"]
fn my_fn() -> &'static str {
    greeting
}

assert_eq!(foo_bar(), "Hello, world!");
assert_eq!(foo_baz(), "Hallo, welt!");

Links

2 Likes

I sometimes use a combination of duplicate and paste, so I'm interested in this crate.

I have a question: why did you adopt the format % placeholder % for string formatting?

In my experience, when enclosing a placeholder with characters, using characters with distinct opening and closing characters like {placeholder} or ${placeholder} makes the separation clear and enhances readability. It also has the advantage of supporting nesting more easily.

On the other hand, when using a character without distinct opening and closing (e.g. %), it's sufficient to prepend that character to the placeholder, like $placeholder or %placeholder (though nesting isn't possible).

If the crate does not support nesting, the trailing % in % placeholder % seems redundant to me.

1 Like

There was not much deep thinking on the syntax for this particular feature.

I actually agree that it looks a bit odd and is not something that other template-engines use. And usage of the same character for opening and closing is also odd.

Should I change it?

I want to drive the whole project towards Jinja-like experience so that a user can manipulate Rust code using for-loops, conditionals, and {% ... %} like placeholders (not only in the strings - in the code too). What do you think of that?

Should I change it?

I think it should be changed somehow. If you refer to Jinja, statements would use {% ... %} and expressions would use {{ ... }}, but the problem is that {{ ... }} is also valid Rust code, which introduces ambiguity.

Due to my lack of experience with Jinja, I cannot determine whether using {% %} for both statements and expressions would cause any problems.

I want to drive the whole project towards Jinja-like experience

I think that loops, conditionals, and placeholders outside of strings can enable powerful code generation, but this would involve designing a kind of template language, which can be quite complex.

It would be sufficient to start by just changing the syntax of string formatting.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.