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
This commit is contained in:
Alex Crichton 2013-10-12 20:00:58 -07:00
parent a84c2999c9
commit fc06f7922d
5 changed files with 155 additions and 23 deletions

View File

@ -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<uint>, 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<uint> {
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<uint> {
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

View File

@ -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<char>,
@ -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<char>,
/// 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() {

View File

@ -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)
}

View File

@ -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| {

View File

@ -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");