virtue-next 0.1.3

A sinless derive macro helper
Documentation
use crate::prelude::Delimiter;
use crate::prelude::Group;
use crate::prelude::Ident;
use crate::prelude::LexError;
use crate::prelude::Literal;
use crate::prelude::Punct;
use crate::prelude::Result;
use crate::prelude::Spacing;
use crate::prelude::Span;
use crate::prelude::TokenStream;
use crate::prelude::TokenTree;
use std::str::FromStr;

/// A helper struct build around a [`TokenStream`] to make it easier to build code.
#[must_use]
#[derive(Default)]
pub struct StreamBuilder {
    pub(crate) stream: TokenStream,
}

impl StreamBuilder {
    /// Generate a new `StreamBuilder`
    pub fn new() -> Self {
        Self {
            stream: TokenStream::new(),
        }
    }

    /// Add multiple `TokenTree` items to the stream.
    pub fn extend(
        &mut self,
        item: impl IntoIterator<Item = TokenTree>,
    ) -> &mut Self {
        self.stream.extend(item);
        self
    }

    /// Append another `StreamBuilder` to the current `StreamBuilder`.
    pub fn append(
        &mut self,
        builder: Self,
    ) -> &mut Self {
        self.stream.extend(builder.stream);
        self
    }

    /// Push a single token to the stream.
    ///
    /// # Errors
    ///
    /// Returns an error if parsing fails.
    pub fn push(
        &mut self,
        item: impl Into<TokenTree>,
    ) -> &mut Self {
        self.stream.extend([item.into()]);
        self
    }

    /// Attempt to parse the given string as valid Rust code, and append the parsed result to the internal stream.
    ///
    /// Currently panics if the string could not be parsed as valid Rust code.
    ///
    /// # Errors
    ///
    /// Returns an error if parsing fails.
    pub fn push_parsed(
        &mut self,
        item: impl AsRef<str>,
    ) -> Result<&mut Self> {
        let tokens = TokenStream::from_str(item.as_ref()).map_err(|e| {
            PushParseError {
                error: e,
                code: item.as_ref().to_string(),
            }
        })?;
        self.stream.extend(tokens);
        Ok(self)
    }

    /// Push a single ident to the stream. An ident is any word that a code file may contain, e.g. `fn`, `struct`, `where`, names of functions and structs, etc.
    pub fn ident(
        &mut self,
        ident: Ident,
    ) -> &mut Self {
        self.stream.extend([TokenTree::Ident(ident)]);
        self
    }

    /// # Errors
    ///
    /// Returns an error if the operation fails.

    /// Push a single ident to the stream. An ident is any word that a code file may contain, e.g. `fn`, `struct`, `where`, names of functions and structs, etc.
    pub fn ident_str(
        &mut self,
        ident: impl AsRef<str>,
    ) -> &mut Self {
        self.stream.extend([TokenTree::Ident(Ident::new(
            ident.as_ref(),
            Span::call_site(),
        ))]);
        self
    }

    /// # Errors
    ///
    /// Returns an error if the operation fails.

    /// Add a group. A group is any block surrounded by `{ .. }`, `[ .. ]` or `( .. )`.
    ///
    /// `delim` indicates which group it is. The `inner` callback is used to fill the contents of the group.
    pub fn group<FN>(
        &mut self,
        delim: Delimiter,
        inner: FN,
    ) -> crate::Result<&mut Self>
    where
        FN: FnOnce(&mut Self) -> crate::Result<()>,
    {
        let mut stream = Self::new();
        inner(&mut stream)?;
        self.stream
            .extend([TokenTree::Group(Group::new(delim, stream.stream))]);
        Ok(self)
    }

    /// Add a single punctuation to the stream. Puncts are single-character tokens like `.`, `<`, `#`, etc
    ///
    /// Note that this should not be used for multi-punct constructions like `::` or `->`. For that use [`puncts`] instead.
    ///
    /// [`puncts`]: #method.puncts
    pub fn punct(
        &mut self,
        p: char,
    ) -> &mut Self {
        self.stream
            .extend([TokenTree::Punct(Punct::new(p, Spacing::Alone))]);
        self
    }

    /// Add multiple punctuations to the stream. Multi punct tokens are e.g. `::`, `->` and `=>`.
    ///
    /// Note that this is the only way to add multi punct tokens.
    /// If you were to use [`Punct`] to insert `->` it would be inserted as `-` and then `>`, and not form a single token. Rust would interpret this as a "minus sign and then a greater than sign", not as a single arrow.
    pub fn puncts(
        &mut self,
        puncts: &str,
    ) -> &mut Self {
        self.stream.extend(
            puncts
                .chars()
                .map(|char| TokenTree::Punct(Punct::new(char, Spacing::Joint))),
        );
        self
    }

    /// Add a lifetime to the stream.
    ///
    /// Note that this is the only way to add lifetimes, if you were to do:
    /// ```ignore
    /// builder.punct('\'');
    /// builder.ident_str("static");
    /// ```
    /// It would not add `'static`, but instead it would add `' static` as seperate tokens, and the lifetime would not work.
    pub fn lifetime(
        &mut self,
        lt: Ident,
    ) -> &mut Self {
        self.stream.extend([
            TokenTree::Punct(Punct::new('\'', Spacing::Joint)),
            TokenTree::Ident(lt),
        ]);
        self
    }

    /// Add a lifetime to the stream.
    ///
    /// Note that this is the only way to add lifetimes, if you were to do:
    /// ```ignore
    /// builder.punct('\'');
    /// builder.ident_str("static");
    /// ```
    /// It would not add `'static`, but instead it would add `' static` as seperate tokens, and the lifetime would not work.
    pub fn lifetime_str(
        &mut self,
        lt: &str,
    ) -> &mut Self {
        self.stream.extend([
            TokenTree::Punct(Punct::new('\'', Spacing::Joint)),
            TokenTree::Ident(Ident::new(lt, Span::call_site())),
        ]);
        self
    }

    /// Add a literal string (`&'static str`) to the stream.
    pub fn lit_str(
        &mut self,
        str: impl AsRef<str>,
    ) -> &mut Self {
        self.stream
            .extend([TokenTree::Literal(Literal::string(str.as_ref()))]);
        self
    }

    /// Add an `usize` value to the stream.
    pub fn lit_usize(
        &mut self,
        val: usize,
    ) -> &mut Self {
        self.stream
            .extend([TokenTree::Literal(Literal::usize_unsuffixed(val))]);
        self
    }

    /// Set the given span on all tokens in the stream. This span is used by rust for e.g. compiler errors, to indicate the position of the error.
    ///
    /// Normally your derive will report an error on the derive, e.g.:
    ///
    /// ```text
    /// #[derive(YourMacro)]
    ///          ^^^^^^^^^
    ///          |
    ///          `self` value is a keyword only available in methods with a `self` parameter
    /// ```
    ///
    /// If you want to improve feedback to the user of your macro, you can use this macro to set the location for a given streambuilder.
    ///
    /// A `span` can be obtained from e.g. an ident with `ident.span()`.
    pub fn set_span_on_all_tokens(
        &mut self,
        span: Span,
    ) {
        self.stream = std::mem::take(&mut self.stream)
            .into_iter()
            .map(|mut token| {
                token.set_span(span);
                token
            })
            .collect();
    }
}

/// Failed to parse the code passed to [`StreamBuilder::push_parsed`]
///
/// [`StreamBuilder::push_parsed`]: struct.StreamBuilder.html#method.push_parsed
#[derive(Debug)]
pub struct PushParseError {
    /// The parsing error
    pub error: LexError,
    /// The code that was being parsed
    pub code: String,
}