]> git.lizzy.rs Git - rust.git/blob - src/libfmt_macros/lib.rs
Add a few more derivings to AST types
[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 && self.consume('$') => {
356                         CountIsName(word)
357                     }
358                     _ => {
359                         self.cur = tmp;
360                         CountImplied
361                     }
362                 }
363             }
364         }
365     }
366
367     /// Parses a word starting at the current position. A word is considered to
368     /// be an alphabetic character followed by any number of alphanumeric
369     /// characters.
370     fn word(&mut self) -> &'a str {
371         let start = match self.cur.clone().next() {
372             Some((pos, c)) if char::is_XID_start(c) => {
373                 self.cur.next();
374                 pos
375             }
376             Some(..) | None => { return self.input.slice(0, 0); }
377         };
378         let mut end;
379         loop {
380             match self.cur.clone().next() {
381                 Some((_, c)) if char::is_XID_continue(c) => {
382                     self.cur.next();
383                 }
384                 Some((pos, _)) => { end = pos; break }
385                 None => { end = self.input.len(); break }
386             }
387         }
388         self.input.slice(start, end)
389     }
390
391     /// Optionally parses an integer at the current position. This doesn't deal
392     /// with overflow at all, it's just accumulating digits.
393     fn integer(&mut self) -> Option<uint> {
394         let mut cur = 0;
395         let mut found = false;
396         loop {
397             match self.cur.clone().next() {
398                 Some((_, c)) => {
399                     match char::to_digit(c, 10) {
400                         Some(i) => {
401                             cur = cur * 10 + i;
402                             found = true;
403                             self.cur.next();
404                         }
405                         None => { break }
406                     }
407                 }
408                 None => { break }
409             }
410         }
411         if found {
412             return Some(cur);
413         } else {
414             return None;
415         }
416     }
417 }
418
419 #[cfg(test)]
420 mod tests {
421     use super::*;
422
423     fn same(fmt: &'static str, p: &[Piece<'static>]) {
424         let mut parser = Parser::new(fmt);
425         assert!(p == parser.collect::<Vec<Piece<'static>>>().as_slice());
426     }
427
428     fn fmtdflt() -> FormatSpec<'static> {
429         return FormatSpec {
430             fill: None,
431             align: AlignUnknown,
432             flags: 0,
433             precision: CountImplied,
434             width: CountImplied,
435             ty: "",
436         }
437     }
438
439     fn musterr(s: &str) {
440         let mut p = Parser::new(s);
441         p.next();
442         assert!(p.errors.len() != 0);
443     }
444
445     #[test]
446     fn simple() {
447         same("asdf", [String("asdf")]);
448         same("a{{b", [String("a"), String("{b")]);
449         same("a}}b", [String("a"), String("}b")]);
450         same("a}}", [String("a"), String("}")]);
451         same("}}", [String("}")]);
452         same("\\}}", [String("\\"), String("}")]);
453     }
454
455     #[test] fn invalid01() { musterr("{") }
456     #[test] fn invalid02() { musterr("}") }
457     #[test] fn invalid04() { musterr("{3a}") }
458     #[test] fn invalid05() { musterr("{:|}") }
459     #[test] fn invalid06() { musterr("{:>>>}") }
460
461     #[test]
462     fn format_nothing() {
463         same("{}", [Argument(Argument {
464             position: ArgumentNext,
465             format: fmtdflt(),
466         })]);
467     }
468     #[test]
469     fn format_position() {
470         same("{3}", [Argument(Argument {
471             position: ArgumentIs(3),
472             format: fmtdflt(),
473         })]);
474     }
475     #[test]
476     fn format_position_nothing_else() {
477         same("{3:}", [Argument(Argument {
478             position: ArgumentIs(3),
479             format: fmtdflt(),
480         })]);
481     }
482     #[test]
483     fn format_type() {
484         same("{3:a}", [Argument(Argument {
485             position: ArgumentIs(3),
486             format: FormatSpec {
487                 fill: None,
488                 align: AlignUnknown,
489                 flags: 0,
490                 precision: CountImplied,
491                 width: CountImplied,
492                 ty: "a",
493             },
494         })]);
495     }
496     #[test]
497     fn format_align_fill() {
498         same("{3:>}", [Argument(Argument {
499             position: ArgumentIs(3),
500             format: FormatSpec {
501                 fill: None,
502                 align: AlignRight,
503                 flags: 0,
504                 precision: CountImplied,
505                 width: CountImplied,
506                 ty: "",
507             },
508         })]);
509         same("{3:0<}", [Argument(Argument {
510             position: ArgumentIs(3),
511             format: FormatSpec {
512                 fill: Some('0'),
513                 align: AlignLeft,
514                 flags: 0,
515                 precision: CountImplied,
516                 width: CountImplied,
517                 ty: "",
518             },
519         })]);
520         same("{3:*<abcd}", [Argument(Argument {
521             position: ArgumentIs(3),
522             format: FormatSpec {
523                 fill: Some('*'),
524                 align: AlignLeft,
525                 flags: 0,
526                 precision: CountImplied,
527                 width: CountImplied,
528                 ty: "abcd",
529             },
530         })]);
531     }
532     #[test]
533     fn format_counts() {
534         same("{:10s}", [Argument(Argument {
535             position: ArgumentNext,
536             format: FormatSpec {
537                 fill: None,
538                 align: AlignUnknown,
539                 flags: 0,
540                 precision: CountImplied,
541                 width: CountIs(10),
542                 ty: "s",
543             },
544         })]);
545         same("{:10$.10s}", [Argument(Argument {
546             position: ArgumentNext,
547             format: FormatSpec {
548                 fill: None,
549                 align: AlignUnknown,
550                 flags: 0,
551                 precision: CountIs(10),
552                 width: CountIsParam(10),
553                 ty: "s",
554             },
555         })]);
556         same("{:.*s}", [Argument(Argument {
557             position: ArgumentNext,
558             format: FormatSpec {
559                 fill: None,
560                 align: AlignUnknown,
561                 flags: 0,
562                 precision: CountIsNextParam,
563                 width: CountImplied,
564                 ty: "s",
565             },
566         })]);
567         same("{:.10$s}", [Argument(Argument {
568             position: ArgumentNext,
569             format: FormatSpec {
570                 fill: None,
571                 align: AlignUnknown,
572                 flags: 0,
573                 precision: CountIsParam(10),
574                 width: CountImplied,
575                 ty: "s",
576             },
577         })]);
578         same("{:a$.b$s}", [Argument(Argument {
579             position: ArgumentNext,
580             format: FormatSpec {
581                 fill: None,
582                 align: AlignUnknown,
583                 flags: 0,
584                 precision: CountIsName("b"),
585                 width: CountIsName("a"),
586                 ty: "s",
587             },
588         })]);
589     }
590     #[test]
591     fn format_flags() {
592         same("{:-}", [Argument(Argument {
593             position: ArgumentNext,
594             format: FormatSpec {
595                 fill: None,
596                 align: AlignUnknown,
597                 flags: (1 << FlagSignMinus as uint),
598                 precision: CountImplied,
599                 width: CountImplied,
600                 ty: "",
601             },
602         })]);
603         same("{:+#}", [Argument(Argument {
604             position: ArgumentNext,
605             format: FormatSpec {
606                 fill: None,
607                 align: AlignUnknown,
608                 flags: (1 << FlagSignPlus as uint) | (1 << FlagAlternate as uint),
609                 precision: CountImplied,
610                 width: CountImplied,
611                 ty: "",
612             },
613         })]);
614     }
615     #[test]
616     fn format_mixture() {
617         same("abcd {3:a} efg", [String("abcd "), Argument(Argument {
618             position: ArgumentIs(3),
619             format: FormatSpec {
620                 fill: None,
621                 align: AlignUnknown,
622                 flags: 0,
623                 precision: CountImplied,
624                 width: CountImplied,
625                 ty: "a",
626             },
627         }), String(" efg")]);
628     }
629 }