1#![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#[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 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
342fn 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 (None, None) => break,
386
387 (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
459fn 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
478fn 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 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 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 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 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 assert!(super::unescape(" }", span).is_err());
626 assert!(super::unescape("} ", span).is_err());
627 assert!(super::unescape("}", span).is_err());
628
629 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}