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, Spanned};
7use crate::compile::{self, WithSpan};
8use crate::macros::{quote, MacroContext, Quote, ToTokens, TokenStream};
9use crate::parse::{Parse, Parser, Peek, Peeker};
10use crate::runtime::format;
11
12pub struct FormatArgs {
18 format: ast::Expr,
20 args: Vec<FormatArg>,
22}
23
24impl FormatArgs {
25 pub fn expand(&self, cx: &mut MacroContext<'_, '_, '_>) -> compile::Result<Quote<'_>> {
27 let format = cx.eval(&self.format)?;
28
29 let mut pos = Vec::new();
30 let mut named = HashMap::<Box<str>, _>::new();
31
32 for a in &self.args {
33 match a {
34 FormatArg::Positional(expr) => {
35 if !named.is_empty() {
36 return Err(compile::Error::msg(
37 expr.span(),
38 "unnamed positional arguments must come before named ones",
39 ));
40 }
41
42 pos.try_push(expr)?;
43 }
44 FormatArg::Named(n) => {
45 let name = cx.resolve(n.key)?;
46 named.try_insert(name.try_into()?, n)?;
47 }
48 }
49 }
50
51 let format = format.downcast::<String>().with_span(&self.format)?;
52
53 let mut unused_pos = (0..pos.len()).try_collect::<BTreeSet<_>>()?;
54 let mut unused_named = named
55 .iter()
56 .map(|(key, n)| Ok::<_, alloc::Error>((key.try_clone()?, n.span())))
57 .try_collect::<alloc::Result<BTreeMap<_, _>>>()??;
58
59 let result = expand_format_spec(
60 cx,
61 self.format.span(),
62 &format,
63 &pos,
64 &mut unused_pos,
65 &named,
66 &mut unused_named,
67 );
68
69 let expanded = match result {
70 Ok(expanded) => expanded,
71 Err(message) => return Err(compile::Error::msg(self.format.span(), message)),
72 };
73
74 if let Some(expr) = unused_pos.into_iter().flat_map(|n| pos.get(n)).next() {
75 return Err(compile::Error::msg(
76 expr.span(),
77 "unused positional argument",
78 ));
79 }
80
81 if let Some((key, span)) = unused_named.into_iter().next() {
82 return Err(compile::Error::msg(
83 span,
84 format!("unused named argument `{key}`"),
85 ));
86 }
87
88 Ok(expanded)
89 }
90}
91
92impl Parse for FormatArgs {
93 fn parse(p: &mut Parser<'_>) -> compile::Result<Self> {
95 if p.is_eof()? {
96 return Err(compile::Error::msg(
97 p.last_span(),
98 "expected format specifier",
99 ));
100 }
101
102 let format = p.parse::<ast::Expr>()?;
103
104 let mut args = Vec::new();
105
106 while p.parse::<Option<T![,]>>()?.is_some() {
107 if p.is_eof()? {
108 break;
109 }
110
111 args.try_push(p.parse()?)?;
112 }
113
114 Ok(Self { format, args })
115 }
116}
117
118impl Peek for FormatArgs {
119 fn peek(p: &mut Peeker<'_>) -> bool {
120 !p.is_eof()
121 }
122}
123
124#[derive(Debug, TryClone, Parse, Spanned)]
126pub struct NamedFormatArg {
127 pub key: ast::Ident,
129 pub eq_token: T![=],
131 pub expr: ast::Expr,
133}
134
135#[derive(Debug, TryClone)]
137pub enum FormatArg {
138 Positional(ast::Expr),
140 Named(NamedFormatArg),
142}
143
144impl Parse for FormatArg {
145 fn parse(p: &mut Parser<'_>) -> compile::Result<Self> {
146 Ok(if let (K![ident], K![=]) = (p.nth(0)?, p.nth(1)?) {
147 FormatArg::Named(p.parse()?)
148 } else {
149 FormatArg::Positional(p.parse()?)
150 })
151 }
152}
153
154fn expand_format_spec<'a>(
155 cx: &mut MacroContext<'_, '_, '_>,
156 span: Span,
157 input: &str,
158 pos: &[&'a ast::Expr],
159 unused_pos: &mut BTreeSet<usize>,
160 named: &HashMap<Box<str>, &'a NamedFormatArg>,
161 unused_named: &mut BTreeMap<Box<str>, Span>,
162) -> compile::Result<Quote<'a>> {
163 let mut iter = Iter::new(input);
164
165 let mut name = String::new();
166 let mut width = String::new();
167 let mut precision = String::new();
168
169 let mut buf = String::new();
170 let mut components = Vec::new();
171 let mut count = 0;
172 let mut start = Some(0);
173
174 while let Some((at, a, b)) = iter.next() {
175 match (a, b) {
176 ('}', '}') => {
177 if let Some(start) = start.take() {
178 buf.try_push_str(&input[start..at])?;
179 }
180
181 buf.try_push('}')?;
182 iter.next();
183 }
184 ('{', '{') => {
185 if let Some(start) = start.take() {
186 buf.try_push_str(&input[start..at])?;
187 }
188
189 buf.try_push('{')?;
190 iter.next();
191 }
192 ('}', _) => {
193 return Err(compile::Error::msg(
194 span,
195 "unsupported close `}`, if you meant to escape this use `}}`",
196 ));
197 }
198 ('{', _) => {
199 if let Some(start) = start.take() {
200 buf.try_push_str(&input[start..at])?;
201 }
202
203 if !buf.is_empty() {
204 components.try_push(C::Literal(Box::try_from(&buf[..])?))?;
205 buf.clear();
206 }
207
208 components.try_push(parse_group(
209 cx,
210 span,
211 &mut iter,
212 &mut count,
213 &mut name,
214 &mut width,
215 &mut precision,
216 pos,
217 unused_pos,
218 named,
219 unused_named,
220 )?)?;
221 }
222 _ => {
223 if start.is_none() {
224 start = Some(at);
225 }
226 }
227 }
228 }
229
230 if let Some(start) = start.take() {
231 buf.try_push_str(&input[start..])?;
232 }
233
234 if !buf.is_empty() {
235 components.try_push(C::Literal(Box::try_from(&buf[..])?))?;
236 buf.clear();
237 }
238
239 if components.is_empty() {
240 return Ok(quote!(""));
241 }
242
243 let mut args = Vec::<Quote<'static>>::new();
244
245 for c in components {
246 match c {
247 C::Literal(literal) => {
248 let lit = cx.lit(literal.as_ref())?;
249 args.try_push(quote!(#lit))?;
250 }
251 C::Format {
252 expr,
253 fill,
254 align,
255 width,
256 precision,
257 flags,
258 format_type,
259 } => {
260 let mut specs = Vec::new();
261
262 let fill = fill
263 .map(|fill| {
264 let fill = cx.lit(fill)?;
265 Ok::<_, alloc::Error>(quote!(fill = #fill))
266 })
267 .transpose()?;
268
269 let width = width
270 .map(|width| {
271 let width = cx.lit(width)?;
272 Ok::<_, alloc::Error>(quote!(width = #width))
273 })
274 .transpose()?;
275
276 let precision = precision
277 .map(|precision| {
278 let precision = cx.lit(precision)?;
279 Ok::<_, alloc::Error>(quote!(precision = #precision))
280 })
281 .transpose()?;
282
283 let align = align
284 .map(|align| {
285 let align = align.try_to_string()?;
286 let align = cx.ident(&align)?;
287 Ok::<_, alloc::Error>(quote!(align = #align))
288 })
289 .transpose()?;
290
291 specs.try_extend(fill)?;
292 specs.try_extend(width)?;
293 specs.try_extend(precision)?;
294 specs.try_extend(align)?;
295
296 if !flags.is_empty() {
297 let flags = cx.lit(flags.into_u32())?;
298 specs.try_push(quote!(flags = #flags))?;
299 }
300
301 let format_type = format_type
302 .map(|format_type| {
303 let format_type = format_type.try_to_string()?;
304 let format_type = cx.ident(&format_type)?;
305 Ok::<_, alloc::Error>(quote!(type = #format_type))
306 })
307 .transpose()?;
308
309 specs.try_extend(format_type)?;
310
311 if specs.is_empty() {
312 args.try_push(quote!(#expr))?;
313 } else {
314 args.try_push(quote!(
315 #[builtin]
316 format!(#expr, #(specs),*)
317 ))?;
318 }
319 }
320 }
321 }
322
323 return Ok(quote! {
324 #[builtin] template!(#(args),*)
325 });
326
327 enum ExprOrIdent<'a> {
328 Expr(&'a ast::Expr),
329 Ident(ast::Ident),
330 }
331
332 impl ToTokens for ExprOrIdent<'_> {
333 fn to_tokens(
334 &self,
335 cx: &mut MacroContext<'_, '_, '_>,
336 stream: &mut TokenStream,
337 ) -> alloc::Result<()> {
338 match self {
339 Self::Expr(expr) => expr.to_tokens(cx, stream),
340 Self::Ident(ident) => ident.to_tokens(cx, stream),
341 }
342 }
343 }
344
345 enum C<'a> {
346 Literal(Box<str>),
347 Format {
348 expr: ExprOrIdent<'a>,
349 fill: Option<char>,
350 align: Option<format::Alignment>,
351 width: Option<usize>,
352 precision: Option<usize>,
353 flags: format::Flags,
354 format_type: Option<format::Type>,
355 },
356 }
357
358 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
359 enum Mode {
360 Start,
362 FillAllign,
364 Sign,
366 Alternate,
368 SignAwareZeroPad,
370 Width,
372 Precision,
374 Type,
376 End,
378 }
379
380 fn parse_group<'a>(
382 cx: &mut MacroContext<'_, '_, '_>,
383 span: Span,
384 iter: &mut Iter<'_>,
385 count: &mut usize,
386 name: &mut String,
387 width: &mut String,
388 precision: &mut String,
389 pos: &[&'a ast::Expr],
390 unused_pos: &mut BTreeSet<usize>,
391 named: &HashMap<Box<str>, &'a NamedFormatArg>,
392 unused_named: &mut BTreeMap<Box<str>, Span>,
393 ) -> compile::Result<C<'a>> {
394 let mut flags = format::Flags::default();
396 let mut fill = None;
398 let mut align = None;
400 let mut input_precision = false;
402 let mut format_type = None;
404
405 name.clear();
407 width.clear();
408 precision.clear();
409
410 let mut mode = Mode::Start;
411
412 loop {
413 let Some((_, a, b)) = iter.current() else {
414 return Err(compile::Error::msg(span, "unexpected end of format string"));
415 };
416
417 match mode {
418 Mode::Start => match a {
419 ':' => {
420 mode = Mode::FillAllign;
421 iter.next();
422 }
423 '}' => {
424 mode = Mode::End;
425 }
426 c => {
427 name.try_push(c)?;
428 iter.next();
429 }
430 },
431 Mode::FillAllign => {
432 if matches!(a, '<' | '^' | '>') {
434 align = Some(parse_align(a));
435 iter.next();
436 } else if matches!(b, '<' | '^' | '>') {
437 fill = Some(a);
438 align = Some(parse_align(b));
439
440 iter.next();
441 iter.next();
442 }
443
444 mode = Mode::Sign;
445 }
446 Mode::Sign => {
447 match a {
448 '-' => {
449 flags.set(format::Flag::SignMinus);
450 iter.next();
451 }
452 '+' => {
453 flags.set(format::Flag::SignPlus);
454 iter.next();
455 }
456 _ => (),
457 }
458
459 mode = Mode::Alternate;
460 }
461 Mode::Alternate => {
462 if a == '#' {
463 flags.set(format::Flag::Alternate);
464 iter.next();
465 }
466
467 mode = Mode::SignAwareZeroPad;
468 }
469 Mode::SignAwareZeroPad => {
470 if a == '0' {
471 flags.set(format::Flag::SignAwareZeroPad);
472 iter.next();
473 }
474
475 mode = Mode::Width;
476 }
477 Mode::Width => {
478 match a {
479 '0'..='9' => {
480 width.try_push(a)?;
481 iter.next();
482 continue;
483 }
484 '.' => {
485 mode = Mode::Precision;
486 iter.next();
487 continue;
488 }
489 _ => (),
490 }
491
492 mode = Mode::Type;
493 }
494 Mode::Precision => {
495 match a {
496 '*' if precision.is_empty() => {
497 input_precision = true;
498 iter.next();
499 }
500 '0'..='9' => {
501 precision.try_push(a)?;
502 iter.next();
503 continue;
504 }
505 _ => (),
506 }
507
508 mode = Mode::Type;
509 }
510 Mode::Type => {
511 match a {
512 '?' => {
513 format_type = Some(format::Type::Debug);
514 iter.next();
515 }
516 'x' => {
517 format_type = Some(format::Type::LowerHex);
518 iter.next();
519 }
520 'X' => {
521 format_type = Some(format::Type::UpperHex);
522 iter.next();
523 }
524 'b' => {
525 format_type = Some(format::Type::Binary);
526 iter.next();
527 }
528 'p' => {
529 format_type = Some(format::Type::Pointer);
530 iter.next();
531 }
532 _ => (),
533 }
534
535 mode = Mode::End;
536 }
537 Mode::End => {
538 match a {
539 '}' => (),
540 c => {
541 return Err(compile::Error::msg(
542 span,
543 format!("unsupported char `{c}` in spec"),
544 ));
545 }
546 }
547
548 iter.next();
549 break;
550 }
551 }
552 }
553
554 let precision = if input_precision {
555 let &expr = match pos.get(*count) {
556 Some(expr) => expr,
557 None => {
558 return Err(compile::Error::msg(
559 span,
560 format!(
561 "missing positional argument #{count} \
562 which is required for position parameter",
563 ),
564 ));
565 }
566 };
567
568 unused_pos.remove(count);
569
570 let value = cx.eval(expr)?;
571 let precision = value.as_usize().with_span(span)?;
572
573 *count += 1;
574 Some(precision)
575 } else if !precision.is_empty() {
576 str::parse::<usize>(precision).ok()
577 } else {
578 None
579 };
580
581 let expr = 'expr: {
582 if name.is_empty() {
583 let Some(expr) = pos.get(*count) else {
584 return Err(compile::Error::msg(
585 span,
586 format!("missing positional argument #{count}"),
587 ));
588 };
589
590 unused_pos.remove(count);
591 *count += 1;
592 break 'expr ExprOrIdent::Expr(expr);
593 };
594
595 if let Ok(n) = str::parse::<usize>(name) {
596 let expr = match pos.get(n) {
597 Some(expr) => *expr,
598 None => {
599 return Err(compile::Error::msg(
600 span,
601 format!("missing positional argument #{n}"),
602 ));
603 }
604 };
605
606 unused_pos.remove(&n);
607 break 'expr ExprOrIdent::Expr(expr);
608 }
609
610 if let Some(n) = named.get(name.as_str()) {
611 unused_named.remove(name.as_str());
612 break 'expr ExprOrIdent::Expr(&n.expr);
613 }
614
615 let mut ident = cx.ident(name.as_str())?;
616 ident.span = span;
617 ExprOrIdent::Ident(ident)
618 };
619
620 let width = if !width.is_empty() {
621 str::parse::<usize>(width).ok()
622 } else {
623 None
624 };
625
626 Ok(C::Format {
627 expr,
628 fill,
629 align,
630 width,
631 precision,
632 format_type,
633 flags,
634 })
635 }
636
637 fn parse_align(c: char) -> format::Alignment {
638 match c {
639 '<' => format::Alignment::Left,
640 '^' => format::Alignment::Center,
641 _ => format::Alignment::Right,
642 }
643 }
644}
645
646struct Iter<'a> {
647 iter: str::CharIndices<'a>,
648 a: Option<(usize, char)>,
649 b: Option<(usize, char)>,
650}
651
652impl<'a> Iter<'a> {
653 fn new(input: &'a str) -> Self {
654 let mut iter = input.char_indices();
655 let a = iter.next();
656 let b = iter.next();
657 Self { iter, a, b }
658 }
659
660 fn current(&self) -> Option<(usize, char, char)> {
661 let (pos, a) = self.a?;
662 let (_, b) = self.b.unwrap_or_default();
663 Some((pos, a, b))
664 }
665}
666
667impl Iterator for Iter<'_> {
668 type Item = (usize, char, char);
669
670 fn next(&mut self) -> Option<Self::Item> {
671 let value = self.current()?;
672
673 self.a = self.b;
674 self.b = self.iter.next();
675
676 Some(value)
677 }
678}