mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Reimpl cformat to suit for bytes
This commit is contained in:
1 parent
249f121896
commit
d4af9d0bfe
5 files changed
+483
-285
No files matched your search
@@ -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
|
||||
|
||||
@@ -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__")]
|
||||
|
||||
@@ -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__")]
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user