qemit 0.1.0

A minimalist quasi-quoting library using declarative macros
Documentation
#![warn(missing_docs)]
//! # qemit
//!
//! `qemit` is a minimal quasi-quoting library designed as a lightweight alternative to `quote`.
//! It allows for the construction of `proc_macro2::TokenStream` objects using a declarative
//! syntax, including support for variable interpolation using the `~` operator.
//!
//! ## Core Concepts
//!
//! The library revolves around the `qemit!` macro, which consumes standard Rust tokens
//! and converts them into a `TokenStream`.
//!
//! ### Interpolation
//! You can inject external variables into the stream using the tilde (`~`) operator.
//! Any type implementing the [`ToTokens`] trait can be interpolated.
//!
//! ```rust
//! use qemit::qemit;
//! let name = "World";
//! let tokens = qemit! { println!("Hello, ~name"); };
//! ```
//!
//! ### Nested Groups
//! The macro recursively handles groups—tokens enclosed in `()`, `{}`, or `[]`.
//! This ensures that interpolation works correctly even deep inside function bodies
//! or complex expressions.
//!
//! ## Technical Implementation
//! `qemit` uses a "sliding window" declarative macro technique. It processes tokens
//! by looking at the current token tree (`tt`) and its immediate neighbors to
//! identify the `~ $var` pattern without requiring a procedural macro for the
//! transformation logic.

pub use proc_macro2;
use proc_macro2::TokenStream;

/// A trait for types that can be converted into a sequence of token trees.
///
/// This trait is used by the `qemit!` macro to interpolate variables into the
/// generated `TokenStream`.
pub trait ToTokens {
    /// Appends the token representation of `self` to the given `TokenStream`.
    fn to_tokens(&self, tokens: &mut TokenStream);
}

// ----------------------------------------------------------------
// Implementations for common proc_macro2 types
// ----------------------------------------------------------------

impl ToTokens for proc_macro2::Ident {
    /// Converts an identifier into a `TokenTree::Ident` and appends it.
    fn to_tokens(&self, tokens: &mut TokenStream) {
        tokens.extend(std::iter::once(proc_macro2::TokenTree::Ident(self.clone())));
    }
}

impl ToTokens for proc_macro2::Literal {
    /// Converts a literal into a `TokenTree::Literal` and appends it.
    fn to_tokens(&self, tokens: &mut TokenStream) {
        tokens.extend(std::iter::once(proc_macro2::TokenTree::Literal(
            self.clone(),
        )));
    }
}

impl ToTokens for proc_macro2::Group {
    /// Converts a delimited group into a `TokenTree::Group` and appends it.
    fn to_tokens(&self, tokens: &mut TokenStream) {
        tokens.extend(std::iter::once(proc_macro2::TokenTree::Group(self.clone())));
    }
}

impl ToTokens for TokenStream {
    /// Appends the entire contents of the `TokenStream` to the destination.
    fn to_tokens(&self, tokens: &mut TokenStream) {
        tokens.extend(self.clone());
    }
}

/// The primary entry point for quasi-quoting.
///
/// This macro generates a `proc_macro2::TokenStream` from the provided tokens.
/// You can interpolate variables using the `~` prefix.
///
/// # Examples
///
/// Basic usage:
/// ```
/// use qemit::qemit;
/// let tokens = qemit! { fn hello() {} };
/// ```
///
/// Interpolating a variable:
/// ```
/// use qemit::qemit;
/// let name = proc_macro2::Ident::new("world", proc_macro2::Span::call_site());
/// let tokens = qemit! { fn ~name() {} };
/// ```
#[macro_export]
macro_rules! qemit {
    () => {
        ::qemit::proc_macro2::TokenStream::new()
    };

    (~ $var:ident) => {{
        let mut _s = ::proc_macro2::TokenStream::new();
        $crate::ToTokens::to_tokens(&$var, &mut _s);
        _s
    }};

    ($($tt:tt)*) => {{
        let mut _s = ::qemit::proc_macro2::TokenStream::new();
        $crate::qemit_each_token!(_s $($tt)*);
        _s
    }};
}

#[macro_export]
#[doc(hidden)]
macro_rules! qemit_each_token {
    ($tokens:ident $($tts:tt)*) => {
        $crate::qemit_tokens_with_context!{ $tokens
            (@ @ @ @ @ @ $($tts)* )
            (@ @ @ @ @   $($tts)* @   )
            (@ @ @ @     $($tts)* @ @ )
            (@ @ @     $(($tts))* @ @ @)
            (@ @         $($tts)* @ @ @ @  )
            (@           $($tts)* @ @ @ @ @)
            (            $($tts)* @ @ @ @ @ @)
        }
    };
}

#[macro_export]
#[doc(hidden)]
macro_rules! qemit_tokens_with_context {
    (
        $tokens:ident
        ($($b3:tt)*)
        ($($b2:tt)*)
        ($($b1:tt)*)
        ($($curr:tt)*)
        ($($a1:tt)*)
        ($($a2:tt)*)
        ($($a3:tt)*)
    ) => {
        $(
            $crate::__qemit_token_with_context!(
                $tokens $b3 $b2 $b1 $curr $a1 $a2 $a3
            );
        )*
    };
}

#[macro_export]
#[doc(hidden)]
macro_rules! __qemit_token_with_context {
    ($tokens:ident $b3:tt $b2:tt $b1:tt @ $a1:tt $a2:tt $a3:tt) => {};

    ($tokens:ident $b3:tt $b2:tt $b1:tt (~) $var:ident $a2:tt $a3:tt) => {
        $crate::ToTokens::to_tokens(&$var, &mut $tokens);
    };

    ($tokens:ident $b3:tt $b2:tt ~ ($var:ident) $a1:tt $a2:tt $a3:tt) => {};

    ($tokens:ident $b3:tt $b2:tt $b1:tt ({ $($inner:tt)* }) $a1:tt $a2:tt $a3:tt) => {
        let mut inner_ts = ::qemit::proc_macro2::TokenStream::new();
        $crate::qemit_each_token!(inner_ts $($inner)*);
        $tokens.extend(std::iter::once(::qemit::proc_macro2::TokenTree::Group(
            ::qemit::proc_macro2::Group::new(::qemit::proc_macro2::Delimiter::Brace, inner_ts)
        )));
    };

    ($tokens:ident $b3:tt $b2:tt $b1:tt (( $($inner:tt)* )) $a1:tt $a2:tt $a3:tt) => {
        let mut inner_ts = ::qemit::proc_macro2::TokenStream::new();
        $crate::qemit_each_token!(inner_ts $($inner)*);
        $tokens.extend(std::iter::once(::qemit::proc_macro2::TokenTree::Group(
            ::qemit::proc_macro2::Group::new(::qemit::proc_macro2::Delimiter::Parenthesis, inner_ts)
        )));
    };

    ($tokens:ident $b3:tt $b2:tt $b1:tt ([ $($inner:tt)* ]) $a1:tt $a2:tt $a3:tt) => {
        let mut inner_ts = ::qemit::proc_macro2::TokenStream::new();
        $crate::qemit_each_token!(inner_ts $($inner)*);
        $tokens.extend(std::iter::once(::qemit::proc_macro2::TokenTree::Group(
            ::qemit::proc_macro2::Group::new(::qemit::proc_macro2::Delimiter::Bracket, inner_ts)
        )));
    };

    ($tokens:ident $b3:tt $b2:tt $b1:tt ($curr:tt) $a1:tt $a2:tt $a3:tt) => {{
        let text = stringify!($curr);
        let frag: ::proc_macro2::TokenStream = text
            .parse()
            .expect("qemit: failed to re-parse token, this is a bug");
        $tokens.extend(frag);
    }};
}