Reimpl cformat to suit for bytes

This commit is contained in:
Kangzhi Shi committed 2020-11-01 20:04:41 +02:00
1 parent 249f121896
commit d4af9d0bfe
5 files changed
+483 -285

No files matched your search

-4
View File
@@ -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
+1 -1
View File
@@ -608,7 +608,7 @@ impl PyByteArray {
#[pymethod(name = "__mod__")]
fn modulo(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyByteArray> {
let formatted = self.borrow_value().cformat(values, vm)?;
Ok(formatted.into_bytes().into())
Ok(formatted.into())
}
#[pymethod(name = "__rmod__")]
+2 -2
View File
@@ -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<PyBytes> {
let formatted = self.inner.cformat(values, vm)?;
Ok(vm.ctx.new_bytes(formatted.into_bytes()))
Ok(formatted.into())
}
#[pymethod(name = "__rmod__")]
+5 -2
View File
@@ -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<String> {
self.elements.py_cformat(values, vm)
pub fn cformat(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
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<u8> {
+475 -276
View File
@@ -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<CFormatQuantity>,
format_type: CFormatType,
format_char: char,
chars_consumed: usize,
// chars_consumed: usize,
}
impl CFormatSpec {
fn parse<I: Iterator<Item = char>>(iter: &mut ParseIter<I>) -> Result<Self, ParsingError> {
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<u8> {
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<Vec<u8>> {
// 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<String> {
// 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<I: Iterator<Item = char>>(
iter: &mut ParseIter<I>,
) -> Result<Self, CFormatError> {
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<Self, CFormatError> {
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<Vec<u8>> {
fn try_update_quantity_from_tuple(
vm: &VirtualMachine,
elements: &mut dyn Iterator<Item = PyObjectRef>,
q: &mut Option<CFormatQuantity>,
mut tuple_index: usize,
) -> PyResult<usize> {
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::<isize>(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<u8> = 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<Self, Self::Err> {
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<I: Iterator<Item = char>>(
iter: &mut ParseIter<I>,
) -> Result<Self, CFormatError> {
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<CFormatQuantity>, &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::<isize>()
.map_err(|_| CFormatErrorType::IntTooBig)? as usize;
(Some(CFormatQuantity::Amount(q)), &text[num_digits..])
};
Ok(ret)
}
type ParseIter<I> = Peekable<Enumerate<I>>;
fn parse_precision(text: &str) -> Result<(Option<CFormatQuantity>, &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<I: Iterator<Item = char>>(
iter: &mut ParseIter<I>,
) -> Result<Vec<(usize, CFormatPart)>, 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<I: Iterator<Item = char>>(
iter: &mut ParseIter<I>,
) -> Result<Option<CFormatQuantity>, 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<I: Iterator<Item = char>>(
iter: &mut ParseIter<I>,
) -> Result<Option<CFormatQuantity>, ParsingError> {
if let Some(&(_, c)) = iter.peek() {
if c == '.' {
iter.next().unwrap();
return parse_quantity(iter);
}
}
Ok(None)
}
fn parse_text_inside_parentheses<I: Iterator<Item = char>>(
iter: &mut ParseIter<I>,
) -> Option<String> {
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<String>, &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<I: Iterator<Item = char>>(
iter: &mut ParseIter<I>,
) -> Result<Option<String>, 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<CConversionFlags>, &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<I: Iterator<Item = char>>(iter: &mut ParseIter<I>) -> 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<I: Iterator<Item = char>>(iter: &mut ParseIter<I>) {
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<I: Iterator<Item = char>>(
iter: &mut ParseIter<I>,
) -> 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<Self, Self::Err> {
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::<CFormatSpec>()?;
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::<CFormatSpec>()
.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::<CFormatSpec>()
.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::<CFormatSpec>()
.unwrap()
.format_float(f64::from(1.2345))
.format_float(1.2345)
.ok(),
Some(" 1.234500".to_owned())
);
assert_eq!(
"%f".parse::<CFormatSpec>()
.unwrap()
.format_float(f64::from(-1.2345))
.format_float(-1.2345)
.ok(),
Some("-1.234500".to_owned())
);
assert_eq!(
"%f".parse::<CFormatSpec>()
.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,