about summary refs log tree commit diff
path: root/src/libfmt_macros
diff options
context:
space:
mode:
Diffstat (limited to 'src/libfmt_macros')
-rw-r--r--src/libfmt_macros/lib.rs989
1 files changed, 989 insertions, 0 deletions
diff --git a/src/libfmt_macros/lib.rs b/src/libfmt_macros/lib.rs
new file mode 100644
index 00000000000..91b3fefdd02
--- /dev/null
+++ b/src/libfmt_macros/lib.rs
@@ -0,0 +1,989 @@
+// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+//! Macro support for format strings
+//!
+//! These structures are used when parsing format strings for the compiler.
+//! Parsing does not happen at runtime: structures of `std::fmt::rt` are
+//! generated instead.
+
+#![crate_id = "fmt_macros#0.11-pre"]
+#![license = "MIT/ASL2"]
+#![crate_type = "rlib"]
+#![crate_type = "dylib"]
+#![feature(macro_rules, globs)]
+#![experimental]
+
+use std::char;
+use std::str;
+
+/// A piece is a portion of the format string which represents the next part
+/// to emit. These are emitted as a stream by the `Parser` class.
+#[deriving(Eq)]
+pub enum Piece<'a> {
+    /// A literal string which should directly be emitted
+    String(&'a str),
+    /// A back-reference to whatever the current argument is. This is used
+    /// inside of a method call to refer back to the original argument.
+    CurrentArgument,
+    /// This describes that formatting should process the next argument (as
+    /// specified inside) for emission.
+    Argument(Argument<'a>),
+}
+
+/// Representation of an argument specification.
+#[deriving(Eq)]
+pub struct Argument<'a> {
+    /// Where to find this argument
+    pub position: Position<'a>,
+    /// How to format the argument
+    pub format: FormatSpec<'a>,
+    /// If not `None`, what method to invoke on the argument
+    pub method: Option<Box<Method<'a>>>
+}
+
+/// Specification for the formatting of an argument in the format string.
+#[deriving(Eq)]
+pub struct FormatSpec<'a> {
+    /// Optionally specified character to fill alignment with
+    pub fill: Option<char>,
+    /// Optionally specified alignment
+    pub align: Alignment,
+    /// Packed version of various flags provided
+    pub flags: uint,
+    /// The integer precision to use
+    pub precision: Count<'a>,
+    /// The string width requested for the resulting format
+    pub width: Count<'a>,
+    /// The descriptor string representing the name of the format desired for
+    /// this argument, this can be empty or any number of characters, although
+    /// it is required to be one word.
+    pub ty: &'a str
+}
+
+/// Enum describing where an argument for a format can be located.
+#[deriving(Eq)]
+pub enum Position<'a> {
+    /// The argument will be in the next position. This is the default.
+    ArgumentNext,
+    /// The argument is located at a specific index.
+    ArgumentIs(uint),
+    /// The argument has a name.
+    ArgumentNamed(&'a str),
+}
+
+/// Enum of alignments which are supported.
+#[deriving(Eq)]
+pub enum Alignment {
+    /// The value will be aligned to the left.
+    AlignLeft,
+    /// The value will be aligned to the right.
+    AlignRight,
+    /// The value will take on a default alignment.
+    AlignUnknown,
+}
+
+/// Various flags which can be applied to format strings. The meaning of these
+/// flags is defined by the formatters themselves.
+#[deriving(Eq)]
+pub enum Flag {
+    /// A `+` will be used to denote positive numbers.
+    FlagSignPlus,
+    /// A `-` will be used to denote negative numbers. This is the default.
+    FlagSignMinus,
+    /// An alternate form will be used for the value. In the case of numbers,
+    /// this means that the number will be prefixed with the supplied string.
+    FlagAlternate,
+    /// For numbers, this means that the number will be padded with zeroes,
+    /// and the sign (`+` or `-`) will precede them.
+    FlagSignAwareZeroPad,
+}
+
+/// A count is used for the precision and width parameters of an integer, and
+/// can reference either an argument or a literal integer.
+#[deriving(Eq)]
+pub enum Count<'a> {
+    /// The count is specified explicitly.
+    CountIs(uint),
+    /// The count is specified by the argument with the given name.
+    CountIsName(&'a str),
+    /// The count is specified by the argument at the given index.
+    CountIsParam(uint),
+    /// The count is specified by the next parameter.
+    CountIsNextParam,
+    /// The count is implied and cannot be explicitly specified.
+    CountImplied,
+}
+
+/// Enum describing all of the possible methods which the formatting language
+/// currently supports.
+#[deriving(Eq)]
+pub enum Method<'a> {
+    /// A plural method selects on an integer over a list of either integer or
+    /// keyword-defined clauses. The meaning of the keywords is defined by the
+    /// current locale.
+    ///
+    /// An offset is optionally present at the beginning which is used to
+    /// match against keywords, but it is not matched against the literal
+    /// integers.
+    ///
+    /// The final element of this enum is the default "other" case which is
+    /// always required to be specified.
+    Plural(Option<uint>, Vec<PluralArm<'a>>, Vec<Piece<'a>>),
+
+    /// A select method selects over a string. Each arm is a different string
+    /// which can be selected for.
+    ///
+    /// As with `Plural`, a default "other" case is required as well.
+    Select(Vec<SelectArm<'a>>, Vec<Piece<'a>>),
+}
+
+/// A selector for what pluralization a plural method should take
+#[deriving(Eq, TotalEq, Hash)]
+pub enum PluralSelector {
+    /// One of the plural keywords should be used
+    Keyword(PluralKeyword),
+    /// A literal pluralization should be used
+    Literal(uint),
+}
+
+/// Structure representing one "arm" of the `plural` function.
+#[deriving(Eq)]
+pub struct PluralArm<'a> {
+    /// A selector can either be specified by a keyword or with an integer
+    /// literal.
+    pub selector: PluralSelector,
+    /// Array of pieces which are the format of this arm
+    pub result: Vec<Piece<'a>>,
+}
+
+/// Enum of the 5 CLDR plural keywords. There is one more, "other", but that
+/// is specially placed in the `Plural` variant of `Method`.
+///
+/// http://www.icu-project.org/apiref/icu4c/classicu_1_1PluralRules.html
+#[deriving(Eq, TotalEq, Hash, Show)]
+#[allow(missing_doc)]
+pub enum PluralKeyword {
+    /// The plural form for zero objects.
+    Zero,
+    /// The plural form for one object.
+    One,
+    /// The plural form for two objects.
+    Two,
+    /// The plural form for few objects.
+    Few,
+    /// The plural form for many objects.
+    Many,
+}
+
+/// Structure representing one "arm" of the `select` function.
+#[deriving(Eq)]
+pub struct SelectArm<'a> {
+    /// String selector which guards this arm
+    pub selector: &'a str,
+    /// Array of pieces which are the format of this arm
+    pub result: Vec<Piece<'a>>,
+}
+
+/// The parser structure for interpreting the input format string. This is
+/// modelled as an iterator over `Piece` structures to form a stream of tokens
+/// being output.
+///
+/// This is a recursive-descent parser for the sake of simplicity, and if
+/// necessary there's probably lots of room for improvement performance-wise.
+pub struct Parser<'a> {
+    input: &'a str,
+    cur: str::CharOffsets<'a>,
+    depth: uint,
+    /// Error messages accumulated during parsing
+    pub errors: Vec<~str>,
+}
+
+impl<'a> Iterator<Piece<'a>> for Parser<'a> {
+    fn next(&mut self) -> Option<Piece<'a>> {
+        match self.cur.clone().next() {
+            Some((_, '#')) => { self.cur.next(); Some(CurrentArgument) }
+            Some((_, '{')) => {
+                self.cur.next();
+                let ret = Some(Argument(self.argument()));
+                self.must_consume('}');
+                ret
+            }
+            Some((pos, '\\')) => {
+                self.cur.next();
+                self.escape(); // ensure it's a valid escape sequence
+                Some(String(self.string(pos + 1))) // skip the '\' character
+            }
+            Some((_, '}')) if self.depth == 0 => {
+                self.cur.next();
+                self.err("unmatched `}` found");
+                None
+            }
+            Some((_, '}')) | None => { None }
+            Some((pos, _)) => {
+                Some(String(self.string(pos)))
+            }
+        }
+    }
+}
+
+impl<'a> Parser<'a> {
+    /// Creates a new parser for the given format string
+    pub fn new<'a>(s: &'a str) -> Parser<'a> {
+        Parser {
+            input: s,
+            cur: s.char_indices(),
+            depth: 0,
+            errors: vec!(),
+        }
+    }
+
+    /// Notifies of an error. The message doesn't actually need to be of type
+    /// ~str, but I think it does when this eventually uses conditions so it
+    /// might as well start using it now.
+    fn err(&mut self, msg: &str) {
+        self.errors.push(msg.to_owned());
+    }
+
+    /// Optionally consumes the specified character. If the character is not at
+    /// the current position, then the current iterator isn't moved and false is
+    /// returned, otherwise the character is consumed and true is returned.
+    fn consume(&mut self, c: char) -> bool {
+        match self.cur.clone().next() {
+            Some((_, maybe)) if c == maybe => {
+                self.cur.next();
+                true
+            }
+            Some(..) | None => false,
+        }
+    }
+
+    /// Forces consumption of the specified character. If the character is not
+    /// found, an error is emitted.
+    fn must_consume(&mut self, c: char) {
+        self.ws();
+        match self.cur.clone().next() {
+            Some((_, maybe)) if c == maybe => {
+                self.cur.next();
+            }
+            Some((_, other)) => {
+                self.err(
+                    format!("expected `{}` but found `{}`", c, other));
+            }
+            None => {
+                self.err(
+                    format!("expected `{}` but string was terminated", c));
+            }
+        }
+    }
+
+    /// Attempts to consume any amount of whitespace followed by a character
+    fn wsconsume(&mut self, c: char) -> bool {
+        self.ws(); self.consume(c)
+    }
+
+    /// Consumes all whitespace characters until the first non-whitespace
+    /// character
+    fn ws(&mut self) {
+        loop {
+            match self.cur.clone().next() {
+                Some((_, c)) if char::is_whitespace(c) => { self.cur.next(); }
+                Some(..) | None => { return }
+            }
+        }
+    }
+
+    /// Consumes an escape sequence, failing if there is not a valid character
+    /// to be escaped.
+    fn escape(&mut self) -> char {
+        match self.cur.next() {
+            Some((_, c @ '#')) | Some((_, c @ '{')) |
+            Some((_, c @ '\\')) | Some((_, c @ '}')) => { c }
+            Some((_, c)) => {
+                self.err(format!("invalid escape character `{}`", c));
+                c
+            }
+            None => {
+                self.err("expected an escape sequence, but format string was \
+                           terminated");
+                ' '
+            }
+        }
+    }
+
+    /// Parses all of a string which is to be considered a "raw literal" in a
+    /// format string. This is everything outside of the braces.
+    fn string(&mut self, start: uint) -> &'a str {
+        loop {
+            // we may not consume the character, so clone the iterator
+            match self.cur.clone().next() {
+                Some((pos, '\\')) | Some((pos, '#')) |
+                Some((pos, '}')) | Some((pos, '{')) => {
+                    return self.input.slice(start, pos);
+                }
+                Some(..) => { self.cur.next(); }
+                None => {
+                    self.cur.next();
+                    return self.input.slice(start, self.input.len());
+                }
+            }
+        }
+    }
+
+    /// Parses an Argument structure, or what's contained within braces inside
+    /// the format string
+    fn argument(&mut self) -> Argument<'a> {
+        Argument {
+            position: self.position(),
+            format: self.format(),
+            method: self.method(),
+        }
+    }
+
+    /// Parses a positional argument for a format. This could either be an
+    /// integer index of an argument, a named argument, or a blank string.
+    fn position(&mut self) -> Position<'a> {
+        match self.integer() {
+            Some(i) => { ArgumentIs(i) }
+            None => {
+                match self.cur.clone().next() {
+                    Some((_, c)) if char::is_alphabetic(c) => {
+                        ArgumentNamed(self.word())
+                    }
+                    _ => ArgumentNext
+                }
+            }
+        }
+    }
+
+    /// Parses a format specifier at the current position, returning all of the
+    /// relevant information in the FormatSpec struct.
+    fn format(&mut self) -> FormatSpec<'a> {
+        let mut spec = FormatSpec {
+            fill: None,
+            align: AlignUnknown,
+            flags: 0,
+            precision: CountImplied,
+            width: CountImplied,
+            ty: self.input.slice(0, 0),
+        };
+        if !self.consume(':') { return spec }
+
+        // fill character
+        match self.cur.clone().next() {
+            Some((_, c)) => {
+                match self.cur.clone().skip(1).next() {
+                    Some((_, '>')) | Some((_, '<')) => {
+                        spec.fill = Some(c);
+                        self.cur.next();
+                    }
+                    Some(..) | None => {}
+                }
+            }
+            None => {}
+        }
+        // Alignment
+        if self.consume('<') {
+            spec.align = AlignLeft;
+        } else if self.consume('>') {
+            spec.align = AlignRight;
+        }
+        // Sign flags
+        if self.consume('+') {
+            spec.flags |= 1 << (FlagSignPlus as uint);
+        } else if self.consume('-') {
+            spec.flags |= 1 << (FlagSignMinus as uint);
+        }
+        // Alternate marker
+        if self.consume('#') {
+            spec.flags |= 1 << (FlagAlternate as uint);
+        }
+        // Width and precision
+        let mut havewidth = false;
+        if self.consume('0') {
+            // small ambiguity with '0$' as a format string. In theory this is a
+            // '0' flag and then an ill-formatted format string with just a '$'
+            // and no count, but this is better if we instead interpret this as
+            // no '0' flag and '0$' as the width instead.
+            if self.consume('$') {
+                spec.width = CountIsParam(0);
+                havewidth = true;
+            } else {
+                spec.flags |= 1 << (FlagSignAwareZeroPad as uint);
+            }
+        }
+        if !havewidth {
+            spec.width = self.count();
+        }
+        if self.consume('.') {
+            if self.consume('*') {
+                spec.precision = CountIsNextParam;
+            } else {
+                spec.precision = self.count();
+            }
+        }
+        // Finally the actual format specifier
+        if self.consume('?') {
+            spec.ty = "?";
+        } else {
+            spec.ty = self.word();
+        }
+        return spec;
+    }
+
+    /// Parses a method to be applied to the previously specified argument and
+    /// its format. The two current supported methods are 'plural' and 'select'
+    fn method(&mut self) -> Option<Box<Method<'a>>> {
+        if !self.wsconsume(',') {
+            return None;
+        }
+        self.ws();
+        match self.word() {
+            "select" => {
+                self.must_consume(',');
+                Some(self.select())
+            }
+            "plural" => {
+                self.must_consume(',');
+                Some(self.plural())
+            }
+            "" => {
+                self.err("expected method after comma");
+                return None;
+            }
+            method => {
+                self.err(format!("unknown method: `{}`", method));
+                return None;
+            }
+        }
+    }
+
+    /// Parses a 'select' statement (after the initial 'select' word)
+    fn select(&mut self) -> Box<Method<'a>> {
+        let mut other = None;
+        let mut arms = vec!();
+        // Consume arms one at a time
+        loop {
+            self.ws();
+            let selector = self.word();
+            if selector == "" {
+                self.err("cannot have an empty selector");
+                break
+            }
+            self.must_consume('{');
+            self.depth += 1;
+            let pieces = self.collect();
+            self.depth -= 1;
+            self.must_consume('}');
+            if selector == "other" {
+                if !other.is_none() {
+                    self.err("multiple `other` statements in `select");
+                }
+                other = Some(pieces);
+            } else {
+                arms.push(SelectArm { selector: selector, result: pieces });
+            }
+            self.ws();
+            match self.cur.clone().next() {
+                Some((_, '}')) => { break }
+                Some(..) | None => {}
+            }
+        }
+        // The "other" selector must be present
+        let other = match other {
+            Some(arm) => { arm }
+            None => {
+                self.err("`select` statement must provide an `other` case");
+                vec!()
+            }
+        };
+        box Select(arms, other)
+    }
+
+    /// Parses a 'plural' statement (after the initial 'plural' word)
+    fn plural(&mut self) -> Box<Method<'a>> {
+        let mut offset = None;
+        let mut other = None;
+        let mut arms = vec!();
+
+        // First, attempt to parse the 'offset:' field. We know the set of
+        // selector words which can appear in plural arms, and the only ones
+        // which start with 'o' are "other" and "offset", hence look two
+        // characters deep to see if we can consume the word "offset"
+        self.ws();
+        let mut it = self.cur.clone();
+        match it.next() {
+            Some((_, 'o')) => {
+                match it.next() {
+                    Some((_, 'f')) => {
+                        let word = self.word();
+                        if word != "offset" {
+                            self.err(format!("expected `offset`, found `{}`",
+                                             word));
+                        } else {
+                            self.must_consume(':');
+                            match self.integer() {
+                                Some(i) => { offset = Some(i); }
+                                None => {
+                                    self.err("offset must be an integer");
+                                }
+                            }
+                        }
+                    }
+                    Some(..) | None => {}
+                }
+            }
+            Some(..) | None => {}
+        }
+
+        // Next, generate all the arms
+        loop {
+            let mut isother = false;
+            let selector = if self.wsconsume('=') {
+                match self.integer() {
+                    Some(i) => Literal(i),
+                    None => {
+                        self.err("plural `=` selectors must be followed by an \
+                                  integer");
+                        Literal(0)
+                    }
+                }
+            } else {
+                let word = self.word();
+                match word {
+                    "other" => { isother = true; Keyword(Zero) }
+                    "zero"  => Keyword(Zero),
+                    "one"   => Keyword(One),
+                    "two"   => Keyword(Two),
+                    "few"   => Keyword(Few),
+                    "many"  => Keyword(Many),
+                    word    => {
+                        self.err(format!("unexpected plural selector `{}`",
+                                         word));
+                        if word == "" {
+                            break
+                        } else {
+                            Keyword(Zero)
+                        }
+                    }
+                }
+            };
+            self.must_consume('{');
+            self.depth += 1;
+            let pieces = self.collect();
+            self.depth -= 1;
+            self.must_consume('}');
+            if isother {
+                if !other.is_none() {
+                    self.err("multiple `other` statements in `select");
+                }
+                other = Some(pieces);
+            } else {
+                arms.push(PluralArm { selector: selector, result: pieces });
+            }
+            self.ws();
+            match self.cur.clone().next() {
+                Some((_, '}')) => { break }
+                Some(..) | None => {}
+            }
+        }
+
+        let other = match other {
+            Some(arm) => { arm }
+            None => {
+                self.err("`plural` statement must provide an `other` case");
+                vec!()
+            }
+        };
+        box Plural(offset, arms, other)
+    }
+
+    /// Parses a Count parameter at the current position. This does not check
+    /// for 'CountIsNextParam' because that is only used in precision, not
+    /// width.
+    fn count(&mut self) -> Count<'a> {
+        match self.integer() {
+            Some(i) => {
+                if self.consume('$') {
+                    CountIsParam(i)
+                } else {
+                    CountIs(i)
+                }
+            }
+            None => {
+                let tmp = self.cur.clone();
+                match self.word() {
+                    word if word.len() > 0 && self.consume('$') => {
+                        CountIsName(word)
+                    }
+                    _ => {
+                        self.cur = tmp;
+                        CountImplied
+                    }
+                }
+            }
+        }
+    }
+
+    /// Parses a word starting at the current position. A word is considered to
+    /// be an alphabetic character followed by any number of alphanumeric
+    /// characters.
+    fn word(&mut self) -> &'a str {
+        let start = match self.cur.clone().next() {
+            Some((pos, c)) if char::is_XID_start(c) => {
+                self.cur.next();
+                pos
+            }
+            Some(..) | None => { return self.input.slice(0, 0); }
+        };
+        let mut end;
+        loop {
+            match self.cur.clone().next() {
+                Some((_, c)) if char::is_XID_continue(c) => {
+                    self.cur.next();
+                }
+                Some((pos, _)) => { end = pos; break }
+                None => { end = self.input.len(); break }
+            }
+        }
+        self.input.slice(start, end)
+    }
+
+    /// Optionally parses an integer at the current position. This doesn't deal
+    /// with overflow at all, it's just accumulating digits.
+    fn integer(&mut self) -> Option<uint> {
+        let mut cur = 0;
+        let mut found = false;
+        loop {
+            match self.cur.clone().next() {
+                Some((_, c)) => {
+                    match char::to_digit(c, 10) {
+                        Some(i) => {
+                            cur = cur * 10 + i;
+                            found = true;
+                            self.cur.next();
+                        }
+                        None => { break }
+                    }
+                }
+                None => { break }
+            }
+        }
+        if found {
+            return Some(cur);
+        } else {
+            return None;
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    fn same(fmt: &'static str, p: &[Piece<'static>]) {
+        let mut parser = Parser::new(fmt);
+        assert!(p == parser.collect::<Vec<Piece<'static>>>().as_slice());
+    }
+
+    fn fmtdflt() -> FormatSpec<'static> {
+        return FormatSpec {
+            fill: None,
+            align: AlignUnknown,
+            flags: 0,
+            precision: CountImplied,
+            width: CountImplied,
+            ty: "",
+        }
+    }
+
+    fn musterr(s: &str) {
+        let mut p = Parser::new(s);
+        p.next();
+        assert!(p.errors.len() != 0);
+    }
+
+    #[test]
+    fn simple() {
+        same("asdf", [String("asdf")]);
+        same("a\\{b", [String("a"), String("{b")]);
+        same("a\\#b", [String("a"), String("#b")]);
+        same("a\\}b", [String("a"), String("}b")]);
+        same("a\\}", [String("a"), String("}")]);
+        same("\\}", [String("}")]);
+    }
+
+    #[test] fn invalid01() { musterr("{") }
+    #[test] fn invalid02() { musterr("\\") }
+    #[test] fn invalid03() { musterr("\\a") }
+    #[test] fn invalid04() { musterr("{3a}") }
+    #[test] fn invalid05() { musterr("{:|}") }
+    #[test] fn invalid06() { musterr("{:>>>}") }
+
+    #[test]
+    fn format_nothing() {
+        same("{}", [Argument(Argument {
+            position: ArgumentNext,
+            format: fmtdflt(),
+            method: None,
+        })]);
+    }
+    #[test]
+    fn format_position() {
+        same("{3}", [Argument(Argument {
+            position: ArgumentIs(3),
+            format: fmtdflt(),
+            method: None,
+        })]);
+    }
+    #[test]
+    fn format_position_nothing_else() {
+        same("{3:}", [Argument(Argument {
+            position: ArgumentIs(3),
+            format: fmtdflt(),
+            method: None,
+        })]);
+    }
+    #[test]
+    fn format_type() {
+        same("{3:a}", [Argument(Argument {
+            position: ArgumentIs(3),
+            format: FormatSpec {
+                fill: None,
+                align: AlignUnknown,
+                flags: 0,
+                precision: CountImplied,
+                width: CountImplied,
+                ty: "a",
+            },
+            method: None,
+        })]);
+    }
+    #[test]
+    fn format_align_fill() {
+        same("{3:>}", [Argument(Argument {
+            position: ArgumentIs(3),
+            format: FormatSpec {
+                fill: None,
+                align: AlignRight,
+                flags: 0,
+                precision: CountImplied,
+                width: CountImplied,
+                ty: "",
+            },
+            method: None,
+        })]);
+        same("{3:0<}", [Argument(Argument {
+            position: ArgumentIs(3),
+            format: FormatSpec {
+                fill: Some('0'),
+                align: AlignLeft,
+                flags: 0,
+                precision: CountImplied,
+                width: CountImplied,
+                ty: "",
+            },
+            method: None,
+        })]);
+        same("{3:*<abcd}", [Argument(Argument {
+            position: ArgumentIs(3),
+            format: FormatSpec {
+                fill: Some('*'),
+                align: AlignLeft,
+                flags: 0,
+                precision: CountImplied,
+                width: CountImplied,
+                ty: "abcd",
+            },
+            method: None,
+        })]);
+    }
+    #[test]
+    fn format_counts() {
+        same("{:10s}", [Argument(Argument {
+            position: ArgumentNext,
+            format: FormatSpec {
+                fill: None,
+                align: AlignUnknown,
+                flags: 0,
+                precision: CountImplied,
+                width: CountIs(10),
+                ty: "s",
+            },
+            method: None,
+        })]);
+        same("{:10$.10s}", [Argument(Argument {
+            position: ArgumentNext,
+            format: FormatSpec {
+                fill: None,
+                align: AlignUnknown,
+                flags: 0,
+                precision: CountIs(10),
+                width: CountIsParam(10),
+                ty: "s",
+            },
+            method: None,
+        })]);
+        same("{:.*s}", [Argument(Argument {
+            position: ArgumentNext,
+            format: FormatSpec {
+                fill: None,
+                align: AlignUnknown,
+                flags: 0,
+                precision: CountIsNextParam,
+                width: CountImplied,
+                ty: "s",
+            },
+            method: None,
+        })]);
+        same("{:.10$s}", [Argument(Argument {
+            position: ArgumentNext,
+            format: FormatSpec {
+                fill: None,
+                align: AlignUnknown,
+                flags: 0,
+                precision: CountIsParam(10),
+                width: CountImplied,
+                ty: "s",
+            },
+            method: None,
+        })]);
+        same("{:a$.b$s}", [Argument(Argument {
+            position: ArgumentNext,
+            format: FormatSpec {
+                fill: None,
+                align: AlignUnknown,
+                flags: 0,
+                precision: CountIsName("b"),
+                width: CountIsName("a"),
+                ty: "s",
+            },
+            method: None,
+        })]);
+    }
+    #[test]
+    fn format_flags() {
+        same("{:-}", [Argument(Argument {
+            position: ArgumentNext,
+            format: FormatSpec {
+                fill: None,
+                align: AlignUnknown,
+                flags: (1 << FlagSignMinus as uint),
+                precision: CountImplied,
+                width: CountImplied,
+                ty: "",
+            },
+            method: None,
+        })]);
+        same("{:+#}", [Argument(Argument {
+            position: ArgumentNext,
+            format: FormatSpec {
+                fill: None,
+                align: AlignUnknown,
+                flags: (1 << FlagSignPlus as uint) | (1 << FlagAlternate as uint),
+                precision: CountImplied,
+                width: CountImplied,
+                ty: "",
+            },
+            method: None,
+        })]);
+    }
+    #[test]
+    fn format_mixture() {
+        same("abcd {3:a} efg", [String("abcd "), Argument(Argument {
+            position: ArgumentIs(3),
+            format: FormatSpec {
+                fill: None,
+                align: AlignUnknown,
+                flags: 0,
+                precision: CountImplied,
+                width: CountImplied,
+                ty: "a",
+            },
+            method: None,
+        }), String(" efg")]);
+    }
+
+    #[test]
+    fn select_simple() {
+        same("{, select, other { haha } }", [Argument(Argument{
+            position: ArgumentNext,
+            format: fmtdflt(),
+            method: Some(box Select(vec![], vec![String(" haha ")]))
+        })]);
+        same("{1, select, other { haha } }", [Argument(Argument{
+            position: ArgumentIs(1),
+            format: fmtdflt(),
+            method: Some(box Select(vec![], vec![String(" haha ")]))
+        })]);
+        same("{1, select, other {#} }", [Argument(Argument{
+            position: ArgumentIs(1),
+            format: fmtdflt(),
+            method: Some(box Select(vec![], vec![CurrentArgument]))
+        })]);
+        same("{1, select, other {{2, select, other {lol}}} }", [Argument(Argument{
+            position: ArgumentIs(1),
+            format: fmtdflt(),
+            method: Some(box Select(vec![], vec![Argument(Argument{
+                position: ArgumentIs(2),
+                format: fmtdflt(),
+                method: Some(box Select(vec![], vec![String("lol")]))
+            })])) // wat
+        })]);
+    }
+
+    #[test]
+    fn select_cases() {
+        same("{1, select, a{1} b{2} c{3} other{4} }", [Argument(Argument{
+            position: ArgumentIs(1),
+            format: fmtdflt(),
+            method: Some(box Select(vec![
+                SelectArm{ selector: "a", result: vec![String("1")] },
+                SelectArm{ selector: "b", result: vec![String("2")] },
+                SelectArm{ selector: "c", result: vec![String("3")] },
+            ], vec![String("4")]))
+        })]);
+    }
+
+    #[test] fn badselect01() { musterr("{select, }") }
+    #[test] fn badselect02() { musterr("{1, select}") }
+    #[test] fn badselect03() { musterr("{1, select, }") }
+    #[test] fn badselect04() { musterr("{1, select, a {}}") }
+    #[test] fn badselect05() { musterr("{1, select, other }}") }
+    #[test] fn badselect06() { musterr("{1, select, other {}") }
+    #[test] fn badselect07() { musterr("{select, other {}") }
+    #[test] fn badselect08() { musterr("{1 select, other {}") }
+    #[test] fn badselect09() { musterr("{:d select, other {}") }
+    #[test] fn badselect10() { musterr("{1:d select, other {}") }
+
+    #[test]
+    fn plural_simple() {
+        same("{, plural, other { haha } }", [Argument(Argument{
+            position: ArgumentNext,
+            format: fmtdflt(),
+            method: Some(box Plural(None, vec![], vec![String(" haha ")]))
+        })]);
+        same("{:, plural, other { haha } }", [Argument(Argument{
+            position: ArgumentNext,
+            format: fmtdflt(),
+            method: Some(box Plural(None, vec![], vec![String(" haha ")]))
+        })]);
+        same("{, plural, offset:1 =2{2} =3{3} many{yes} other{haha} }",
+        [Argument(Argument{
+            position: ArgumentNext,
+            format: fmtdflt(),
+            method: Some(box Plural(Some(1), vec![
+                PluralArm{ selector: Literal(2), result: vec![String("2")] },
+                PluralArm{ selector: Literal(3), result: vec![String("3")] },
+                PluralArm{ selector: Keyword(Many), result: vec![String("yes")] }
+            ], vec![String("haha")]))
+        })]);
+    }
+}