repetitive 0.2.0

Macro for generating repetitive code
Documentation
//! `repetitive!` macro crate.

#![deny(missing_docs)]

mod context;
mod expr;
mod fragment;
mod stages;
mod tokens;
use context::*;
use expr::*;
use fragment::*;
use stages::*;
use tokens::*;

/// The macro!
///
/// This macro emits the exact same code it gets,
/// except for when you use fragments through the `@` prefix.
///
/// # Fragments
///
/// ### For Loop
///
/// Emits the body per value of the specified list.
///
/// Syntax:
/// `@for <pattern> in <list> { <code> }`
///
/// Also accepts multiple patterns separated by commas.
///
/// ```rust
/// @for a in [...], b in [...] {
///     <code>
/// }
/// ```
///
/// Acts like:
/// ```rust
/// @for a in [...] {
///     @for b in [...] {
///         <code>
///     }
/// }
/// ```
///
/// Example:
/// ```rust
/// @for Color in ['Red, 'Green, 'Blue] {
///     struct @Color;
/// }
/// ```
///
/// ### Concat
///
/// Concats multiple identifiers into one.
///
/// Syntax:
/// `@[<expr> <expr>...]`
///
/// Also accepts `@str[...]` which emits a string literal instead of an identifier.
///
/// Example:
/// ```rust
/// @for N in 2..=4 {
///     struct @['Vec N];
/// }
/// ```
///
/// ### Let Statement
///
/// Binds a value to a name.
///
/// Syntax:
/// `@let <pattern> = <expr>;`
///
/// Example:
/// ```rust
/// @for N in 2..=4 {
///     @let VecN = @['Vec N];
///
///     struct @VecN;
/// }
/// ```
///
/// ### If Statement
///
/// Emits the body if the condition is true.
///
/// Syntax:
/// `@if <expr> { <code> }`
///
/// Also accepts `@if <expr> { <code> } else { <code> }`.
///
/// Example:
/// ```rust
/// @for a in 0..5, b in 0..5 {
///     @if a != b {
///         println!("{} != {}", a, b);
///     }
/// }
/// ```
///
/// ### Expression
///
/// Emits the result of an expression.
///
/// Syntax:
/// `@(<expr>)`
///
/// Example:
/// ```rust
/// @for idx in 0..5 {
///     example::<@(idx + 1)>();
/// }
/// ```
///
/// ### Tokens
///
/// Stores tokens as a fragment value.
///
/// Syntax:
/// `@{ <token> }`
///
/// Example:
/// ```rust
/// @let tokens = @{ @frag + 1 };
///
/// @for frag in [1, 2, 3] {
///     println!("{}", @tokens);
/// }
/// ```
///
/// # Expressions
///
/// Expressions can evaluate to these types:
/// - bool lit,
/// - int lit,
/// - float lit,
/// - char lit,
/// - string lit,
/// - ident,
/// - list,
/// - tokenstream.
///
/// An expression can be:
/// - a literal: `1`, `1.0`, `'a'`, `"hello"`, `true`, `false`,
/// - an "ident literal": `'Ident`, `~Ident` (useful for declarative macros. `~$ident_lit_frag`),
/// - a list: `[... <expr> ...]`,
/// - a name: `name`,
/// - a fragment: `@{...}`,
/// - if else: `@if <expr> { <expr> } else { <expr> }`,
/// - an operator: `<expr> + <expr>`, `!<expr>`,
/// - a method call: `<expr>.<method>(<expr>)`.
///
/// These operators are supported:
/// - add `+`, sub `-`, mul `*`, div `/`, rem `%`, neg `-`,
/// - bitand `&`, bitor `|`, bitxor `^`, shl `<<`, shr `>>`,
/// - eq `==`, ne `!=`, lt `<`, le `<=`, gt `>`, ge `>=`,
/// - and `&&`, or `||`, not `!`,
/// - range `..`, range_inclusive `..=`.
///
/// Supported methods are known by auto-completion.
///
/// # Match
///
/// Can be used as an expression or as a fragment.
///
/// Example:
/// ```rust
/// @for weird in [
///     "Weird",
///     [1, 2, 3],
///     @{ @frag + 1 },
/// ] {
///     @match weird {
///         "Weird" => { println!("Its a string!") },
///         [1, 2, _] => { println!("It starts with 1 and 2!") },
///         something_else => { println!("Its something else!") },
///     }
/// }
/// ```
#[proc_macro]
pub fn repetitive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    main::repetitive(input.into()).into()
}

mod main {
    use proc_macro2::TokenStream;
    use quote::quote;

    use super::*;

    pub fn repetitive(input: TokenStream) -> TokenStream {
        let mut ctx = Context::new();

        let tokens = 'tokens: {
            let tokens = match Tokens::ctx_parse.ctx_parse2(input.into(), &mut ctx) {
                Ok(tokens) => tokens,
                Err(err) => {
                    ctx.push_error(err);
                    break 'tokens Err(());
                }
            };

            let mut output = TokenStream::new();
            tokens.expand(&mut output, &mut ctx, &mut Namespace::new());

            if ctx.has_errors() {
                break 'tokens Err(());
            }

            Ok(output)
        };

        let tokens = match tokens {
            Ok(output) => output,
            Err(()) => ctx
                .take_errors()
                .collect::<Vec<_>>()
                .into_iter()
                .map(|err| err.into_compile_error(&mut ctx))
                .collect(),
        };

        let warnings = ctx
            .take_warnings()
            .map(|warning| warning.into_compile_error());

        #[cfg(feature = "doc")]
        let doc = paste_method_doc(ctx.get_method_calls());

        #[cfg(not(feature = "doc"))]
        let doc = TokenStream::new();

        quote! {
            #tokens

            #(#warnings)*

            #doc
        }
        .into()
    }
}