diff --git a/Lib/test/test_bytes.py b/Lib/test/test_bytes.py index c26d9f506..148225da7 100644 --- a/Lib/test/test_bytes.py +++ b/Lib/test/test_bytes.py @@ -639,8 +639,6 @@ class BaseBytesTest: self.assertEqual(b.rindex(i, 3, 9), 7) self.assertRaises(ValueError, b.rindex, w, 1, 3) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_mod(self): b = self.type2test(b'hello, %b!') orig = b @@ -658,8 +656,6 @@ class BaseBytesTest: self.assertEqual(b, b'hello,\x00world!') self.assertIs(type(b), self.type2test) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_imod(self): b = self.type2test(b'hello, %b!') orig = b diff --git a/vm/src/builtins/bytearray.rs b/vm/src/builtins/bytearray.rs index 27a31c2ed..e8f99a51b 100644 --- a/vm/src/builtins/bytearray.rs +++ b/vm/src/builtins/bytearray.rs @@ -608,7 +608,7 @@ impl PyByteArray { #[pymethod(name = "__mod__")] fn modulo(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult { let formatted = self.borrow_value().cformat(values, vm)?; - Ok(formatted.into_bytes().into()) + Ok(formatted.into()) } #[pymethod(name = "__rmod__")] diff --git a/vm/src/builtins/bytes.rs b/vm/src/builtins/bytes.rs index 117e95fd4..37b10c9af 100644 --- a/vm/src/builtins/bytes.rs +++ b/vm/src/builtins/bytes.rs @@ -441,9 +441,9 @@ impl PyBytes { } #[pymethod(name = "__mod__")] - fn modulo(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn modulo(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult { let formatted = self.inner.cformat(values, vm)?; - Ok(vm.ctx.new_bytes(formatted.into_bytes())) + Ok(formatted.into()) } #[pymethod(name = "__rmod__")] diff --git a/vm/src/bytesinner.rs b/vm/src/bytesinner.rs index 9cf8fe494..cdc77a259 100644 --- a/vm/src/bytesinner.rs +++ b/vm/src/bytesinner.rs @@ -11,6 +11,7 @@ use crate::builtins::pystr::{self, PyStr, PyStrRef}; use crate::builtins::singletons::PyNoneRef; use crate::builtins::PyTypeRef; use crate::byteslike::try_bytes_like; +use crate::cformat::CFormatBytes; use crate::function::{OptionalArg, OptionalOption}; use crate::pyobject::{ BorrowValue, Either, IdProtocol, PyComparisonValue, PyIterable, PyObjectRef, PyRef, PyResult, @@ -894,8 +895,10 @@ impl PyBytesInner { res } - pub fn cformat(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.elements.py_cformat(values, vm) + pub fn cformat(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult> { + CFormatBytes::parse_from_bytes(self.elements.as_slice()) + .map_err(|err| vm.new_value_error(err.to_string()))? + .format(vm, values) } pub fn repeat(&self, n: isize) -> Vec { diff --git a/vm/src/cformat.rs b/vm/src/cformat.rs index ab9302165..869324f28 100644 --- a/vm/src/cformat.rs +++ b/vm/src/cformat.rs @@ -1,7 +1,6 @@ -use crate::builtins::{float, int, pystr, tuple}; /// Implementation of Printf-Style string formatting /// [https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting] -use crate::format::get_num_digits; +use crate::builtins::{float, int, memory::try_buffer_from_object, pystr, tuple, PyBytes}; use crate::pyobject::{ BorrowValue, ItemProtocol, PyObjectRef, PyResult, TryFromObject, TypeProtocol, }; @@ -9,15 +8,14 @@ use crate::vm::VirtualMachine; use num_bigint::{BigInt, Sign}; use num_traits::cast::ToPrimitive; use num_traits::Signed; -use std::cmp; -use std::fmt; +use std::iter::{Enumerate, Peekable}; use std::str::FromStr; +use std::{cmp, fmt}; #[derive(Debug, PartialEq)] enum CFormatErrorType { UnmatchedKeyParentheses, MissingModuloSign, - UnescapedModuloSignInLiteral, UnsupportedFormatChar(char), IncompleteFormat, IntTooBig, @@ -122,10 +120,36 @@ struct CFormatSpec { precision: Option, format_type: CFormatType, format_char: char, - chars_consumed: usize, + // chars_consumed: usize, } impl CFormatSpec { + fn parse>(iter: &mut ParseIter) -> Result { + let mapping_key = parse_spec_mapping_key(iter)?; + let flags = parse_flags(iter); + let min_field_width = parse_quantity(iter)?; + let precision = parse_precision(iter)?; + consume_length(iter); + let (format_type, format_char) = parse_format_type(iter)?; + let precision = if precision.is_some() { + precision + } else { + match format_type { + CFormatType::Float(_) => Some(CFormatQuantity::Amount(6)), + _ => None, + } + }; + + Ok(CFormatSpec { + mapping_key, + flags, + min_field_width, + precision, + format_type, + format_char, + }) + } + fn compute_fill_string(fill_char: char, fill_chars_needed: usize) -> String { (0..fill_chars_needed) .map(|_| fill_char) @@ -185,6 +209,28 @@ impl CFormatSpec { self.format_string_with_precision(ch.to_string(), Some(&CFormatQuantity::Amount(1))) } + fn format_bytes(&self, bytes: &[u8]) -> Vec { + let bytes = if let Some(CFormatQuantity::Amount(precision)) = self.precision { + &bytes[..cmp::min(bytes.len(), precision)] + } else { + bytes + }; + if let Some(CFormatQuantity::Amount(width)) = self.min_field_width { + let fill = cmp::max(0, width - bytes.len()); + let mut v = Vec::with_capacity(bytes.len() + fill); + if self.flags.contains(CConversionFlags::LEFT_ADJUST) { + v.extend_from_slice(bytes); + v.append(&mut vec![b' '; fill]); + } else { + v.append(&mut vec![b' '; fill]); + v.extend_from_slice(bytes); + } + v + } else { + bytes.to_vec() + } + } + fn format_number(&self, num: &BigInt) -> String { use CFormatCase::{Lowercase, Uppercase}; use CNumberType::*; @@ -311,6 +357,107 @@ impl CFormatSpec { Ok(formatted) } + fn bytes_format(&self, vm: &VirtualMachine, obj: PyObjectRef) -> PyResult> { + // do the formatting by type + let formatted = match &self.format_type { + CFormatType::String(preconversor) => match preconversor { + CFormatPreconversor::Repr | CFormatPreconversor::Ascii => { + let s = vm.to_repr(&obj)?; + let s = self.format_string(s.borrow_value().to_owned()); + s.into_bytes() + } + CFormatPreconversor::Str | CFormatPreconversor::Bytes => { + if let Ok(buffer) = try_buffer_from_object(vm, &obj) { + let guard; + let vec; + let bytes = match buffer.as_contiguous() { + Some(bytes) => { + guard = bytes; + &*guard + } + None => { + vec = buffer.to_contiguous(); + vec.as_slice() + } + }; + self.format_bytes(bytes) + } else { + let bytes = vm.call_method(&obj, "__bytes__", ())?; + let bytes = PyBytes::try_from_object(vm, bytes)?; + self.format_bytes(bytes.borrow_value()) + } + } + }, + CFormatType::Number(number_type) => { + let err = || { + let required_type_string = match number_type { + CNumberType::Decimal => "a number", + _ => "an integer", + }; + vm.new_type_error(format!( + "%{} format: {} is required, not {}", + self.format_char, + required_type_string, + obj.class() + )) + }; + match_class!(match &obj { + ref i @ int::PyInt => { + self.format_number(i.borrow_value()).into_bytes() + } + // TODO: if guards for match_class + ref f @ float::PyFloat => { + if let CNumberType::Decimal = number_type { + self.format_number(&float::try_bigint(f.to_f64(), vm)?) + .into_bytes() + } else { + return Err(err()); + } + } + _ => return Err(err()), + }) + } + CFormatType::Float(_) => { + let value = float::try_float(&obj, vm)?.ok_or_else(|| { + vm.new_type_error(format!( + "%{} format: an floating point or integer is required, not {}", + self.format_char, + obj.class().name + )) + })?; + self.format_float(value) + .map_err(|e| vm.new_not_implemented_error(e))? + .into_bytes() + } + CFormatType::Character => { + let ch = { + if obj.isinstance(&vm.ctx.types.int_type) { + // BigInt truncation is fine in this case because only the unicode range is relevant + int::get_value(&obj) + .to_u32() + .and_then(std::char::from_u32) + .ok_or_else(|| { + vm.new_overflow_error("%c arg not in range(0x110000)".to_owned()) + }) + } else if obj.isinstance(&vm.ctx.types.str_type) { + let s = pystr::borrow_value(&obj); + let num_chars = s.chars().count(); + if num_chars != 1 { + Err(vm.new_type_error("%c requires int or char".to_owned())) + } else { + Ok(s.chars().next().unwrap()) + } + } else { + // TODO re-arrange this block so this error is only created once + Err(vm.new_type_error("%c requires int or char".to_owned())) + } + }?; + self.format_char(ch).into_bytes() + } + }; + Ok(formatted) + } + fn format(&self, vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { // do the formatting by type let formatted = match &self.format_type { @@ -319,7 +466,9 @@ impl CFormatSpec { CFormatPreconversor::Str => vm.to_str(&obj)?, CFormatPreconversor::Repr | CFormatPreconversor::Ascii => vm.to_repr(&obj)?, CFormatPreconversor::Bytes => { - TryFromObject::try_from_object(vm, vm.call_method(&obj, "decode", ())?)? + return Err(vm.new_value_error( + "unsupported format character 'b' (0x62)".to_owned(), + )); } }; self.format_string(result.borrow_value().to_owned()) @@ -412,6 +561,159 @@ impl CFormatPart { } } +#[derive(Debug, PartialEq)] +pub(crate) struct CFormatBytes { + parts: Vec<(usize, CFormatPart)>, +} + +impl CFormatBytes { + pub(crate) fn parse>( + iter: &mut ParseIter, + ) -> Result { + match parse(iter) { + Ok(parts) => Ok(Self { parts }), + Err(err) => Err(CFormatError { + typ: err.0, + index: err.1, + }), + } + } + pub(crate) fn parse_from_bytes(bytes: &[u8]) -> Result { + let mut iter = bytes.iter().map(|&x| x as char).enumerate().peekable(); + Self::parse(&mut iter) + } + pub(crate) fn format( + &mut self, + vm: &VirtualMachine, + values_obj: PyObjectRef, + ) -> PyResult> { + fn try_update_quantity_from_tuple( + vm: &VirtualMachine, + elements: &mut dyn Iterator, + q: &mut Option, + mut tuple_index: usize, + ) -> PyResult { + match q { + Some(CFormatQuantity::FromValuesTuple) => match elements.next() { + Some(width_obj) => { + tuple_index += 1; + if !width_obj.isinstance(&vm.ctx.types.int_type) { + Err(vm.new_type_error("* wants int".to_owned())) + } else { + let i = int::get_value(&width_obj); + let i = int::try_to_primitive::(i, vm)? as usize; + *q = Some(CFormatQuantity::Amount(i)); + Ok(tuple_index) + } + } + None => { + Err(vm.new_type_error("not enough arguments for format string".to_owned())) + } + }, + _ => Ok(tuple_index), + } + } + + let mut final_bytes = vec![]; + let num_specifiers = self + .parts + .iter() + .filter(|(_, part)| CFormatPart::is_specifier(part)) + .count(); + let mapping_required = self + .parts + .iter() + .any(|(_, part)| CFormatPart::has_key(part)) + && self + .parts + .iter() + .filter(|(_, part)| CFormatPart::is_specifier(part)) + .all(|(_, part)| CFormatPart::has_key(part)); + + let values = if mapping_required { + if !values_obj.isinstance(&vm.ctx.types.dict_type) { + return Err(vm.new_type_error("format requires a mapping".to_owned())); + } + values_obj.clone() + } else { + // check for only literal parts, in which case only dict or empty tuple is allowed + if num_specifiers == 0 + && !(values_obj.isinstance(&vm.ctx.types.tuple_type) + && tuple::get_value(&values_obj).is_empty()) + && !values_obj.isinstance(&vm.ctx.types.dict_type) + { + return Err(vm.new_type_error( + "not all arguments converted during string formatting".to_owned(), + )); + } + + // convert `values_obj` to a new tuple if it's not a tuple + if !values_obj.isinstance(&vm.ctx.types.tuple_type) { + vm.ctx.new_tuple(vec![values_obj.clone()]) + } else { + values_obj.clone() + } + }; + + let mut tuple_index: usize = 0; + for (_, part) in &mut self.parts { + let mut result_bytes: Vec = match part { + CFormatPart::Spec(format_spec) => { + // try to get the object + let obj: PyObjectRef = match &format_spec.mapping_key { + Some(key) => { + // TODO: change the KeyError message to match the one in cpython + values.get_item(key, vm)? + } + None => { + let mut elements = tuple::get_value(&values) + .to_vec() + .into_iter() + .skip(tuple_index); + + tuple_index = try_update_quantity_from_tuple( + vm, + &mut elements, + &mut format_spec.min_field_width, + tuple_index, + )?; + tuple_index = try_update_quantity_from_tuple( + vm, + &mut elements, + &mut format_spec.precision, + tuple_index, + )?; + + let obj = match elements.next() { + Some(obj) => Ok(obj), + None => Err(vm.new_type_error( + "not enough arguments for format string".to_owned(), + )), + }?; + tuple_index += 1; + + obj + } + }; + format_spec.bytes_format(vm, obj)? + } + CFormatPart::Literal(literal) => literal.clone().into_bytes(), + }; + final_bytes.append(&mut result_bytes); + } + + // check that all arguments were converted + if (!mapping_required && tuple::get_value(&values).get(tuple_index).is_some()) + && !values_obj.isinstance(&vm.ctx.types.dict_type) + { + return Err(vm.new_type_error( + "not all arguments converted during string formatting".to_owned(), + )); + } + Ok(final_bytes) + } +} + #[derive(Debug, PartialEq)] pub(crate) struct CFormatString { parts: Vec<(usize, CFormatPart)>, @@ -421,28 +723,23 @@ impl FromStr for CFormatString { type Err = CFormatError; fn from_str(text: &str) -> Result { - let mut cur_text: &str = text; - let mut index = 0; - let mut parts: Vec<(usize, CFormatPart)> = Vec::new(); - while !cur_text.is_empty() { - cur_text = parse_literal(cur_text) - .or_else(|_| parse_specifier(cur_text)) - .map(|(format_part, new_text, consumed)| { - parts.push((index, format_part)); - index += consumed; - new_text - }) - .map_err(|(e, consumed)| CFormatError { - typ: e, - index: index + consumed, - })?; - } - - Ok(CFormatString { parts }) + let mut iter = text.chars().enumerate().peekable(); + Self::parse(&mut iter) } } impl CFormatString { + pub(crate) fn parse>( + iter: &mut ParseIter, + ) -> Result { + match parse(iter) { + Ok(parts) => Ok(Self { parts }), + Err(err) => Err(CFormatError { + typ: err.0, + index: err.1, + }), + } + } pub(crate) fn format( &mut self, vm: &VirtualMachine, @@ -575,86 +872,93 @@ impl CFormatString { } } -fn parse_quantity(text: &str) -> Result<(Option, &str), CFormatErrorType> { - let num_digits: usize = get_num_digits(text); - let ret = if num_digits == 0 { - let mut chars = text.chars(); - match chars.next() { - Some('*') => (Some(CFormatQuantity::FromValuesTuple), chars.as_str()), - _ => (None, text), - } - } else { - let q = text[..num_digits] - .parse::() - .map_err(|_| CFormatErrorType::IntTooBig)? as usize; - (Some(CFormatQuantity::Amount(q)), &text[num_digits..]) - }; - Ok(ret) -} +type ParseIter = Peekable>; -fn parse_precision(text: &str) -> Result<(Option, &str), CFormatErrorType> { - let mut chars = text.chars(); - let ret = match chars.next() { - Some('.') => parse_quantity(&chars.as_str())?, - _ => (None, text), - }; - Ok(ret) -} - -fn parse_literal_single(text: &str) -> Result<(char, &str), CFormatErrorType> { - let mut chars = text.chars(); - // TODO get rid of the unwrap - let first_char = chars.next().unwrap(); - if first_char == '%' { - // if we see a %, it has to be escaped - match chars.next() { - Some(next_char) => { - if next_char != first_char { - Err(CFormatErrorType::UnescapedModuloSignInLiteral) +fn parse>( + iter: &mut ParseIter, +) -> Result, ParsingError> { + let mut parts = vec![]; + let mut literal = String::new(); + let mut part_index = 0; + while let Some((index, c)) = iter.next() { + if c == '%' { + if let Some(&(_, second)) = iter.peek() { + if second == '%' { + iter.next().unwrap(); + literal.push('%'); + continue; } else { - Ok((first_char, chars.as_str())) + if !literal.is_empty() { + parts.push(( + part_index, + CFormatPart::Literal(std::mem::take(&mut literal)), + )); + } + let spec = CFormatSpec::parse(iter)?; + parts.push((index, CFormatPart::Spec(spec))); + if let Some(&(index, _)) = iter.peek() { + part_index = index; + } + } + } else { + return Err((CFormatErrorType::IncompleteFormat, index + 1)); + } + } else { + literal.push(c); + } + } + if !literal.is_empty() { + parts.push((part_index, CFormatPart::Literal(literal))); + } + Ok(parts) +} + +fn parse_quantity>( + iter: &mut ParseIter, +) -> Result, ParsingError> { + if let Some(&(_, c)) = iter.peek() { + if c == '*' { + return Ok(Some(CFormatQuantity::FromValuesTuple)); + } + if let Some(i) = c.to_digit(10) { + let mut num = i as isize; + iter.next().unwrap(); + while let Some(&(index, c)) = iter.peek() { + if let Some(i) = c.to_digit(10) { + num = num + .checked_mul(10) + .and_then(|num| num.checked_add(i as isize)) + .ok_or((CFormatErrorType::IntTooBig, index))?; + iter.next().unwrap(); + } else { + break; } } - None => Err(CFormatErrorType::IncompleteFormat), - } - } else { - Ok((first_char, chars.as_str())) - } -} - -fn parse_literal(text: &str) -> Result<(CFormatPart, &str, usize), ParsingError> { - let mut cur_text = text; - let mut result_string = String::new(); - let mut consumed = 0; - while !cur_text.is_empty() { - match parse_literal_single(cur_text) { - Ok((next_char, remaining)) => { - result_string.push(next_char); - consumed += 1; - cur_text = remaining; - } - Err(err) => { - return if !result_string.is_empty() { - Ok((CFormatPart::Literal(result_string), cur_text, consumed)) - } else { - Err((err, consumed)) - }; - } + return Ok(Some(CFormatQuantity::Amount(num as usize))); } } - Ok(( - CFormatPart::Literal(result_string), - "", - text.chars().count(), - )) + Ok(None) } -fn parse_text_inside_parentheses(text: &str) -> Option<(String, &str)> { - let mut counter = 1; - let mut chars = text.chars(); +fn parse_precision>( + iter: &mut ParseIter, +) -> Result, ParsingError> { + if let Some(&(_, c)) = iter.peek() { + if c == '.' { + iter.next().unwrap(); + return parse_quantity(iter); + } + } + Ok(None) +} + +fn parse_text_inside_parentheses>( + iter: &mut ParseIter, +) -> Option { + let mut counter: i32 = 1; let mut contained_text = String::new(); - while counter > 0 { - let c = chars.next()?; + loop { + let (_, c) = iter.next()?; match c { '(' => { counter += 1; @@ -667,206 +971,106 @@ fn parse_text_inside_parentheses(text: &str) -> Option<(String, &str)> { if counter > 0 { contained_text.push(c); + } else { + break; } } - Some((contained_text, chars.as_str())) + Some(contained_text) } -fn parse_spec_mapping_key(text: &str) -> Result<(Option, &str), CFormatErrorType> { - let mut chars = text.chars(); - - let next_char = chars.next(); - if next_char == Some('(') { - match parse_text_inside_parentheses(chars.as_str()) { - Some((key, remaining_text)) => Ok((Some(key), remaining_text)), - None => Err(CFormatErrorType::UnmatchedKeyParentheses), +fn parse_spec_mapping_key>( + iter: &mut ParseIter, +) -> Result, ParsingError> { + if let Some(&(index, c)) = iter.peek() { + if c == '(' { + iter.next().unwrap(); + return match parse_text_inside_parentheses(iter) { + Some(key) => Ok(Some(key)), + None => Err((CFormatErrorType::UnmatchedKeyParentheses, index)), + }; } - } else { - Ok((None, text)) } + Ok(None) } -fn parse_flag_single(text: &str) -> (Option, &str) { - let mut chars = text.chars(); - match chars.next() { - Some('#') => (Some(CConversionFlags::ALTERNATE_FORM), chars.as_str()), - Some('0') => (Some(CConversionFlags::ZERO_PAD), chars.as_str()), - Some('-') => (Some(CConversionFlags::LEFT_ADJUST), chars.as_str()), - Some(' ') => (Some(CConversionFlags::BLANK_SIGN), chars.as_str()), - Some('+') => (Some(CConversionFlags::SIGN_CHAR), chars.as_str()), - _ => (None, text), - } -} - -fn parse_flags(text: &str) -> (CConversionFlags, &str) { +fn parse_flags>(iter: &mut ParseIter) -> CConversionFlags { let mut flags = CConversionFlags::empty(); - let mut cur_text = text; - while !cur_text.is_empty() { - match parse_flag_single(cur_text) { - (Some(flag), text) => { - flags |= flag; - cur_text = text; - } + while let Some(&(_, c)) = iter.peek() { + let flag = match c { + '#' => CConversionFlags::ALTERNATE_FORM, + '0' => CConversionFlags::ZERO_PAD, + '-' => CConversionFlags::LEFT_ADJUST, + ' ' => CConversionFlags::BLANK_SIGN, + '+' => CConversionFlags::SIGN_CHAR, + _ => break, + }; + iter.next().unwrap(); + flags |= flag; + } + flags +} - (None, text) => { - return (flags, text); - } +fn consume_length>(iter: &mut ParseIter) { + match iter.peek().map(|x| x.1) { + Some('h') | Some('l') | Some('L') => { + iter.next().unwrap(); } - } - - (flags, "") -} - -fn consume_length(text: &str) -> &str { - let mut chars = text.chars(); - match chars.next() { - Some('h') | Some('l') | Some('L') => chars.as_str(), - _ => text, + _ => {} } } -fn parse_format_type(text: &str) -> Result<(CFormatType, &str, char), CFormatErrorType> { +fn parse_format_type>( + iter: &mut ParseIter, +) -> Result<(CFormatType, char), ParsingError> { use CFloatType::*; use CFormatCase::{Lowercase, Uppercase}; use CNumberType::*; - let mut chars = text.chars(); - let next_char = chars.next(); - match next_char { - Some('d') | Some('i') | Some('u') => Ok(( - CFormatType::Number(Decimal), - chars.as_str(), - next_char.unwrap(), - )), - Some('o') => Ok(( - CFormatType::Number(Octal), - chars.as_str(), - next_char.unwrap(), - )), - Some('x') => Ok(( - CFormatType::Number(Hex(Lowercase)), - chars.as_str(), - next_char.unwrap(), - )), - Some('X') => Ok(( - CFormatType::Number(Hex(Uppercase)), - chars.as_str(), - next_char.unwrap(), - )), - Some('e') => Ok(( - CFormatType::Float(Exponent(Lowercase)), - chars.as_str(), - next_char.unwrap(), - )), - Some('E') => Ok(( - CFormatType::Float(Exponent(Uppercase)), - chars.as_str(), - next_char.unwrap(), - )), - Some('f') => Ok(( - CFormatType::Float(PointDecimal), - chars.as_str(), - next_char.unwrap(), - )), - Some('F') => Ok(( - CFormatType::Float(PointDecimal), - chars.as_str(), - next_char.unwrap(), - )), - Some('g') => Ok(( - CFormatType::Float(General(Lowercase)), - text, - next_char.unwrap(), - )), - Some('G') => Ok(( - CFormatType::Float(General(Uppercase)), - text, - next_char.unwrap(), - )), - Some('c') => Ok((CFormatType::Character, chars.as_str(), next_char.unwrap())), - Some('r') => Ok(( - CFormatType::String(CFormatPreconversor::Repr), - chars.as_str(), - next_char.unwrap(), - )), - Some('s') => Ok(( - CFormatType::String(CFormatPreconversor::Str), - 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(), - next_char.unwrap(), - )), - Some(c) => Err(CFormatErrorType::UnsupportedFormatChar(c)), - None => Err(CFormatErrorType::IncompleteFormat), // should not happen because it is handled earlier in the parsing - } -} - -fn calc_consumed(a: &str, b: &str) -> usize { - a.chars().count() - b.chars().count() + let (index, c) = match iter.next() { + Some(c) => c, + None => { + return Err(( + CFormatErrorType::IncompleteFormat, + iter.peek().map(|x| x.0).unwrap_or(0), + )); + } + }; + let format_type = match c { + 'd' | 'i' | 'u' => CFormatType::Number(Decimal), + 'o' => CFormatType::Number(Octal), + 'x' => CFormatType::Number(Hex(Lowercase)), + 'X' => CFormatType::Number(Hex(Uppercase)), + 'e' => CFormatType::Float(Exponent(Lowercase)), + 'E' => CFormatType::Float(Exponent(Uppercase)), + 'f' => CFormatType::Float(PointDecimal), + 'F' => CFormatType::Float(PointDecimal), + //TODO: Same as "e" if exponent is greater than -4 or less than precision, "f" otherwise. + 'g' => CFormatType::Float(General(Lowercase)), + //TODO: Same as "E" if exponent is greater than -4 or less than precision, "F" otherwise. + 'G' => CFormatType::Float(General(Uppercase)), + 'c' => CFormatType::Character, + 'r' => CFormatType::String(CFormatPreconversor::Repr), + 's' => CFormatType::String(CFormatPreconversor::Str), + 'b' => CFormatType::String(CFormatPreconversor::Bytes), + 'a' => CFormatType::String(CFormatPreconversor::Ascii), + _ => return Err((CFormatErrorType::UnsupportedFormatChar(c), index)), + }; + Ok((format_type, c)) } impl FromStr for CFormatSpec { type Err = ParsingError; fn from_str(text: &str) -> Result { - let mut chars = text.chars(); - if chars.next() != Some('%') { + let mut chars = text.chars().enumerate().peekable(); + if chars.next().map(|x| x.1) != Some('%') { return Err((CFormatErrorType::MissingModuloSign, 1)); } - let after_modulo_sign = chars.as_str(); - let (mapping_key, after_mapping_key) = parse_spec_mapping_key(after_modulo_sign) - .map_err(|err| (err, calc_consumed(text, after_modulo_sign)))?; - let (flags, after_flags) = parse_flags(after_mapping_key); - let (width, after_width) = - parse_quantity(after_flags).map_err(|err| (err, calc_consumed(text, after_flags)))?; - let (precision, after_precision) = - parse_precision(after_width).map_err(|err| (err, calc_consumed(text, after_width)))?; - // A length modifier (h, l, or L) may be present, - // but is ignored as it is not necessary for Python – so e.g. %ld is identical to %d. - let after_length = consume_length(after_precision); - let (format_type, remaining_text, format_char) = parse_format_type(after_length) - .map_err(|err| (err, calc_consumed(text, after_length)))?; - - // apply default precision for float types - let precision = match precision { - Some(precision) => Some(precision), - None => match format_type { - CFormatType::Float(_) => Some(CFormatQuantity::Amount(6)), - _ => None, - }, - }; - - Ok(CFormatSpec { - mapping_key, - flags, - min_field_width: width, - precision, - format_type, - format_char, - chars_consumed: calc_consumed(text, remaining_text), - }) + CFormatSpec::parse(&mut chars) } } -fn parse_specifier(text: &str) -> Result<(CFormatPart, &str, usize), ParsingError> { - let spec = text.parse::()?; - let chars_consumed = spec.chars_consumed; - Ok(( - CFormatPart::Spec(spec), - &text[chars_consumed..], - chars_consumed, - )) -} - #[cfg(test)] mod tests { use super::*; @@ -909,7 +1113,6 @@ mod tests { mapping_key: Some("amount".to_owned()), format_type: CFormatType::Number(CNumberType::Decimal), format_char: 'd', - chars_consumed: 10, min_field_width: None, precision: None, flags: CConversionFlags::empty(), @@ -920,7 +1123,6 @@ mod tests { mapping_key: Some("m((u(((l((((ti))))p)))l))e".to_owned()), format_type: CFormatType::Number(CNumberType::Decimal), format_char: 'd', - chars_consumed: 30, min_field_width: None, precision: None, flags: CConversionFlags::empty(), @@ -969,7 +1171,6 @@ mod tests { let expected = Ok(CFormatSpec { format_type: CFormatType::Number(CNumberType::Decimal), format_char: 'd', - chars_consumed: 17, min_field_width: Some(CFormatQuantity::Amount(10)), precision: None, mapping_key: None, @@ -1077,7 +1278,7 @@ mod tests { assert_eq!( "%f".parse::() .unwrap() - .format_float(f64::from(1.2345)) + .format_float(1.2345) .ok(), Some("1.234500".to_owned()) ); @@ -1085,7 +1286,7 @@ mod tests { "%+f" .parse::() .unwrap() - .format_float(f64::from(1.2345)) + .format_float(1.2345) .ok(), Some("+1.234500".to_owned()) ); @@ -1093,21 +1294,21 @@ mod tests { "% f" .parse::() .unwrap() - .format_float(f64::from(1.2345)) + .format_float(1.2345) .ok(), Some(" 1.234500".to_owned()) ); assert_eq!( "%f".parse::() .unwrap() - .format_float(f64::from(-1.2345)) + .format_float(-1.2345) .ok(), Some("-1.234500".to_owned()) ); assert_eq!( "%f".parse::() .unwrap() - .format_float(f64::from(1.2345678901)) + .format_float(1.2345678901) .ok(), Some("1.234568".to_owned()) ); @@ -1124,7 +1325,6 @@ mod tests { CFormatPart::Spec(CFormatSpec { format_type: CFormatType::String(CFormatPreconversor::Str), format_char: 's', - chars_consumed: 2, mapping_key: None, min_field_width: None, precision: None, @@ -1137,7 +1337,6 @@ mod tests { CFormatPart::Spec(CFormatSpec { format_type: CFormatType::Number(CNumberType::Decimal), format_char: 'd', - chars_consumed: 2, mapping_key: None, min_field_width: None, precision: None,