diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 7c510c37e..33774fe2c 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -608,3 +608,7 @@ assert b'\xc2\xae\x75\x73\x74'.decode('ascii', 'ignore') == 'ust' assert b'\xc2\xae\x75\x73\x74'.decode('utf-8') == '®ust' assert b'\xc2\xae\x75\x73\x74'.decode() == '®ust' assert b'\xe4\xb8\xad\xe6\x96\x87\xe5\xad\x97'.decode('utf-8') == '中文字' + +# mod +assert b'rust%bpython%b' % (b' ', b'!') == b'rust python!' +assert b'x=%i y=%f' % (1, 2.5) == b'x=1 y=2.500000' diff --git a/vm/src/cformat.rs b/vm/src/cformat.rs index 8cc573117..e816562d3 100644 --- a/vm/src/cformat.rs +++ b/vm/src/cformat.rs @@ -48,6 +48,7 @@ pub enum CFormatPreconversor { Repr, Str, Ascii, + Bytes, } #[derive(Debug, PartialEq)] @@ -545,6 +546,11 @@ fn parse_format_type(text: &str) -> Result<(CFormatType, &str, char), CFormatErr chars.as_str(), next_char.unwrap(), )), + Some('b') => Ok(( + CFormatType::String(CFormatPreconversor::Bytes), + chars.as_str(), + next_char.unwrap(), + )), Some('a') => Ok(( CFormatType::String(CFormatPreconversor::Ascii), chars.as_str(), diff --git a/vm/src/format.rs b/vm/src/format.rs index aaf5f4163..6180dd3e9 100644 --- a/vm/src/format.rs +++ b/vm/src/format.rs @@ -8,6 +8,7 @@ pub enum FormatPreconversor { Str, Repr, Ascii, + Bytes, } impl FormatPreconversor { @@ -16,6 +17,7 @@ impl FormatPreconversor { 's' => Some(FormatPreconversor::Str), 'r' => Some(FormatPreconversor::Repr), 'a' => Some(FormatPreconversor::Ascii), + 'b' => Some(FormatPreconversor::Bytes), _ => None, } } diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index db561e435..71845e819 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -15,13 +15,16 @@ use super::objslice::PySliceRef; use super::objstr::PyStringRef; use super::objtuple::PyTupleRef; use super::objtype::PyClassRef; +use crate::cformat::CFormatString; use crate::function::OptionalArg; +use crate::obj::objstr::do_cformat_string; use crate::pyhash; use crate::pyobject::{ Either, IntoPyObject, PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, }; use crate::vm::VirtualMachine; +use std::str::FromStr; /// "bytes(iterable_of_ints) -> bytes\n\ /// bytes(string, encoding[, errors]) -> bytes\n\ @@ -436,6 +439,33 @@ impl PyBytesRef { self.repeat(n, vm) } + fn do_cformat( + &self, + vm: &VirtualMachine, + format_string: CFormatString, + values_obj: PyObjectRef, + ) -> PyResult { + let final_string = do_cformat_string(vm, format_string, values_obj)?; + Ok(vm.ctx.new_bytes( + PyBytes::from_string(final_string.as_str(), "utf8", vm)? + .inner + .elements, + )) + } + + #[pymethod(name = "__mod__")] + fn modulo(self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let format_string_text = std::str::from_utf8(&self.inner.elements).unwrap(); + let format_string = CFormatString::from_str(format_string_text) + .map_err(|err| vm.new_value_error(err.to_string()))?; + self.do_cformat(vm, format_string, values.clone()) + } + + #[pymethod(name = "__rmod__")] + fn rmod(self, _values: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.not_implemented()) + } + /// Return a string decoded from the given bytes. /// Default encoding is 'utf-8'. /// Default errors is 'strict', meaning that encoding errors raise a UnicodeError. diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 9b6438e33..6f8eb7b1d 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -1278,6 +1278,7 @@ fn call_object_format(vm: &VirtualMachine, argument: PyObjectRef, format_spec: & Some(FormatPreconversor::Str) => vm.call_method(&argument, "__str__", vec![])?, Some(FormatPreconversor::Repr) => vm.call_method(&argument, "__repr__", vec![])?, Some(FormatPreconversor::Ascii) => vm.call_method(&argument, "__repr__", vec![])?, + Some(FormatPreconversor::Bytes) => vm.call_method(&argument, "decode", vec![])?, None => argument, }; let returned_type = vm.ctx.new_str(new_format_spec.to_string()); @@ -1306,6 +1307,7 @@ fn do_cformat_specifier( CFormatPreconversor::Str => vm.call_method(&obj.clone(), "__str__", vec![])?, CFormatPreconversor::Repr => vm.call_method(&obj.clone(), "__repr__", vec![])?, CFormatPreconversor::Ascii => vm.call_method(&obj.clone(), "__repr__", vec![])?, + CFormatPreconversor::Bytes => vm.call_method(&obj.clone(), "decode", vec![])?, }; Ok(format_spec.format_string(get_value(&result))) } @@ -1398,11 +1400,11 @@ fn try_update_quantity_from_tuple( } } -fn do_cformat( +pub fn do_cformat_string( vm: &VirtualMachine, mut format_string: CFormatString, values_obj: PyObjectRef, -) -> PyResult { +) -> PyResult { let mut final_string = String::new(); let num_specifiers = format_string .format_parts @@ -1501,7 +1503,17 @@ fn do_cformat( vm.new_type_error("not all arguments converted during string formatting".to_string()) ); } - Ok(vm.ctx.new_str(final_string)) + Ok(final_string) +} + +fn do_cformat( + vm: &VirtualMachine, + format_string: CFormatString, + values_obj: PyObjectRef, +) -> PyResult { + Ok(vm + .ctx + .new_str(do_cformat_string(vm, format_string, values_obj)?)) } fn perform_format(