mini_macro_magic/
lib.rs

1#![no_std]
2#![forbid(unsafe_code)]
3//! Export tokens to other modules or crates. Now with 100% less proc macros!
4//!
5//! See the [integration tests](https://gitlab.com/konnorandrews/mini-macro-magic/-/tree/main/tests) for examples
6//! that show *why* you would want to use [`export`].
7//!
8//! This crate provides the [`export`] macro which allows exporting tokens.
9//! The concept is similar to that used by the inspiration for this crate [`macro_magic`](https://docs.rs/macro_magic/latest/macro_magic/).
10//! Namely, a `macro_rules!` is generated. This `macro_rules!` will invoke a passed macro with the exported tokens.
11//!
12//! The difference to `macro_magic` is that this
13//! crate does it all with one `macro_rules!` macro. No more need to poll in a set of proc macros
14//! if you don't need the full power of `macro_magic`. Instead use [`export`] to generate a
15//! macro you or a dependant crate can use. Also, a dependant crate doesn't need to know about
16//! [`mini_macro_magic`][self] to use the generated `macro_rules!`. They are fully self contained.
17//!
18//! The [`export`] macro allows for a form of reflection. Reflection over the definition
19//! of items by inspecting the tokens of the definition. `macro_rules` (and Rust macros
20//! in general) have no way to eagerly expand their input. As a result [`export`] creates
21//! a macro that you pass another macro call for it to expand into.
22//!
23//! ```
24//! use mini_macro_magic::{export, emit};
25//!
26//! // Export the definition of `MyStruct`.
27//! // Note without using `emit!()` `MyStruct` isn't actually created in this scope.
28//! export!(
29//!     #[export(my_struct$)]
30//!     {
31//!         struct MyStruct;
32//!     }
33//! );
34//!
35//! // An example macro that can parse a struct definition and get the name
36//! // of the struct as a string.
37//! macro_rules! name_of_struct {
38//!     {{
39//!         $(#[$($attr:tt)*])*
40//!         $vis:vis struct $name:ident;
41//!     }} => {
42//!         stringify!($name)
43//!     };
44//! }
45//!
46//! // Invoke `name_of_struct` with the definition of `MyStruct`.
47//! assert_eq!(my_struct!(name_of_struct!()), "MyStruct");
48//! ```
49//!
50//! ## `#![no_std]`
51//! [`mini_macro_magic`][self] is `#![no_std]`, it can be used anywhere Rust can.
52//!
53//! # Minimum Supported Rust Version
54//!
55//! Requires Rust 1.56.0.
56//!
57//! This crate follows the ["Latest stable Rust" policy](https://gist.github.com/alexheretic/d1e98d8433b602e57f5d0a9637927e0c). The listed MSRV won't be changed unless needed.
58//! However, updating the MSRV anywhere up to the latest stable at time of release
59//! is allowed.
60
61/// Identity macro that just outputs what it is given.
62///
63/// This can be used with a [`export!()`] generated macro
64/// to emit the exported tokens. See the [`example`] module for an example
65/// of it's usage.
66#[macro_export]
67macro_rules! emit {
68    {{$($t:tt)*}} => {$($t)*};
69}
70
71/// Export tokens for later inspection by other macros.
72///
73/// This macro enables exporting arbitrary token sequences to other
74/// modules or even crates. Call the macro with the following form.
75/// ```ignore
76/// export!(
77///     #[export(
78///         /// Any extra docs you want.
79///         <visibility> <macro name>$
80///     )]
81///     {
82///         <some arbitrary tokens>
83///     }
84/// );
85/// ```
86/// This will generate a `macro_rules!` with the name given at `<macro name>`.
87/// Any docs given in the `export` attribute will be added to the top of the macro
88/// docs. The macro is already given a set of basic docs and an example (see [`example::demo_struct`]
89/// for what this basic docs looks like).
90///
91/// `<visibility>` can be any of the normal visibility specifiers (nothing, `pub`, `pub(crate)`,
92/// `pub(super)`, ...). It can also be `pub crate` which has a special meaning. `pub crate` is used
93/// when in the root of a crate (`main.rs` or `lib.rs`) and we want the macro to be public outside
94/// the crate. This is needed because the normal visibility specifiers don't work there.
95///
96/// The public visibility specifiers also automatically put the macro's docs in the module
97/// [`export`] was invoked in instead of having the macro docs at the root of the crate.
98///
99/// The `$` after the macro name is always required. This is do to a limitation of `macro_ruiles!`
100/// where `$` can't be escaped.
101///
102/// The form above was chossen because it allows code formatting to work as you would expect.
103/// Any code in `<some arbitrary tokens>` is seen by rustfmt as a normal expression and can be
104/// formatted accordingly. rustfmt will not format the `export` attribute though.
105///
106/// *Note:* The docs generated for the macro include a doc test example. The doc test should always
107/// pass (if it doesn't please post an issue). However, sometimes this
108/// may not be wanted. To disable the doc test generation (it will be generated as a text code
109/// block instead) add the `mmm_no_gen_examples` cfg item (its not a feature) when compiling.
110///
111/// ### `macro_rules_attribute` Support
112/// There is also another form [`export`] can be called with designed for use with the
113/// [`macro_rules_attribute`](https://docs.rs/macro_rules_attribute/latest/macro_rules_attribute/) crate.
114/// ```ignore
115/// export!(
116///     /// Any docs or attributes for the item (not the macro).
117///     #[custom(export(
118///         /// Any extra docs you want.
119///         <visibility> <macro name>$
120///     ))]
121///     <some arbitrary tokens>
122/// );
123/// ```
124/// When used with the [`macro_rules_attribute::derive`](https://docs.rs/macro_rules_attribute/latest/macro_rules_attribute/attr.derive.html) macro this allows the following.
125/// ```
126/// # use mini_macro_magic::export;
127/// # use macro_rules_attribute::derive;
128/// /// Some docs for `Demo`.
129/// #[derive(export!)]
130/// #[custom(export(
131///     pub demo_struct$
132/// ))]
133/// struct Demo {
134///     pub name: String,
135/// }
136/// ```
137///
138/// # Examples
139///
140/// See the [integration tests](https://gitlab.com/konnorandrews/mini-macro-magic/-/tree/main/tests) for examples
141/// that show *why* you would want to use [`export`].
142///
143/// ```
144/// # use mini_macro_magic::export;
145/// export!(
146///     #[export(
147///         demo_struct$
148///     )]
149///     {
150///         struct Demo {
151///             pub name: String,
152///         }
153///     }
154/// );
155/// ```
156///
157/// ```
158/// # use mini_macro_magic::export;
159/// export!(
160///     #[export(
161///         pub a_plus_b$
162///     )]
163///     {
164///         a + b
165///     }
166/// );
167/// ```
168///
169/// ```
170/// # use mini_macro_magic::export;
171/// export!(
172///     #[export(
173///         /// Some docs.
174///         pub(crate) allow_unused$
175///     )]
176///     {
177///         #[allow(unused)]
178///     }
179/// );
180/// ```
181///
182/// # Invalid Input
183///
184/// If [`export`] is called with incorrect syntax a compile error will be generated with the
185/// expected calling syntax and the syntax it was given.
186///
187/// ```compile_fail
188/// # use mini_macro_magic::export;
189/// export!(
190///     #[export(demo_struct)]
191///     {
192///         struct Demo {
193///             pub name: String,
194///         }
195///     }
196/// );
197/// ```
198/// ```text
199/// error: `export!()` expects input of the form:
200///       
201///        #[export(<vis> <name>$)] { <tokens> }
202///       
203///        instead got:
204///       
205///        #[export(demo_struct)] { struct Demo { pub name : String, } }
206///       
207///   --> src/lib.rs:132:1
208///    |
209/// 6  | / export!(
210/// 7  | |     #[export(demo_struct)]
211/// 8  | |     {
212/// 9  | |         struct Demo {
213/// ...  |
214/// 12 | |     }
215/// 13 | | );
216///    | |_^
217/// ```
218///
219#[macro_export]
220macro_rules! export {
221    {
222        #[export(
223            $(#[$($attr:tt)*])*
224            $name:ident $dollar:tt
225        )]
226        {
227            $($t:tt)*
228        }
229    } => {
230        $crate::__export_impl! {
231            $(#[$($attr)*])*
232            $name $dollar
233
234            {
235                use $name; // The standard use trick to make the macro an item.
236            }
237
238            {$($t)*}
239        }
240    };
241    {
242        #[export(
243            $(#[$($attr:tt)*])*
244            pub($($module:tt)*) $name:ident $dollar:tt
245        )]
246        {
247            $($t:tt)*
248        }
249    } => {
250        $crate::__export_impl! {
251            $(#[$($attr)*])*
252            $name $dollar
253
254            {
255                pub($($module)*) use $name; // The standard use trick to make the macro an item.
256            }
257
258            {$($t)*}
259        }
260    };
261    {
262        #[export(
263            $(#[$($attr:tt)*])*
264            pub $name:ident $dollar:tt
265        )]
266        {
267            $($t:tt)*
268        }
269    } => {
270        $crate::__export_impl! {
271            $(#[$($attr)*])*
272            #[doc(hidden)] // Hides the docs from the crate root.
273            #[macro_export]
274            $name $dollar
275
276            // This makes the macro appear as a normal item with it's docs.
277            {
278                #[doc(inline)]
279                pub use $name;
280            }
281
282            {$($t)*}
283        }
284    };
285    {
286        #[export(
287            $(#[$($attr:tt)*])*
288            pub crate $name:ident $dollar:tt
289        )]
290        {
291            $($t:tt)*
292        }
293    } => {
294        $crate::__export_impl! {
295            $(#[$($attr)*])*
296            #[macro_export]
297            $name $dollar
298
299            {} // A public macro at the crate root can't have a use.
300
301            {$($t)*}
302        }
303    };
304    {$($t:tt)*} => {
305        // Assume all other invocations are using the derive form.
306        //
307        // If the syntax is wrong, then find_custom_export_attr will
308        // emit a compile error.
309        $crate::__find_custom_export_attr! { {} {$($t)*} }
310    };
311}
312
313/// Unified impl for all the export macro variants.
314#[doc(hidden)]
315#[macro_export]
316macro_rules! __export_impl {
317    (
318        // Any attributes for the macro.
319        $(#[$($attr:tt)*])*
320
321        // The name of the macro.
322        $name:ident
323
324        // Needed to inject the $ token into the generated macro_rules.
325        $dollar:tt
326
327        // The `use` statements wanted.
328        {$($use:tt)*}
329
330        // The actual tokens to export.
331        {$($t:tt)*}
332    ) => {
333        $crate::__check_dollar!($dollar);
334
335        $(#[$($attr)*])*
336        ///
337        /// Injects the exported tokens (see section below) into the given macro call.
338        /// The exported tokens are injected in a `{}` block as the first thing.
339        /// For example with `some_macro!(a + b)` the actual macro call would look like
340        /// `some_macro!({ ... <exported tokens> ... } a + b)`.
341        ///
342        /// <details>
343        /// <summary>Expand to show exported tokens</summary>
344        ///
345        /// *Note: The tokens here are formatted via [`stringify!()`] so may not be very
346        /// readable.*
347        ///
348        /// ```text
349        #[doc = stringify!($($t)*)]
350        /// ```
351        ///
352        /// </details>
353        ///
354        /// # Examples
355        ///
356        #[cfg_attr(mmm_no_gen_examples, doc = "```text")]
357        #[cfg_attr(not(mmm_no_gen_examples), doc = "```")]
358        #[doc = concat!("use ", module_path!(), "::", stringify!($name), ";")]
359        ///
360        /// // Stringify all the exported tokens. The exported tokens are passed as a `{}`
361        /// // block to `stringify!()` at the beginning.
362        #[doc = concat!("let as_string = ", stringify!($name), "!(stringify!(some extra tokens));")]
363        ///
364        /// // Check that it matches what we expect.
365        #[doc = concat!("assert_eq!(as_string, r#####\"", stringify!({$($t)*} some extra tokens), "\"#####);")]
366        /// ```
367        macro_rules! $name {
368            ($dollar ($dollar t:tt)*) => {
369                $crate::__invoke! { {} {$dollar ($dollar t)*} {$($t)*} }
370            }
371        }
372
373        $($use)*
374    };
375}
376
377/// TT muncher to find the path for a macro invocation.
378///
379/// `some::path::macro!(some stuff)`
380/// this finds the `some::path::macro` and allows injecting extra tokens
381/// as a `{ ... tokens ... }` as the first token of the invocation.
382#[doc(hidden)]
383#[macro_export]
384macro_rules! __invoke {
385    // `{<path>} {!(<arg tokens>)} {<extra tokens>}`
386    //
387    // This arm invokes the macro.
388    ({$($path:tt)*} {!($($t:tt)*)} {$($extra:tt)*}) => {
389        $($path)*!({$($extra)*} $($t)*);
390    };
391    // `{<path>} {![<arg tokens>]} {<extra tokens>}`
392    //
393    // This arm invokes the macro.
394    ({$($path:tt)*} {![$($t:tt)*]} {$($extra:tt)*}) => {
395        $($path)*![{$($extra)*} $($t)*];
396    };
397    // `{<path>} {!{<arg tokens>}} {<extra tokens>}`
398    //
399    // This arm invokes the macro.
400    ({$($path:tt)*} {!{$($t:tt)*}} {$($extra:tt)*}) => {
401        $($path)*!{ {$($extra)*} $($t)* }
402    };
403    // `{<part of path>} {<next token> <arg tokens>}} {<extra tokens>}`
404    //
405    // This arm recurses until it parses out the path.
406    ({$($path:tt)*} {$next:tt $($t:tt)*} {$($extra:tt)*}) => {
407        $crate::__invoke! { {$($path)* $next} {$($t)*} {$($extra)*} }
408    };
409}
410
411/// Check that the passed in token is actually a literal `$`.
412///
413/// If it's not then a compile error is produced.
414#[doc(hidden)]
415#[macro_export]
416macro_rules! __check_dollar {
417    ($) => {};
418    ($other:tt) => {compile_error! { concat!("`export!()` expects `$` after macro name, got `", stringify!($other), "`") }};
419}
420
421/// TT muncher to find the `#[custom(export(...))]` attribute.
422#[doc(hidden)]
423#[macro_export]
424macro_rules! __find_custom_export_attr {
425    ({$($attr:tt)*} {#[custom(export($($stuff:tt)*))] $($t:tt)*}) => {
426        $crate::export! {
427            #[export($($stuff)*)]
428            {
429                $($attr)*
430                $($t)*
431            }
432        }
433    };
434    ({$($attr:tt)*} {$next:tt $($t:tt)*}) => {
435        $crate::__find_custom_export_attr! {
436            {$($attr)* $next}
437            {$($t)*}
438        }
439    };
440    ({$($a:tt)*} {$($t:tt)*}) => {
441        compile_error! { concat!(
442"`export!()` expects input of the form:
443
444#[export(<vis> <name>$)] { <tokens> }
445
446instead got:
447
448",
449stringify!($($a)* $($t)*),
450"
451\n")
452        }
453    }
454}
455
456/// Example of using [`export`] and [`emit`].
457///
458/// This module is not part of the crate's public API and is only visible on docs.
459///
460/// Code used for this module:
461/// ```
462/// pub mod example {
463///     mini_macro_magic::export!(
464///         #[export(
465///             /// This is the macro generated by the example.
466///             pub demo_struct$
467///         )]
468///         {
469///             /// Demo struct definition.
470///             pub struct Demo {
471///                 /// The X value.
472///                 pub x: i32,
473///
474///                 /// The Y value.
475///                 pub y: i32,
476///             }
477///         }
478///     );
479///
480///     // Emit the struct definition for Demo.
481///     demo_struct!(mini_macro_magic::emit!());
482/// }
483/// ```
484///
485/// [`Demo`](example::Demo) is a struct definition exported by [`export`].
486/// As you can see it exists as expected as a normal struct because we emitted
487/// the tokens into the module with [`emit`].
488///
489/// [`demo_struct`] is the macro that was
490/// generated by [`export`].
491/// This is the macro you would call to inspect the tokens of [`Demo`](example::Demo).
492#[cfg(doc_example)]
493pub mod example {
494    export!(
495        #[export(
496            /// This is the macro generated by the example.
497            pub demo_struct$
498        )]
499        {
500            /// Demo struct definition.
501            pub struct Demo {
502                /// The X value.
503                pub x: i32,
504
505                /// The Y value.
506                pub y: i32,
507            }
508        }
509    );
510
511    // Emit the struct definition for Demo.
512    demo_struct!(emit!());
513
514    #[test]
515    fn check_demo_exists() {
516        let _x = Demo { x: 1, y: 2 };
517    }
518}
519
520#[doc(hidden)]
521#[cfg(not(doc_example))]
522pub mod example {
523    #[doc(hidden)]
524    pub mod demo_struct {}
525}