Skip to main content

virtue_next/generate/
stream_builder.rs

1use crate::prelude::Delimiter;
2use crate::prelude::Group;
3use crate::prelude::Ident;
4use crate::prelude::LexError;
5use crate::prelude::Literal;
6use crate::prelude::Punct;
7use crate::prelude::Result;
8use crate::prelude::Spacing;
9use crate::prelude::Span;
10use crate::prelude::TokenStream;
11use crate::prelude::TokenTree;
12use std::str::FromStr;
13
14/// A helper struct build around a [`TokenStream`] to make it easier to build code.
15#[must_use]
16#[derive(Default)]
17pub struct StreamBuilder {
18    pub(crate) stream: TokenStream,
19}
20
21impl StreamBuilder {
22    /// Generate a new `StreamBuilder`
23    pub fn new() -> Self {
24        Self {
25            stream: TokenStream::new(),
26        }
27    }
28
29    /// Add multiple `TokenTree` items to the stream.
30    pub fn extend(
31        &mut self,
32        item: impl IntoIterator<Item = TokenTree>,
33    ) -> &mut Self {
34        self.stream.extend(item);
35        self
36    }
37
38    /// Append another `StreamBuilder` to the current `StreamBuilder`.
39    pub fn append(
40        &mut self,
41        builder: Self,
42    ) -> &mut Self {
43        self.stream.extend(builder.stream);
44        self
45    }
46
47    /// Push a single token to the stream.
48    ///
49    /// # Errors
50    ///
51    /// Returns an error if parsing fails.
52    pub fn push(
53        &mut self,
54        item: impl Into<TokenTree>,
55    ) -> &mut Self {
56        self.stream.extend([item.into()]);
57        self
58    }
59
60    /// Attempt to parse the given string as valid Rust code, and append the parsed result to the internal stream.
61    ///
62    /// Currently panics if the string could not be parsed as valid Rust code.
63    ///
64    /// # Errors
65    ///
66    /// Returns an error if parsing fails.
67    pub fn push_parsed(
68        &mut self,
69        item: impl AsRef<str>,
70    ) -> Result<&mut Self> {
71        let tokens = TokenStream::from_str(item.as_ref()).map_err(|e| {
72            PushParseError {
73                error: e,
74                code: item.as_ref().to_string(),
75            }
76        })?;
77        self.stream.extend(tokens);
78        Ok(self)
79    }
80
81    /// 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.
82    pub fn ident(
83        &mut self,
84        ident: Ident,
85    ) -> &mut Self {
86        self.stream.extend([TokenTree::Ident(ident)]);
87        self
88    }
89
90    /// # Errors
91    ///
92    /// Returns an error if the operation fails.
93
94    /// 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.
95    pub fn ident_str(
96        &mut self,
97        ident: impl AsRef<str>,
98    ) -> &mut Self {
99        self.stream.extend([TokenTree::Ident(Ident::new(
100            ident.as_ref(),
101            Span::call_site(),
102        ))]);
103        self
104    }
105
106    /// # Errors
107    ///
108    /// Returns an error if the operation fails.
109
110    /// Add a group. A group is any block surrounded by `{ .. }`, `[ .. ]` or `( .. )`.
111    ///
112    /// `delim` indicates which group it is. The `inner` callback is used to fill the contents of the group.
113    pub fn group<FN>(
114        &mut self,
115        delim: Delimiter,
116        inner: FN,
117    ) -> crate::Result<&mut Self>
118    where
119        FN: FnOnce(&mut Self) -> crate::Result<()>,
120    {
121        let mut stream = Self::new();
122        inner(&mut stream)?;
123        self.stream
124            .extend([TokenTree::Group(Group::new(delim, stream.stream))]);
125        Ok(self)
126    }
127
128    /// Add a single punctuation to the stream. Puncts are single-character tokens like `.`, `<`, `#`, etc
129    ///
130    /// Note that this should not be used for multi-punct constructions like `::` or `->`. For that use [`puncts`] instead.
131    ///
132    /// [`puncts`]: #method.puncts
133    pub fn punct(
134        &mut self,
135        p: char,
136    ) -> &mut Self {
137        self.stream
138            .extend([TokenTree::Punct(Punct::new(p, Spacing::Alone))]);
139        self
140    }
141
142    /// Add multiple punctuations to the stream. Multi punct tokens are e.g. `::`, `->` and `=>`.
143    ///
144    /// Note that this is the only way to add multi punct tokens.
145    /// 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.
146    pub fn puncts(
147        &mut self,
148        puncts: &str,
149    ) -> &mut Self {
150        self.stream.extend(
151            puncts
152                .chars()
153                .map(|char| TokenTree::Punct(Punct::new(char, Spacing::Joint))),
154        );
155        self
156    }
157
158    /// Add a lifetime to the stream.
159    ///
160    /// Note that this is the only way to add lifetimes, if you were to do:
161    /// ```ignore
162    /// builder.punct('\'');
163    /// builder.ident_str("static");
164    /// ```
165    /// It would not add `'static`, but instead it would add `' static` as seperate tokens, and the lifetime would not work.
166    pub fn lifetime(
167        &mut self,
168        lt: Ident,
169    ) -> &mut Self {
170        self.stream.extend([
171            TokenTree::Punct(Punct::new('\'', Spacing::Joint)),
172            TokenTree::Ident(lt),
173        ]);
174        self
175    }
176
177    /// Add a lifetime to the stream.
178    ///
179    /// Note that this is the only way to add lifetimes, if you were to do:
180    /// ```ignore
181    /// builder.punct('\'');
182    /// builder.ident_str("static");
183    /// ```
184    /// It would not add `'static`, but instead it would add `' static` as seperate tokens, and the lifetime would not work.
185    pub fn lifetime_str(
186        &mut self,
187        lt: &str,
188    ) -> &mut Self {
189        self.stream.extend([
190            TokenTree::Punct(Punct::new('\'', Spacing::Joint)),
191            TokenTree::Ident(Ident::new(lt, Span::call_site())),
192        ]);
193        self
194    }
195
196    /// Add a literal string (`&'static str`) to the stream.
197    pub fn lit_str(
198        &mut self,
199        str: impl AsRef<str>,
200    ) -> &mut Self {
201        self.stream
202            .extend([TokenTree::Literal(Literal::string(str.as_ref()))]);
203        self
204    }
205
206    /// Add an `usize` value to the stream.
207    pub fn lit_usize(
208        &mut self,
209        val: usize,
210    ) -> &mut Self {
211        self.stream
212            .extend([TokenTree::Literal(Literal::usize_unsuffixed(val))]);
213        self
214    }
215
216    /// 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.
217    ///
218    /// Normally your derive will report an error on the derive, e.g.:
219    ///
220    /// ```text
221    /// #[derive(YourMacro)]
222    ///          ^^^^^^^^^
223    ///          |
224    ///          `self` value is a keyword only available in methods with a `self` parameter
225    /// ```
226    ///
227    /// 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.
228    ///
229    /// A `span` can be obtained from e.g. an ident with `ident.span()`.
230    pub fn set_span_on_all_tokens(
231        &mut self,
232        span: Span,
233    ) {
234        self.stream = std::mem::take(&mut self.stream)
235            .into_iter()
236            .map(|mut token| {
237                token.set_span(span);
238                token
239            })
240            .collect();
241    }
242}
243
244/// Failed to parse the code passed to [`StreamBuilder::push_parsed`]
245///
246/// [`StreamBuilder::push_parsed`]: struct.StreamBuilder.html#method.push_parsed
247#[derive(Debug)]
248pub struct PushParseError {
249    /// The parsing error
250    pub error: LexError,
251    /// The code that was being parsed
252    pub code: String,
253}