token_goblin_runtime/
ux.rs1use core::fmt::{self, Display};
49use std::{cell::RefCell, fmt::Debug, str::FromStr};
50
51use proc_macro2::{Span, TokenStream};
52use quote::ToTokens;
53use syn::parse::{Parse, ParseStream, Parser};
54
55#[derive(Clone)]
70pub struct SnifedEntry {
71 pub snif_path: syn::Path,
73 arrow: syn::Token![=>],
74 brace: syn::token::Brace,
75 pub item: syn::Item,
77}
78#[derive(Clone)]
92pub struct SnifedEntries {
93 first_group: syn::token::Bracket,
94 pub entries: Vec<SnifedEntry>,
95 second_group: syn::token::Bracket,
96 pub macro_input: TokenStream,
97}
98impl SnifedEntries {
99 #[must_use]
100 pub fn span(&self) -> proc_macro2::Span {
101 self.entries
102 .first()
103 .map_or_else(Span::call_site, SnifedEntry::span)
104 }
105}
106impl SnifedEntry {
107 #[must_use]
108 pub fn span(&self) -> proc_macro2::Span {
109 self.snif_path
110 .segments
111 .first()
112 .map_or_else(Span::call_site, |segment| segment.ident.span())
113 }
114}
115pub struct CommaSeparated<T>(pub Vec<T>);
133
134impl From<CommaSeparated<Token>> for Vec<String> {
135 fn from(value: CommaSeparated<Token>) -> Self {
136 value.0.into_iter().map(|t| t.to_string()).collect()
137 }
138}
139
140pub enum Token {
157 Ident(syn::Ident),
158 Literal(syn::LitStr),
159}
160impl Display for Token {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 match self {
163 Token::Ident(ident) => write!(f, "{ident}"),
164 Token::Literal(literal) => write!(f, "{}", literal.value()),
165 }
166 }
167}
168
169impl Debug for Token {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 match self {
172 Token::Ident(ident) => write!(f, "Ident({ident:?})"),
173 Token::Literal(literal) => write!(f, "Literal({:?})", literal.value()),
174 }
175 }
176}
177impl PartialEq<&str> for Token {
178 fn eq(&self, other: &&str) -> bool {
179 match self {
180 Token::Ident(ident) => ident == *other,
181 Token::Literal(literal) => literal.value() == *other,
183 }
184 }
185}
186
187#[doc(hidden)] pub trait TokenStreamInto<T> {
189 fn convert_token_stream(self) -> syn::Result<T>;
190}
191impl<T: syn::parse::Parse> TokenStreamInto<T> for TokenStream {
192 fn convert_token_stream(self) -> syn::Result<T> {
193 T::parse.parse2(self)
194 }
195}
196
197pub trait IntoTokenStream {
206 fn into_token_stream(self) -> TokenStream;
207}
208
209impl IntoTokenStream for String {
210 fn into_token_stream(self) -> TokenStream {
211 TokenStream::from_str(&self).unwrap_or_else(|e| {
212 compile_error(&format!("Failed to convert String to TokenStream: {e}"))
213 })
214 }
215}
216impl IntoTokenStream for TokenStream {
217 fn into_token_stream(self) -> TokenStream {
218 self
219 }
220}
221impl IntoTokenStream for () {
222 fn into_token_stream(self) -> TokenStream {
223 TokenStream::new()
224 }
225}
226
227fn compile_error(text: &str) -> TokenStream {
228 quote::quote! {
229 ::core::compile_error!(#text)
230 }
231}
232
233#[macro_export]
249macro_rules! output_str {
250 ($($tokens:tt)*) => {
251 $crate::ux::push_output(format!($($tokens)*));
252 };
253}
254
255#[macro_export]
281macro_rules! output {
282 ($($tokens:tt)*) => {
283 $crate::ux::push_output($crate::prelude::quote!($($tokens)*));
284 };
285}
286
287thread_local! {
288 static COLLECTED_OUTPUT: RefCell<TokenStream> = RefCell::new(TokenStream::new());
289}
290
291pub fn push_output(output: impl IntoTokenStream) {
296 COLLECTED_OUTPUT.with(|collected_output| {
297 collected_output
298 .borrow_mut()
299 .extend(output.into_token_stream());
300 });
301}
302
303#[doc(hidden)]
304#[must_use]
305pub(crate) fn flush_output(last_part: TokenStream) -> TokenStream {
306 COLLECTED_OUTPUT.with(|collected_output| {
307 let mut collected_output = std::mem::take(&mut *collected_output.borrow_mut());
308 collected_output.extend(last_part);
309 collected_output
310 })
311}
312
313impl Parse for Token {
314 fn parse(input: ParseStream) -> syn::Result<Self> {
315 if input.peek(syn::Ident) {
316 Ok(Token::Ident(input.parse()?))
317 } else if input.peek(syn::LitStr) {
318 Ok(Token::Literal(input.parse()?))
319 } else {
320 Err(syn::Error::new(input.span(), "Expected ident or literal"))
321 }
322 }
323}
324
325impl<T: Parse> Parse for CommaSeparated<T> {
326 fn parse(input: ParseStream) -> syn::Result<Self> {
327 let parser = syn::punctuated::Punctuated::<T, syn::Token![,]>::parse_terminated;
328 let components = parser(input)?;
329 Ok(CommaSeparated(components.into_iter().collect()))
330 }
331}
332
333impl syn::parse::Parse for SnifedEntry {
334 fn parse(input: ParseStream) -> syn::Result<Self> {
335 let path = syn::Path::parse_mod_style(input)?;
338
339 let arrow = input.parse()?;
340
341 let content;
342 let brace = syn::braced!(content in input);
343 let item = content.parse()?;
344
345 Ok(SnifedEntry {
346 snif_path: path,
347 arrow,
348 brace,
349 item,
350 })
351 }
352}
353impl syn::parse::Parse for SnifedEntries {
354 fn parse(input: ParseStream) -> syn::Result<Self> {
355 let items_input;
356 let first_group = syn::bracketed!(items_input in input);
357 let mut items = Vec::new();
358 while !items_input.is_empty() {
359 items.push(SnifedEntry::parse(&items_input)?);
360 }
361 let macro_input;
362 let second_group = syn::bracketed!(macro_input in input);
363
364 Ok(SnifedEntries {
365 first_group,
366 entries: items,
367 second_group,
368 macro_input: macro_input.parse()?,
369 })
370 }
371}
372impl ToTokens for SnifedEntry {
373 fn to_tokens(&self, tokens: &mut TokenStream) {
374 self.snif_path.to_tokens(tokens);
375 self.arrow.to_tokens(tokens);
376 self.brace.surround(tokens, |tokens| {
377 self.item.to_tokens(tokens);
378 });
379 }
380}
381impl ToTokens for SnifedEntries {
382 fn to_tokens(&self, tokens: &mut TokenStream) {
383 self.first_group.surround(tokens, |tokens| {
384 for item in &self.entries {
385 item.to_tokens(tokens);
386 }
387 });
388 self.second_group.surround(tokens, |tokens| {
389 self.macro_input.to_tokens(tokens);
390 });
391 }
392}
393#[cfg(test)]
394mod tests {
395 use std::str::FromStr;
396
397 use super::*;
398
399 #[test]
400 fn test_parse_string() {
401 let tokens = TokenStream::from_str(" \"123\" ").unwrap();
402 let into: Token = tokens.convert_token_stream().unwrap();
403 assert_eq!(into.to_string(), "123");
404 }
405 #[test]
406 fn test_parse_vec() {
407 let tokens = TokenStream::from_str(" \"1\", \"2\", \"3\" ").unwrap();
408 let into: CommaSeparated<Token> = tokens.convert_token_stream().unwrap();
409 assert_eq!(into.0, vec!["1", "2", "3"]);
410 }
411
412 #[test]
413 fn test_parse_tts() {
414 let tokens = TokenStream::from_str("123").unwrap();
415 let into: TokenStream = tokens.clone().convert_token_stream().unwrap();
416 assert_eq!(into.to_string(), tokens.to_string());
417 }
418
419 #[test]
420 fn test_parse_syn_type() {
421 let tokens = TokenStream::from_str("asd").unwrap();
422 let into: syn::Ident = tokens.convert_token_stream().unwrap();
423 assert_eq!(into.to_string(), "asd");
424 }
425
426 #[test]
427 fn test_streaming_output() {
428 output_str!("foo");
429 output_str!("bar");
430 output! {
431 "baz" };
433 let output = flush_output(TokenStream::from_str("qux").unwrap());
434 assert_eq!(output.to_string(), "foo bar \"baz\" qux");
435 }
436}