]> git.lizzy.rs Git - rust.git/blob - src/libstd/fmt/parse.rs
rand: Use fill() instead of read()
[rust.git] / src / libstd / fmt / parse.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 //! Parsing of format strings
12 //!
13 //! These structures are used when parsing format strings for the compiler.
14 //! Parsing does not currently happen at runtime (structures of std::fmt::rt are
15 //! generated instead).
16
17 use prelude::*;
18
19 use char;
20 use str;
21
22 /// A piece is a portion of the format string which represents the next part to
23 /// emit. These are emitted as a stream by the `Parser` class.
24 #[deriving(Eq)]
25 pub enum Piece<'a> {
26     /// A literal string which should directly be emitted
27     String(&'a str),
28     /// A back-reference to whatever the current argument is. This is used
29     /// inside of a method call to refer back to the original argument.
30     CurrentArgument,
31     /// This describes that formatting should process the next argument (as
32     /// specified inside) for emission.
33     Argument(Argument<'a>),
34 }
35
36 /// Representation of an argument specification.
37 #[deriving(Eq)]
38 pub struct Argument<'a> {
39     /// Where to find this argument
40     position: Position<'a>,
41     /// How to format the argument
42     format: FormatSpec<'a>,
43     /// If not `None`, what method to invoke on the argument
44     method: Option<~Method<'a>>
45 }
46
47 /// Specification for the formatting of an argument in the format string.
48 #[deriving(Eq)]
49 pub struct FormatSpec<'a> {
50     /// Optionally specified character to fill alignment with
51     fill: Option<char>,
52     /// Optionally specified alignment
53     align: Alignment,
54     /// Packed version of various flags provided
55     flags: uint,
56     /// The integer precision to use
57     precision: Count<'a>,
58     /// The string width requested for the resulting format
59     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     ty: &'a str
64 }
65
66 /// Enum describing where an argument for a format can be located.
67 #[deriving(Eq)]
68 #[allow(missing_doc)]
69 pub enum Position<'a> {
70     ArgumentNext, ArgumentIs(uint), ArgumentNamed(&'a str)
71 }
72
73 /// Enum of alignments which are supported.
74 #[deriving(Eq)]
75 #[allow(missing_doc)]
76 pub enum Alignment { AlignLeft, AlignRight, AlignUnknown }
77
78 /// Various flags which can be applied to format strings, the meaning of these
79 /// flags is defined by the formatters themselves.
80 #[deriving(Eq)]
81 #[allow(missing_doc)]
82 pub enum Flag {
83     FlagSignPlus,
84     FlagSignMinus,
85     FlagAlternate,
86     FlagSignAwareZeroPad,
87 }
88
89 /// A count is used for the precision and width parameters of an integer, and
90 /// can reference either an argument or a literal integer.
91 #[deriving(Eq)]
92 #[allow(missing_doc)]
93 pub enum Count<'a> {
94     CountIs(uint),
95     CountIsName(&'a str),
96     CountIsParam(uint),
97     CountIsNextParam,
98     CountImplied,
99 }
100
101 /// Enum describing all of the possible methods which the formatting language
102 /// currently supports.
103 #[deriving(Eq)]
104 pub enum Method<'a> {
105     /// A plural method selects on an integer over a list of either integer or
106     /// keyword-defined clauses. The meaning of the keywords is defined by the
107     /// current locale.
108     ///
109     /// An offset is optionally present at the beginning which is used to match
110     /// against keywords, but it is not matched against the literal integers.
111     ///
112     /// The final element of this enum is the default "other" case which is
113     /// always required to be specified.
114     Plural(Option<uint>, ~[PluralArm<'a>], ~[Piece<'a>]),
115
116     /// A select method selects over a string. Each arm is a different string
117     /// which can be selected for.
118     ///
119     /// As with `Plural`, a default "other" case is required as well.
120     Select(~[SelectArm<'a>], ~[Piece<'a>]),
121 }
122
123 /// A selector for what pluralization a plural method should take
124 #[deriving(Eq, Hash)]
125 pub enum PluralSelector {
126     /// One of the plural keywords should be used
127     Keyword(PluralKeyword),
128     /// A literal pluralization should be used
129     Literal(uint),
130 }
131
132 /// Structure representing one "arm" of the `plural` function.
133 #[deriving(Eq)]
134 pub struct PluralArm<'a> {
135     /// A selector can either be specified by a keyword or with an integer
136     /// literal.
137     selector: PluralSelector,
138     /// Array of pieces which are the format of this arm
139     result: ~[Piece<'a>],
140 }
141
142 /// Enum of the 5 CLDR plural keywords. There is one more, "other", but that is
143 /// specially placed in the `Plural` variant of `Method`
144 ///
145 /// http://www.icu-project.org/apiref/icu4c/classicu_1_1PluralRules.html
146 #[deriving(Eq, Hash)]
147 #[allow(missing_doc)]
148 pub enum PluralKeyword {
149     Zero, One, Two, Few, Many
150 }
151
152 /// Structure representing one "arm" of the `select` function.
153 #[deriving(Eq)]
154 pub struct SelectArm<'a> {
155     /// String selector which guards this arm
156     selector: &'a str,
157     /// Array of pieces which are the format of this arm
158     result: ~[Piece<'a>],
159 }
160
161 /// The parser structure for interpreting the input format string. This is
162 /// modelled as an iterator over `Piece` structures to form a stream of tokens
163 /// being output.
164 ///
165 /// This is a recursive-descent parser for the sake of simplicity, and if
166 /// necessary there's probably lots of room for improvement performance-wise.
167 pub struct Parser<'a> {
168     priv input: &'a str,
169     priv cur: str::CharOffsets<'a>,
170     priv depth: uint,
171     /// Error messages accumulated during parsing
172     errors: ~[~str],
173 }
174
175 impl<'a> Iterator<Piece<'a>> for Parser<'a> {
176     fn next(&mut self) -> Option<Piece<'a>> {
177         match self.cur.clone().next() {
178             Some((_, '#')) => { self.cur.next(); Some(CurrentArgument) }
179             Some((_, '{')) => {
180                 self.cur.next();
181                 let ret = Some(Argument(self.argument()));
182                 self.must_consume('}');
183                 ret
184             }
185             Some((pos, '\\')) => {
186                 self.cur.next();
187                 self.escape(); // ensure it's a valid escape sequence
188                 Some(String(self.string(pos + 1))) // skip the '\' character
189             }
190             Some((_, '}')) if self.depth == 0 => {
191                 self.cur.next();
192                 self.err("unmatched `}` found");
193                 None
194             }
195             Some((_, '}')) | None => { None }
196             Some((pos, _)) => {
197                 Some(String(self.string(pos)))
198             }
199         }
200     }
201 }
202
203 impl<'a> Parser<'a> {
204     /// Creates a new parser for the given format string
205     pub fn new<'a>(s: &'a str) -> Parser<'a> {
206         Parser {
207             input: s,
208             cur: s.char_indices(),
209             depth: 0,
210             errors: ~[],
211         }
212     }
213
214     /// Notifies of an error. The message doesn't actually need to be of type
215     /// ~str, but I think it does when this eventually uses conditions so it
216     /// might as well start using it now.
217     fn err(&mut self, msg: &str) {
218         self.errors.push(msg.to_owned());
219     }
220
221     /// Optionally consumes the specified character. If the character is not at
222     /// the current position, then the current iterator isn't moved and false is
223     /// returned, otherwise the character is consumed and true is returned.
224     fn consume(&mut self, c: char) -> bool {
225         match self.cur.clone().next() {
226             Some((_, maybe)) if c == maybe => {
227                 self.cur.next();
228                 true
229             }
230             Some(..) | None => false,
231         }
232     }
233
234     /// Forces consumption of the specified character. If the character is not
235     /// found, an error is emitted.
236     fn must_consume(&mut self, c: char) {
237         self.ws();
238         match self.cur.clone().next() {
239             Some((_, maybe)) if c == maybe => {
240                 self.cur.next();
241             }
242             Some((_, other)) => {
243                 self.err(
244                     format!("expected `{}` but found `{}`", c, other));
245             }
246             None => {
247                 self.err(
248                     format!("expected `{}` but string was terminated", c));
249             }
250         }
251     }
252
253     /// Attempts to consume any amount of whitespace followed by a character
254     fn wsconsume(&mut self, c: char) -> bool {
255         self.ws(); self.consume(c)
256     }
257
258     /// Consumes all whitespace characters until the first non-whitespace
259     /// character
260     fn ws(&mut self) {
261         loop {
262             match self.cur.clone().next() {
263                 Some((_, c)) if char::is_whitespace(c) => { self.cur.next(); }
264                 Some(..) | None => { return }
265             }
266         }
267     }
268
269     /// Consumes an escape sequence, failing if there is not a valid character
270     /// to be escaped.
271     fn escape(&mut self) -> char {
272         match self.cur.next() {
273             Some((_, c @ '#')) | Some((_, c @ '{')) |
274             Some((_, c @ '\\')) | Some((_, c @ '}')) => { c }
275             Some((_, c)) => {
276                 self.err(format!("invalid escape character `{}`", c));
277                 c
278             }
279             None => {
280                 self.err("expected an escape sequence, but format string was \
281                            terminated");
282                 ' '
283             }
284         }
285     }
286
287     /// Parses all of a string which is to be considered a "raw literal" in a
288     /// format string. This is everything outside of the braces.
289     fn string(&mut self, start: uint) -> &'a str {
290         loop {
291             // we may not consume the character, so clone the iterator
292             match self.cur.clone().next() {
293                 Some((pos, '\\')) | Some((pos, '#')) |
294                 Some((pos, '}')) | Some((pos, '{')) => {
295                     return self.input.slice(start, pos);
296                 }
297                 Some(..) => { self.cur.next(); }
298                 None => {
299                     self.cur.next();
300                     return self.input.slice(start, self.input.len());
301                 }
302             }
303         }
304     }
305
306     /// Parses an Argument structure, or what's contained within braces inside
307     /// the format string
308     fn argument(&mut self) -> Argument<'a> {
309         Argument {
310             position: self.position(),
311             format: self.format(),
312             method: self.method(),
313         }
314     }
315
316     /// Parses a positional argument for a format. This could either be an
317     /// integer index of an argument, a named argument, or a blank string.
318     fn position(&mut self) -> Position<'a> {
319         match self.integer() {
320             Some(i) => { ArgumentIs(i) }
321             None => {
322                 match self.cur.clone().next() {
323                     Some((_, c)) if char::is_alphabetic(c) => {
324                         ArgumentNamed(self.word())
325                     }
326                     _ => ArgumentNext
327                 }
328             }
329         }
330     }
331
332     /// Parses a format specifier at the current position, returning all of the
333     /// relevant information in the FormatSpec struct.
334     fn format(&mut self) -> FormatSpec<'a> {
335         let mut spec = FormatSpec {
336             fill: None,
337             align: AlignUnknown,
338             flags: 0,
339             precision: CountImplied,
340             width: CountImplied,
341             ty: self.input.slice(0, 0),
342         };
343         if !self.consume(':') { return spec }
344
345         // fill character
346         match self.cur.clone().next() {
347             Some((_, c)) => {
348                 match self.cur.clone().skip(1).next() {
349                     Some((_, '>')) | Some((_, '<')) => {
350                         spec.fill = Some(c);
351                         self.cur.next();
352                     }
353                     Some(..) | None => {}
354                 }
355             }
356             None => {}
357         }
358         // Alignment
359         if self.consume('<') {
360             spec.align = AlignLeft;
361         } else if self.consume('>') {
362             spec.align = AlignRight;
363         }
364         // Sign flags
365         if self.consume('+') {
366             spec.flags |= 1 << (FlagSignPlus as uint);
367         } else if self.consume('-') {
368             spec.flags |= 1 << (FlagSignMinus as uint);
369         }
370         // Alternate marker
371         if self.consume('#') {
372             spec.flags |= 1 << (FlagAlternate as uint);
373         }
374         // Width and precision
375         let mut havewidth = false;
376         if self.consume('0') {
377             // small ambiguity with '0$' as a format string. In theory this is a
378             // '0' flag and then an ill-formatted format string with just a '$'
379             // and no count, but this is better if we instead interpret this as
380             // no '0' flag and '0$' as the width instead.
381             if self.consume('$') {
382                 spec.width = CountIsParam(0);
383                 havewidth = true;
384             } else {
385                 spec.flags |= 1 << (FlagSignAwareZeroPad as uint);
386             }
387         }
388         if !havewidth {
389             spec.width = self.count();
390         }
391         if self.consume('.') {
392             if self.consume('*') {
393                 spec.precision = CountIsNextParam;
394             } else {
395                 spec.precision = self.count();
396             }
397         }
398         // Finally the actual format specifier
399         if self.consume('?') {
400             spec.ty = "?";
401         } else {
402             spec.ty = self.word();
403         }
404         return spec;
405     }
406
407     /// Parses a method to be applied to the previously specified argument and
408     /// its format. The two current supported methods are 'plural' and 'select'
409     fn method(&mut self) -> Option<~Method<'a>> {
410         if !self.wsconsume(',') {
411             return None;
412         }
413         self.ws();
414         match self.word() {
415             "select" => {
416                 self.must_consume(',');
417                 Some(self.select())
418             }
419             "plural" => {
420                 self.must_consume(',');
421                 Some(self.plural())
422             }
423             "" => {
424                 self.err("expected method after comma");
425                 return None;
426             }
427             method => {
428                 self.err(format!("unknown method: `{}`", method));
429                 return None;
430             }
431         }
432     }
433
434     /// Parses a 'select' statement (after the initial 'select' word)
435     fn select(&mut self) -> ~Method<'a> {
436         let mut other = None;
437         let mut arms = ~[];
438         // Consume arms one at a time
439         loop {
440             self.ws();
441             let selector = self.word();
442             if selector == "" {
443                 self.err("cannot have an empty selector");
444                 break
445             }
446             self.must_consume('{');
447             self.depth += 1;
448             let pieces = self.collect();
449             self.depth -= 1;
450             self.must_consume('}');
451             if selector == "other" {
452                 if !other.is_none() {
453                     self.err("multiple `other` statements in `select");
454                 }
455                 other = Some(pieces);
456             } else {
457                 arms.push(SelectArm { selector: selector, result: pieces });
458             }
459             self.ws();
460             match self.cur.clone().next() {
461                 Some((_, '}')) => { break }
462                 Some(..) | None => {}
463             }
464         }
465         // The "other" selector must be present
466         let other = match other {
467             Some(arm) => { arm }
468             None => {
469                 self.err("`select` statement must provide an `other` case");
470                 ~[]
471             }
472         };
473         ~Select(arms, other)
474     }
475
476     /// Parses a 'plural' statement (after the initial 'plural' word)
477     fn plural(&mut self) -> ~Method<'a> {
478         let mut offset = None;
479         let mut other = None;
480         let mut arms = ~[];
481
482         // First, attempt to parse the 'offset:' field. We know the set of
483         // selector words which can appear in plural arms, and the only ones
484         // which start with 'o' are "other" and "offset", hence look two
485         // characters deep to see if we can consume the word "offset"
486         self.ws();
487         let mut it = self.cur.clone();
488         match it.next() {
489             Some((_, 'o')) => {
490                 match it.next() {
491                     Some((_, 'f')) => {
492                         let word = self.word();
493                         if word != "offset" {
494                             self.err(format!("expected `offset`, found `{}`",
495                                              word));
496                         } else {
497                             self.must_consume(':');
498                             match self.integer() {
499                                 Some(i) => { offset = Some(i); }
500                                 None => {
501                                     self.err("offset must be an integer");
502                                 }
503                             }
504                         }
505                     }
506                     Some(..) | None => {}
507                 }
508             }
509             Some(..) | None => {}
510         }
511
512         // Next, generate all the arms
513         loop {
514             let mut isother = false;
515             let selector = if self.wsconsume('=') {
516                 match self.integer() {
517                     Some(i) => Literal(i),
518                     None => {
519                         self.err("plural `=` selectors must be followed by an \
520                                   integer");
521                         Literal(0)
522                     }
523                 }
524             } else {
525                 let word = self.word();
526                 match word {
527                     "other" => { isother = true; Keyword(Zero) }
528                     "zero"  => Keyword(Zero),
529                     "one"   => Keyword(One),
530                     "two"   => Keyword(Two),
531                     "few"   => Keyword(Few),
532                     "many"  => Keyword(Many),
533                     word    => {
534                         self.err(format!("unexpected plural selector `{}`",
535                                          word));
536                         if word == "" {
537                             break
538                         } else {
539                             Keyword(Zero)
540                         }
541                     }
542                 }
543             };
544             self.must_consume('{');
545             self.depth += 1;
546             let pieces = self.collect();
547             self.depth -= 1;
548             self.must_consume('}');
549             if isother {
550                 if !other.is_none() {
551                     self.err("multiple `other` statements in `select");
552                 }
553                 other = Some(pieces);
554             } else {
555                 arms.push(PluralArm { selector: selector, result: pieces });
556             }
557             self.ws();
558             match self.cur.clone().next() {
559                 Some((_, '}')) => { break }
560                 Some(..) | None => {}
561             }
562         }
563
564         let other = match other {
565             Some(arm) => { arm }
566             None => {
567                 self.err("`plural` statement must provide an `other` case");
568                 ~[]
569             }
570         };
571         ~Plural(offset, arms, other)
572     }
573
574     /// Parses a Count parameter at the current position. This does not check
575     /// for 'CountIsNextParam' because that is only used in precision, not
576     /// width.
577     fn count(&mut self) -> Count<'a> {
578         match self.integer() {
579             Some(i) => {
580                 if self.consume('$') {
581                     CountIsParam(i)
582                 } else {
583                     CountIs(i)
584                 }
585             }
586             None => {
587                 let tmp = self.cur.clone();
588                 match self.word() {
589                     word if word.len() > 0 && self.consume('$') => {
590                         CountIsName(word)
591                     }
592                     _ => {
593                         self.cur = tmp;
594                         CountImplied
595                     }
596                 }
597             }
598         }
599     }
600
601     /// Parses a word starting at the current position. A word is considered to
602     /// be an alphabetic character followed by any number of alphanumeric
603     /// characters.
604     fn word(&mut self) -> &'a str {
605         let start = match self.cur.clone().next() {
606             Some((pos, c)) if char::is_XID_start(c) => {
607                 self.cur.next();
608                 pos
609             }
610             Some(..) | None => { return self.input.slice(0, 0); }
611         };
612         let mut end;
613         loop {
614             match self.cur.clone().next() {
615                 Some((_, c)) if char::is_XID_continue(c) => {
616                     self.cur.next();
617                 }
618                 Some((pos, _)) => { end = pos; break }
619                 None => { end = self.input.len(); break }
620             }
621         }
622         self.input.slice(start, end)
623     }
624
625     /// Optionally parses an integer at the current position. This doesn't deal
626     /// with overflow at all, it's just accumulating digits.
627     fn integer(&mut self) -> Option<uint> {
628         let mut cur = 0;
629         let mut found = false;
630         loop {
631             match self.cur.clone().next() {
632                 Some((_, c)) => {
633                     match char::to_digit(c, 10) {
634                         Some(i) => {
635                             cur = cur * 10 + i;
636                             found = true;
637                             self.cur.next();
638                         }
639                         None => { break }
640                     }
641                 }
642                 None => { break }
643             }
644         }
645         if found {
646             return Some(cur);
647         } else {
648             return None;
649         }
650     }
651 }
652
653 #[cfg(test)]
654 mod tests {
655     use super::*;
656     use prelude::*;
657
658     fn same(fmt: &'static str, p: ~[Piece<'static>]) {
659         let mut parser = Parser::new(fmt);
660         assert!(p == parser.collect());
661     }
662
663     fn fmtdflt() -> FormatSpec<'static> {
664         return FormatSpec {
665             fill: None,
666             align: AlignUnknown,
667             flags: 0,
668             precision: CountImplied,
669             width: CountImplied,
670             ty: "",
671         }
672     }
673
674     fn musterr(s: &str) {
675         let mut p = Parser::new(s);
676         p.next();
677         assert!(p.errors.len() != 0);
678     }
679
680     #[test]
681     fn simple() {
682         same("asdf", ~[String("asdf")]);
683         same("a\\{b", ~[String("a"), String("{b")]);
684         same("a\\#b", ~[String("a"), String("#b")]);
685         same("a\\}b", ~[String("a"), String("}b")]);
686         same("a\\}", ~[String("a"), String("}")]);
687         same("\\}", ~[String("}")]);
688     }
689
690     #[test] fn invalid01() { musterr("{") }
691     #[test] fn invalid02() { musterr("\\") }
692     #[test] fn invalid03() { musterr("\\a") }
693     #[test] fn invalid04() { musterr("{3a}") }
694     #[test] fn invalid05() { musterr("{:|}") }
695     #[test] fn invalid06() { musterr("{:>>>}") }
696
697     #[test]
698     fn format_nothing() {
699         same("{}", ~[Argument(Argument {
700             position: ArgumentNext,
701             format: fmtdflt(),
702             method: None,
703         })]);
704     }
705     #[test]
706     fn format_position() {
707         same("{3}", ~[Argument(Argument {
708             position: ArgumentIs(3),
709             format: fmtdflt(),
710             method: None,
711         })]);
712     }
713     #[test]
714     fn format_position_nothing_else() {
715         same("{3:}", ~[Argument(Argument {
716             position: ArgumentIs(3),
717             format: fmtdflt(),
718             method: None,
719         })]);
720     }
721     #[test]
722     fn format_type() {
723         same("{3:a}", ~[Argument(Argument {
724             position: ArgumentIs(3),
725             format: FormatSpec {
726                 fill: None,
727                 align: AlignUnknown,
728                 flags: 0,
729                 precision: CountImplied,
730                 width: CountImplied,
731                 ty: "a",
732             },
733             method: None,
734         })]);
735     }
736     #[test]
737     fn format_align_fill() {
738         same("{3:>}", ~[Argument(Argument {
739             position: ArgumentIs(3),
740             format: FormatSpec {
741                 fill: None,
742                 align: AlignRight,
743                 flags: 0,
744                 precision: CountImplied,
745                 width: CountImplied,
746                 ty: "",
747             },
748             method: None,
749         })]);
750         same("{3:0<}", ~[Argument(Argument {
751             position: ArgumentIs(3),
752             format: FormatSpec {
753                 fill: Some('0'),
754                 align: AlignLeft,
755                 flags: 0,
756                 precision: CountImplied,
757                 width: CountImplied,
758                 ty: "",
759             },
760             method: None,
761         })]);
762         same("{3:*<abcd}", ~[Argument(Argument {
763             position: ArgumentIs(3),
764             format: FormatSpec {
765                 fill: Some('*'),
766                 align: AlignLeft,
767                 flags: 0,
768                 precision: CountImplied,
769                 width: CountImplied,
770                 ty: "abcd",
771             },
772             method: None,
773         })]);
774     }
775     #[test]
776     fn format_counts() {
777         same("{:10s}", ~[Argument(Argument {
778             position: ArgumentNext,
779             format: FormatSpec {
780                 fill: None,
781                 align: AlignUnknown,
782                 flags: 0,
783                 precision: CountImplied,
784                 width: CountIs(10),
785                 ty: "s",
786             },
787             method: None,
788         })]);
789         same("{:10$.10s}", ~[Argument(Argument {
790             position: ArgumentNext,
791             format: FormatSpec {
792                 fill: None,
793                 align: AlignUnknown,
794                 flags: 0,
795                 precision: CountIs(10),
796                 width: CountIsParam(10),
797                 ty: "s",
798             },
799             method: None,
800         })]);
801         same("{:.*s}", ~[Argument(Argument {
802             position: ArgumentNext,
803             format: FormatSpec {
804                 fill: None,
805                 align: AlignUnknown,
806                 flags: 0,
807                 precision: CountIsNextParam,
808                 width: CountImplied,
809                 ty: "s",
810             },
811             method: None,
812         })]);
813         same("{:.10$s}", ~[Argument(Argument {
814             position: ArgumentNext,
815             format: FormatSpec {
816                 fill: None,
817                 align: AlignUnknown,
818                 flags: 0,
819                 precision: CountIsParam(10),
820                 width: CountImplied,
821                 ty: "s",
822             },
823             method: None,
824         })]);
825         same("{:a$.b$s}", ~[Argument(Argument {
826             position: ArgumentNext,
827             format: FormatSpec {
828                 fill: None,
829                 align: AlignUnknown,
830                 flags: 0,
831                 precision: CountIsName("b"),
832                 width: CountIsName("a"),
833                 ty: "s",
834             },
835             method: None,
836         })]);
837     }
838     #[test]
839     fn format_flags() {
840         same("{:-}", ~[Argument(Argument {
841             position: ArgumentNext,
842             format: FormatSpec {
843                 fill: None,
844                 align: AlignUnknown,
845                 flags: (1 << FlagSignMinus as uint),
846                 precision: CountImplied,
847                 width: CountImplied,
848                 ty: "",
849             },
850             method: None,
851         })]);
852         same("{:+#}", ~[Argument(Argument {
853             position: ArgumentNext,
854             format: FormatSpec {
855                 fill: None,
856                 align: AlignUnknown,
857                 flags: (1 << FlagSignPlus as uint) | (1 << FlagAlternate as uint),
858                 precision: CountImplied,
859                 width: CountImplied,
860                 ty: "",
861             },
862             method: None,
863         })]);
864     }
865     #[test]
866     fn format_mixture() {
867         same("abcd {3:a} efg", ~[String("abcd "), Argument(Argument {
868             position: ArgumentIs(3),
869             format: FormatSpec {
870                 fill: None,
871                 align: AlignUnknown,
872                 flags: 0,
873                 precision: CountImplied,
874                 width: CountImplied,
875                 ty: "a",
876             },
877             method: None,
878         }), String(" efg")]);
879     }
880
881     #[test]
882     fn select_simple() {
883         same("{, select, other { haha } }", ~[Argument(Argument{
884             position: ArgumentNext,
885             format: fmtdflt(),
886             method: Some(~Select(~[], ~[String(" haha ")]))
887         })]);
888         same("{1, select, other { haha } }", ~[Argument(Argument{
889             position: ArgumentIs(1),
890             format: fmtdflt(),
891             method: Some(~Select(~[], ~[String(" haha ")]))
892         })]);
893         same("{1, select, other {#} }", ~[Argument(Argument{
894             position: ArgumentIs(1),
895             format: fmtdflt(),
896             method: Some(~Select(~[], ~[CurrentArgument]))
897         })]);
898         same("{1, select, other {{2, select, other {lol}}} }", ~[Argument(Argument{
899             position: ArgumentIs(1),
900             format: fmtdflt(),
901             method: Some(~Select(~[], ~[Argument(Argument{
902                 position: ArgumentIs(2),
903                 format: fmtdflt(),
904                 method: Some(~Select(~[], ~[String("lol")]))
905             })])) // wat
906         })]);
907     }
908
909     #[test]
910     fn select_cases() {
911         same("{1, select, a{1} b{2} c{3} other{4} }", ~[Argument(Argument{
912             position: ArgumentIs(1),
913             format: fmtdflt(),
914             method: Some(~Select(~[
915                 SelectArm{ selector: "a", result: ~[String("1")] },
916                 SelectArm{ selector: "b", result: ~[String("2")] },
917                 SelectArm{ selector: "c", result: ~[String("3")] },
918             ], ~[String("4")]))
919         })]);
920     }
921
922     #[test] fn badselect01() { musterr("{select, }") }
923     #[test] fn badselect02() { musterr("{1, select}") }
924     #[test] fn badselect03() { musterr("{1, select, }") }
925     #[test] fn badselect04() { musterr("{1, select, a {}}") }
926     #[test] fn badselect05() { musterr("{1, select, other }}") }
927     #[test] fn badselect06() { musterr("{1, select, other {}") }
928     #[test] fn badselect07() { musterr("{select, other {}") }
929     #[test] fn badselect08() { musterr("{1 select, other {}") }
930     #[test] fn badselect09() { musterr("{:d select, other {}") }
931     #[test] fn badselect10() { musterr("{1:d select, other {}") }
932
933     #[test]
934     fn plural_simple() {
935         same("{, plural, other { haha } }", ~[Argument(Argument{
936             position: ArgumentNext,
937             format: fmtdflt(),
938             method: Some(~Plural(None, ~[], ~[String(" haha ")]))
939         })]);
940         same("{:, plural, other { haha } }", ~[Argument(Argument{
941             position: ArgumentNext,
942             format: fmtdflt(),
943             method: Some(~Plural(None, ~[], ~[String(" haha ")]))
944         })]);
945         same("{, plural, offset:1 =2{2} =3{3} many{yes} other{haha} }",
946         ~[Argument(Argument{
947             position: ArgumentNext,
948             format: fmtdflt(),
949             method: Some(~Plural(Some(1), ~[
950                 PluralArm{ selector: Literal(2), result: ~[String("2")] },
951                 PluralArm{ selector: Literal(3), result: ~[String("3")] },
952                 PluralArm{ selector: Keyword(Many), result: ~[String("yes")] }
953             ], ~[String("haha")]))
954         })]);
955     }
956 }