compose_idents/
lib.rs

1#![allow(clippy::needless_doctest_main)]
2#![doc = include_str!("../snippets/docs.md")]
3
4mod ast;
5mod core;
6mod error;
7mod eval;
8mod expand;
9mod funcs;
10mod interpreter;
11mod parse;
12mod resolve;
13mod substitution;
14mod util;
15
16use crate::ast::{ComposeItemSpec, RawAST};
17use crate::core::Environment;
18use crate::interpreter::Interpreter;
19use crate::util::unique_id::next_unique_id;
20use proc_macro::TokenStream;
21use quote::quote;
22use std::rc::Rc;
23use syn::parse_macro_input;
24use util::deprecation::DeprecationService;
25
26enum InvocationType {
27    Func(TokenStream),
28    Attr(TokenStream, TokenStream),
29}
30
31fn compose_core(prefix: &'static str, invocation: InvocationType) -> TokenStream {
32    let deprecation_service = DeprecationService::new_rc(prefix);
33    DeprecationService::maybe_set_global(deprecation_service);
34    let deprecation_service_scope = DeprecationService::scoped();
35
36    let environment = Rc::new(Environment::new_initialized(next_unique_id()));
37    Environment::maybe_set_global(environment.clone());
38
39    let interpreter = Interpreter::new(environment, deprecation_service_scope);
40
41    let args = match invocation {
42        InvocationType::Func(input) => parse_macro_input!(input as RawAST),
43        InvocationType::Attr(attr, item) => {
44            // Parse attribute prefix tailored for the attribute macro form
45            let spec: ComposeItemSpec = match syn::parse(attr) {
46                Ok(v) => v,
47                Err(err) => return TokenStream::from(err.into_compile_error()),
48            };
49
50            // Treat the decorated item as the block
51            let item: proc_macro2::TokenStream = item.into();
52            let block_tokens: proc_macro2::TokenStream = quote!({ #item });
53            let block: syn::Block = match syn::parse2(block_tokens) {
54                Ok(v) => v,
55                Err(err) => return TokenStream::from(err.into_compile_error()),
56            };
57
58            RawAST::from_compose_item_spec(&spec, block)
59        }
60    };
61    match interpreter.execute(args) {
62        Ok(ts) => ts.into(),
63        Err(err) => {
64            let syn_err: syn::Error = err.into();
65            TokenStream::from(syn_err.into_compile_error())
66        }
67    }
68}
69
70/// Compose identifiers from the provided parts and replace their aliases in the decorated item.
71///
72/// This attribute macro is equivalent to [`compose!`], but treats the annotated item as the
73/// code block.
74///
75/// # Example
76///
77/// ```rust
78/// use compose_idents::compose_item;
79///
80/// #[compose_item(
81///     // For-in loops could be used to generate multiple variations of the code.
82///     for (suffix, (interjection, noun)) in [
83///         (BAR, (Hello, "world")),
84///         (baz, ("Hallo", "welt")),
85///     ]
86///
87///     // A simple alias definition.
88///     my_fn = concat(foo, _, 1, _, lower(suffix)),
89///     // Many functions are overloaded support different input argument types.
90///     greeting = concat(to_str(interjection), ", ", noun, "!"),
91/// )]
92/// // String placeholders `% my_alias %` are expanded inside literals and doc attributes.
93/// #[doc = "Greets: % greeting %"]
94/// fn my_fn() -> &'static str { greeting }
95///
96/// assert_eq!(foo_1_bar(), "Hello, world!");
97/// assert_eq!(foo_1_baz(), "Hallo, welt!");
98/// ```
99///
100/// # Reference
101///
102#[doc = include_str!("../snippets/reference_h2.md")]
103#[proc_macro_attribute]
104pub fn compose_item(attr: TokenStream, item: TokenStream) -> TokenStream {
105    compose_core("compose_item!: ", InvocationType::Attr(attr, item))
106}
107
108/// Compose identifiers from the provided parts and replace their aliases in the code block.
109///
110/// In addition to replacing identifier aliases it replaces tokens like `% alias %` in string
111/// literals (including in doc-attributes).
112///
113/// # Example
114///
115/// ```rust
116/// use compose_idents::compose;
117///
118/// compose!(
119///     // For-in loops could be used to generate multiple variations of the code.
120///     for (suffix, (interjection, noun)) in [
121///         (BAR, (Hello, "world")),
122///         (baz, ("Hallo", "welt")),
123///     ]
124///
125///     // A simple alias definition.
126///     my_fn = concat(foo, _, 1, _, lower(suffix)),
127///     // Many functions are overloaded support different input argument types.
128///     greeting = concat(to_str(interjection), ", ", noun, "!"),
129///     {
130///         // String placeholders `% my_alias %` are expanded inside literals and doc attributes.
131///         #[doc = "Greets: % greeting %"]
132///         fn my_fn() -> &'static str { greeting }
133///     },
134/// );
135///
136/// assert_eq!(foo_1_bar(), "Hello, world!");
137/// assert_eq!(foo_1_baz(), "Hallo, welt!");
138/// ```
139///
140/// # Reference
141///
142#[doc = include_str!("../snippets/reference_h2.md")]
143#[proc_macro]
144pub fn compose(input: TokenStream) -> TokenStream {
145    compose_core("compose!: ", InvocationType::Func(input))
146}
147
148/// Compose identifiers from the provided parts and replace their aliases in the code block.
149///
150/// In addition to replacing identifier aliases it replaces tokens like `% alias %` in string
151/// literals (including in doc-attributes).
152///
153/// # Deprecation
154///
155/// The macro is deprecated. Please use [`compose!`] instead.
156///
157/// # Example
158///
159/// ```rust
160/// use compose_idents::compose_idents;
161///
162/// compose_idents!(
163///     // For-in loops could be used to generate multiple variations of the code.
164///     for (suffix, (interjection, noun)) in [
165///         (BAR, (Hello, "world")),
166///         (baz, ("Hallo", "welt")),
167///     ]
168///
169///     // A simple alias definition.
170///     my_fn = concat(foo, _, 1, _, lower(suffix)),
171///     // Many functions are overloaded support different input argument types.
172///     greeting = concat(to_str(interjection), ", ", noun, "!"),
173///     {
174///         // String placeholders `% my_alias %` are expanded inside literals and doc attributes.
175///         #[doc = "Greets: % greeting %"]
176///         fn my_fn() -> &'static str { greeting }
177///     },
178/// );
179///
180/// assert_eq!(foo_1_bar(), "Hello, world!");
181/// assert_eq!(foo_1_baz(), "Hallo, welt!");
182/// ```
183///
184/// # Reference
185///
186#[doc = include_str!("../snippets/reference_h2_compose_idents.md")]
187#[deprecated(
188    since = "0.3.0",
189    note = "Renamed to compose!. Use compose!(...) instead."
190)]
191#[proc_macro]
192pub fn compose_idents(input: TokenStream) -> TokenStream {
193    compose_core("compose_idents!: ", InvocationType::Func(input))
194}