1use core::str;
2
3use crate as rune;
4use crate::alloc::prelude::*;
5use crate::alloc::{self, BTreeMap, BTreeSet, Box, HashMap, String, Vec};
6use crate::ast::{self, Span};
7use crate::compile::{self, WithSpan};
8use crate::macros::{quote, MacroContext, Quote, ToTokens, TokenStream};
9use crate::runtime::format;
10
11pub struct FormatArgs {
21 format: TokenStream,
23 format_span: Span,
25 args: Vec<FormatArg>,
27}
28
29impl FormatArgs {
30 pub fn parse(cx: &mut MacroContext<'_, '_, '_>, stream: &TokenStream) -> compile::Result<Self> {
48 let exprs = cx.exprs(stream)?;
49 Self::from_exprs(cx, exprs)
50 }
51
52 pub fn from_exprs<I>(cx: &mut MacroContext<'_, '_, '_>, exprs: I) -> compile::Result<Self>
58 where
59 I: IntoIterator<Item = TokenStream>,
60 {
61 let mut it = exprs.into_iter();
62
63 let Some(format) = it.next() else {
64 return Err(compile::Error::msg(
65 cx.input_span(),
66 "expected format specifier",
67 ));
68 };
69
70 let format_span = cx.stream_span(&format);
71
72 let mut args = Vec::new();
73
74 for expr in it {
75 args.try_push(FormatArg::new(cx, expr)?)?;
76 }
77
78 Ok(Self {
79 format,
80 format_span,
81 args,
82 })
83 }
84
85 pub fn expand(&self, cx: &mut MacroContext<'_, '_, '_>) -> compile::Result<Quote<'_>> {
87 let format = cx.eval_stream(&self.format)?;
88
89 let mut pos = Vec::new();
90 let mut named = HashMap::<Box<str>, _>::new();
91
92 for a in &self.args {
93 match &a.name {
94 None => {
95 if !named.is_empty() {
96 return Err(compile::Error::msg(
97 a.span,
98 "unnamed positional arguments must come before named ones",
99 ));
100 }
101
102 pos.try_push(a)?;
103 }
104 Some(name) => {
105 named.try_insert(name.try_clone()?, a)?;
106 }
107 }
108 }
109
110 let format = format.downcast::<String>().with_span(self.format_span)?;
111
112 let mut unused_pos = (0..pos.len()).try_collect::<BTreeSet<_>>()?;
113 let mut unused_named = named
114 .iter()
115 .map(|(key, n)| Ok::<_, alloc::Error>((key.try_clone()?, n.span)))
116 .try_collect::<alloc::Result<BTreeMap<_, _>>>()??;
117
118 let result = expand_format_spec(
119 cx,
120 self.format_span,
121 &format,
122 &pos,
123 &mut unused_pos,
124 &named,
125 &mut unused_named,
126 );
127
128 let expanded = match result {
129 Ok(expanded) => expanded,
130 Err(message) => return Err(compile::Error::msg(self.format_span, message)),
131 };
132
133 if let Some(span) = unused_pos
134 .into_iter()
135 .flat_map(|n| pos.get(n))
136 .map(|a| a.span)
137 .next()
138 {
139 return Err(compile::Error::msg(span, "unused positional argument"));
140 }
141
142 if let Some((key, span)) = unused_named.into_iter().next() {
143 return Err(compile::Error::msg(
144 span,
145 format!("unused named argument `{key}`"),
146 ));
147 }
148
149 Ok(expanded)
150 }
151}
152
153struct FormatArg {
155 name: Option<Box<str>>,
157 value: TokenStream,
159 span: Span,
161}
162
163impl FormatArg {
164 fn new(cx: &mut MacroContext<'_, '_, '_>, expr: TokenStream) -> compile::Result<Self> {
169 let span = cx.stream_span(&expr);
170
171 let mut it = (&expr).into_iter();
172
173 let key = match (it.next(), it.next()) {
174 (Some(key), Some(eq)) if matches!(eq.kind, ast::Kind::Eq) => match key.kind {
175 ast::Kind::Ident(source) => Some(ast::Ident {
176 span: key.span,
177 source,
178 }),
179 _ => None,
180 },
181 _ => None,
182 };
183
184 let Some(key) = key else {
185 return Ok(Self {
186 name: None,
187 value: expr,
188 span,
189 });
190 };
191
192 let name = cx.resolve(key)?.try_into()?;
193
194 let mut value = TokenStream::new();
195
196 for token in expr.into_iter().skip(2) {
197 value.push(token)?;
198 }
199
200 Ok(Self {
201 name: Some(name),
202 value,
203 span,
204 })
205 }
206}
207
208fn expand_format_spec<'a>(
209 cx: &mut MacroContext<'_, '_, '_>,
210 span: Span,
211 input: &str,
212 pos: &[&'a FormatArg],
213 unused_pos: &mut BTreeSet<usize>,
214 named: &HashMap<Box<str>, &'a FormatArg>,
215 unused_named: &mut BTreeMap<Box<str>, Span>,
216) -> compile::Result<Quote<'a>> {
217 let mut iter = Iter::new(input);
218
219 let mut name = String::new();
220 let mut width = String::new();
221 let mut precision = String::new();
222
223 let mut buf = String::new();
224 let mut components = Vec::new();
225 let mut count = 0;
226 let mut start = Some(0);
227
228 while let Some((at, a, b)) = iter.next() {
229 match (a, b) {
230 ('}', '}') => {
231 if let Some(start) = start.take() {
232 buf.try_push_str(&input[start..at])?;
233 }
234
235 buf.try_push('}')?;
236 iter.next();
237 }
238 ('{', '{') => {
239 if let Some(start) = start.take() {
240 buf.try_push_str(&input[start..at])?;
241 }
242
243 buf.try_push('{')?;
244 iter.next();
245 }
246 ('}', _) => {
247 return Err(compile::Error::msg(
248 span,
249 "unsupported close `}`, if you meant to escape this use `}}`",
250 ));
251 }
252 ('{', _) => {
253 if let Some(start) = start.take() {
254 buf.try_push_str(&input[start..at])?;
255 }
256
257 if !buf.is_empty() {
258 components.try_push(C::Literal(Box::try_from(&buf[..])?))?;
259 buf.clear();
260 }
261
262 components.try_push(parse_group(
263 cx,
264 span,
265 &mut iter,
266 &mut count,
267 &mut name,
268 &mut width,
269 &mut precision,
270 pos,
271 unused_pos,
272 named,
273 unused_named,
274 )?)?;
275 }
276 _ => {
277 if start.is_none() {
278 start = Some(at);
279 }
280 }
281 }
282 }
283
284 if let Some(start) = start.take() {
285 buf.try_push_str(&input[start..])?;
286 }
287
288 if !buf.is_empty() {
289 components.try_push(C::Literal(Box::try_from(&buf[..])?))?;
290 buf.clear();
291 }
292
293 if components.is_empty() {
294 return Ok(quote!(""));
295 }
296
297 let mut args = Vec::<Quote<'static>>::new();
298
299 for c in components {
300 match c {
301 C::Literal(literal) => {
302 let lit = cx.lit(literal.as_ref())?;
303 args.try_push(quote!(#lit))?;
304 }
305 C::Format {
306 expr,
307 fill,
308 align,
309 width,
310 precision,
311 flags,
312 format_type,
313 } => {
314 let mut specs = Vec::new();
315
316 let fill = fill
317 .map(|fill| {
318 let fill = cx.lit(fill)?;
319 Ok::<_, alloc::Error>(quote!(fill = #fill))
320 })
321 .transpose()?;
322
323 let width = width
324 .map(|width| {
325 let width = cx.lit(width)?;
326 Ok::<_, alloc::Error>(quote!(width = #width))
327 })
328 .transpose()?;
329
330 let precision = precision
331 .map(|precision| {
332 let precision = cx.lit(precision)?;
333 Ok::<_, alloc::Error>(quote!(precision = #precision))
334 })
335 .transpose()?;
336
337 let align = align
338 .map(|align| {
339 let align = align.try_to_string()?;
340 let align = cx.ident(&align)?;
341 Ok::<_, alloc::Error>(quote!(align = #align))
342 })
343 .transpose()?;
344
345 specs.try_extend(fill)?;
346 specs.try_extend(width)?;
347 specs.try_extend(precision)?;
348 specs.try_extend(align)?;
349
350 if !flags.is_empty() {
351 let flags = cx.lit(flags.into_u32())?;
352 specs.try_push(quote!(flags = #flags))?;
353 }
354
355 let format_type = format_type
356 .map(|format_type| {
357 let format_type = format_type.try_to_string()?;
358 let format_type = cx.ident(&format_type)?;
359 Ok::<_, alloc::Error>(quote!(type = #format_type))
360 })
361 .transpose()?;
362
363 specs.try_extend(format_type)?;
364
365 if specs.is_empty() {
366 args.try_push(quote!(#expr))?;
367 } else {
368 args.try_push(quote!(
369 #[builtin]
370 format!(#expr, #(specs),*)
371 ))?;
372 }
373 }
374 }
375 }
376
377 return Ok(quote! {
378 #[builtin] template!(#(args),*)
379 });
380
381 enum ExprOrIdent<'a> {
382 Expr(&'a TokenStream),
383 Ident(ast::Ident),
384 }
385
386 impl ToTokens for ExprOrIdent<'_> {
387 fn to_tokens(
388 &self,
389 cx: &mut MacroContext<'_, '_, '_>,
390 stream: &mut TokenStream,
391 ) -> alloc::Result<()> {
392 match self {
393 Self::Expr(expr) => expr.to_tokens(cx, stream),
394 Self::Ident(ident) => ident.to_tokens(cx, stream),
395 }
396 }
397 }
398
399 enum C<'a> {
400 Literal(Box<str>),
401 Format {
402 expr: ExprOrIdent<'a>,
403 fill: Option<char>,
404 align: Option<format::Alignment>,
405 width: Option<usize>,
406 precision: Option<usize>,
407 flags: format::Flags,
408 format_type: Option<format::Type>,
409 },
410 }
411
412 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
413 enum Mode {
414 Start,
416 FillAllign,
418 Sign,
420 Alternate,
422 SignAwareZeroPad,
424 Width,
426 Precision,
428 Type,
430 End,
432 }
433
434 const MAX_FORMAT_ARGUMENT: usize = u16::MAX as usize;
441
442 fn bound_format_argument(span: Span, what: &str, n: usize) -> compile::Result<usize> {
444 if n > MAX_FORMAT_ARGUMENT {
445 return Err(compile::Error::msg(
446 span,
447 format!("{what} {n} is larger than the maximum of {MAX_FORMAT_ARGUMENT}"),
448 ));
449 }
450
451 Ok(n)
452 }
453
454 fn parse_format_argument(span: Span, what: &str, digits: &str) -> compile::Result<usize> {
459 let Ok(n) = str::parse::<usize>(digits) else {
462 return Err(compile::Error::msg(
463 span,
464 format!("{what} {digits} is larger than the maximum of {MAX_FORMAT_ARGUMENT}"),
465 ));
466 };
467
468 bound_format_argument(span, what, n)
469 }
470
471 fn parse_group<'a>(
473 cx: &mut MacroContext<'_, '_, '_>,
474 span: Span,
475 iter: &mut Iter<'_>,
476 count: &mut usize,
477 name: &mut String,
478 width: &mut String,
479 precision: &mut String,
480 pos: &[&'a FormatArg],
481 unused_pos: &mut BTreeSet<usize>,
482 named: &HashMap<Box<str>, &'a FormatArg>,
483 unused_named: &mut BTreeMap<Box<str>, Span>,
484 ) -> compile::Result<C<'a>> {
485 let mut flags = format::Flags::default();
487 let mut fill = None;
489 let mut align = None;
491 let mut input_precision = false;
493 let mut format_type = None;
495
496 name.clear();
498 width.clear();
499 precision.clear();
500
501 let mut mode = Mode::Start;
502
503 loop {
504 let Some((_, a, b)) = iter.current() else {
505 return Err(compile::Error::msg(span, "unexpected end of format string"));
506 };
507
508 match mode {
509 Mode::Start => match a {
510 ':' => {
511 mode = Mode::FillAllign;
512 iter.next();
513 }
514 '}' => {
515 mode = Mode::End;
516 }
517 c => {
518 name.try_push(c)?;
519 iter.next();
520 }
521 },
522 Mode::FillAllign => {
523 if matches!(a, '<' | '^' | '>') {
525 align = Some(parse_align(a));
526 iter.next();
527 } else if matches!(b, '<' | '^' | '>') {
528 fill = Some(a);
529 align = Some(parse_align(b));
530
531 iter.next();
532 iter.next();
533 }
534
535 mode = Mode::Sign;
536 }
537 Mode::Sign => {
538 match a {
539 '-' => {
540 flags.set(format::Flag::SignMinus);
541 iter.next();
542 }
543 '+' => {
544 flags.set(format::Flag::SignPlus);
545 iter.next();
546 }
547 _ => (),
548 }
549
550 mode = Mode::Alternate;
551 }
552 Mode::Alternate => {
553 if a == '#' {
554 flags.set(format::Flag::Alternate);
555 iter.next();
556 }
557
558 mode = Mode::SignAwareZeroPad;
559 }
560 Mode::SignAwareZeroPad => {
561 if a == '0' {
562 flags.set(format::Flag::SignAwareZeroPad);
563 iter.next();
564 }
565
566 mode = Mode::Width;
567 }
568 Mode::Width => {
569 match a {
570 '0'..='9' => {
571 width.try_push(a)?;
572 iter.next();
573 continue;
574 }
575 '.' => {
576 mode = Mode::Precision;
577 iter.next();
578 continue;
579 }
580 _ => (),
581 }
582
583 mode = Mode::Type;
584 }
585 Mode::Precision => {
586 match a {
587 '*' if precision.is_empty() => {
588 input_precision = true;
589 iter.next();
590 }
591 '0'..='9' => {
592 precision.try_push(a)?;
593 iter.next();
594 continue;
595 }
596 _ => (),
597 }
598
599 mode = Mode::Type;
600 }
601 Mode::Type => {
602 match a {
603 '?' => {
604 format_type = Some(format::Type::Debug);
605 iter.next();
606 }
607 'x' => {
608 format_type = Some(format::Type::LowerHex);
609 iter.next();
610 }
611 'X' => {
612 format_type = Some(format::Type::UpperHex);
613 iter.next();
614 }
615 'b' => {
616 format_type = Some(format::Type::Binary);
617 iter.next();
618 }
619 'p' => {
620 format_type = Some(format::Type::Pointer);
621 iter.next();
622 }
623 _ => (),
624 }
625
626 mode = Mode::End;
627 }
628 Mode::End => {
629 match a {
630 '}' => (),
631 c => {
632 return Err(compile::Error::msg(
633 span,
634 format!("unsupported char `{c}` in spec"),
635 ));
636 }
637 }
638
639 iter.next();
640 break;
641 }
642 }
643 }
644
645 let precision = if input_precision {
646 let &arg = match pos.get(*count) {
647 Some(arg) => arg,
648 None => {
649 return Err(compile::Error::msg(
650 span,
651 format!(
652 "missing positional argument #{count} \
653 which is required for position parameter",
654 ),
655 ));
656 }
657 };
658
659 unused_pos.remove(count);
660
661 let value = cx.eval_stream(&arg.value)?;
662 let precision = value.as_usize().with_span(span)?;
663
664 *count += 1;
665 Some(bound_format_argument(span, "precision", precision)?)
666 } else if !precision.is_empty() {
667 Some(parse_format_argument(span, "precision", precision)?)
668 } else {
669 None
670 };
671
672 let expr = 'expr: {
673 if name.is_empty() {
674 let Some(arg) = pos.get(*count) else {
675 return Err(compile::Error::msg(
676 span,
677 format!("missing positional argument #{count}"),
678 ));
679 };
680
681 unused_pos.remove(count);
682 *count += 1;
683 break 'expr ExprOrIdent::Expr(&arg.value);
684 };
685
686 if let Ok(n) = str::parse::<usize>(name) {
687 let arg = match pos.get(n) {
688 Some(arg) => *arg,
689 None => {
690 return Err(compile::Error::msg(
691 span,
692 format!("missing positional argument #{n}"),
693 ));
694 }
695 };
696
697 unused_pos.remove(&n);
698 break 'expr ExprOrIdent::Expr(&arg.value);
699 }
700
701 if let Some(n) = named.get(name.as_str()) {
702 unused_named.remove(name.as_str());
703 break 'expr ExprOrIdent::Expr(&n.value);
704 }
705
706 let mut ident = cx.ident(name.as_str())?;
707 ident.span = span;
708 ExprOrIdent::Ident(ident)
709 };
710
711 let width = if !width.is_empty() {
712 Some(parse_format_argument(span, "width", width)?)
713 } else {
714 None
715 };
716
717 Ok(C::Format {
718 expr,
719 fill,
720 align,
721 width,
722 precision,
723 format_type,
724 flags,
725 })
726 }
727
728 fn parse_align(c: char) -> format::Alignment {
729 match c {
730 '<' => format::Alignment::Left,
731 '^' => format::Alignment::Center,
732 _ => format::Alignment::Right,
733 }
734 }
735}
736
737struct Iter<'a> {
738 iter: str::CharIndices<'a>,
739 a: Option<(usize, char)>,
740 b: Option<(usize, char)>,
741}
742
743impl<'a> Iter<'a> {
744 fn new(input: &'a str) -> Self {
745 let mut iter = input.char_indices();
746 let a = iter.next();
747 let b = iter.next();
748 Self { iter, a, b }
749 }
750
751 fn current(&self) -> Option<(usize, char, char)> {
752 let (pos, a) = self.a?;
753 let (_, b) = self.b.unwrap_or_default();
754 Some((pos, a, b))
755 }
756}
757
758impl Iterator for Iter<'_> {
759 type Item = (usize, char, char);
760
761 fn next(&mut self) -> Option<Self::Item> {
762 let value = self.current()?;
763
764 self.a = self.b;
765 self.b = self.iter.next();
766
767 Some(value)
768 }
769}