diff --git a/src/libcore/extfmt.rs b/src/libcore/extfmt.rs index 5d7e0306b064b..312fc18a033c1 100644 --- a/src/libcore/extfmt.rs +++ b/src/libcore/extfmt.rs @@ -102,8 +102,13 @@ pub mod ct { use str; use vec; + #[deriving_eq] pub enum Signedness { Signed, Unsigned, } + + #[deriving_eq] pub enum Caseness { CaseUpper, CaseLower, } + + #[deriving_eq] pub enum Ty { TyBool, TyStr, @@ -115,6 +120,8 @@ pub mod ct { TyFloat, TyPoly, } + + #[deriving_eq] pub enum Flag { FlagLeftJustify, FlagLeftZeroPad, @@ -122,6 +129,8 @@ pub mod ct { FlagSignAlways, FlagAlternate, } + + #[deriving_eq] pub enum Count { CountIs(uint), CountIsParam(uint), @@ -129,204 +138,324 @@ pub mod ct { CountImplied, } - // A formatted conversion from an expression to a string - pub type Conv = - {param: Option, - flags: ~[Flag], - width: Count, - precision: Count, - ty: Ty}; + #[deriving_eq] + struct Parsed { + val: T, + next: uint + } + impl Parsed { + static pure fn new(val: T, next: uint) -> Parsed { + Parsed {val: val, next: next} + } + } + + // A formatted conversion from an expression to a string + #[deriving_eq] + pub struct Conv { + param: Option, + flags: ~[Flag], + width: Count, + precision: Count, + ty: Ty + } // A fragment of the output sequence + #[deriving_eq] pub enum Piece { PieceString(~str), PieceConv(Conv), } - pub type ErrorFn = fn@(&str) -> ! ; + + pub type ErrorFn = @fn(&str) -> !; pub fn parse_fmt_string(s: &str, err: ErrorFn) -> ~[Piece] { - let mut pieces: ~[Piece] = ~[]; - let lim = str::len(s); - let mut buf = ~""; - fn flush_buf(buf: ~str, pieces: &mut ~[Piece]) -> ~str { - if buf.len() > 0 { - let piece = PieceString(move buf); - pieces.push(move piece); + fn push_slice(ps: &mut ~[Piece], s: &str, from: uint, to: uint) { + if to > from { + ps.push(PieceString(s.slice(from, to))); } - return ~""; } + + let lim = s.len(); + let mut h = 0; let mut i = 0; + let mut pieces = ~[]; + while i < lim { - let size = str::utf8_char_width(s[i]); - let curr = str::slice(s, i, i+size); - if curr == ~"%" { + if s[i] == '%' as u8 { i += 1; + if i >= lim { err(~"unterminated conversion at end of string"); - } - let curr2 = str::slice(s, i, i+1); - if curr2 == ~"%" { - buf += curr2; + } else if s[i] == '%' as u8 { + push_slice(&mut pieces, s, h, i); i += 1; } else { - buf = flush_buf(move buf, &mut pieces); - let rs = parse_conversion(s, i, lim, err); - pieces.push(copy rs.piece); - i = rs.next; + push_slice(&mut pieces, s, h, i - 1); + let Parsed {val, next} = parse_conversion(s, i, lim, err); + pieces.push(val); + i = next; } - } else { buf += curr; i += size; } + + h = i; + } else { + i += str::utf8_char_width(s[i]); + } } - flush_buf(move buf, &mut pieces); - move pieces + + push_slice(&mut pieces, s, h, i); + pieces } - pub fn peek_num(s: &str, i: uint, lim: uint) -> - Option<{num: uint, next: uint}> { - let mut j = i; - let mut accum = 0u; + + pub fn peek_num(s: &str, i: uint, lim: uint) -> Option> { + let mut i = i; + let mut accum = 0; let mut found = false; - while j < lim { - match char::to_digit(s[j] as char, 10) { + + while i < lim { + match char::to_digit(s[i] as char, 10) { Some(x) => { found = true; accum *= 10; accum += x; - j += 1; - }, + i += 1; + } None => break } } + if found { - Some({num: accum, next: j}) + Some(Parsed::new(accum, i)) } else { None } } - pub fn parse_conversion(s: &str, i: uint, lim: uint, - err: ErrorFn) -> - {piece: Piece, next: uint} { - let parm = parse_parameter(s, i, lim); - let flags = parse_flags(s, parm.next, lim); - let width = parse_count(s, flags.next, lim); + + pub fn parse_conversion(s: &str, i: uint, lim: uint, err: ErrorFn) -> + Parsed { + let param = parse_parameter(s, i, lim); + // avoid copying ~[Flag] by destructuring + let Parsed {val: flags_val, next: flags_next} = parse_flags(s, + param.next, lim); + let width = parse_count(s, flags_next, lim); let prec = parse_precision(s, width.next, lim); let ty = parse_type(s, prec.next, lim, err); - return {piece: - PieceConv({param: parm.param, - flags: copy flags.flags, - width: width.count, - precision: prec.count, - ty: ty.ty}), - next: ty.next}; + + Parsed::new(PieceConv(Conv { + param: param.val, + flags: flags_val, + width: width.val, + precision: prec.val, + ty: ty.val}), ty.next) } + pub fn parse_parameter(s: &str, i: uint, lim: uint) -> - {param: Option, next: uint} { - if i >= lim { return {param: None, next: i}; } - let num = peek_num(s, i, lim); - return match num { - None => {param: None, next: i}, - Some(t) => { - let n = t.num; - let j = t.next; - if j < lim && s[j] == '$' as u8 { - {param: Some(n), next: j + 1} - } else { {param: None, next: i} } - } + Parsed> { + if i >= lim { return Parsed::new(None, i); } + + match peek_num(s, i, lim) { + Some(num) if num.next < lim && s[num.next] == '$' as u8 => + Parsed::new(Some(num.val), num.next + 1), + _ => Parsed::new(None, i) + } + } + + pub fn parse_flags(s: &str, i: uint, lim: uint) -> Parsed<~[Flag]> { + let mut i = i; + let mut flags = ~[]; + + while i < lim { + let f = match s[i] { + '-' as u8 => FlagLeftJustify, + '0' as u8 => FlagLeftZeroPad, + ' ' as u8 => FlagSpaceForSign, + '+' as u8 => FlagSignAlways, + '#' as u8 => FlagAlternate, + _ => break }; + + flags.push(f); + i += 1; + } + + Parsed::new(flags, i) } - pub fn parse_flags(s: &str, i: uint, lim: uint) -> - {flags: ~[Flag], next: uint} { - let noflags: ~[Flag] = ~[]; - if i >= lim { return {flags: move noflags, next: i}; } - - fn more(f: Flag, s: &str, i: uint, lim: uint) -> - {flags: ~[Flag], next: uint} { - let next = parse_flags(s, i + 1u, lim); - let rest = copy next.flags; - let j = next.next; - let curr: ~[Flag] = ~[f]; - return {flags: vec::append(move curr, rest), next: j}; + + pub fn parse_count(s: &str, i: uint, lim: uint) -> Parsed { + if i >= lim { + Parsed::new(CountImplied, i) + } else if s[i] == '*' as u8 { + let param = parse_parameter(s, i + 1, lim); + let j = param.next; + + match param.val { + None => Parsed::new(CountIsNextParam, j), + Some(n) => Parsed::new(CountIsParam(n), j) + } + } else { + match peek_num(s, i, lim) { + None => Parsed::new(CountImplied, i), + Some(num) => Parsed::new(CountIs(num.val), num.next) + } } - // Unfortunate, but because s is borrowed, can't use a closure - // fn more(f: Flag, s: &str) { more_(f, s, i, lim); } - let f = s[i]; - return if f == '-' as u8 { - more(FlagLeftJustify, s, i, lim) - } else if f == '0' as u8 { - more(FlagLeftZeroPad, s, i, lim) - } else if f == ' ' as u8 { - more(FlagSpaceForSign, s, i, lim) - } else if f == '+' as u8 { - more(FlagSignAlways, s, i, lim) - } else if f == '#' as u8 { - more(FlagAlternate, s, i, lim) - } else { {flags: move noflags, next: i} }; - } - pub fn parse_count(s: &str, i: uint, lim: uint) - -> {count: Count, next: uint} { - return if i >= lim { - {count: CountImplied, next: i} - } else if s[i] == '*' as u8 { - let param = parse_parameter(s, i + 1, lim); - let j = param.next; - match param.param { - None => {count: CountIsNextParam, next: j}, - Some(n) => {count: CountIsParam(n), next: j} - } - } else { - let num = peek_num(s, i, lim); - match num { - None => {count: CountImplied, next: i}, - Some(num) => { - count: CountIs(num.num), - next: num.next - } - } - }; } - pub fn parse_precision(s: &str, i: uint, lim: uint) -> - {count: Count, next: uint} { - return if i >= lim { - {count: CountImplied, next: i} - } else if s[i] == '.' as u8 { - let count = parse_count(s, i + 1u, lim); + pub fn parse_precision(s: &str, i: uint, lim: uint) -> Parsed { + if i < lim && s[i] == '.' as u8 { + let count = parse_count(s, i + 1, lim); - // If there were no digits specified, i.e. the precision - // was ".", then the precision is 0 - match count.count { - CountImplied => {count: CountIs(0), next: count.next}, - _ => count - } - } else { {count: CountImplied, next: i} }; + // If there were no digits specified, i.e. the precision + // was ".", then the precision is 0 + match count.val { + CountImplied => Parsed::new(CountIs(0), count.next), + _ => count + } + } else { + Parsed::new(CountImplied, i) + } } + pub fn parse_type(s: &str, i: uint, lim: uint, err: ErrorFn) -> - {ty: Ty, next: uint} { + Parsed { if i >= lim { err(~"missing type in conversion"); } - let tstr = str::slice(s, i, i+1u); + // FIXME (#2249): Do we really want two signed types here? // How important is it to be printf compatible? - let t = - if tstr == ~"b" { - TyBool - } else if tstr == ~"s" { - TyStr - } else if tstr == ~"c" { - TyChar - } else if tstr == ~"d" || tstr == ~"i" { - TyInt(Signed) - } else if tstr == ~"u" { - TyInt(Unsigned) - } else if tstr == ~"x" { - TyHex(CaseLower) - } else if tstr == ~"X" { - TyHex(CaseUpper) - } else if tstr == ~"t" { - TyBits - } else if tstr == ~"o" { - TyOctal - } else if tstr == ~"f" { - TyFloat - } else if tstr == ~"?" { - TyPoly - } else { err(~"unknown type in conversion: " + tstr) }; - return {ty: t, next: i + 1u}; + let t = match s[i] { + 'b' as u8 => TyBool, + 's' as u8 => TyStr, + 'c' as u8 => TyChar, + 'd' as u8 | 'i' as u8 => TyInt(Signed), + 'u' as u8 => TyInt(Unsigned), + 'x' as u8 => TyHex(CaseLower), + 'X' as u8 => TyHex(CaseUpper), + 't' as u8 => TyBits, + 'o' as u8 => TyOctal, + 'f' as u8 => TyFloat, + '?' as u8 => TyPoly, + _ => err(~"unknown type in conversion: " + s.substr(i, 1)) + }; + + Parsed::new(t, i + 1) + } + + #[cfg(test)] + fn die(s: &str) -> ! { fail s.to_owned() } + + #[test] + fn test_parse_count() { + fn test(s: &str, count: Count, next: uint) -> bool { + parse_count(s, 0, s.len()) == Parsed::new(count, next) + } + + assert test("", CountImplied, 0); + assert test("*", CountIsNextParam, 1); + assert test("*1", CountIsNextParam, 1); + assert test("*1$", CountIsParam(1), 3); + assert test("123", CountIs(123), 3); + } + + #[test] + fn test_parse_flags() { + fn pack(fs: &[Flag]) -> uint { + fs.foldl(0, |&p, &f| p | (1 << f as uint)) + } + + fn test(s: &str, flags: &[Flag], next: uint) { + let f = parse_flags(s, 0, s.len()); + assert pack(f.val) == pack(flags); + assert f.next == next; + } + + test("", [], 0); + test("!#-+ 0", [], 0); + test("#-+", [FlagAlternate, FlagLeftJustify, FlagSignAlways], 3); + test(" 0", [FlagSpaceForSign, FlagLeftZeroPad], 2); + } + + #[test] + fn test_parse_fmt_string() { + assert parse_fmt_string("foo %s bar", die) == ~[ + PieceString(~"foo "), + PieceConv(Conv {param: None, flags: ~[], width: CountImplied, + precision: CountImplied, ty: TyStr}), + PieceString(~" bar")]; + + assert parse_fmt_string("%s", die) == ~[ + PieceConv(Conv {param: None, flags: ~[], width: CountImplied, + precision: CountImplied, ty: TyStr })]; + + assert parse_fmt_string("%%%%", die) == ~[ + PieceString(~"%"), PieceString(~"%")]; + } + + #[test] + fn test_parse_parameter() { + fn test(s: &str, param: Option, next: uint) -> bool { + parse_parameter(s, 0, s.len()) == Parsed::new(param, next) + } + + assert test("", None, 0); + assert test("foo", None, 0); + assert test("123", None, 0); + assert test("123$", Some(123), 4); + } + + #[test] + fn test_parse_precision() { + fn test(s: &str, count: Count, next: uint) -> bool { + parse_precision(s, 0, s.len()) == Parsed::new(count, next) + } + + assert test("", CountImplied, 0); + assert test(".", CountIs(0), 1); + assert test(".*", CountIsNextParam, 2); + assert test(".*1", CountIsNextParam, 2); + assert test(".*1$", CountIsParam(1), 4); + assert test(".123", CountIs(123), 4); + } + + #[test] + fn test_parse_type() { + fn test(s: &str, ty: Ty) -> bool { + parse_type(s, 0, s.len(), die) == Parsed::new(ty, 1) + } + + assert test("b", TyBool); + assert test("c", TyChar); + assert test("d", TyInt(Signed)); + assert test("f", TyFloat); + assert test("i", TyInt(Signed)); + assert test("o", TyOctal); + assert test("s", TyStr); + assert test("t", TyBits); + assert test("x", TyHex(CaseLower)); + assert test("X", TyHex(CaseUpper)); + assert test("?", TyPoly); + } + + #[test] + #[should_fail] + fn test_parse_type_missing() { + parse_type("", 0, 0, die); + } + + #[test] + #[should_fail] + fn test_parse_type_unknown() { + parse_type("!", 0, 1, die); + } + + #[test] + fn test_peek_num() { + let s1 = ""; + assert peek_num(s1, 0, s1.len()).is_none(); + + let s2 = "foo"; + assert peek_num(s2, 0, s2.len()).is_none(); + + let s3 = "123"; + assert peek_num(s3, 0, s3.len()) == Some(Parsed::new(123, 3)); + + let s4 = "123foo"; + assert peek_num(s4, 0, s4.len()) == Some(Parsed::new(123, 3)); } }