Skip to content

Commit 115357d

Browse files
committed
Implement lone-dot semantics for %-style format strings
1 parent ff90fe5 commit 115357d

File tree

3 files changed

+136
-38
lines changed

3 files changed

+136
-38
lines changed

Lib/test/test_locale.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,6 @@ def test_grouping(self):
170170
self._test_format("%f", -42, grouping=1, out='-42.000000')
171171
self._test_format("%+f", -42, grouping=1, out='-42.000000')
172172

173-
# TODO: RUSTPYTHON
174-
@unittest.expectedFailure
175173
def test_grouping_and_padding(self):
176174
self._test_format("%20.f", -42, grouping=1, out='-42'.rjust(20))
177175
if self.sep:
@@ -197,8 +195,6 @@ def test_simple(self):
197195
self._test_format("%f", -42, grouping=0, out='-42.000000')
198196
self._test_format("%+f", -42, grouping=0, out='-42.000000')
199197

200-
# TODO: RUSTPYTHON
201-
@unittest.expectedFailure
202198
def test_padding(self):
203199
self._test_format("%20.f", -42, grouping=0, out='-42'.rjust(20))
204200
self._test_format("%+10.f", -4200, grouping=0, out='-4200'.rjust(10))

common/src/cformat.rs

Lines changed: 109 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ impl fmt::Display for CFormatError {
3434
use CFormatErrorType::*;
3535
match self.typ {
3636
UnmatchedKeyParentheses => write!(f, "incomplete format key"),
37-
CFormatErrorType::IncompleteFormat => write!(f, "incomplete format"),
37+
IncompleteFormat => write!(f, "incomplete format"),
3838
UnsupportedFormatChar(c) => write!(
3939
f,
4040
"unsupported format character '{}' ({:#x}) at index {}",
@@ -76,6 +76,18 @@ pub enum CFormatType {
7676
String(CFormatPreconversor),
7777
}
7878

79+
#[derive(Debug, PartialEq)]
80+
pub enum CFormatPrecision {
81+
Quantity(CFormatQuantity),
82+
Dot,
83+
}
84+
85+
impl From<CFormatQuantity> for CFormatPrecision {
86+
fn from(quantity: CFormatQuantity) -> Self {
87+
CFormatPrecision::Quantity(quantity)
88+
}
89+
}
90+
7991
bitflags! {
8092
pub struct CConversionFlags: u32 {
8193
const ALTERNATE_FORM = 0b0000_0001;
@@ -110,7 +122,7 @@ pub struct CFormatSpec {
110122
pub mapping_key: Option<String>,
111123
pub flags: CConversionFlags,
112124
pub min_field_width: Option<CFormatQuantity>,
113-
pub precision: Option<CFormatQuantity>,
125+
pub precision: Option<CFormatPrecision>,
114126
pub format_type: CFormatType,
115127
pub format_char: char,
116128
// chars_consumed: usize,
@@ -143,10 +155,6 @@ impl CFormatSpec {
143155
let precision = parse_precision(iter)?;
144156
consume_length(iter);
145157
let (format_type, format_char) = parse_format_type(iter)?;
146-
let precision = precision.or(match format_type {
147-
CFormatType::Float(_) => Some(CFormatQuantity::Amount(6)),
148-
_ => None,
149-
});
150158

151159
Ok(CFormatSpec {
152160
mapping_key,
@@ -169,31 +177,22 @@ impl CFormatSpec {
169177
string: String,
170178
fill_char: char,
171179
num_prefix_chars: Option<usize>,
172-
fill_with_precision: bool,
173180
) -> String {
174-
let target_width = if fill_with_precision {
175-
&self.precision
176-
} else {
177-
&self.min_field_width
178-
};
179181
let mut num_chars = string.chars().count();
180182
if let Some(num_prefix_chars) = num_prefix_chars {
181183
num_chars += num_prefix_chars;
182184
}
183185
let num_chars = num_chars;
184186

185-
let width = match target_width {
187+
let width = match &self.min_field_width {
186188
Some(CFormatQuantity::Amount(width)) => cmp::max(width, &num_chars),
187189
_ => &num_chars,
188190
};
189191
let fill_chars_needed = width.saturating_sub(num_chars);
190192
let fill_string = CFormatSpec::compute_fill_string(fill_char, fill_chars_needed);
191193

192194
if !fill_string.is_empty() {
193-
// Don't left-adjust if precision-filling: that will always be prepending 0s to %d
194-
// arguments, the LEFT_ADJUST flag will be used by a later call to fill_string with
195-
// the 0-filled string as the string param.
196-
if !fill_with_precision && self.flags.contains(CConversionFlags::LEFT_ADJUST) {
195+
if self.flags.contains(CConversionFlags::LEFT_ADJUST) {
197196
format!("{string}{fill_string}")
198197
} else {
199198
format!("{fill_string}{string}")
@@ -203,19 +202,47 @@ impl CFormatSpec {
203202
}
204203
}
205204

205+
fn fill_string_with_precision(&self, string: String, fill_char: char) -> String {
206+
let num_chars = string.chars().count();
207+
208+
let width = match &self.precision {
209+
Some(CFormatPrecision::Quantity(CFormatQuantity::Amount(width))) => {
210+
cmp::max(width, &num_chars)
211+
}
212+
_ => &num_chars,
213+
};
214+
let fill_chars_needed = width.saturating_sub(num_chars);
215+
let fill_string = CFormatSpec::compute_fill_string(fill_char, fill_chars_needed);
216+
217+
if !fill_string.is_empty() {
218+
// Don't left-adjust if precision-filling: that will always be prepending 0s to %d
219+
// arguments, the LEFT_ADJUST flag will be used by a later call to fill_string with
220+
// the 0-filled string as the string param.
221+
format!("{fill_string}{string}")
222+
} else {
223+
string
224+
}
225+
}
226+
206227
fn format_string_with_precision(
207228
&self,
208229
string: String,
209-
precision: Option<&CFormatQuantity>,
230+
precision: Option<&CFormatPrecision>,
210231
) -> String {
211232
// truncate if needed
212233
let string = match precision {
213-
Some(CFormatQuantity::Amount(precision)) if string.chars().count() > *precision => {
234+
Some(CFormatPrecision::Quantity(CFormatQuantity::Amount(precision)))
235+
if string.chars().count() > *precision =>
236+
{
214237
string.chars().take(*precision).collect::<String>()
215238
}
239+
Some(CFormatPrecision::Dot) => {
240+
// truncate to 0
241+
String::new()
242+
}
216243
_ => string,
217244
};
218-
self.fill_string(string, ' ', None, false)
245+
self.fill_string(string, ' ', None)
219246
}
220247

221248
#[inline]
@@ -225,11 +252,16 @@ impl CFormatSpec {
225252

226253
#[inline]
227254
pub fn format_char(&self, ch: char) -> String {
228-
self.format_string_with_precision(ch.to_string(), Some(&CFormatQuantity::Amount(1)))
255+
self.format_string_with_precision(
256+
ch.to_string(),
257+
Some(&(CFormatQuantity::Amount(1).into())),
258+
)
229259
}
230260

231261
pub fn format_bytes(&self, bytes: &[u8]) -> Vec<u8> {
232-
let bytes = if let Some(CFormatQuantity::Amount(precision)) = self.precision {
262+
let bytes = if let Some(CFormatPrecision::Quantity(CFormatQuantity::Amount(precision))) =
263+
self.precision
264+
{
233265
&bytes[..cmp::min(bytes.len(), precision)]
234266
} else {
235267
bytes
@@ -282,7 +314,7 @@ impl CFormatSpec {
282314
_ => self.flags.sign_string(),
283315
};
284316

285-
let padded_magnitude_string = self.fill_string(magnitude_string, '0', None, true);
317+
let padded_magnitude_string = self.fill_string_with_precision(magnitude_string, '0');
286318

287319
if self.flags.contains(CConversionFlags::ZERO_PAD) {
288320
let fill_char = if !self.flags.contains(CConversionFlags::LEFT_ADJUST) {
@@ -298,15 +330,13 @@ impl CFormatSpec {
298330
padded_magnitude_string,
299331
fill_char,
300332
Some(signed_prefix.chars().count()),
301-
false
302333
),
303334
)
304335
} else {
305336
self.fill_string(
306337
format!("{sign_string}{prefix}{padded_magnitude_string}"),
307338
' ',
308339
None,
309-
false,
310340
)
311341
}
312342
}
@@ -318,9 +348,13 @@ impl CFormatSpec {
318348
self.flags.sign_string()
319349
};
320350

321-
let precision = match self.precision {
322-
Some(CFormatQuantity::Amount(p)) => p,
323-
_ => 6,
351+
let precision = match &self.precision {
352+
Some(CFormatPrecision::Quantity(quantity)) => match quantity {
353+
CFormatQuantity::Amount(amount) => *amount,
354+
CFormatQuantity::FromValuesTuple => 6,
355+
},
356+
Some(CFormatPrecision::Dot) => 0,
357+
None => 6,
324358
};
325359

326360
let magnitude_string = match &self.format_type {
@@ -381,11 +415,10 @@ impl CFormatSpec {
381415
magnitude_string,
382416
fill_char,
383417
Some(sign_string.chars().count()),
384-
false
385418
)
386419
)
387420
} else {
388-
self.fill_string(format!("{sign_string}{magnitude_string}"), ' ', None, false)
421+
self.fill_string(format!("{sign_string}{magnitude_string}"), ' ', None)
389422
}
390423
}
391424
}
@@ -510,15 +543,19 @@ where
510543
Ok(None)
511544
}
512545

513-
fn parse_precision<T, I>(iter: &mut ParseIter<I>) -> Result<Option<CFormatQuantity>, ParsingError>
546+
fn parse_precision<T, I>(iter: &mut ParseIter<I>) -> Result<Option<CFormatPrecision>, ParsingError>
514547
where
515548
T: Into<char> + Copy,
516549
I: Iterator<Item = T>,
517550
{
518551
if let Some(&(_, c)) = iter.peek() {
519552
if c.into() == '.' {
520553
iter.next().unwrap();
521-
return parse_quantity(iter);
554+
return Ok(Some(
555+
parse_quantity(iter)?
556+
.map(CFormatPrecision::Quantity)
557+
.unwrap_or(CFormatPrecision::Dot),
558+
));
522559
}
523560
}
524561
Ok(None)
@@ -848,6 +885,20 @@ mod tests {
848885
.format_string("Hello, World!".to_owned()),
849886
"Hell ".to_owned()
850887
);
888+
assert_eq!(
889+
"%.s"
890+
.parse::<CFormatSpec>()
891+
.unwrap()
892+
.format_string("Hello, World!".to_owned()),
893+
"".to_owned()
894+
);
895+
assert_eq!(
896+
"%5.s"
897+
.parse::<CFormatSpec>()
898+
.unwrap()
899+
.format_string("Hello, World!".to_owned()),
900+
" ".to_owned()
901+
);
851902
}
852903

853904
#[test]
@@ -863,13 +914,27 @@ mod tests {
863914

864915
#[test]
865916
fn test_parse_and_format_number() {
917+
assert_eq!(
918+
"%5d"
919+
.parse::<CFormatSpec>()
920+
.unwrap()
921+
.format_number(&BigInt::from(27)),
922+
" 27".to_owned()
923+
);
866924
assert_eq!(
867925
"%05d"
868926
.parse::<CFormatSpec>()
869927
.unwrap()
870928
.format_number(&BigInt::from(27)),
871929
"00027".to_owned()
872930
);
931+
assert_eq!(
932+
"%.5d"
933+
.parse::<CFormatSpec>()
934+
.unwrap()
935+
.format_number(&BigInt::from(27)),
936+
"00027".to_owned()
937+
);
873938
assert_eq!(
874939
"%+05d"
875940
.parse::<CFormatSpec>()
@@ -927,6 +992,18 @@ mod tests {
927992
"%f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
928993
"1.234500"
929994
);
995+
assert_eq!(
996+
"%.2f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
997+
"1.23"
998+
);
999+
assert_eq!(
1000+
"%.f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
1001+
"1"
1002+
);
1003+
assert_eq!(
1004+
"%+.f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
1005+
"+1"
1006+
);
9301007
assert_eq!(
9311008
"%+f".parse::<CFormatSpec>().unwrap().format_float(1.2345),
9321009
"+1.234500"

vm/src/cformat.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,31 @@ fn try_update_quantity_from_tuple<'a, I: Iterator<Item = &'a PyObjectRef>>(
223223
}
224224
}
225225

226+
fn try_update_precision_from_tuple<'a, I: Iterator<Item = &'a PyObjectRef>>(
227+
vm: &VirtualMachine,
228+
elements: &mut I,
229+
p: &mut Option<CFormatPrecision>,
230+
) -> PyResult<()> {
231+
match p {
232+
Some(CFormatPrecision::Quantity(CFormatQuantity::FromValuesTuple)) => match elements.next()
233+
{
234+
Some(width_obj) => {
235+
if let Some(i) = width_obj.payload::<PyInt>() {
236+
let i = i.try_to_primitive::<i32>(vm)?.unsigned_abs();
237+
*p = Some(CFormatPrecision::Quantity(CFormatQuantity::Amount(
238+
i as usize,
239+
)));
240+
Ok(())
241+
} else {
242+
Err(vm.new_type_error("* wants int".to_owned()))
243+
}
244+
}
245+
None => Err(vm.new_type_error("not enough arguments for format string".to_owned())),
246+
},
247+
_ => Ok(()),
248+
}
249+
}
250+
226251
fn specifier_error(vm: &VirtualMachine) -> PyBaseExceptionRef {
227252
vm.new_type_error("format requires a mapping".to_owned())
228253
}
@@ -299,7 +324,7 @@ pub(crate) fn cformat_bytes(
299324
CFormatPart::Literal(literal) => result.append(literal),
300325
CFormatPart::Spec(spec) => {
301326
try_update_quantity_from_tuple(vm, &mut value_iter, &mut spec.min_field_width)?;
302-
try_update_quantity_from_tuple(vm, &mut value_iter, &mut spec.precision)?;
327+
try_update_precision_from_tuple(vm, &mut value_iter, &mut spec.precision)?;
303328

304329
let value = match value_iter.next() {
305330
Some(obj) => Ok(obj.clone()),
@@ -393,7 +418,7 @@ pub(crate) fn cformat_string(
393418
CFormatPart::Literal(literal) => result.push_str(literal),
394419
CFormatPart::Spec(spec) => {
395420
try_update_quantity_from_tuple(vm, &mut value_iter, &mut spec.min_field_width)?;
396-
try_update_quantity_from_tuple(vm, &mut value_iter, &mut spec.precision)?;
421+
try_update_precision_from_tuple(vm, &mut value_iter, &mut spec.precision)?;
397422

398423
let value = match value_iter.next() {
399424
Some(obj) => Ok(obj.clone()),

0 commit comments

Comments
 (0)