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}