ufmt_macros/
lib.rs

1//! `μfmt` macros
2
3#![deny(warnings)]
4
5extern crate proc_macro;
6
7use core::mem;
8use proc_macro::TokenStream;
9use std::borrow::Cow;
10use std::cmp::Ordering;
11
12use proc_macro2::{Literal, Span};
13use quote::quote;
14use syn::{
15    parse::{self, Parse, ParseStream},
16    parse_macro_input, parse_quote,
17    punctuated::Punctuated,
18    spanned::Spanned,
19    Data, DeriveInput, Expr, Fields, GenericParam, Ident, LitStr, Token,
20};
21
22/// Automatically derive the `uDebug` trait for a `struct` or `enum`
23///
24/// Supported items
25///
26/// - all kind of `struct`-s
27/// - all kind of `enum`-s
28///
29/// `union`-s are not supported
30#[proc_macro_derive(uDebug)]
31pub fn debug(input: TokenStream) -> TokenStream {
32    let input = parse_macro_input!(input as DeriveInput);
33
34    let mut generics = input.generics;
35
36    for param in &mut generics.params {
37        if let GenericParam::Type(type_param) = param {
38            type_param.bounds.push(parse_quote!(ufmt::uDebug));
39        }
40    }
41
42    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
43
44    let ident = &input.ident;
45    let ts = match input.data {
46        Data::Struct(data) => {
47            let ident_s = ident.to_string();
48
49            let body = match data.fields {
50                Fields::Named(fields) => {
51                    let fields = fields
52                        .named
53                        .iter()
54                        .map(|field| {
55                            let ident = field.ident.as_ref().expect("UNREACHABLE");
56                            let name = ident.to_string();
57
58                            quote!(field(#name, &self.#ident)?)
59                        })
60                        .collect::<Vec<_>>();
61
62                    quote!(f.debug_struct(#ident_s)?#(.#fields)*.finish())
63                }
64
65                Fields::Unnamed(fields) => {
66                    let fields = (0..fields.unnamed.len())
67                        .map(|i| {
68                            let i = Literal::u64_unsuffixed(i as u64);
69
70                            quote!(field(&self.#i)?)
71                        })
72                        .collect::<Vec<_>>();
73
74                    quote!(f.debug_tuple(#ident_s)?#(.#fields)*.finish())
75                }
76
77                Fields::Unit => quote!(f.write_str(#ident_s)),
78            };
79
80            quote!(
81                impl #impl_generics ufmt::uDebug for #ident #ty_generics #where_clause {
82                    fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> core::result::Result<(), W::Error>
83                    where
84                        W: ufmt::uWrite + ?Sized,
85                    {
86                        #body
87                    }
88                }
89
90            )
91        }
92
93        Data::Enum(data) => {
94            let arms = data
95                .variants
96                .iter()
97                .map(|var| {
98                    let variant = &var.ident;
99                    let variant_s = variant.to_string();
100
101                    match &var.fields {
102                        Fields::Named(fields) => {
103                            let mut pats = Vec::with_capacity(fields.named.len());
104                            let mut methods = Vec::with_capacity(fields.named.len());
105                            for field in &fields.named {
106                                let ident = field.ident.as_ref().unwrap();
107                                let ident_s = ident.to_string();
108
109                                pats.push(quote!(#ident));
110                                methods.push(quote!(field(#ident_s, #ident)?));
111                            }
112
113                            quote!(
114                                #ident::#variant { #(#pats),* } => {
115                                    f.debug_struct(#variant_s)?#(.#methods)*.finish()
116                                }
117                            )
118                        }
119
120                        Fields::Unnamed(fields) => {
121                            let pats = &(0..fields.unnamed.len())
122                                .map(|i| Ident::new(&format!("_{}", i), Span::call_site()))
123                                .collect::<Vec<_>>();
124
125                            quote!(
126                                #ident::#variant(#(#pats),*) => {
127                                    f.debug_tuple(#variant_s)?#(.field(#pats)?)*.finish()
128                                }
129                            )
130                        }
131
132                        Fields::Unit => quote!(
133                            #ident::#variant => {
134                                f.write_str(#variant_s)
135                            }
136                        ),
137                    }
138                })
139                .collect::<Vec<_>>();
140
141            let body = if arms.is_empty() {
142                // Debug's implementation uses `::core::intrinsics::unreachable()`
143                quote!(unsafe { core::unreachable!() })
144            } else {
145                quote!(
146                    match self {
147                        #(#arms),*
148                    }
149                )
150            };
151
152            quote!(
153                impl #impl_generics ufmt::uDebug for #ident #ty_generics #where_clause {
154                    fn fmt<W>(&self, f: &mut ufmt::Formatter<'_, W>) -> core::result::Result<(), W::Error>
155                        where
156                        W: ufmt::uWrite + ?Sized,
157                    {
158                        #body
159                    }
160                }
161            )
162        }
163
164        Data::Union(..) => {
165            return parse::Error::new(Span::call_site(), "this trait cannot be derived for unions")
166                .to_compile_error()
167                .into();
168        }
169    };
170
171    ts.into()
172}
173
174#[proc_macro]
175pub fn uwrite(input: TokenStream) -> TokenStream {
176    write(input, false)
177}
178
179#[proc_macro]
180pub fn uwriteln(input: TokenStream) -> TokenStream {
181    write(input, true)
182}
183
184fn write(input: TokenStream, newline: bool) -> TokenStream {
185    let input = parse_macro_input!(input as Input);
186
187    let formatter = &input.formatter;
188    let literal = input.literal;
189
190    let mut format = literal.value();
191    if newline {
192        format.push('\n');
193    }
194    let pieces = match parse(&format, literal.span()) {
195        Err(e) => return e.to_compile_error().into(),
196        Ok(pieces) => pieces,
197    };
198
199    let required_args = pieces.iter().filter(|piece| !piece.is_str()).count();
200    let supplied_args = input.args.len();
201    match supplied_args.cmp(&required_args) {
202        Ordering::Less => {
203            return parse::Error::new(
204                literal.span(),
205                &format!(
206                    "format string requires {} arguments but {} {} supplied",
207                    required_args,
208                    supplied_args,
209                    if supplied_args == 1 { "was" } else { "were" }
210                ),
211            )
212            .to_compile_error()
213            .into();
214        }
215        Ordering::Greater => {
216            return parse::Error::new(
217                input.args[required_args].span(),
218                "argument never used".to_string(),
219            )
220            .to_compile_error()
221            .into();
222        }
223        Ordering::Equal => {}
224    }
225
226    let mut args = vec![];
227    let mut pats = vec![];
228    let mut exprs = vec![];
229    let mut i = 0;
230    for piece in pieces {
231        if let Piece::Str(s) = piece {
232            exprs.push(quote!(f.write_str(#s)?;))
233        } else {
234            let pat = mk_ident(i);
235            let arg = &input.args[i];
236            i += 1;
237
238            args.push(quote!(&(#arg)));
239            pats.push(quote!(#pat));
240
241            match piece {
242                Piece::Display => {
243                    exprs.push(quote!(ufmt::uDisplay::fmt(#pat, f)?;));
244                }
245
246                Piece::Debug { pretty } => {
247                    exprs.push(if pretty {
248                        quote!(f.pretty(|f| ufmt::uDebug::fmt(#pat, f))?;)
249                    } else {
250                        quote!(ufmt::uDebug::fmt(#pat, f)?;)
251                    });
252                }
253                Piece::Hex {
254                    upper_case,
255                    pad_char,
256                    pad_length,
257                    prefix,
258                } => {
259                    exprs.push(quote!(ufmt::uDisplayHex::fmt_hex(#pat, f, ufmt::HexOptions{
260                        upper_case:#upper_case,
261                        pad_char: #pad_char,
262                        pad_length: #pad_length,
263                        ox_prefix: #prefix})?;));
264                }
265                Piece::Str(_) => unreachable!(),
266            }
267        }
268    }
269
270    quote!(match (#(#args),*) {
271        (#(#pats),*) => {
272            use ufmt::UnstableDoAsFormatter as _;
273
274            (#formatter).do_as_formatter(|f| {
275                #(#exprs)*
276                core::result::Result::Ok(())
277            })
278        }
279    })
280    .into()
281}
282
283struct Input {
284    formatter: Expr,
285    _comma: Token![,],
286    literal: LitStr,
287    _comma2: Option<Token![,]>,
288    args: Punctuated<Expr, Token![,]>,
289}
290
291impl Parse for Input {
292    fn parse(input: ParseStream) -> parse::Result<Self> {
293        let formatter = input.parse()?;
294        let _comma = input.parse()?;
295        let literal = input.parse()?;
296
297        if input.is_empty() {
298            Ok(Input {
299                formatter,
300                _comma,
301                literal,
302                _comma2: None,
303                args: Punctuated::new(),
304            })
305        } else {
306            Ok(Input {
307                formatter,
308                _comma,
309                literal,
310                _comma2: input.parse()?,
311                args: Punctuated::parse_terminated(input)?,
312            })
313        }
314    }
315}
316
317#[derive(Debug, PartialEq)]
318enum Piece<'a> {
319    Debug {
320        pretty: bool,
321    },
322    Display,
323    Str(Cow<'a, str>),
324    Hex {
325        upper_case: bool,
326        pad_char: u8,
327        pad_length: usize,
328        prefix: bool,
329    },
330}
331
332impl Piece<'_> {
333    fn is_str(&self) -> bool {
334        matches!(self, Piece::Str(_))
335    }
336}
337
338fn mk_ident(i: usize) -> Ident {
339    Ident::new(&format!("__{}", i), Span::call_site())
340}
341
342// `}}` -> `}`
343fn unescape(mut literal: &str, span: Span) -> parse::Result<Cow<str>> {
344    if literal.contains('}') {
345        let mut buf = String::new();
346
347        while literal.contains('}') {
348            const ERR: &str = "format string contains an unmatched right brace";
349            let mut parts = literal.splitn(2, '}');
350
351            match (parts.next(), parts.next()) {
352                (Some(left), Some(right)) => {
353                    const ESCAPED_BRACE: &str = "}";
354
355                    if let Some(tail) = right.strip_prefix(ESCAPED_BRACE) {
356                        buf.push_str(left);
357                        buf.push('}');
358
359                        literal = tail;
360                    } else {
361                        return Err(parse::Error::new(span, ERR));
362                    }
363                }
364
365                _ => unreachable!(),
366            }
367        }
368
369        buf.push_str(literal);
370
371        Ok(buf.into())
372    } else {
373        Ok(Cow::Borrowed(literal))
374    }
375}
376
377fn parse(mut literal: &str, span: Span) -> parse::Result<Vec<Piece>> {
378    let mut pieces = vec![];
379
380    let mut buf = String::new();
381    loop {
382        let mut parts = literal.splitn(2, '{');
383        match (parts.next(), parts.next()) {
384            // empty string literal
385            (None, None) => break,
386
387            // end of the string literal
388            (Some(s), None) => {
389                if buf.is_empty() {
390                    if !s.is_empty() {
391                        pieces.push(Piece::Str(unescape(s, span)?));
392                    }
393                } else {
394                    buf.push_str(&unescape(s, span)?);
395
396                    pieces.push(Piece::Str(Cow::Owned(buf)));
397                }
398
399                break;
400            }
401
402            (head, Some(tail)) => {
403                const DEBUG: &str = ":?}";
404                const DEBUG_PRETTY: &str = ":#?}";
405                const DISPLAY: &str = "}";
406                const ESCAPED_BRACE: &str = "{";
407
408                let head = head.unwrap_or("");
409                if tail.starts_with(DEBUG)
410                    || tail.starts_with(DEBUG_PRETTY)
411                    || tail.starts_with(DISPLAY)
412                    || tail.starts_with(':')
413                {
414                    if buf.is_empty() {
415                        if !head.is_empty() {
416                            pieces.push(Piece::Str(unescape(head, span)?));
417                        }
418                    } else {
419                        buf.push_str(&unescape(head, span)?);
420
421                        pieces.push(Piece::Str(Cow::Owned(mem::take(&mut buf))));
422                    }
423
424                    if let Some(tail_tail) = tail.strip_prefix(DEBUG) {
425                        pieces.push(Piece::Debug { pretty: false });
426
427                        literal = tail_tail;
428                    } else if let Some(tail_tail) = tail.strip_prefix(DEBUG_PRETTY) {
429                        pieces.push(Piece::Debug { pretty: true });
430
431                        literal = tail_tail;
432                    } else if let Some(tail2) = tail.strip_prefix(':') {
433                        let (piece, remainder) = parse_colon(tail2, span)?;
434                        pieces.push(piece);
435                        literal = remainder;
436                    } else {
437                        pieces.push(Piece::Display);
438
439                        literal = &tail[DISPLAY.len()..];
440                    }
441                } else if let Some(tail_tail) = tail.strip_prefix(ESCAPED_BRACE) {
442                    buf.push_str(&unescape(head, span)?);
443                    buf.push('{');
444
445                    literal = tail_tail;
446                } else {
447                    return Err(parse::Error::new(
448                        span,
449                        "invalid format string: expected `{{`, `{}`, `{:?}` or `{:#?}`",
450                    ));
451                }
452            }
453        }
454    }
455
456    Ok(pieces)
457}
458
459/// given a string src that begins with a text decimal number, return the tail (characters after the number) and the value of the decimal number
460fn split_number(src: &str) -> (&str, usize) {
461    let mut rval = 0;
462    let mut cursor = 0;
463
464    let chars = src.chars();
465    for (i, ch) in chars.enumerate() {
466        match ch.to_digit(10) {
467            Some(val) => {
468                rval = rval * 10 + val as usize;
469                cursor = i + 1;
470            }
471            None => break,
472        }
473    }
474
475    (&src[cursor..], rval)
476}
477
478/// parses the stuff after a `{:` into a [Piece] and the trailing `&str` (what comes after the `}`)
479fn parse_colon(format: &str, span: Span) -> parse::Result<(Piece, &str)> {
480    let (format, prefix) = if let Some(tail) = format.strip_prefix('#') {
481        (tail, true)
482    } else {
483        (format, false)
484    };
485    let (format, pad_char) = if let Some(tail) = format.strip_prefix('0') {
486        (tail, b'0')
487    } else {
488        (format, b' ')
489    };
490    let (format, pad_length) = if !format.is_empty()
491        && if let Some(ch) = format.chars().next() {
492            ch.is_ascii_digit()
493        } else {
494            false
495        } {
496        split_number(format)
497    } else {
498        (format, 0)
499    };
500    if let Some(tail) = format.strip_prefix("x}") {
501        Ok((
502            Piece::Hex {
503                upper_case: false,
504                pad_char,
505                pad_length,
506                prefix,
507            },
508            tail,
509        ))
510    } else if let Some(tail) = format.strip_prefix("X}") {
511        Ok((
512            Piece::Hex {
513                upper_case: true,
514                pad_char,
515                pad_length,
516                prefix,
517            },
518            tail,
519        ))
520    } else {
521        Err(parse::Error::new(
522            span,
523            "invalid format string: expected `{{`, `{}`, `{:?}`, `{:#?}` or '{:x}'",
524        ))
525    }
526}
527
528#[cfg(test)]
529mod tests {
530    use std::borrow::Cow;
531
532    use proc_macro2::Span;
533
534    use crate::Piece;
535
536    #[test]
537    fn pieces() {
538        let span = Span::call_site();
539
540        // string interpolation
541        assert_eq!(
542            super::parse("The answer is {}", span).ok(),
543            Some(vec![
544                Piece::Str(Cow::Borrowed("The answer is ")),
545                Piece::Display
546            ]),
547        );
548
549        assert_eq!(
550            super::parse("{:?}", span).ok(),
551            Some(vec![Piece::Debug { pretty: false }]),
552        );
553
554        assert_eq!(
555            super::parse("{:#?}", span).ok(),
556            Some(vec![Piece::Debug { pretty: true }]),
557        );
558
559        assert_eq!(
560            super::parse("{:x}", span).ok(),
561            Some(vec![Piece::Hex {
562                upper_case: false,
563                pad_char: b' ',
564                pad_length: 0,
565                prefix: false
566            }]),
567        );
568
569        assert_eq!(
570            super::parse("{:9x}", span).ok(),
571            Some(vec![Piece::Hex {
572                upper_case: false,
573                pad_char: b' ',
574                pad_length: 9,
575                prefix: false
576            }]),
577        );
578
579        assert_eq!(
580            super::parse("{:9X}", span).ok(),
581            Some(vec![Piece::Hex {
582                upper_case: true,
583                pad_char: b' ',
584                pad_length: 9,
585                prefix: false
586            }]),
587        );
588
589        assert_eq!(
590            super::parse("{:#X}", span).ok(),
591            Some(vec![Piece::Hex {
592                upper_case: true,
593                pad_char: b' ',
594                pad_length: 0,
595                prefix: true
596            }]),
597        );
598
599        // escaped braces
600        assert_eq!(
601            super::parse("{{}} is not an argument", span).ok(),
602            Some(vec![Piece::Str(Cow::Borrowed("{} is not an argument"))]),
603        );
604
605        // left brace & junk
606        assert!(super::parse("{", span).is_err());
607        assert!(super::parse(" {", span).is_err());
608        assert!(super::parse("{ ", span).is_err());
609        assert!(super::parse("{ {", span).is_err());
610        assert!(super::parse("{:q}", span).is_err());
611    }
612
613    #[test]
614    fn unescape() {
615        let span = Span::call_site();
616
617        // no right brace
618        assert_eq!(super::unescape("", span).ok(), Some(Cow::Borrowed("")));
619        assert_eq!(
620            super::unescape("Hello", span).ok(),
621            Some(Cow::Borrowed("Hello"))
622        );
623
624        // unmatched right brace
625        assert!(super::unescape(" }", span).is_err());
626        assert!(super::unescape("} ", span).is_err());
627        assert!(super::unescape("}", span).is_err());
628
629        // escaped right brace
630        assert_eq!(super::unescape("}}", span).ok(), Some(Cow::Borrowed("}")));
631        assert_eq!(super::unescape("}} ", span).ok(), Some(Cow::Borrowed("} ")));
632    }
633
634    #[test]
635    fn split_number() {
636        let (a, b) = crate::split_number("42 card pickup");
637        assert_eq!(" card pickup", a);
638        assert_eq!(42, b);
639    }
640}