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}