]> git.lizzy.rs Git - rust.git/blob - src/libfmt_macros/lib.rs
32ae878909f30676a76a4738b20400611f19148d
[rust.git] / src / libfmt_macros / lib.rs
1 //! Macro support for format strings
2 //!
3 //! These structures are used when parsing format strings for the compiler.
4 //! Parsing does not happen at runtime: structures of `std::fmt::rt` are
5 //! generated instead.
6
7 #![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
8        html_favicon_url = "https://doc.rust-lang.org/favicon.ico",
9        html_root_url = "https://doc.rust-lang.org/nightly/",
10        html_playground_url = "https://play.rust-lang.org/",
11        test(attr(deny(warnings))))]
12
13 #![feature(nll)]
14
15 pub use self::Piece::*;
16 pub use self::Position::*;
17 pub use self::Alignment::*;
18 pub use self::Flag::*;
19 pub use self::Count::*;
20
21 use std::str;
22 use std::string;
23 use std::iter;
24
25 /// A piece is a portion of the format string which represents the next part
26 /// to emit. These are emitted as a stream by the `Parser` class.
27 #[derive(Copy, Clone, PartialEq)]
28 pub enum Piece<'a> {
29     /// A literal string which should directly be emitted
30     String(&'a str),
31     /// This describes that formatting should process the next argument (as
32     /// specified inside) for emission.
33     NextArgument(Argument<'a>),
34 }
35
36 /// Representation of an argument specification.
37 #[derive(Copy, Clone, PartialEq)]
38 pub struct Argument<'a> {
39     /// Where to find this argument
40     pub position: Position<'a>,
41     /// How to format the argument
42     pub format: FormatSpec<'a>,
43 }
44
45 /// Specification for the formatting of an argument in the format string.
46 #[derive(Copy, Clone, PartialEq)]
47 pub struct FormatSpec<'a> {
48     /// Optionally specified character to fill alignment with
49     pub fill: Option<char>,
50     /// Optionally specified alignment
51     pub align: Alignment,
52     /// Packed version of various flags provided
53     pub flags: u32,
54     /// The integer precision to use
55     pub precision: Count<'a>,
56     /// The string width requested for the resulting format
57     pub width: Count<'a>,
58     /// The descriptor string representing the name of the format desired for
59     /// this argument, this can be empty or any number of characters, although
60     /// it is required to be one word.
61     pub ty: &'a str,
62 }
63
64 /// Enum describing where an argument for a format can be located.
65 #[derive(Copy, Clone, PartialEq)]
66 pub enum Position<'a> {
67     /// The argument is implied to be located at an index
68     ArgumentImplicitlyIs(usize),
69     /// The argument is located at a specific index given in the format
70     ArgumentIs(usize),
71     /// The argument has a name.
72     ArgumentNamed(&'a str),
73 }
74
75 /// Enum of alignments which are supported.
76 #[derive(Copy, Clone, PartialEq)]
77 pub enum Alignment {
78     /// The value will be aligned to the left.
79     AlignLeft,
80     /// The value will be aligned to the right.
81     AlignRight,
82     /// The value will be aligned in the center.
83     AlignCenter,
84     /// The value will take on a default alignment.
85     AlignUnknown,
86 }
87
88 /// Various flags which can be applied to format strings. The meaning of these
89 /// flags is defined by the formatters themselves.
90 #[derive(Copy, Clone, PartialEq)]
91 pub enum Flag {
92     /// A `+` will be used to denote positive numbers.
93     FlagSignPlus,
94     /// A `-` will be used to denote negative numbers. This is the default.
95     FlagSignMinus,
96     /// An alternate form will be used for the value. In the case of numbers,
97     /// this means that the number will be prefixed with the supplied string.
98     FlagAlternate,
99     /// For numbers, this means that the number will be padded with zeroes,
100     /// and the sign (`+` or `-`) will precede them.
101     FlagSignAwareZeroPad,
102     /// For Debug / `?`, format integers in lower-case hexadecimal.
103     FlagDebugLowerHex,
104     /// For Debug / `?`, format integers in upper-case hexadecimal.
105     FlagDebugUpperHex,
106 }
107
108 /// A count is used for the precision and width parameters of an integer, and
109 /// can reference either an argument or a literal integer.
110 #[derive(Copy, Clone, PartialEq)]
111 pub enum Count<'a> {
112     /// The count is specified explicitly.
113     CountIs(usize),
114     /// The count is specified by the argument with the given name.
115     CountIsName(&'a str),
116     /// The count is specified by the argument at the given index.
117     CountIsParam(usize),
118     /// The count is implied and cannot be explicitly specified.
119     CountImplied,
120 }
121
122 pub struct ParseError {
123     pub description: string::String,
124     pub note: Option<string::String>,
125     pub label: string::String,
126     pub start: SpanIndex,
127     pub end: SpanIndex,
128     pub secondary_label: Option<(string::String, SpanIndex, SpanIndex)>,
129 }
130
131 /// The parser structure for interpreting the input format string. This is
132 /// modeled as an iterator over `Piece` structures to form a stream of tokens
133 /// being output.
134 ///
135 /// This is a recursive-descent parser for the sake of simplicity, and if
136 /// necessary there's probably lots of room for improvement performance-wise.
137 pub struct Parser<'a> {
138     input: &'a str,
139     cur: iter::Peekable<str::CharIndices<'a>>,
140     /// Error messages accumulated during parsing
141     pub errors: Vec<ParseError>,
142     /// Current position of implicit positional argument pointer
143     curarg: usize,
144     /// `Some(raw count)` when the string is "raw", used to position spans correctly
145     style: Option<usize>,
146     /// Start and end byte offset of every successfully parsed argument
147     pub arg_places: Vec<(SpanIndex, SpanIndex)>,
148     /// Characters that need to be shifted
149     skips: Vec<usize>,
150     /// Span offset of the last opening brace seen, used for error reporting
151     last_opening_brace_pos: Option<SpanIndex>,
152     /// Wether the source string is comes from `println!` as opposed to `format!` or `print!`
153     append_newline: bool,
154 }
155
156 #[derive(Clone, Copy, Debug)]
157 pub struct SpanIndex(pub usize);
158
159 impl SpanIndex {
160     pub fn unwrap(self) -> usize {
161         self.0
162     }
163 }
164
165 impl<'a> Iterator for Parser<'a> {
166     type Item = Piece<'a>;
167
168     fn next(&mut self) -> Option<Piece<'a>> {
169         if let Some(&(pos, c)) = self.cur.peek() {
170             match c {
171                 '{' => {
172                     let curr_last_brace = self.last_opening_brace_pos;
173                     self.last_opening_brace_pos = Some(self.to_span_index(pos));
174                     self.cur.next();
175                     if self.consume('{') {
176                         self.last_opening_brace_pos = curr_last_brace;
177
178                         Some(String(self.string(pos + 1)))
179                     } else {
180                         let arg = self.argument();
181                         if let Some(arg_pos) = self.must_consume('}').map(|end| {
182                             (self.to_span_index(pos), self.to_span_index(end + 1))
183                         }) {
184                             self.arg_places.push(arg_pos);
185                         }
186                         Some(NextArgument(arg))
187                     }
188                 }
189                 '}' => {
190                     self.cur.next();
191                     if self.consume('}') {
192                         Some(String(self.string(pos + 1)))
193                     } else {
194                         let err_pos = self.to_span_index(pos);
195                         self.err_with_note(
196                             "unmatched `}` found",
197                             "unmatched `}`",
198                             "if you intended to print `}`, you can escape it using `}}`",
199                             err_pos,
200                             err_pos,
201                         );
202                         None
203                     }
204                 }
205                 '\n' => {
206                     Some(String(self.string(pos)))
207                 }
208                 _ => Some(String(self.string(pos))),
209             }
210         } else {
211             None
212         }
213     }
214 }
215
216 impl<'a> Parser<'a> {
217     /// Creates a new parser for the given format string
218     pub fn new(
219         s: &'a str,
220         style: Option<usize>,
221         skips: Vec<usize>,
222         append_newline: bool,
223     ) -> Parser<'a> {
224         Parser {
225             input: s,
226             cur: s.char_indices().peekable(),
227             errors: vec![],
228             curarg: 0,
229             style,
230             arg_places: vec![],
231             skips,
232             last_opening_brace_pos: None,
233             append_newline,
234         }
235     }
236
237     /// Notifies of an error. The message doesn't actually need to be of type
238     /// String, but I think it does when this eventually uses conditions so it
239     /// might as well start using it now.
240     fn err<S1: Into<string::String>, S2: Into<string::String>>(
241         &mut self,
242         description: S1,
243         label: S2,
244         start: SpanIndex,
245         end: SpanIndex,
246     ) {
247         self.errors.push(ParseError {
248             description: description.into(),
249             note: None,
250             label: label.into(),
251             start,
252             end,
253             secondary_label: None,
254         });
255     }
256
257     /// Notifies of an error. The message doesn't actually need to be of type
258     /// String, but I think it does when this eventually uses conditions so it
259     /// might as well start using it now.
260     fn err_with_note<S1: Into<string::String>, S2: Into<string::String>, S3: Into<string::String>>(
261         &mut self,
262         description: S1,
263         label: S2,
264         note: S3,
265         start: SpanIndex,
266         end: SpanIndex,
267     ) {
268         self.errors.push(ParseError {
269             description: description.into(),
270             note: Some(note.into()),
271             label: label.into(),
272             start,
273             end,
274             secondary_label: None,
275         });
276     }
277
278     /// Optionally consumes the specified character. If the character is not at
279     /// the current position, then the current iterator isn't moved and false is
280     /// returned, otherwise the character is consumed and true is returned.
281     fn consume(&mut self, c: char) -> bool {
282         if let Some(&(_, maybe)) = self.cur.peek() {
283             if c == maybe {
284                 self.cur.next();
285                 true
286             } else {
287                 false
288             }
289         } else {
290             false
291         }
292     }
293
294     fn raw(&self) -> usize {
295         self.style.map(|raw| raw + 1).unwrap_or(0)
296     }
297
298     fn to_span_index(&self, pos: usize) -> SpanIndex {
299         let mut pos = pos;
300         for skip in &self.skips {
301             if pos > *skip {
302                 pos += 1;
303             } else if pos == *skip && self.raw() == 0 {
304                 pos += 1;
305             } else {
306                 break;
307             }
308         }
309         SpanIndex(self.raw() + pos + 1)
310     }
311
312     /// Forces consumption of the specified character. If the character is not
313     /// found, an error is emitted.
314     fn must_consume(&mut self, c: char) -> Option<usize> {
315         self.ws();
316
317         if let Some(&(pos, maybe)) = self.cur.peek() {
318             if c == maybe {
319                 self.cur.next();
320                 Some(pos)
321             } else {
322                 let pos = self.to_span_index(pos);
323                 let description = format!("expected `'}}'`, found `{:?}`", maybe);
324                 let label = "expected `}`".to_owned();
325                 let (note, secondary_label) = if c == '}' {
326                     (Some("if you intended to print `{`, you can escape it using `{{`".to_owned()),
327                      self.last_opening_brace_pos.map(|pos| {
328                         ("because of this opening brace".to_owned(), pos, pos)
329                      }))
330                 } else {
331                     (None, None)
332                 };
333                 self.errors.push(ParseError {
334                     description,
335                     note,
336                     label,
337                     start: pos,
338                     end: pos,
339                     secondary_label,
340                 });
341                 None
342             }
343         } else {
344             let description = format!("expected `{:?}` but string was terminated", c);
345             // point at closing `"`
346             let pos = self.input.len() - if self.append_newline { 1 } else { 0 };
347             let pos = self.to_span_index(pos);
348             if c == '}' {
349                 let label = format!("expected `{:?}`", c);
350                 let (note, secondary_label) = if c == '}' {
351                     (Some("if you intended to print `{`, you can escape it using `{{`".to_owned()),
352                      self.last_opening_brace_pos.map(|pos| {
353                         ("because of this opening brace".to_owned(), pos, pos)
354                      }))
355                 } else {
356                     (None, None)
357                 };
358                 self.errors.push(ParseError {
359                     description,
360                     note,
361                     label,
362                     start: pos,
363                     end: pos,
364                     secondary_label,
365                 });
366             } else {
367                 self.err(description, format!("expected `{:?}`", c), pos, pos);
368             }
369             None
370         }
371     }
372
373     /// Consumes all whitespace characters until the first non-whitespace character
374     fn ws(&mut self) {
375         while let Some(&(_, c)) = self.cur.peek() {
376             if c.is_whitespace() {
377                 self.cur.next();
378             } else {
379                 break;
380             }
381         }
382     }
383
384     /// Parses all of a string which is to be considered a "raw literal" in a
385     /// format string. This is everything outside of the braces.
386     fn string(&mut self, start: usize) -> &'a str {
387         // we may not consume the character, peek the iterator
388         while let Some(&(pos, c)) = self.cur.peek() {
389             match c {
390                 '{' | '}' => {
391                     return &self.input[start..pos];
392                 }
393                 _ => {
394                     self.cur.next();
395                 }
396             }
397         }
398         &self.input[start..self.input.len()]
399     }
400
401     /// Parses an Argument structure, or what's contained within braces inside the format string
402     fn argument(&mut self) -> Argument<'a> {
403         let pos = self.position();
404         let format = self.format();
405
406         // Resolve position after parsing format spec.
407         let pos = match pos {
408             Some(position) => position,
409             None => {
410                 let i = self.curarg;
411                 self.curarg += 1;
412                 ArgumentImplicitlyIs(i)
413             }
414         };
415
416         Argument {
417             position: pos,
418             format,
419         }
420     }
421
422     /// Parses a positional argument for a format. This could either be an
423     /// integer index of an argument, a named argument, or a blank string.
424     /// Returns `Some(parsed_position)` if the position is not implicitly
425     /// consuming a macro argument, `None` if it's the case.
426     fn position(&mut self) -> Option<Position<'a>> {
427         if let Some(i) = self.integer() {
428             Some(ArgumentIs(i))
429         } else {
430             match self.cur.peek() {
431                 Some(&(_, c)) if c.is_alphabetic() => Some(ArgumentNamed(self.word())),
432                 Some(&(pos, c)) if c == '_' => {
433                     let invalid_name = self.string(pos);
434                     self.err_with_note(format!("invalid argument name `{}`", invalid_name),
435                                        "invalid argument name",
436                                        "argument names cannot start with an underscore",
437                                        self.to_span_index(pos),
438                                        self.to_span_index(pos + invalid_name.len()));
439                     Some(ArgumentNamed(invalid_name))
440                 },
441
442                 // This is an `ArgumentNext`.
443                 // Record the fact and do the resolution after parsing the
444                 // format spec, to make things like `{:.*}` work.
445                 _ => None,
446             }
447         }
448     }
449
450     /// Parses a format specifier at the current position, returning all of the
451     /// relevant information in the FormatSpec struct.
452     fn format(&mut self) -> FormatSpec<'a> {
453         let mut spec = FormatSpec {
454             fill: None,
455             align: AlignUnknown,
456             flags: 0,
457             precision: CountImplied,
458             width: CountImplied,
459             ty: &self.input[..0],
460         };
461         if !self.consume(':') {
462             return spec;
463         }
464
465         // fill character
466         if let Some(&(_, c)) = self.cur.peek() {
467             match self.cur.clone().nth(1) {
468                 Some((_, '>')) | Some((_, '<')) | Some((_, '^')) => {
469                     spec.fill = Some(c);
470                     self.cur.next();
471                 }
472                 _ => {}
473             }
474         }
475         // Alignment
476         if self.consume('<') {
477             spec.align = AlignLeft;
478         } else if self.consume('>') {
479             spec.align = AlignRight;
480         } else if self.consume('^') {
481             spec.align = AlignCenter;
482         }
483         // Sign flags
484         if self.consume('+') {
485             spec.flags |= 1 << (FlagSignPlus as u32);
486         } else if self.consume('-') {
487             spec.flags |= 1 << (FlagSignMinus as u32);
488         }
489         // Alternate marker
490         if self.consume('#') {
491             spec.flags |= 1 << (FlagAlternate as u32);
492         }
493         // Width and precision
494         let mut havewidth = false;
495         if self.consume('0') {
496             // small ambiguity with '0$' as a format string. In theory this is a
497             // '0' flag and then an ill-formatted format string with just a '$'
498             // and no count, but this is better if we instead interpret this as
499             // no '0' flag and '0$' as the width instead.
500             if self.consume('$') {
501                 spec.width = CountIsParam(0);
502                 havewidth = true;
503             } else {
504                 spec.flags |= 1 << (FlagSignAwareZeroPad as u32);
505             }
506         }
507         if !havewidth {
508             spec.width = self.count();
509         }
510         if self.consume('.') {
511             if self.consume('*') {
512                 // Resolve `CountIsNextParam`.
513                 // We can do this immediately as `position` is resolved later.
514                 let i = self.curarg;
515                 self.curarg += 1;
516                 spec.precision = CountIsParam(i);
517             } else {
518                 spec.precision = self.count();
519             }
520         }
521         // Optional radix followed by the actual format specifier
522         if self.consume('x') {
523             if self.consume('?') {
524                 spec.flags |= 1 << (FlagDebugLowerHex as u32);
525                 spec.ty = "?";
526             } else {
527                 spec.ty = "x";
528             }
529         } else if self.consume('X') {
530             if self.consume('?') {
531                 spec.flags |= 1 << (FlagDebugUpperHex as u32);
532                 spec.ty = "?";
533             } else {
534                 spec.ty = "X";
535             }
536         } else if self.consume('?') {
537             spec.ty = "?";
538         } else {
539             spec.ty = self.word();
540         }
541         spec
542     }
543
544     /// Parses a Count parameter at the current position. This does not check
545     /// for 'CountIsNextParam' because that is only used in precision, not
546     /// width.
547     fn count(&mut self) -> Count<'a> {
548         if let Some(i) = self.integer() {
549             if self.consume('$') {
550                 CountIsParam(i)
551             } else {
552                 CountIs(i)
553             }
554         } else {
555             let tmp = self.cur.clone();
556             let word = self.word();
557             if word.is_empty() {
558                 self.cur = tmp;
559                 CountImplied
560             } else if self.consume('$') {
561                 CountIsName(word)
562             } else {
563                 self.cur = tmp;
564                 CountImplied
565             }
566         }
567     }
568
569     /// Parses a word starting at the current position. A word is considered to
570     /// be an alphabetic character followed by any number of alphanumeric
571     /// characters.
572     fn word(&mut self) -> &'a str {
573         let start = match self.cur.peek() {
574             Some(&(pos, c)) if c.is_xid_start() => {
575                 self.cur.next();
576                 pos
577             }
578             _ => {
579                 return &self.input[..0];
580             }
581         };
582         while let Some(&(pos, c)) = self.cur.peek() {
583             if c.is_xid_continue() {
584                 self.cur.next();
585             } else {
586                 return &self.input[start..pos];
587             }
588         }
589         &self.input[start..self.input.len()]
590     }
591
592     /// Optionally parses an integer at the current position. This doesn't deal
593     /// with overflow at all, it's just accumulating digits.
594     fn integer(&mut self) -> Option<usize> {
595         let mut cur = 0;
596         let mut found = false;
597         while let Some(&(_, c)) = self.cur.peek() {
598             if let Some(i) = c.to_digit(10) {
599                 cur = cur * 10 + i as usize;
600                 found = true;
601                 self.cur.next();
602             } else {
603                 break;
604             }
605         }
606         if found {
607             Some(cur)
608         } else {
609             None
610         }
611     }
612 }
613
614 #[cfg(test)]
615 mod tests {
616     use super::*;
617
618     fn same(fmt: &'static str, p: &[Piece<'static>]) {
619         let parser = Parser::new(fmt, None, vec![], false);
620         assert!(parser.collect::<Vec<Piece<'static>>>() == p);
621     }
622
623     fn fmtdflt() -> FormatSpec<'static> {
624         return FormatSpec {
625             fill: None,
626             align: AlignUnknown,
627             flags: 0,
628             precision: CountImplied,
629             width: CountImplied,
630             ty: "",
631         };
632     }
633
634     fn musterr(s: &str) {
635         let mut p = Parser::new(s, None, vec![], false);
636         p.next();
637         assert!(!p.errors.is_empty());
638     }
639
640     #[test]
641     fn simple() {
642         same("asdf", &[String("asdf")]);
643         same("a{{b", &[String("a"), String("{b")]);
644         same("a}}b", &[String("a"), String("}b")]);
645         same("a}}", &[String("a"), String("}")]);
646         same("}}", &[String("}")]);
647         same("\\}}", &[String("\\"), String("}")]);
648     }
649
650     #[test]
651     fn invalid01() {
652         musterr("{")
653     }
654     #[test]
655     fn invalid02() {
656         musterr("}")
657     }
658     #[test]
659     fn invalid04() {
660         musterr("{3a}")
661     }
662     #[test]
663     fn invalid05() {
664         musterr("{:|}")
665     }
666     #[test]
667     fn invalid06() {
668         musterr("{:>>>}")
669     }
670
671     #[test]
672     fn format_nothing() {
673         same("{}",
674              &[NextArgument(Argument {
675                    position: ArgumentImplicitlyIs(0),
676                    format: fmtdflt(),
677                })]);
678     }
679     #[test]
680     fn format_position() {
681         same("{3}",
682              &[NextArgument(Argument {
683                    position: ArgumentIs(3),
684                    format: fmtdflt(),
685                })]);
686     }
687     #[test]
688     fn format_position_nothing_else() {
689         same("{3:}",
690              &[NextArgument(Argument {
691                    position: ArgumentIs(3),
692                    format: fmtdflt(),
693                })]);
694     }
695     #[test]
696     fn format_type() {
697         same("{3:a}",
698              &[NextArgument(Argument {
699                    position: ArgumentIs(3),
700                    format: FormatSpec {
701                        fill: None,
702                        align: AlignUnknown,
703                        flags: 0,
704                        precision: CountImplied,
705                        width: CountImplied,
706                        ty: "a",
707                    },
708                })]);
709     }
710     #[test]
711     fn format_align_fill() {
712         same("{3:>}",
713              &[NextArgument(Argument {
714                    position: ArgumentIs(3),
715                    format: FormatSpec {
716                        fill: None,
717                        align: AlignRight,
718                        flags: 0,
719                        precision: CountImplied,
720                        width: CountImplied,
721                        ty: "",
722                    },
723                })]);
724         same("{3:0<}",
725              &[NextArgument(Argument {
726                    position: ArgumentIs(3),
727                    format: FormatSpec {
728                        fill: Some('0'),
729                        align: AlignLeft,
730                        flags: 0,
731                        precision: CountImplied,
732                        width: CountImplied,
733                        ty: "",
734                    },
735                })]);
736         same("{3:*<abcd}",
737              &[NextArgument(Argument {
738                    position: ArgumentIs(3),
739                    format: FormatSpec {
740                        fill: Some('*'),
741                        align: AlignLeft,
742                        flags: 0,
743                        precision: CountImplied,
744                        width: CountImplied,
745                        ty: "abcd",
746                    },
747                })]);
748     }
749     #[test]
750     fn format_counts() {
751         same("{:10s}",
752              &[NextArgument(Argument {
753                    position: ArgumentImplicitlyIs(0),
754                    format: FormatSpec {
755                        fill: None,
756                        align: AlignUnknown,
757                        flags: 0,
758                        precision: CountImplied,
759                        width: CountIs(10),
760                        ty: "s",
761                    },
762                })]);
763         same("{:10$.10s}",
764              &[NextArgument(Argument {
765                    position: ArgumentImplicitlyIs(0),
766                    format: FormatSpec {
767                        fill: None,
768                        align: AlignUnknown,
769                        flags: 0,
770                        precision: CountIs(10),
771                        width: CountIsParam(10),
772                        ty: "s",
773                    },
774                })]);
775         same("{:.*s}",
776              &[NextArgument(Argument {
777                    position: ArgumentImplicitlyIs(1),
778                    format: FormatSpec {
779                        fill: None,
780                        align: AlignUnknown,
781                        flags: 0,
782                        precision: CountIsParam(0),
783                        width: CountImplied,
784                        ty: "s",
785                    },
786                })]);
787         same("{:.10$s}",
788              &[NextArgument(Argument {
789                    position: ArgumentImplicitlyIs(0),
790                    format: FormatSpec {
791                        fill: None,
792                        align: AlignUnknown,
793                        flags: 0,
794                        precision: CountIsParam(10),
795                        width: CountImplied,
796                        ty: "s",
797                    },
798                })]);
799         same("{:a$.b$s}",
800              &[NextArgument(Argument {
801                    position: ArgumentImplicitlyIs(0),
802                    format: FormatSpec {
803                        fill: None,
804                        align: AlignUnknown,
805                        flags: 0,
806                        precision: CountIsName("b"),
807                        width: CountIsName("a"),
808                        ty: "s",
809                    },
810                })]);
811     }
812     #[test]
813     fn format_flags() {
814         same("{:-}",
815              &[NextArgument(Argument {
816                    position: ArgumentImplicitlyIs(0),
817                    format: FormatSpec {
818                        fill: None,
819                        align: AlignUnknown,
820                        flags: (1 << FlagSignMinus as u32),
821                        precision: CountImplied,
822                        width: CountImplied,
823                        ty: "",
824                    },
825                })]);
826         same("{:+#}",
827              &[NextArgument(Argument {
828                    position: ArgumentImplicitlyIs(0),
829                    format: FormatSpec {
830                        fill: None,
831                        align: AlignUnknown,
832                        flags: (1 << FlagSignPlus as u32) | (1 << FlagAlternate as u32),
833                        precision: CountImplied,
834                        width: CountImplied,
835                        ty: "",
836                    },
837                })]);
838     }
839     #[test]
840     fn format_mixture() {
841         same("abcd {3:a} efg",
842              &[String("abcd "),
843                NextArgument(Argument {
844                    position: ArgumentIs(3),
845                    format: FormatSpec {
846                        fill: None,
847                        align: AlignUnknown,
848                        flags: 0,
849                        precision: CountImplied,
850                        width: CountImplied,
851                        ty: "a",
852                    },
853                }),
854                String(" efg")]);
855     }
856 }