diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index 087fe26cb..7b219ba8a 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -212,6 +212,13 @@ assert "%(first)s %(second)s" % {'second': 'World!', 'first': "Hello,"} == "Hell assert "%(key())s" % {'key()': 'aaa'} assert "%s %a %r" % (f, f, f) == "str(Foo) repr(Foo) repr(Foo)" assert "repr() shows quotes: %r; str() doesn't: %s" % ("test1", "test2") == "repr() shows quotes: 'test1'; str() doesn't: test2" +assert "%f" % (1.2345) == "1.234500" +assert "%+f" % (1.2345) == "+1.234500" +assert "% f" % (1.2345) == " 1.234500" +assert "%f" % (-1.2345) == "-1.234500" +assert "%f" % (1.23456789012) == "1.234568" +assert "%f" % (123) == "123.000000" +assert "%f" % (-123) == "-123.000000" assert_raises(TypeError, lambda: "My name is %s and I'm %(age)d years old" % ("Foo", 25), msg="format requires a mapping") assert_raises(TypeError, lambda: "My name is %(name)s" % "Foo", msg="format requires a mapping") diff --git a/vm/src/cformat.rs b/vm/src/cformat.rs index 2b17b992a..cac22a3a5 100644 --- a/vm/src/cformat.rs +++ b/vm/src/cformat.rs @@ -209,6 +209,44 @@ impl CFormatSpec { self.fill_string(format!("{}{}", prefix, magnitude_string), ' ', None) } } + + pub fn format_float(&self, num: f64) -> String { + let magnitude = num.abs(); + + let sign_string = if num.is_sign_positive() { + if self.flags.contains(CConversionFlags::SIGN_CHAR) { + "+" + } else if self.flags.contains(CConversionFlags::BLANK_SIGN) { + " " + } else { + "" + } + } else { + "-" + }; + + // TODO: Support precision + let magnitude_string = format!("{:.6}", magnitude); + + if self.flags.contains(CConversionFlags::ZERO_PAD) { + let fill_char = if !self.flags.contains(CConversionFlags::LEFT_ADJUST) { + '0' + } else { + ' ' + }; + format!( + "{}{}", + sign_string, + self.fill_string( + magnitude_string, + fill_char, + Some(sign_string.chars().count()) + ) + ) + } else { + self.fill_string(format!("{}{}", sign_string, magnitude_string), ' ', None) + } + } } #[derive(Debug, PartialEq)] @@ -763,6 +801,42 @@ mod tests { ); } + #[test] + fn test_parse_and_format_float() { + assert_eq!( + "%f".parse::() + .unwrap() + .format_float(f64::from(1.2345)), + "1.234500".to_string() + ); + assert_eq!( + "%+f" + .parse::() + .unwrap() + .format_float(f64::from(1.2345)), + "+1.234500".to_string() + ); + assert_eq!( + "% f" + .parse::() + .unwrap() + .format_float(f64::from(1.2345)), + " 1.234500".to_string() + ); + assert_eq!( + "%f".parse::() + .unwrap() + .format_float(f64::from(-1.2345)), + "-1.234500".to_string() + ); + assert_eq!( + "%f".parse::() + .unwrap() + .format_float(f64::from(1.2345678901)), + "1.234568".to_string() + ); + } + #[test] fn test_format_parse() { let fmt = "Hello, my name is %s and I'm %d years old"; diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 6e7945e64..96949553c 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -28,6 +28,7 @@ use crate::vm::VirtualMachine; use super::objbytes::PyBytes; use super::objdict::PyDict; +use super::objfloat; use super::objint::{self, PyInt}; use super::objiter; use super::objnone::PyNone; @@ -1225,6 +1226,21 @@ fn do_cformat_specifier( } Ok(format_spec.format_number(objint::get_value(&obj))) } + CFormatType::Float(_) => { + if objtype::isinstance(&obj, &vm.ctx.float_type()) { + Ok(format_spec.format_float(objfloat::get_value(&obj))) + } else if objtype::isinstance(&obj, &vm.ctx.int_type()) { + Ok(format_spec.format_float(objint::get_value(&obj).to_f64().unwrap())) + } else { + let required_type_string = "an floating point or integer"; + Err(vm.new_type_error(format!( + "%{} format: {} is required, not {}", + format_spec.format_char, + required_type_string, + obj.class() + ))) + } + } CFormatType::Character => { let char_string = { if objtype::isinstance(&obj, &vm.ctx.int_type()) { @@ -1251,10 +1267,6 @@ fn do_cformat_specifier( format_spec.precision = Some(CFormatQuantity::Amount(1)); Ok(format_spec.format_string(char_string)) } - _ => Err(vm.new_not_implemented_error(format!( - "Not yet implemented for %{}", - format_spec.format_char - ))), } }