compile_symbol_macros/
lib.rs

1//! This crate houses the [`s!`] macro, used to create `Symbol`s at compile-time from a
2//! provided ident.
3
4use proc_macro::{TokenStream, TokenTree};
5
6fn bad_symbol_error() -> TokenStream {
7    return "compile_error!(\"s!() takes a single ident, constrained to a maximum of 25 characters long using an \
8            alphabet of lowercase a-z as well as `_`. No other characters are allowed, and you must specify at \
9            least one character.\")".parse().unwrap();
10}
11
12/// Generates a `Symbol` at compile-time from the provided ident.
13///
14/// Your ident should be constrained to a minimum of one character and a maximum of 25
15/// characters long, and may only use an alphabet of lowercase a-z as well as `_`. No other
16/// characters are allowed, and specifying other characters or breaking any of these rules will
17/// result in a compile error.
18///
19/// At runtime, each unique`Symbol` is represented internally as a unique [`u128`] that encodes
20/// the bits of the symbol (5 bits per character), and enough information is preserved in this
21/// representation that the [`u128`] can be converted back into a [`String`] during at runtime,
22/// if desired.
23///
24/// These are great for scenarios where you need a human-readable globally unique identifier.
25/// The `Symbol` type is intended to be similar to the `Symbol` type in the Crystal programming
26/// language, with the additional capability that `Symbol`s can be created and runtime in
27/// addition to compile-time.
28#[proc_macro]
29pub fn s(tokens: TokenStream) -> TokenStream {
30    let mut backing: u128 = 0;
31    let mut iter = tokens.into_iter();
32    let Some(TokenTree::Ident(ident)) = iter.next() else { return bad_symbol_error() };
33    let ident = ident.to_string();
34    if ident.is_empty() || ident.len() > 25 {
35        return bad_symbol_error();
36    }
37    for c in ident.chars() {
38        let val = match c {
39            '-' => 0, // not used
40            'a' => 1,
41            'b' => 2,
42            'c' => 3,
43            'd' => 4,
44            'e' => 5,
45            'f' => 6,
46            'g' => 7,
47            'h' => 8,
48            'i' => 9,
49            'j' => 10,
50            'k' => 11,
51            'l' => 12,
52            'm' => 13,
53            'n' => 14,
54            'o' => 15,
55            'p' => 16,
56            'q' => 17,
57            'r' => 18,
58            's' => 19,
59            't' => 20,
60            'u' => 21,
61            'v' => 22,
62            'w' => 23,
63            'x' => 24,
64            'y' => 25,
65            'z' => 26,
66            '_' => 27,
67            _ => return bad_symbol_error(),
68        };
69        backing *= 28;
70        backing += val;
71    }
72    format!("::compile_symbol::Symbol::from_raw({backing}u128)")
73        .as_str()
74        .parse()
75        .unwrap()
76}