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