compose-idents 0.1.0

A Rust macro for generating new identifiers (names of variables, functions, traits, etc) by concatenating one or more arbitrary parts and applying other manipulations.
Documentation
![Build](https://github.com/AndreiPashkin/compose-idents/actions/workflows/build.yml/badge.svg)
[![Crates.io Version](https://img.shields.io/crates/v/compose-idents)](https://crates.io/crates/compose-idents)
[![docs.rs](https://img.shields.io/docsrs/compose-idents)](https://docs.rs/compose-idents)

# compose-idents

A macro for generating new identifiers (names of variables, functions, traits, etc.) by concatenating one or more
arbitrary parts and applying other manipulations.

It was created as an alternative to `macro_rules!` that doesn't allow creating new identifiers from the macro arguments
and [`concat_idents!`][1] macro from the nightly Rust, which is limited in capabilities and has not been stabilized
[since 2015][2].

[1]: https://doc.rust-lang.org/std/macro.concat_idents.html
[2]: https://github.com/rust-lang/rust/issues/29599

## Features

- **Identifier generation**

  Identifiers can be generated via concatenation of multiple parts. Arguments of the outer macro
  definitions and literals are supported for identifier definitions.
- **Functions**

  Functions can be applied when defining new identifiers for changing case and style.
- **String formatting**

  Strings can be formatted with `%alias%` syntax, which is useful for generating doc-attributes.
- **Unique identifier generation**

  Unique identifiers can be deterministically generated by using `hash()` function which is uniquely
  seeded each invocation of the macro. This might be useful for generating unique global variables.

## Usage

This section contains various usage examples. For even more usage examples look into `tests/` directory
of the repository.

### Quick start

`compose_idents!` works by accepting definitions of aliases and a code block where aliases
could be used as normal identifiers. When the macro is expanded, the aliases are replaced with their
definitions:
```rust
use compose_idents::compose_idents;

// We generate separate const-functions for each type as a workaround
// since Rust doesn't allow us to use `core::ops::Add` in `const fn`.

macro_rules! gen_const_add {
    ($T:ty) => {
        compose_idents!(
            Type = [upper($T)],   // Alias for the type - make it uppercase in addition
            add_fn = [add_, $T],  // Alias for the function name
            {
                // Strings (including in doc-attributes) can be formatted with %alias% syntax.
                #[doc = "Adds two arguments of type `%Type%` at compile time."]
                pub const fn add_fn(a: $T, b: $T) -> $T {  // Aliases are used as normal identifiers
                    a + b
                }
            }
        );
    };
}

gen_const_add!(u32);  // Expands into `add_u32()` function.
gen_const_add!(u64);  // Expands into `add_u64()` function.

assert_eq!(add_u32(2_u32, 2_u32), 4_u32);
assert_eq!(add_u64(2_u64, 2_u64), 4_u64);
```

### Generating tests for different types

A practical example for how to auto-generate names for macro-generated tests for different data types:
```rust
use compose_idents::compose_idents;

pub trait Frobnicate {
  type Output;

  fn frobnicate(&self, value: Self) -> Self::Output;
}

impl Frobnicate for u32 {
  type Output = u32;

  fn frobnicate(&self, value: Self) -> Self::Output {
    self + value
  }
}

impl Frobnicate for &'static str {
  type Output = String;

  fn frobnicate(&self, value: Self) -> Self::Output {
    format!("{}_{}", self, value)
  }
}

macro_rules! generate_frobnicate_test {
    ($type:ty, $initial:expr, $input:expr, $expected:expr) => {
        // Notice - we are using normalize() to make `&'static str` fit for
        // being part of the test function's identifier.
        compose_idents!(test_fn = [test_frobnicate_, normalize($type)], {
          fn test_fn() {
            let actual = ($initial as $type).frobnicate($input);
            let expected = $expected;

            assert_eq!(actual, expected);
          }
        });
    };
}

// Generates tests for u32 and &'static str types
generate_frobnicate_test!(u32, 0, 42_u32, 42_u32);
generate_frobnicate_test!(&'static str, "foo", "bar", "foo_bar".to_string());

test_frobnicate_u32();
// Notice - "&'static str" has been turned into just "static_str"
test_frobnicate_static_str();
```

### Reference example

This example includes all the features of the macro:
```rust
use compose_idents::compose_idents;

compose_idents!(
    // Literal strings are accepted as arguments and their content is parsed.
    my_fn_1 = [foo, _, "baz"],
    // So as literal integers and underscores (or free-form token sequences).
    my_fn_2 = [spam, _, 1, _, eggs],
    // Functions can be applied to the arguments.
    my_const = [upper(foo), _, lower(BAR)],
    // Function calls can be arbitrarily nested and combined.
    my_static = [upper(lower(BAR))],
    MY_SNAKE_CASE_STATIC = [snake_case(snakeCase)],
    MY_CAMEL_CASE_STATIC = [camel_case(camel_case)],
    MY_PASCAL_CASE_STATIC = [pascal_case(pascal_case)],
    // normalize() allows to turn an arbitrary sequence of tokens into a valid identifier.
    MY_NORMALIZED_ALIAS = [my, _, normalize(&'static str)],
    // This function is useful to create identifiers that are unique across multiple macro invocations.
    // `hash(0b11001010010111)` will generate the same value even if called twice in the same macro call,
    // but will be different in different macro calls.
    MY_UNIQUE_STATIC = [hash(0b11001010010111)],
    MY_FORMATTED_STR = [FOO, _, BAR],
    MY_REUSED_ALIAS = [REUSED, _, FOO, _, my_static],
    {
        fn my_fn_1() -> u32 {
            123
        }

        // You can use %alias% syntax to replace aliases with their replacements
        // in string literals and doc-attributes.
        #[doc = "This is a docstring for %my_fn_2%"]
        fn my_fn_2() -> u32 {
            321
        }

        const my_const: u32 = 42;
        static my_static: u32 = 42;
        static MY_SNAKE_CASE_STATIC: u32 = 42;
        static MY_CAMEL_CASE_STATIC: u32 = 42;
        static MY_PASCAL_CASE_STATIC: u32 = 42;
        static MY_NORMALIZED_ALIAS: &'static str = "This alias is made from a normalized argument";
        static MY_UNIQUE_STATIC: u32 = 42;
        // This is an example of string literal formatting.
        static MY_FORMATTED_STR: &str = "This is %MY_FORMATTED_STR%";
        static MY_REUSED_ALIAS: u32 = 42;
    }
);

// It's possible to use arguments of declarative macros as parts of the identifiers.
macro_rules! outer_macro {
    ($name:tt) => {
        compose_idents!(my_nested_fn = [nested, _, $name], {
            fn my_nested_fn() -> u32 {
                42
            }
        });
    };
}

outer_macro!(foo);

macro_rules! global_var_macro {
    () => {
        // `my_static` is going to be unique in each invocation of `global_var_macro!()`.
        // But within the same invocation `hash(1)` will yield the same result.
        compose_idents!(my_static = [foo, _, hash(1)], {
            static my_static: u32 = 42;
        });
    };
}

global_var_macro!();
global_var_macro!();

assert_eq!(foo_baz(), 123);
assert_eq!(spam_1_eggs(), 321);
assert_eq!(nested_foo(), 42);
assert_eq!(FOO_bar, 42);
assert_eq!(BAR, 42);
assert_eq!(snake_case, 42);
assert_eq!(camelCase, 42);
assert_eq!(PascalCase, 42);
assert_eq!(
    my_static_str,
    "This alias is made from a normalized argument"
);
assert_eq!(FOO_BAR, "This is FOO_BAR");
assert_eq!(REUSED_FOO_BAR, 42);
```

## Functions

| Function            | Description                                                               |
|---------------------|---------------------------------------------------------------------------|
| `upper(arg)`        | Converts the `arg` to upper case.                                         |
| `lower(arg)`        | Converts the `arg` to lower case.                                         |
| `snake_case(arg)`   | Converts the `arg` to snake_case.                                         |
| `camel_case(arg)`   | Converts the `arg` to camelCase.                                          |
| `pascal_case(arg)`  | Converts the `arg` to PascalCase.                                         |
| `normalize(tokens)` | Transforms a random sequence of tokens `tokens` into a valid identifier. |
| `hash(arg)`         | Hashes the `arg` deterministically within a single macro invocation.      |


## Alternatives

There some other tools and projects dedicated to identifier manipulation:

- A macro from Nightly Rust that allows to concatenate identifiers. It is limited in functionality and nowhere near
  to be stabilized:
  <https://doc.rust-lang.org/std/macro.concat_idents.html>
- A very similar macro that doesn't support multiple aliases and is not maintained:
  <https://github.com/DzenanJupic/concat-idents>
- A macro that allows to define and refer to unique temporary variables:
  <https://crates.io/crates/templest>

## Development

The following standards are followed to maintain the project:
- <https://www.conventionalcommits.org/en/v1.0.0/>
- <https://semver.org/>
- <https://keepachangelog.com/en/1.1.0/>
- <https://adr.github.io/madr/>