8000 Add support for the `%` format code for floats. · doyshinda/RustPython@2007961 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2007961

Browse files
committed
Add support for the % format code for floats.
Contributes to RustPython#1656
1 parent 102b91a commit 2007961

File tree

4 files changed

+47
-10
lines changed

4 files changed

+47
-10
lines changed

tests/snippets/strings.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,3 +431,21 @@ def try_mutate_str():
431431
assert f'{1234567890.1234:_.2f}' == '1_234_567_890.12'
432432
with AssertRaises(ValueError, msg="Unknown format code 'd' for object of type 'float'"):
433433
f'{5.0:04d}'
434+
435+
# Test % formatting
436+
assert f'{10.0:%}' == '1000.000000%'
437+
assert f'{10.0:.2%}' == '1000.00%'
438+
assert f'{10.0:.8%}' == '1000.00000000%'
439+
assert f'{-10.0:%}' == '-1000.000000%'
440+
assert f'{-10.0:.2%}' == '-1000.00%'
441+
assert f'{-10.0:.8%}' == '-1000.00000000%'
442+
assert '{:%}'.format(float('nan')) == 'nan%'
443+
assert '{:%}'.format(float('NaN')) == 'nan%'
444+
assert '{:%}'.format(float('NAN')) == 'nan%'
445+
assert '{:.2%}'.format(float('nan')) == 'nan%'
446+
assert '{:%}'.format(float('inf')) == 'inf%'
447+
assert '{:%}'.format(float('Inf')) == 'inf%'
448+
assert '{:%}'.format(float('INF')) == 'inf%'
449+
assert '{:.2%}'.format(float('inf')) == 'inf%'
450+
with AssertRaises(ValueError, msg='Invalid format specifier'):
451+
f'{10.0:%3}'

vm/src/format.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ pub enum FormatType {
9898
GeneralFormatUpper,
9999
FixedPointLower,
100100
FixedPointUpper,
101+
Percentage,
101102
}
102103

103104
#[derive(Debug, PartialEq)]
@@ -232,11 +233,12 @@ fn parse_format_type(text: &str) -> (Option<FormatType>, &str) {
232233
Some('g') => (Some(FormatType::GeneralFormatLower), chars.as_str()),
233234
Some('G') => (Some(FormatType::GeneralFormatUpper), chars.as_str()),
234235
Some('n') => (Some(FormatType::Number), chars.as_str()),
236+
Some('%') => (Some(FormatType::Percentage), chars.as_str()),
235237
_ => (None, text),
236238
}
237239
}
238240

239-
fn parse_format_spec(text: &str) -> FormatSpec {
241+
fn parse_format_spec(text: &str) -> Result<FormatSpec, &'static str> {
240242
let (preconversor, after_preconversor) = parse_preconversor(text);
241243
let (mut fill, mut align, after_align) = parse_fill_and_align(after_preconversor);
242244
let (sign, after_sign) = parse_sign(after_align);
@@ -245,14 +247,17 @@ fn parse_format_spec(text: &str) -> FormatSpec {
245247
let (width, after_width) = parse_number(after_zero);
246248
let (grouping_option, after_grouping_option) = parse_grouping_option(after_width);
247249
let (precision, after_precision) = parse_precision(after_grouping_option);
248-
let (format_type, _) = parse_format_type(after_precision);
250+
let (format_type, after_format_type) = parse_format_type(after_precision);
251+
if !after_format_type.is_empty() {
252+
return Err("Invalid format spec");
253+
}
249254

250255
if zero && fill.is_none() {
251256
fill.replace('0');
252257
align = align.or(Some(FormatAlign::AfterSign));
253258
}
254259

255-
FormatSpec {
260+
Ok(FormatSpec {
256261
preconversor,
257262
fill,
258263
align,
@@ -262,11 +267,11 @@ fn parse_format_spec(text: &str) -> FormatSpec {
262267
grouping_option,
263268
precision,
264269
format_type,
265-
}
270+
})
266271
}
267272

268273
impl FormatSpec {
269-
pub fn parse(text: &str) -> FormatSpec {
274+
pub fn parse(text: &str) -> Result<FormatSpec, &'static str> {
270275
parse_format_spec(text)
271276
}
272277

@@ -369,6 +374,11 @@ impl FormatSpec {
369374
Some(FormatType::ExponentLower) => {
370375
Err("Format code 'e' for object of type 'float' not implemented yet")
371376
}
377+
Some(FormatType::Percentage) => match magnitude {
378+
magnitude if magnitude.is_nan() => Ok("nan%".to_string()),
379+
magnitude if magnitude.is_infinite() => Ok("inf%".to_string()),
380+
_ => Ok(format!("{:.*}%", precision, magnitude * 100.0)),
381+
},
372382
None => {
373383
match magnitude {
374384
magnitude if magnitude.is_nan() => Ok("nan".to_string()),
@@ -443,6 +453,9 @@ impl FormatSpec {
443453
_ => Err("Unable to convert int to float"),
444454
}
445455
}
456+
Some(FormatType::Percentage) => {
457+
Err("Format code '%' for object of type 'int' not implemented yet")
458+
}
446459
None => Ok(magnitude.to_str_radix(10)),
447460
};
448461
if raw_magnitude_string_result.is_err() {
@@ -525,7 +538,7 @@ pub enum FormatParseError {
525538
impl FromStr for FormatSpec {
526539
type Err = &'static str;
527540
fn from_str(s: &str) -> Result<Self, Self::Err> {
528-
Ok(FormatSpec::parse(s))
541+
FormatSpec::parse(s)
529542
}
530543
}
531544

vm/src/obj/objfloat.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,11 @@ impl PyFloat {
213213

214214
#[pymethod(name = "__format__")]
215215
fn format(&self, spec: PyStringRef, vm: &VirtualMachine) -> PyResult<String> {
216-
let format_spec = FormatSpec::parse(spec.as_str());
217-
match format_spec.format_float(self.value) {
216+
let try_format = || {
217+
let format_spec = FormatSpec::parse(spec.as_str())?;
218+
format_spec.format_float(self.value)
219+
};
220+
match try_format() {
218221
Ok(string) => Ok(string),
219222
Err(err) => Err(vm.new_value_error(err.to_string())),
220223
}

vm/src/obj/objint.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -506,8 +506,11 @@ impl PyInt {
506506

507507
#[pymethod(name = "__format__")]
508508
fn format(&self, spec: PyStringRef, vm: &VirtualMachine) -> PyResult<String> {
509-
let format_spec = FormatSpec::parse(spec.as_str());
510-
match format_spec.format_int(&self.value) {
509+
let try_format = || {
510+
let format_spec = FormatSpec::parse(spec.as_str())?;
511+
format_spec.format_int(&self.value)
512+
};
513+
match try_format() {
511514
Ok(string) => Ok(string),
512515
Err(err) => Err(vm.new_value_error(err.to_string())),
513516
}

0 commit comments

Comments
 (0)
0