From fc06f7922db0b4d1063f4f29157635117d853426 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 12 Oct 2013 20:00:58 -0700 Subject: [PATCH] Build a few extra features into format! parsing * Allow named parameters to specify width/precision * Intepret the format string '0$' as "width is the 0th argument" instead of thinking the lone '0' was the sign-aware-zero-padding flag. To get both you'd need to put '00$' which makes more sense if you want both to happen. Closes #9669 --- src/libstd/fmt/mod.rs | 52 ++++++++++++++-------- src/libstd/fmt/parse.rs | 87 ++++++++++++++++++++++++++++++++++++- src/libstd/fmt/rt.rs | 15 +++++++ src/libsyntax/ext/format.rs | 21 +++++++-- src/test/run-pass/ifmt.rs | 3 ++ 5 files changed, 155 insertions(+), 23 deletions(-) diff --git a/src/libstd/fmt/mod.rs b/src/libstd/fmt/mod.rs index a03f21d69c8..4032515f985 100644 --- a/src/libstd/fmt/mod.rs +++ b/src/libstd/fmt/mod.rs @@ -647,21 +647,6 @@ impl<'self> Formatter<'self> { // the format! syntax extension. fn run(&mut self, piece: &rt::Piece, cur: Option<&str>) { - let setcount = |slot: &mut Option, cnt: &parse::Count| { - match *cnt { - parse::CountIs(n) => { *slot = Some(n); } - parse::CountImplied => { *slot = None; } - parse::CountIsParam(i) => { - let v = self.args[i].value; - unsafe { *slot = Some(*(v as *util::Void as *uint)); } - } - parse::CountIsNextParam => { - let v = self.curarg.next().unwrap().value; - unsafe { *slot = Some(*(v as *util::Void as *uint)); } - } - } - }; - match *piece { rt::String(s) => { self.buf.write(s.as_bytes()); } rt::CurrentArgument(()) => { self.buf.write(cur.unwrap().as_bytes()); } @@ -670,8 +655,8 @@ impl<'self> Formatter<'self> { self.fill = arg.format.fill; self.align = arg.format.align; self.flags = arg.format.flags; - setcount(&mut self.width, &arg.format.width); - setcount(&mut self.precision, &arg.format.precision); + self.width = self.getcount(&arg.format.width); + self.precision = self.getcount(&arg.format.precision); // Extract the correct argument let value = match arg.position { @@ -688,6 +673,39 @@ impl<'self> Formatter<'self> { } } + #[cfg(stage0)] + fn getcount(&mut self, cnt: &parse::Count) -> Option { + match *cnt { + parse::CountIs(n) => { Some(n) } + parse::CountImplied => { None } + parse::CountIsParam(i) => { + let v = self.args[i].value; + unsafe { Some(*(v as *util::Void as *uint)) } + } + parse::CountIsNextParam => { + let v = self.curarg.next().unwrap().value; + unsafe { Some(*(v as *util::Void as *uint)) } + } + parse::CountIsName(*) => unreachable!() + } + } + + #[cfg(not(stage0))] + fn getcount(&mut self, cnt: &rt::Count) -> Option { + match *cnt { + rt::CountIs(n) => { Some(n) } + rt::CountImplied => { None } + rt::CountIsParam(i) => { + let v = self.args[i].value; + unsafe { Some(*(v as *util::Void as *uint)) } + } + rt::CountIsNextParam => { + let v = self.curarg.next().unwrap().value; + unsafe { Some(*(v as *util::Void as *uint)) } + } + } + } + fn execute(&mut self, method: &rt::Method, arg: Argument) { match *method { // Pluralization is selection upon a numeric value specified as the diff --git a/src/libstd/fmt/parse.rs b/src/libstd/fmt/parse.rs index 1b8998b5c6c..11b869c930e 100644 --- a/src/libstd/fmt/parse.rs +++ b/src/libstd/fmt/parse.rs @@ -48,6 +48,7 @@ pub struct Argument<'self> { /// Specification for the formatting of an argument in the format string. #[deriving(Eq)] +#[cfg(stage0)] pub struct FormatSpec<'self> { /// Optionally specified character to fill alignment with fill: Option, @@ -65,6 +66,26 @@ pub struct FormatSpec<'self> { ty: &'self str } +/// Specification for the formatting of an argument in the format string. +#[deriving(Eq)] +#[cfg(not(stage0))] +pub struct FormatSpec<'self> { + /// Optionally specified character to fill alignment with + fill: Option, + /// Optionally specified alignment + align: Alignment, + /// Packed version of various flags provided + flags: uint, + /// The integer precision to use + precision: Count<'self>, + /// The string width requested for the resulting format + width: Count<'self>, + /// 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. + ty: &'self str +} + /// Enum describing where an argument for a format can be located. #[deriving(Eq)] #[allow(missing_doc)] @@ -92,9 +113,22 @@ pub enum Flag { /// can reference either an argument or a literal integer. #[deriving(Eq)] #[allow(missing_doc)] +#[cfg(stage0)] pub enum Count { CountIs(uint), CountIsParam(uint), + CountIsName(&'static str), // not actually used, see stage1 + CountIsNextParam, + CountImplied, +} + +#[deriving(Eq)] +#[allow(missing_doc)] +#[cfg(not(stage0))] +pub enum Count<'self> { + CountIs(uint), + CountIsName(&'self str), + CountIsParam(uint), CountIsNextParam, CountImplied, } @@ -344,10 +378,22 @@ impl<'self> Parser<'self> { spec.flags |= 1 << (FlagAlternate as uint); } // Width and precision + let mut havewidth = false; if self.consume('0') { - spec.flags |= 1 << (FlagSignAwareZeroPad as uint); + // 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(); } - spec.width = self.count(); if self.consume('.') { if self.consume('*') { spec.precision = CountIsNextParam; @@ -548,6 +594,7 @@ impl<'self> Parser<'self> { /// Parses a Count parameter at the current position. This does not check /// for 'CountIsNextParam' because that is only used in precision, not /// width. + #[cfg(stage0)] fn count(&mut self) -> Count { match self.integer() { Some(i) => { @@ -560,6 +607,30 @@ impl<'self> Parser<'self> { None => { CountImplied } } } + #[cfg(not(stage0))] + fn count(&mut self) -> Count<'self> { + 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 @@ -783,6 +854,18 @@ mod tests { }, 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() { diff --git a/src/libstd/fmt/rt.rs b/src/libstd/fmt/rt.rs index 90763836fc6..063d712dfa9 100644 --- a/src/libstd/fmt/rt.rs +++ b/src/libstd/fmt/rt.rs @@ -34,6 +34,7 @@ pub struct Argument<'self> { method: Option<&'self Method<'self>> } +#[cfg(stage0)] pub struct FormatSpec { fill: char, align: parse::Alignment, @@ -42,6 +43,20 @@ pub struct FormatSpec { width: parse::Count, } +#[cfg(not(stage0))] +pub struct FormatSpec { + fill: char, + align: parse::Alignment, + flags: uint, + precision: Count, + width: Count, +} + +#[cfg(not(stage0))] +pub enum Count { + CountIs(uint), CountIsParam(uint), CountIsNextParam, CountImplied, +} + pub enum Position { ArgumentNext, ArgumentIs(uint) } diff --git a/src/libsyntax/ext/format.rs b/src/libsyntax/ext/format.rs index 7518816be1e..31befed6c0c 100644 --- a/src/libsyntax/ext/format.rs +++ b/src/libsyntax/ext/format.rs @@ -177,6 +177,9 @@ impl Context { parse::CountIsParam(i) => { self.verify_arg_type(Left(i), Unsigned); } + parse::CountIsName(s) => { + self.verify_arg_type(Right(s.to_managed()), Unsigned); + } parse::CountIsNextParam => { if self.check_positional_ok() { self.verify_arg_type(Left(self.next_arg), Unsigned); @@ -361,21 +364,31 @@ impl Context { let trans_count = |c: parse::Count| { match c { parse::CountIs(i) => { - self.ecx.expr_call_global(sp, ctpath("CountIs"), + self.ecx.expr_call_global(sp, rtpath("CountIs"), ~[self.ecx.expr_uint(sp, i)]) } parse::CountIsParam(i) => { - self.ecx.expr_call_global(sp, ctpath("CountIsParam"), + self.ecx.expr_call_global(sp, rtpath("CountIsParam"), ~[self.ecx.expr_uint(sp, i)]) } parse::CountImplied => { - let path = self.ecx.path_global(sp, ctpath("CountImplied")); + let path = self.ecx.path_global(sp, rtpath("CountImplied")); self.ecx.expr_path(path) } parse::CountIsNextParam => { - let path = self.ecx.path_global(sp, ctpath("CountIsNextParam")); + let path = self.ecx.path_global(sp, rtpath("CountIsNextParam")); self.ecx.expr_path(path) } + parse::CountIsName(n) => { + let n = n.to_managed(); + let i = match self.name_positions.find_copy(&n) { + Some(i) => i, + None => 0, // error already emitted elsewhere + }; + let i = i + self.args.len(); + self.ecx.expr_call_global(sp, rtpath("CountIsParam"), + ~[self.ecx.expr_uint(sp, i)]) + } } }; let trans_method = |method: &parse::Method| { diff --git a/src/test/run-pass/ifmt.rs b/src/test/run-pass/ifmt.rs index 7611871b0e7..75d11eddb1b 100644 --- a/src/test/run-pass/ifmt.rs +++ b/src/test/run-pass/ifmt.rs @@ -119,7 +119,10 @@ pub fn main() { t!(format!("{:0>2s}", "a"), "0a"); t!(format!("{:.*s}", 4, "aaaaaaaaaaaaaaaaaa"), "aaaa"); t!(format!("{:.1$s}", "aaaaaaaaaaaaaaaaaa", 4), "aaaa"); + t!(format!("{:.a$s}", "aaaaaaaaaaaaaaaaaa", a=4), "aaaa"); t!(format!("{:1$s}", "a", 4), "a "); + t!(format!("{1:0$s}", 4, "a"), "a "); + t!(format!("{:a$s}", "a", a=4), "a "); t!(format!("{:-#s}", "a"), "a"); t!(format!("{:+#s}", "a"), "a");