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