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