tt_call/lib.rs
1//! [![github]](https://github.com/dtolnay/tt-call) [![crates-io]](https://crates.io/crates/tt-call) [![docs-rs]](https://docs.rs/tt-call)
2//!
3//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
4//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
5//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
6//!
7//! <br>
8//!
9//! **This library is an attempt at seeding an ecosystem of high-quality modular
10//! interoperable tt-muncher building blocks.**
11//!
12//! Tt-munching is a powerful technique for parsing macro\_rules input grammars
13//! of significant complexity. In building more and more sophisticated
14//! tt-muncher macros it becomes valuable to share code for dealing with certain
15//! common input patterns, rather than reimplementing support for those patterns
16//! in a low quality and poorly tested way each time.
17//!
18//! The core macros provided by this library are **[`tt_call!`]** and
19//! **[`tt_return!`]**. Together these provide a flexible way to propagate input
20//! and output tokens along a recursive descent call hierarchy. One may also
21//! view them as a flexible library-only stable implementation of eager
22//! expansion for macro\_rules.
23//!
24//! [`tt_call!`]: macro.tt_call.html
25//! [`tt_return!`]: macro.tt_return.html
26//!
27//! ```toml
28//! [dependencies]
29//! tt-call = "1.0"
30//! ```
31//!
32//! *Version requirement: tt-call requires a Rust compiler version 1.31 or
33//! newer.*
34//!
35//!
36//! ## Calling convention rules
37//!
38//! - **Macros that conform to tt-call must be invoked with curly braces.**
39//!
40//! ```
41//! # macro_rules! some_macro {
42//! # () => {};
43//! # }
44//! #
45//! some_macro! {
46//! /* ... */
47//! }
48//! ```
49//!
50//! The Rust grammar is very particular about punctuation after
51//! parenthesized and square bracketed macro invocations. In expression or
52//! type position they must not be followed by a semicolon. In item or
53//! statement position they are required to be followed by a semicolon. The
54//! inconsistency is applied transitively to any helper macros they forward
55//! to, and means that parenthesized and square bracketed macro invocations
56//! must decide whether to support expression and type position only or item
57//! and statement position only. They cannot support both, which is a
58//! problem for broadly applicable macro building blocks.
59//!
60//! There is no such punctuation requirement after curly brace invocations.
61//! Consistently using curly braces makes the same macro building blocks
62//! usable in any syntactic position.
63//!
64//! - **Input and output values must be passed in the following key-value
65//! form.**
66//!
67//! ```
68//! # macro_rules! some_macro {
69//! # {
70//! $key:ident = [{ $($value:tt)* }]
71//! # } => {};
72//! # }
73//! ```
74//!
75//! This is enforced by the `tt_call!` and `tt_return!` macros. The
76//! consistency is important for composability and makes it possible to
77//! write higher-order macros that operate on the input or output of an
78//! arbitrary tt-call macro.
79//!
80//! Except in libraries intended specifically as tt-call building blocks,
81//! generally tt-call macros will be private `#[doc(hidden)]` helpers with a
82//! user-facing non-tt-call entry point. Thus the rigid key-value syntax
83//! need not be exposed to users of the public macro.
84//!
85//! - **Before its key-value inputs, every rule must accept a `$caller:tt`.**
86//!
87//! This is an opaque tt bundle used by `tt_call!` and `tt_return!` to
88//! record the call hierarchy. A `tt_return!` accepts a `$caller` to return
89//! back to.
90//!
91//! - **Every rule must expand to exactly one macro invocation and nothing
92//! else.**
93//!
94//! Output tokens are expected to be returned through `tt_return!`.
95//! Expanding to nothing, expanding to more than one macro invocation, or
96//! expanding to anything other than a macro invocation are not permitted.
97//!
98//!
99//! ## Examples
100//!
101//! Just as a flavor of the syntax, one of the rules from the implementation of
102//! the built-in [`tt_replace!`] macro is written as follows. The macro takes in
103//! a token stream and for each token that matches a given predicate it replaces
104//! that token with a given replacement sequence of tokens. For example the
105//! caller may want to replace the token `self` with the single token `__value`.
106//!
107//! The rule shown here is responsible for performing one step of the
108//! replacement. It matches one token of input in `$first:tt`, uses [`tt_if!`]
109//! to invoke the predicate with `$first` as input, recurses with an accumulated
110//! copy of the replacement tokens if the predicate returns true, and recurses
111//! on the remaining tokens with `$first` preserved unchanged if the predicate
112//! returns false.
113//!
114//! [`tt_replace!`]: macro.tt_replace.html
115//! [`tt_if!`]: macro.tt_if.html
116//!
117//! ```
118//! # macro_rules! ignore {
119//! {
120//! $caller:tt
121//! condition = [{ $condition:ident }]
122//! replace_with = [{ $($with:tt)* }]
123//! tokens = [{ $($tokens:tt)* }]
124//! rest = [{ $first:tt $($rest:tt)* }]
125//! } => {
126//! tt_if! {
127//! condition = [{ $condition }]
128//! input = [{ $first }]
129//! true = [{
130//! private_replace! {
131//! $caller
132//! condition = [{ $condition }]
133//! replace_with = [{ $($with)* }]
134//! tokens = [{ $($tokens)* $($with)* }]
135//! rest = [{ $($rest)* }]
136//! }
137//! }]
138//! false = [{
139//! private_replace! {
140//! $caller
141//! condition = [{ $condition }]
142//! replace_with = [{ $($with)* }]
143//! tokens = [{ $($tokens)* $first }]
144//! rest = [{ $($rest)* }]
145//! }
146//! }]
147//! }
148//! };
149//! # }
150//! ```
151//!
152//! Here is another macro rule selected from `tt_replace!`. This one matches if
153//! the tt-muncher has reached the end of its input. It returns the finished
154//! tokens back to the caller using `tt_return!`.
155//!
156//! ```
157//! # macro_rules! ignore {
158//! {
159//! $caller:tt
160//! condition = [{ $condition:ident }]
161//! replace_with = [{ $($with:tt)* }]
162//! tokens = [{ $($tokens:tt)* }]
163//! rest = [{ }]
164//! } => {
165//! tt_return! {
166//! $caller
167//! tokens = [{ $($tokens)* }]
168//! }
169//! };
170//! # }
171//! ```
172//!
173//! One example of a caller-provided predicate for `tt_replace!` could be
174//! written as follows. This predicate determines whether the input token is
175//! lowercase `self`.
176//!
177//! ```
178//! macro_rules! is_lowercase_self {
179//! // Input token is `self`.
180//! {
181//! $caller:tt
182//! input = [{ self }]
183//! } => {
184//! tt_return! {
185//! $caller
186//! is = [{ true }]
187//! }
188//! };
189//!
190//! // Input token is anything other than `self`.
191//! {
192//! $caller:tt
193//! input = [{ $other:tt }]
194//! } => {
195//! tt_return! {
196//! $caller
197//! is = [{ false }]
198//! }
199//! };
200//! }
201//! ```
202//!
203//! From here, calling `tt_replace!` with our `is_lowercase_self!` as the
204//! condition predicate can be used to implement a fanciful syntax for unary
205//! closures: `closure!(self + 1)` should expand to `|__value| __value + 1`.
206//!
207//! Notice that this user-facing `closure!` macro does not follow the tt-call
208//! calling convention. Internally though it uses several tt-call helpers as
209//! building blocks.
210//!
211//! ```
212//! # macro_rules! tt_call {
213//! # ($($ignore:tt)*) => {
214//! # 2
215//! # };
216//! # }
217//! #
218//! macro_rules! closure {
219//! ($($expr:tt)+) => {
220//! |__value| tt_call! {
221//! macro = [{ tt_replace }]
222//! condition = [{ is_lowercase_self }]
223//! replace_with = [{ __value }]
224//! input = [{ $($expr)+ }]
225//! }
226//! };
227//! }
228//!
229//! fn main() {
230//! let add_one = closure!(self + 1);
231//! println!("{}", add_one(1));
232//! }
233//! ```
234//!
235//!
236//! ## Motivation
237//!
238//! This may seem like a lot of ceremony around what should be very simple macro
239//! calls. After all, couldn't we write `is_lowercase_self` in a much more
240//! straightforward way as follows?
241//!
242//! ```
243//! macro_rules! is_lowercase_self {
244//! (self) => { true };
245//! ($other:tt) => { false };
246//! }
247//!
248//! fn main() {
249//! println!("{}", is_lowercase_self!(self)); // true
250//! println!("{}", is_lowercase_self!(not_self)); // false
251//! }
252//! ```
253//!
254//! Qualified yes. As written, the simpler `is_lowercase_self!` behaves as it
255//! looks like it should.
256//!
257//! But suppose we want to build `tt_replace!` or similar macro that needs to
258//! invoke `is_lowercase_self!` as a helper. There is no way to do it with this
259//! simpler one. No matter what our macro does, there is no way for it to expand
260//! `is_lowercase_self!` before expanding itself. If it expands itself first,
261//! there is no way for it to use the expansion of `is_lowercase_self!` to
262//! decide whether the current token is supposed to be replaced.
263//!
264//! The `tt_call!` and `tt_return!` abstraction along with `$caller:tt` tracking
265//! of the call hierarchy are critical to building composable macros that freely
266//! pass around arbitrary tokens and return in a way that can inform expansion
267//! of their caller.
268//!
269//! A future eager expansion feature for declarative macros may render the
270//! tt-call approach unnecessary. Eager expansion is listed as an unresolved
271//! question in the [tracking issue for declarative macros 2.0][tracking] but is
272//! believed to be quite a ways out, if it ever happens. And even then it is not
273//! clear whether it is desirable to allow macros expanding to arbitrary tokens.
274//! Today macros always expand to an expression, item, statement, type, or
275//! pattern. Eager expansion does not automatically mean that the restriction
276//! would be lifted to allow a macro that expands to arbitrary tokens such as `!
277//! @ #`. The token tree calling convention provides working eager expansion
278//! today with support for passing and returning arbitrary token streams.
279//!
280//! [tracking]: https://github.com/rust-lang/rust/issues/39412
281//!
282//! And function-like procedural macros once those are stable? It is going to
283//! depend on your choice of syntax for the macro input whether a procedural
284//! macro is a better choice, but note that they present their own DIY parsing
285//! adventures and can be even nastier than tt-call once you get the hang of
286//! both. In addition, procedural macros must be defined in a separate crate
287//! from the rest of your library so they are not well suited for quick one-off
288//! helper macros.
289//!
290//!
291//! ## Design philosphy
292//!
293//! As may be no surprise by this point, the calling convention design
294//! prioritizes scalability and composability over conciseness. A reader
295//! familiar with the calling convention (maybe you, six months after writing
296//! the macro) should be able to look at any individual tt-call rule by itself
297//! and comfortably read off what it does top to bottom and identify its
298//! purpose.
299//!
300//!
301//! ## Links
302//!
303//! - The code that implements `closure!(self + 1)`, all of which is shown
304//! above, can be found all together in [`examples/replace.rs`].
305//!
306//! - As a more elaborate example of a tt-call macro,
307//! [`examples/comma_separated.rs`] demonstrates a macro that does primitive
308//! name mangling of Rust types. It uses [`parse_type!`] which is a tt-call
309//! version of `$:ty`.
310//!
311//! ```
312//! # macro_rules! mangle_type_names {
313//! # ($($ignore:tt)*) => {
314//! # &[
315//! # "_std_fs_File",
316//! # "_ref_mut_str",
317//! # "_impl_Display",
318//! # "_fn_s_ref_str_to_String",
319//! # ]
320//! # };
321//! # }
322//! #
323//! static MANGLED: &[&str] = mangle_type_names! {
324//! std::fs::File,
325//! &'a mut str,
326//! impl Display,
327//! fn(s: &str) -> String,
328//! };
329//!
330//! fn main() {
331//! assert_eq!(MANGLED, [
332//! "_std_fs_File",
333//! "_ref_mut_str",
334//! "_impl_Display",
335//! "_fn_s_ref_str_to_String",
336//! ]);
337//! }
338//! ```
339//!
340//! [`examples/replace.rs`]: https://github.com/dtolnay/tt-call/blob/master/examples/replace.rs
341//! [`examples/comma_separated.rs`]: https://github.com/dtolnay/tt-call/blob/master/examples/comma_separated.rs
342//! [`parse_type!`]: macro.parse_type.html
343
344#![no_std]
345#![doc(html_root_url = "https://docs.rs/tt-call/1.0.9")]
346#![allow(clippy::module_name_repetitions, clippy::needless_doctest_main)]
347
348mod predicate;
349mod replace;
350mod rust;
351mod unexpected;
352
353// In general it is not possible today in Rust to produce good error messages
354// and good error spans at the same time. See:
355//
356// https://github.com/rust-lang/rust/issues/44535
357//
358// Within this crate we prefer to produce errors with the right span, even if
359// the message is not good. This scales much better to large input token
360// streams.
361
362/// Evaluate a tt-call macro and return its output to a given return
363/// destination.
364///
365/// # Input
366///
367/// The input must start with an argument called `macro` which provides the name
368/// of the macro for `tt_call!` to invoke.
369///
370/// - `macro = [{` name of macro to call `}]`
371///
372/// After that there may be any number of key-value pairs to be passed as
373/// arguments to the macro being called.
374///
375/// - **`$(`**<br>
376///   arbitrary key `= [{` arbitrary tokens `}]`<br>
377/// **`)*`**
378///
379/// Finally a specification of the macro invocation to which this call should
380/// return its output.
381///
382/// - `~~>` name of return destination macro `! {`<br>
383///   arbitrary tokens<br>
384/// `}`
385///
386/// # Examples
387///
388/// ```
389/// use tt_call::{tt_call, tt_is_ident};
390///
391/// macro_rules! print_is_ident {
392/// {
393/// token = [{ $token:tt }]
394/// is_ident = [{ true }]
395/// } => {
396/// println!("turns out `{}` is an ident", stringify!($token));
397/// };
398///
399/// {
400/// token = [{ $token:tt }]
401/// is_ident = [{ false }]
402/// } => {
403/// println!("nope, `{}` is not an ident", stringify!($token));
404/// };
405/// }
406///
407/// fn main() {
408/// tt_call! {
409/// macro = [{ tt_is_ident }]
410/// input = [{ foo }]
411/// ~~> print_is_ident! {
412/// token = [{ foo }]
413/// }
414/// }
415/// }
416/// ```
417///
418/// If the invoked macro provides the entirety of the input to the return
419/// destination macro, then the `!` and argument list may be omitted.
420///
421/// ```
422/// use tt_call::{tt_call, tt_is_ident};
423///
424/// macro_rules! print_is_ident {
425/// {
426/// is_ident = [{ true }]
427/// } => {
428/// println!("that token is an ident");
429/// };
430///
431/// {
432/// is_ident = [{ false }]
433/// } => {
434/// println!("nope, not an ident");
435/// };
436/// }
437///
438/// fn main() {
439/// tt_call! {
440/// macro = [{ tt_is_ident }]
441/// input = [{ foo }]
442/// ~~> print_is_ident
443/// }
444/// }
445/// ```
446///
447/// And if the invoked macro produces exactly one output value and we just want
448/// to expand to that output value, the destination macro may be omitted
449/// entirely.
450///
451/// ```
452/// use tt_call::{tt_call, tt_is_ident};
453///
454/// fn main() {
455/// let is_ident = tt_call! {
456/// macro = [{ tt_is_ident }]
457/// input = [{ foo }]
458/// };
459/// println!("{}", is_ident); // prints true or false
460/// }
461/// ```
462#[macro_export]
463macro_rules! tt_call {
464 // Call macro and expand into the tokens of its one return value.
465 {
466 macro = [{ $($m:ident)::* }]
467 $(
468 $input:ident = [{ $($tokens:tt)* }]
469 )*
470 } => {
471 $($m)::* ! {
472 (__tt_call_private $crate::tt_identity_return! {})
473 $(
474 $input = [{ $($tokens)* }]
475 )*
476 }
477 };
478
479 // Call macro and pass its return values to the given return destination.
480 {
481 macro = [{ $($m:ident)::* }]
482 $(
483 $input:ident = [{ $($tokens:tt)* }]
484 )*
485 ~~> $($return:ident)::*
486 } => {
487 $($m)::* ! {
488 (__tt_call_private $($return)::* ! {})
489 $(
490 $input = [{ $($tokens)* }]
491 )*
492 }
493 };
494
495 // Call macro and append its return values onto the invocation of the given
496 // return destination without caller.
497 {
498 macro = [{ $($m:ident)::* }]
499 $(
500 $input:ident = [{ $($tokens:tt)* }]
501 )*
502 ~~> $($return:ident)::* ! {
503 $(
504 $name:ident = [{ $($state:tt)* }]
505 )*
506 }
507 } => {
508 $($m)::* ! {
509 (__tt_call_private $($return)::* ! {
510 $(
511 $name = [{ $($state)* }]
512 )*
513 })
514 $(
515 $input = [{ $($tokens)* }]
516 )*
517 }
518 };
519
520 // Call macro and append its return values onto the invocation of the given
521 // return destination with caller.
522 {
523 macro = [{ $($m:ident)::* }]
524 $(
525 $input:ident = [{ $($tokens:tt)* }]
526 )*
527 ~~> $($return:ident)::* ! {
528 $caller:tt
529 $(
530 $name:ident = [{ $($state:tt)* }]
531 )*
532 }
533 } => {
534 $($m)::* ! {
535 (__tt_call_private $($return)::* ! {
536 $caller
537 $(
538 $name = [{ $($state)* }]
539 )*
540 })
541 $(
542 $input = [{ $($tokens)* }]
543 )*
544 }
545 };
546}
547
548#[doc(hidden)]
549#[macro_export]
550macro_rules! tt_identity_return {
551 // Macro returned one value.
552 {
553 $name:ident = [{ $($output:tt)* }]
554 } => {
555 $($output)*
556 };
557
558 // Macro parsed the entire input and returned one value.
559 {
560 $name:ident = [{ $($output:tt)* }]
561 rest = [{ }]
562 } => {
563 $($output)*
564 };
565
566 // Unexpected: macro failed to parse the entire input.
567 {
568 $name:ident = [{ $($output:tt)* }]
569 rest = [{ $($unexpected:tt)* }]
570 } => {
571 $crate::error_unexpected! {
572 $($unexpected)*
573 }
574 };
575}
576
577/// Return zero or more output values to the caller macro.
578///
579/// # Input
580///
581/// The `tt_return!` invocation should be given a `$caller` to return to and a
582/// sequence of zero or more named return values.
583///
584/// - **`$(`**<br>
585///   arbitrary key `= [{` arbitrary tokens `}]`<br>
586/// **`)*`**
587///
588/// # Example
589///
590/// ```
591/// use tt_call::{tt_call, tt_return};
592///
593/// macro_rules! is_lowercase_self {
594/// // Input token is `self`.
595/// {
596/// $caller:tt
597/// input = [{ self }]
598/// } => {
599/// tt_return! {
600/// $caller
601/// is = [{ true }]
602/// }
603/// };
604///
605/// // Input token is anything other than `self`.
606/// {
607/// $caller:tt
608/// input = [{ $other:tt }]
609/// } => {
610/// tt_return! {
611/// $caller
612/// is = [{ false }]
613/// }
614/// };
615/// }
616///
617/// fn main() {
618/// let is = tt_call! {
619/// macro = [{ is_lowercase_self }]
620/// input = [{ self }]
621/// };
622/// println!("{}", is);
623/// }
624/// ```
625#[macro_export]
626macro_rules! tt_return {
627 {
628 $caller:tt
629 $(
630 $output:ident = [{ $($tokens:tt)* }]
631 )*
632 } => {
633 $crate::private_return! {
634 $caller
635 $(
636 $output = [{ $($tokens)* }]
637 )*
638 }
639 };
640}
641
642#[doc(hidden)]
643#[macro_export]
644macro_rules! private_return {
645 {
646 (__tt_call_private $($caller:ident)::* ! { $($state:tt)* })
647 $($append:tt)*
648 } => {
649 $($caller)::* ! {
650 $($state)*
651 $($append)*
652 }
653 };
654}
655
656/// Evaluate a condition and expand to one or the other of two branches.
657///
658/// # Input
659///
660/// - `condition = [{` name of predicate macro to invoke `}]`
661/// - `input = [{` arbitrary tokens to pass as input to the predicate `}]`
662/// - `true = [{` tokens to expand to if the predicate returns true `}]`
663/// - `false = [{` and if the predicate returns false `}]`
664///
665/// The predicate macro must accept a single input value named `input`. It is
666/// expected to return a single output value which may have any name but must
667/// hold the tokens `true` or `false`. For example the built-in `tt_is_comma!`
668/// predicate expands to `is_comma = [{ true }]` or `is_comma = [{ false }]`.
669///
670/// # Example
671///
672/// ```
673/// use tt_call::{tt_call, tt_if, tt_is_comma, tt_return};
674///
675/// macro_rules! parse_until_comma {
676/// ($($input:tt)*) => {
677/// tt_call! {
678/// macro = [{ parse_until_comma_helper }]
679/// before_comma = [{ }]
680/// tokens = [{ $($input)* }]
681/// }
682/// };
683/// }
684///
685/// macro_rules! parse_until_comma_helper {
686/// {
687/// $caller:tt
688/// before_comma = [{ $($before:tt)* }]
689/// tokens = [{ $first:tt $($rest:tt)* }]
690/// } => {
691/// tt_if! {
692/// condition = [{ tt_is_comma }]
693/// input = [{ $first }]
694/// true = [{
695/// tt_return! {
696/// $caller
697/// before_comma = [{ $($before)* }]
698/// }
699/// }]
700/// false = [{
701/// parse_until_comma_helper! {
702/// $caller
703/// before_comma = [{ $($before)* $first }]
704/// tokens = [{ $($rest)* }]
705/// }
706/// }]
707/// }
708/// };
709/// }
710///
711/// fn main() {
712/// assert_eq!(3, parse_until_comma!(1 + 2, three, four));
713/// }
714/// ```
715#[macro_export]
716macro_rules! tt_if {
717 {
718 condition = [{ $($condition:ident)::* }]
719 input = [{ $($input:tt)* }]
720 true = [{ $($then:tt)* }]
721 false = [{ $($else:tt)* }]
722 } => {
723 $crate::tt_call! {
724 macro = [{ $($condition)::* }]
725 input = [{ $($input)* }]
726 ~~> $crate::private_if_branch! {
727 true = [{ $($then)* }]
728 false = [{ $($else)* }]
729 }
730 }
731 };
732}
733
734#[doc(hidden)]
735#[macro_export]
736macro_rules! private_if_branch {
737 // Branch condition returned true.
738 {
739 true = [{ $($then:tt)* }]
740 false = [{ $($else:tt)* }]
741 $condition:ident = [{ true }]
742 } => {
743 $($then)*
744 };
745
746 // Branch condition returned false.
747 {
748 true = [{ $($then:tt)* }]
749 false = [{ $($else:tt)* }]
750 $condition:ident = [{ false }]
751 } => {
752 $($else)*
753 };
754}
755
756/// Print arbitrary output values returned by a tt-call macro. This is valuable
757/// for debugging.
758/// <sup>**[tt-call]**</sup>
759///
760/// # Example
761///
762/// ```
763/// use tt_call::{parse_type, tt_call, tt_debug};
764///
765/// fn main() {
766/// tt_call! {
767/// macro = [{ parse_type }]
768/// input = [{ Vec<u8>, compressed=false }]
769/// ~~> tt_debug
770/// }
771/// }
772/// ```
773///
774/// The output is:
775///
776/// ```text
777/// type = [{ Vec < u8 > }]
778/// rest = [{ , compressed = false }]
779/// ```
780#[macro_export]
781macro_rules! tt_debug {
782 {
783 $(
784 $output:ident = [{ $($tokens:tt)* }]
785 )*
786 } => {
787 $(
788 println!(
789 "{}",
790 concat!(
791 stringify!($output),
792 " = [{ ",
793 stringify!($($tokens)*),
794 " }]",
795 )
796 );
797 )*
798 }
799}