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