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