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