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