1#![doc = include_str!("../README.md")]
2
3mod core;
4mod eval;
5mod funcs;
6
7use crate::core::{Expr, State};
8use crate::eval::Eval;
9use proc_macro::TokenStream;
10use quote::{format_ident, quote};
11use std::collections::HashMap;
12use syn::{
13 bracketed,
14 parse::{Parse, ParseStream},
15 parse_macro_input,
16 visit_mut::VisitMut,
17 Block, Ident, LitStr, Token,
18};
19
20struct IdentSpecItem {
21 alias: Ident,
22 exprs: Vec<Expr>,
23}
24
25impl IdentSpecItem {
26 fn replacement(&self, state: &State) -> Ident {
27 let ident = self.exprs.iter().fold("".to_string(), |acc, item| {
28 format!("{}{}", acc, item.eval(state))
29 });
30 format_ident!("{}", ident)
31 }
32}
33
34struct IdentSpec {
35 items: Vec<IdentSpecItem>,
36}
37
38impl Parse for IdentSpec {
39 fn parse(input: ParseStream) -> syn::Result<Self> {
40 let mut items = Vec::new();
41 loop {
42 let alias: Ident = input.parse()?;
43 input.parse::<Token![=]>()?;
44 let content;
45 bracketed!(content in input);
46 let mut exprs = Vec::new();
47 loop {
48 match content.parse::<Expr>() {
49 Ok(expr) => exprs.push(expr),
50 Err(err) => return Err(err),
51 }
52 if content.is_empty() {
53 break;
54 }
55 content.parse::<Token![,]>()?;
56 }
57 items.push(IdentSpecItem { alias, exprs });
58 input.parse::<Token![;]>()?;
59 if !input.peek(Ident) {
60 break;
61 }
62 }
63 Ok(IdentSpec { items })
64 }
65}
66
67impl IdentSpec {
68 fn replacements(&self, state: &State) -> HashMap<Ident, Ident> {
69 self.items
70 .iter()
71 .map(|item| (item.alias.clone(), item.replacement(state)))
72 .collect()
73 }
74}
75
76struct ComposeIdentsArgs {
77 spec: IdentSpec,
78 block: Block,
79}
80
81impl Parse for ComposeIdentsArgs {
82 fn parse(input: ParseStream) -> syn::Result<Self> {
83 let spec: IdentSpec = input.parse()?;
84 let block: Block = input.parse()?;
85 let _ = input.parse::<Token![;]>();
86 Ok(ComposeIdentsArgs { spec, block })
87 }
88}
89
90struct ComposeIdentsVisitor {
91 replacements: HashMap<Ident, Ident>,
92}
93
94impl VisitMut for ComposeIdentsVisitor {
95 fn visit_ident_mut(&mut self, ident: &mut Ident) {
96 if let Some(replacement) = self.replacements.get(ident) {
97 *ident = replacement.clone();
98 }
99 }
100
101 fn visit_lit_str_mut(&mut self, i: &mut LitStr) {
102 let value = i.value();
103 let mut formatted = i.value().clone();
104
105 for (alias, repl) in self.replacements.iter() {
106 formatted = formatted.replace(&format!("%{}%", alias), &repl.to_string());
107 }
108
109 if formatted != value {
110 *i = LitStr::new(&formatted, i.span());
111 }
112 }
113}
114
115static mut COUNTER: u64 = 0;
116
117#[doc = include_str!("../snippets/usage.rs")]
126#[proc_macro]
128pub fn compose_idents(input: TokenStream) -> TokenStream {
129 let state = State {
130 seed: unsafe {
131 COUNTER += 1;
132 COUNTER
133 },
134 };
135 let args = parse_macro_input!(input as ComposeIdentsArgs);
136 let mut visitor = ComposeIdentsVisitor {
137 replacements: args.spec.replacements(&state),
138 };
139 let mut block = args.block;
140 visitor.visit_block_mut(&mut block);
141
142 let block_content = block.stmts;
143
144 let expanded = quote! { #(#block_content)* };
145 TokenStream::from(expanded)
146}