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}