]> git.lizzy.rs Git - rust.git/blob - src/libfmt_macros/lib.rs
auto merge of #14472 : huonw/rust/native-lib-warnings, r=alexcrichton
[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<String>,
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     /// String, 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_string());
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(format!("expected `{}` but found `{}`",
278                                  c,
279                                  other).as_slice());
280             }
281             None => {
282                 self.err(format!("expected `{}` but string was terminated",
283                                  c).as_slice());
284             }
285         }
286     }
287
288     /// Attempts to consume any amount of whitespace followed by a character
289     fn wsconsume(&mut self, c: char) -> bool {
290         self.ws(); self.consume(c)
291     }
292
293     /// Consumes all whitespace characters until the first non-whitespace
294     /// character
295     fn ws(&mut self) {
296         loop {
297             match self.cur.clone().next() {
298                 Some((_, c)) if char::is_whitespace(c) => { self.cur.next(); }
299                 Some(..) | None => { return }
300             }
301         }
302     }
303
304     /// Consumes an escape sequence, failing if there is not a valid character
305     /// to be escaped.
306     fn escape(&mut self) -> char {
307         match self.cur.next() {
308             Some((_, c @ '#')) | Some((_, c @ '{')) |
309             Some((_, c @ '\\')) | Some((_, c @ '}')) => { c }
310             Some((_, c)) => {
311                 self.err(format!("invalid escape character `{}`",
312                                  c).as_slice());
313                 c
314             }
315             None => {
316                 self.err("expected an escape sequence, but format string was \
317                            terminated");
318                 ' '
319             }
320         }
321     }
322
323     /// Parses all of a string which is to be considered a "raw literal" in a
324     /// format string. This is everything outside of the braces.
325     fn string(&mut self, start: uint) -> &'a str {
326         loop {
327             // we may not consume the character, so clone the iterator
328             match self.cur.clone().next() {
329                 Some((pos, '\\')) | Some((pos, '#')) |
330                 Some((pos, '}')) | Some((pos, '{')) => {
331                     return self.input.slice(start, pos);
332                 }
333                 Some(..) => { self.cur.next(); }
334                 None => {
335                     self.cur.next();
336                     return self.input.slice(start, self.input.len());
337                 }
338             }
339         }
340     }
341
342     /// Parses an Argument structure, or what's contained within braces inside
343     /// the format string
344     fn argument(&mut self) -> Argument<'a> {
345         Argument {
346             position: self.position(),
347             format: self.format(),
348             method: self.method(),
349         }
350     }
351
352     /// Parses a positional argument for a format. This could either be an
353     /// integer index of an argument, a named argument, or a blank string.
354     fn position(&mut self) -> Position<'a> {
355         match self.integer() {
356             Some(i) => { ArgumentIs(i) }
357             None => {
358                 match self.cur.clone().next() {
359                     Some((_, c)) if char::is_alphabetic(c) => {
360                         ArgumentNamed(self.word())
361                     }
362                     _ => ArgumentNext
363                 }
364             }
365         }
366     }
367
368     /// Parses a format specifier at the current position, returning all of the
369     /// relevant information in the FormatSpec struct.
370     fn format(&mut self) -> FormatSpec<'a> {
371         let mut spec = FormatSpec {
372             fill: None,
373             align: AlignUnknown,
374             flags: 0,
375             precision: CountImplied,
376             width: CountImplied,
377             ty: self.input.slice(0, 0),
378         };
379         if !self.consume(':') { return spec }
380
381         // fill character
382         match self.cur.clone().next() {
383             Some((_, c)) => {
384                 match self.cur.clone().skip(1).next() {
385                     Some((_, '>')) | Some((_, '<')) => {
386                         spec.fill = Some(c);
387                         self.cur.next();
388                     }
389                     Some(..) | None => {}
390                 }
391             }
392             None => {}
393         }
394         // Alignment
395         if self.consume('<') {
396             spec.align = AlignLeft;
397         } else if self.consume('>') {
398             spec.align = AlignRight;
399         }
400         // Sign flags
401         if self.consume('+') {
402             spec.flags |= 1 << (FlagSignPlus as uint);
403         } else if self.consume('-') {
404             spec.flags |= 1 << (FlagSignMinus as uint);
405         }
406         // Alternate marker
407         if self.consume('#') {
408             spec.flags |= 1 << (FlagAlternate as uint);
409         }
410         // Width and precision
411         let mut havewidth = false;
412         if self.consume('0') {
413             // small ambiguity with '0$' as a format string. In theory this is a
414             // '0' flag and then an ill-formatted format string with just a '$'
415             // and no count, but this is better if we instead interpret this as
416             // no '0' flag and '0$' as the width instead.
417             if self.consume('$') {
418                 spec.width = CountIsParam(0);
419                 havewidth = true;
420             } else {
421                 spec.flags |= 1 << (FlagSignAwareZeroPad as uint);
422             }
423         }
424         if !havewidth {
425             spec.width = self.count();
426         }
427         if self.consume('.') {
428             if self.consume('*') {
429                 spec.precision = CountIsNextParam;
430             } else {
431                 spec.precision = self.count();
432             }
433         }
434         // Finally the actual format specifier
435         if self.consume('?') {
436             spec.ty = "?";
437         } else {
438             spec.ty = self.word();
439         }
440         return spec;
441     }
442
443     /// Parses a method to be applied to the previously specified argument and
444     /// its format. The two current supported methods are 'plural' and 'select'
445     fn method(&mut self) -> Option<Box<Method<'a>>> {
446         if !self.wsconsume(',') {
447             return None;
448         }
449         self.ws();
450         match self.word() {
451             "select" => {
452                 self.must_consume(',');
453                 Some(self.select())
454             }
455             "plural" => {
456                 self.must_consume(',');
457                 Some(self.plural())
458             }
459             "" => {
460                 self.err("expected method after comma");
461                 return None;
462             }
463             method => {
464                 self.err(format!("unknown method: `{}`", method).as_slice());
465                 return None;
466             }
467         }
468     }
469
470     /// Parses a 'select' statement (after the initial 'select' word)
471     fn select(&mut self) -> Box<Method<'a>> {
472         let mut other = None;
473         let mut arms = vec!();
474         // Consume arms one at a time
475         loop {
476             self.ws();
477             let selector = self.word();
478             if selector == "" {
479                 self.err("cannot have an empty selector");
480                 break
481             }
482             self.must_consume('{');
483             self.depth += 1;
484             let pieces = self.collect();
485             self.depth -= 1;
486             self.must_consume('}');
487             if selector == "other" {
488                 if !other.is_none() {
489                     self.err("multiple `other` statements in `select");
490                 }
491                 other = Some(pieces);
492             } else {
493                 arms.push(SelectArm { selector: selector, result: pieces });
494             }
495             self.ws();
496             match self.cur.clone().next() {
497                 Some((_, '}')) => { break }
498                 Some(..) | None => {}
499             }
500         }
501         // The "other" selector must be present
502         let other = match other {
503             Some(arm) => { arm }
504             None => {
505                 self.err("`select` statement must provide an `other` case");
506                 vec!()
507             }
508         };
509         box Select(arms, other)
510     }
511
512     /// Parses a 'plural' statement (after the initial 'plural' word)
513     fn plural(&mut self) -> Box<Method<'a>> {
514         let mut offset = None;
515         let mut other = None;
516         let mut arms = vec!();
517
518         // First, attempt to parse the 'offset:' field. We know the set of
519         // selector words which can appear in plural arms, and the only ones
520         // which start with 'o' are "other" and "offset", hence look two
521         // characters deep to see if we can consume the word "offset"
522         self.ws();
523         let mut it = self.cur.clone();
524         match it.next() {
525             Some((_, 'o')) => {
526                 match it.next() {
527                     Some((_, 'f')) => {
528                         let word = self.word();
529                         if word != "offset" {
530                             self.err(format!("expected `offset`, found `{}`",
531                                              word).as_slice());
532                         } else {
533                             self.must_consume(':');
534                             match self.integer() {
535                                 Some(i) => { offset = Some(i); }
536                                 None => {
537                                     self.err("offset must be an integer");
538                                 }
539                             }
540                         }
541                     }
542                     Some(..) | None => {}
543                 }
544             }
545             Some(..) | None => {}
546         }
547
548         // Next, generate all the arms
549         loop {
550             let mut isother = false;
551             let selector = if self.wsconsume('=') {
552                 match self.integer() {
553                     Some(i) => Literal(i),
554                     None => {
555                         self.err("plural `=` selectors must be followed by an \
556                                   integer");
557                         Literal(0)
558                     }
559                 }
560             } else {
561                 let word = self.word();
562                 match word {
563                     "other" => { isother = true; Keyword(Zero) }
564                     "zero"  => Keyword(Zero),
565                     "one"   => Keyword(One),
566                     "two"   => Keyword(Two),
567                     "few"   => Keyword(Few),
568                     "many"  => Keyword(Many),
569                     word    => {
570                         self.err(format!("unexpected plural selector `{}`",
571                                          word).as_slice());
572                         if word == "" {
573                             break
574                         } else {
575                             Keyword(Zero)
576                         }
577                     }
578                 }
579             };
580             self.must_consume('{');
581             self.depth += 1;
582             let pieces = self.collect();
583             self.depth -= 1;
584             self.must_consume('}');
585             if isother {
586                 if !other.is_none() {
587                     self.err("multiple `other` statements in `select");
588                 }
589                 other = Some(pieces);
590             } else {
591                 arms.push(PluralArm { selector: selector, result: pieces });
592             }
593             self.ws();
594             match self.cur.clone().next() {
595                 Some((_, '}')) => { break }
596                 Some(..) | None => {}
597             }
598         }
599
600         let other = match other {
601             Some(arm) => { arm }
602             None => {
603                 self.err("`plural` statement must provide an `other` case");
604                 vec!()
605             }
606         };
607         box Plural(offset, arms, other)
608     }
609
610     /// Parses a Count parameter at the current position. This does not check
611     /// for 'CountIsNextParam' because that is only used in precision, not
612     /// width.
613     fn count(&mut self) -> Count<'a> {
614         match self.integer() {
615             Some(i) => {
616                 if self.consume('$') {
617                     CountIsParam(i)
618                 } else {
619                     CountIs(i)
620                 }
621             }
622             None => {
623                 let tmp = self.cur.clone();
624                 match self.word() {
625                     word if word.len() > 0 && self.consume('$') => {
626                         CountIsName(word)
627                     }
628                     _ => {
629                         self.cur = tmp;
630                         CountImplied
631                     }
632                 }
633             }
634         }
635     }
636
637     /// Parses a word starting at the current position. A word is considered to
638     /// be an alphabetic character followed by any number of alphanumeric
639     /// characters.
640     fn word(&mut self) -> &'a str {
641         let start = match self.cur.clone().next() {
642             Some((pos, c)) if char::is_XID_start(c) => {
643                 self.cur.next();
644                 pos
645             }
646             Some(..) | None => { return self.input.slice(0, 0); }
647         };
648         let mut end;
649         loop {
650             match self.cur.clone().next() {
651                 Some((_, c)) if char::is_XID_continue(c) => {
652                     self.cur.next();
653                 }
654                 Some((pos, _)) => { end = pos; break }
655                 None => { end = self.input.len(); break }
656             }
657         }
658         self.input.slice(start, end)
659     }
660
661     /// Optionally parses an integer at the current position. This doesn't deal
662     /// with overflow at all, it's just accumulating digits.
663     fn integer(&mut self) -> Option<uint> {
664         let mut cur = 0;
665         let mut found = false;
666         loop {
667             match self.cur.clone().next() {
668                 Some((_, c)) => {
669                     match char::to_digit(c, 10) {
670                         Some(i) => {
671                             cur = cur * 10 + i;
672                             found = true;
673                             self.cur.next();
674                         }
675                         None => { break }
676                     }
677                 }
678                 None => { break }
679             }
680         }
681         if found {
682             return Some(cur);
683         } else {
684             return None;
685         }
686     }
687 }
688
689 #[cfg(test)]
690 mod tests {
691     use super::*;
692
693     fn same(fmt: &'static str, p: &[Piece<'static>]) {
694         let mut parser = Parser::new(fmt);
695         assert!(p == parser.collect::<Vec<Piece<'static>>>().as_slice());
696     }
697
698     fn fmtdflt() -> FormatSpec<'static> {
699         return FormatSpec {
700             fill: None,
701             align: AlignUnknown,
702             flags: 0,
703             precision: CountImplied,
704             width: CountImplied,
705             ty: "",
706         }
707     }
708
709     fn musterr(s: &str) {
710         let mut p = Parser::new(s);
711         p.next();
712         assert!(p.errors.len() != 0);
713     }
714
715     #[test]
716     fn simple() {
717         same("asdf", [String("asdf")]);
718         same("a\\{b", [String("a"), String("{b")]);
719         same("a\\#b", [String("a"), String("#b")]);
720         same("a\\}b", [String("a"), String("}b")]);
721         same("a\\}", [String("a"), String("}")]);
722         same("\\}", [String("}")]);
723     }
724
725     #[test] fn invalid01() { musterr("{") }
726     #[test] fn invalid02() { musterr("\\") }
727     #[test] fn invalid03() { musterr("\\a") }
728     #[test] fn invalid04() { musterr("{3a}") }
729     #[test] fn invalid05() { musterr("{:|}") }
730     #[test] fn invalid06() { musterr("{:>>>}") }
731
732     #[test]
733     fn format_nothing() {
734         same("{}", [Argument(Argument {
735             position: ArgumentNext,
736             format: fmtdflt(),
737             method: None,
738         })]);
739     }
740     #[test]
741     fn format_position() {
742         same("{3}", [Argument(Argument {
743             position: ArgumentIs(3),
744             format: fmtdflt(),
745             method: None,
746         })]);
747     }
748     #[test]
749     fn format_position_nothing_else() {
750         same("{3:}", [Argument(Argument {
751             position: ArgumentIs(3),
752             format: fmtdflt(),
753             method: None,
754         })]);
755     }
756     #[test]
757     fn format_type() {
758         same("{3:a}", [Argument(Argument {
759             position: ArgumentIs(3),
760             format: FormatSpec {
761                 fill: None,
762                 align: AlignUnknown,
763                 flags: 0,
764                 precision: CountImplied,
765                 width: CountImplied,
766                 ty: "a",
767             },
768             method: None,
769         })]);
770     }
771     #[test]
772     fn format_align_fill() {
773         same("{3:>}", [Argument(Argument {
774             position: ArgumentIs(3),
775             format: FormatSpec {
776                 fill: None,
777                 align: AlignRight,
778                 flags: 0,
779                 precision: CountImplied,
780                 width: CountImplied,
781                 ty: "",
782             },
783             method: None,
784         })]);
785         same("{3:0<}", [Argument(Argument {
786             position: ArgumentIs(3),
787             format: FormatSpec {
788                 fill: Some('0'),
789                 align: AlignLeft,
790                 flags: 0,
791                 precision: CountImplied,
792                 width: CountImplied,
793                 ty: "",
794             },
795             method: None,
796         })]);
797         same("{3:*<abcd}", [Argument(Argument {
798             position: ArgumentIs(3),
799             format: FormatSpec {
800                 fill: Some('*'),
801                 align: AlignLeft,
802                 flags: 0,
803                 precision: CountImplied,
804                 width: CountImplied,
805                 ty: "abcd",
806             },
807             method: None,
808         })]);
809     }
810     #[test]
811     fn format_counts() {
812         same("{:10s}", [Argument(Argument {
813             position: ArgumentNext,
814             format: FormatSpec {
815                 fill: None,
816                 align: AlignUnknown,
817                 flags: 0,
818                 precision: CountImplied,
819                 width: CountIs(10),
820                 ty: "s",
821             },
822             method: None,
823         })]);
824         same("{:10$.10s}", [Argument(Argument {
825             position: ArgumentNext,
826             format: FormatSpec {
827                 fill: None,
828                 align: AlignUnknown,
829                 flags: 0,
830                 precision: CountIs(10),
831                 width: CountIsParam(10),
832                 ty: "s",
833             },
834             method: None,
835         })]);
836         same("{:.*s}", [Argument(Argument {
837             position: ArgumentNext,
838             format: FormatSpec {
839                 fill: None,
840                 align: AlignUnknown,
841                 flags: 0,
842                 precision: CountIsNextParam,
843                 width: CountImplied,
844                 ty: "s",
845             },
846             method: None,
847         })]);
848         same("{:.10$s}", [Argument(Argument {
849             position: ArgumentNext,
850             format: FormatSpec {
851                 fill: None,
852                 align: AlignUnknown,
853                 flags: 0,
854                 precision: CountIsParam(10),
855                 width: CountImplied,
856                 ty: "s",
857             },
858             method: None,
859         })]);
860         same("{:a$.b$s}", [Argument(Argument {
861             position: ArgumentNext,
862             format: FormatSpec {
863                 fill: None,
864                 align: AlignUnknown,
865                 flags: 0,
866                 precision: CountIsName("b"),
867                 width: CountIsName("a"),
868                 ty: "s",
869             },
870             method: None,
871         })]);
872     }
873     #[test]
874     fn format_flags() {
875         same("{:-}", [Argument(Argument {
876             position: ArgumentNext,
877             format: FormatSpec {
878                 fill: None,
879                 align: AlignUnknown,
880                 flags: (1 << FlagSignMinus as uint),
881                 precision: CountImplied,
882                 width: CountImplied,
883                 ty: "",
884             },
885             method: None,
886         })]);
887         same("{:+#}", [Argument(Argument {
888             position: ArgumentNext,
889             format: FormatSpec {
890                 fill: None,
891                 align: AlignUnknown,
892                 flags: (1 << FlagSignPlus as uint) | (1 << FlagAlternate as uint),
893                 precision: CountImplied,
894                 width: CountImplied,
895                 ty: "",
896             },
897             method: None,
898         })]);
899     }
900     #[test]
901     fn format_mixture() {
902         same("abcd {3:a} efg", [String("abcd "), Argument(Argument {
903             position: ArgumentIs(3),
904             format: FormatSpec {
905                 fill: None,
906                 align: AlignUnknown,
907                 flags: 0,
908                 precision: CountImplied,
909                 width: CountImplied,
910                 ty: "a",
911             },
912             method: None,
913         }), String(" efg")]);
914     }
915
916     #[test]
917     fn select_simple() {
918         same("{, select, other { haha } }", [Argument(Argument{
919             position: ArgumentNext,
920             format: fmtdflt(),
921             method: Some(box Select(vec![], vec![String(" haha ")]))
922         })]);
923         same("{1, select, other { haha } }", [Argument(Argument{
924             position: ArgumentIs(1),
925             format: fmtdflt(),
926             method: Some(box Select(vec![], vec![String(" haha ")]))
927         })]);
928         same("{1, select, other {#} }", [Argument(Argument{
929             position: ArgumentIs(1),
930             format: fmtdflt(),
931             method: Some(box Select(vec![], vec![CurrentArgument]))
932         })]);
933         same("{1, select, other {{2, select, other {lol}}} }", [Argument(Argument{
934             position: ArgumentIs(1),
935             format: fmtdflt(),
936             method: Some(box Select(vec![], vec![Argument(Argument{
937                 position: ArgumentIs(2),
938                 format: fmtdflt(),
939                 method: Some(box Select(vec![], vec![String("lol")]))
940             })])) // wat
941         })]);
942     }
943
944     #[test]
945     fn select_cases() {
946         same("{1, select, a{1} b{2} c{3} other{4} }", [Argument(Argument{
947             position: ArgumentIs(1),
948             format: fmtdflt(),
949             method: Some(box Select(vec![
950                 SelectArm{ selector: "a", result: vec![String("1")] },
951                 SelectArm{ selector: "b", result: vec![String("2")] },
952                 SelectArm{ selector: "c", result: vec![String("3")] },
953             ], vec![String("4")]))
954         })]);
955     }
956
957     #[test] fn badselect01() { musterr("{select, }") }
958     #[test] fn badselect02() { musterr("{1, select}") }
959     #[test] fn badselect03() { musterr("{1, select, }") }
960     #[test] fn badselect04() { musterr("{1, select, a {}}") }
961     #[test] fn badselect05() { musterr("{1, select, other }}") }
962     #[test] fn badselect06() { musterr("{1, select, other {}") }
963     #[test] fn badselect07() { musterr("{select, other {}") }
964     #[test] fn badselect08() { musterr("{1 select, other {}") }
965     #[test] fn badselect09() { musterr("{:d select, other {}") }
966     #[test] fn badselect10() { musterr("{1:d select, other {}") }
967
968     #[test]
969     fn plural_simple() {
970         same("{, plural, other { haha } }", [Argument(Argument{
971             position: ArgumentNext,
972             format: fmtdflt(),
973             method: Some(box Plural(None, vec![], vec![String(" haha ")]))
974         })]);
975         same("{:, plural, other { haha } }", [Argument(Argument{
976             position: ArgumentNext,
977             format: fmtdflt(),
978             method: Some(box Plural(None, vec![], vec![String(" haha ")]))
979         })]);
980         same("{, plural, offset:1 =2{2} =3{3} many{yes} other{haha} }",
981         [Argument(Argument{
982             position: ArgumentNext,
983             format: fmtdflt(),
984             method: Some(box Plural(Some(1), vec![
985                 PluralArm{ selector: Literal(2), result: vec![String("2")] },
986                 PluralArm{ selector: Literal(3), result: vec![String("3")] },
987                 PluralArm{ selector: Keyword(Many), result: vec![String("yes")] }
988             ], vec![String("haha")]))
989         })]);
990     }
991 }