Skip to content

Commit e374aff

Browse files
committed
Support '*' for width and precsision for printf-style formatting
1 parent d5ffece commit e374aff

File tree

3 files changed

+85
-19
lines changed

3 files changed

+85
-19
lines changed

vm/src/cformat.rs

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::format::{parse_number, parse_precision};
1+
use crate::format::get_num_digits;
22
/// Implementation of Printf-Style string formatting
33
/// [https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting]
44
use num_bigint::{BigInt, Sign};
@@ -70,12 +70,18 @@ bitflags! {
7070
}
7171
}
7272

73+
#[derive(Debug, PartialEq)]
74+
pub enum CFormatQuantity {
75+
Amount(usize),
76+
FromValuesTuple,
77+
}
78+
7379
#[derive(Debug, PartialEq)]
7480
pub struct CFormatSpec {
7581
pub mapping_key: Option<String>,
7682
pub flags: CConversionFlags,
77-
pub min_field_width: Option<usize>,
78-
pub precision: Option<usize>,
83+
pub min_field_width: Option<CFormatQuantity>,
84+
pub precision: Option<CFormatQuantity>,
7985
pub format_type: CFormatType,
8086
pub format_char: char,
8187
chars_consumed: usize,
@@ -101,8 +107,8 @@ impl CFormatSpec {
101107
let num_chars = num_chars;
102108

103109
let width = match self.min_field_width {
104-
Some(width) => cmp::max(width, num_chars),
105-
None => num_chars,
110+
Some(CFormatQuantity::Amount(width)) => cmp::max(width, num_chars),
111+
_ => num_chars,
106112
};
107113
let fill_chars_needed = width - num_chars;
108114
let fill_string = CFormatSpec::compute_fill_string(fill_char, fill_chars_needed);
@@ -121,7 +127,7 @@ impl CFormatSpec {
121127
pub fn format_string(&self, string: String) -> String {
122128
let mut string = string;
123129
// truncate if needed
124-
if let Some(precision) = self.precision {
130+
if let Some(CFormatQuantity::Amount(precision)) = self.precision {
125131
if string.len() > precision {
126132
string.truncate(precision);
127133
}
@@ -242,6 +248,32 @@ impl FromStr for CFormatString {
242248
}
243249
}
244250

251+
fn parse_quantity(text: &str) -> (Option<CFormatQuantity>, &str) {
252+
let num_digits: usize = get_num_digits(text);
253+
if num_digits == 0 {
254+
let mut chars = text.chars();
255+
return match chars.next() {
256+
Some('*') => (Some(CFormatQuantity::FromValuesTuple), chars.as_str()),
257+
_ => (None, text),
258+
};
259+
}
260+
// This should never fail
261+
(
262+
Some(CFormatQuantity::Amount(
263+
text[..num_digits].parse::<usize>().unwrap(),
264+
)),
265+
&text[num_digits..],
266+
)
267+
}
268+
269+
fn parse_precision(text: &str) -> (Option<CFormatQuantity>, &str) {
270+
let mut chars = text.chars();
271+
match chars.next() {
272+
Some('.') => parse_quantity(&chars.as_str()),
273+
_ => (None, text),
274+
}
275+
}
276+
245277
fn parse_literal_single(text: &str) -> Result<(char, &str), CFormatErrorType> {
246278
let mut chars = text.chars();
247279
// TODO get rid of the unwrap
@@ -445,7 +477,7 @@ impl FromStr for CFormatSpec {
445477
let (mapping_key, after_mapping_key) =
446478
parse_spec_mapping_key(chars.as_str()).map_err(|err| (err, consumed))?;
447479
let (flags, after_flags) = parse_flags(after_mapping_key);
448-
let (width, after_width) = parse_number(after_flags);
480+
let (width, after_width) = parse_quantity(after_flags);
449481
let (precision, after_precision) = parse_precision(after_width);
450482
// A length modifier (h, l, or L) may be present,
451483
// but is ignored as it is not necessary for Python – so e.g. %ld is identical to %d.
@@ -459,7 +491,7 @@ impl FromStr for CFormatSpec {
459491
let precision = match precision {
460492
Some(precision) => Some(precision),
461493
None => match format_type {
462-
CFormatType::Float(_) => Some(6),
494+
CFormatType::Float(_) => Some(CFormatQuantity::Amount(6)),
463495
_ => None,
464496
},
465497
};
@@ -575,7 +607,7 @@ mod tests {
575607
format_type: CFormatType::Number(CNumberType::Decimal),
576608
format_char: 'd',
577609
chars_consumed: 17,
578-
min_field_width: Some(10),
610+
min_field_width: Some(CFormatQuantity::Amount(10)),
579611
precision: None,
580612
mapping_key: None,
581613
flags: CConversionFlags::all(),

vm/src/format.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ pub struct FormatSpec {
110110
format_type: Option<FormatType>,
111111
}
112112

113-
fn get_num_digits(text: &str) -> usize {
113+
pub fn get_num_digits(text: &str) -> usize {
114114
for (index, character) in text.char_indices() {
115115
if !character.is_digit(10) {
116116
return index;
@@ -151,7 +151,7 @@ fn parse_fill_and_align(text: &str) -> (Option<char>, Option<FormatAlign>, &str)
151151
}
152152
}
153153

154-
pub fn parse_number(text: &str) -> (Option<usize>, &str) {
154+
fn parse_number(text: &str) -> (Option<usize>, &str) {
155155
let num_digits: usize = get_num_digits(text);
156156
if num_digits == 0 {
157157
return (None, text);
@@ -189,7 +189,7 @@ fn parse_zero(text: &str) -> &str {
189189
}
190190
}
191191

192-
pub fn parse_precision(text: &str) -> (Option<usize>, &str) {
192+
fn parse_precision(text: &str) -> (Option<usize>, &str) {
193193
let mut chars = text.chars();
194194
match chars.next() {
195195
Some('.') => {

vm/src/obj/objstr.rs

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use unicode_segmentation::UnicodeSegmentation;
1212
use unicode_xid::UnicodeXID;
1313

1414
use crate::cformat::{
15-
CFormatErrorType, CFormatPart, CFormatPreconversor, CFormatSpec, CFormatString, CFormatType,
16-
CNumberType,
15+
CFormatErrorType, CFormatPart, CFormatPreconversor, CFormatQuantity, CFormatSpec,
16+
CFormatString, CFormatType, CNumberType,
1717
};
1818
use crate::format::{FormatParseError, FormatPart, FormatPreconversor, FormatString};
1919
use crate::function::{single_or_tuple_any, OptionalArg, PyFuncArgs};
@@ -1146,7 +1146,7 @@ fn do_cformat_specifier(
11461146

11471147
fn do_cformat(
11481148
vm: &VirtualMachine,
1149-
format_string: CFormatString,
1149+
mut format_string: CFormatString,
11501150
values_obj: PyObjectRef,
11511151
) -> PyResult {
11521152
let mut final_string = String::new();
@@ -1169,7 +1169,7 @@ fn do_cformat(
11691169
if !objtype::isinstance(&values_obj, &vm.ctx.dict_type()) {
11701170
return Err(vm.new_type_error("format requires a mapping".to_string()));
11711171
}
1172-
values_obj
1172+
values_obj.clone()
11731173
} else {
11741174
// check for only literal parts, in which case only empty tuple is allowed
11751175
if 0 == num_specifiers
@@ -1190,7 +1190,7 @@ fn do_cformat(
11901190
};
11911191

11921192
let mut auto_index: usize = 0;
1193-
for (_, part) in &format_string.format_parts {
1193+
for (_, part) in &mut format_string.format_parts {
11941194
let result_string: String = match part {
11951195
CFormatPart::Spec(format_spec) => {
11961196
// try to get the object
@@ -1200,8 +1200,42 @@ fn do_cformat(
12001200
call_getitem(vm, &values_obj, &vm.ctx.new_str(key.to_string()))?
12011201
}
12021202
None => {
1203-
let elements = objtuple::get_value(&values_obj);
1204-
let obj = match elements.into_iter().nth(auto_index) {
1203+
let mut elements = objtuple::get_value(&values_obj)
1204+
.into_iter()
1205+
.skip(auto_index);
1206+
1207+
let mut try_quantity_from_tuple = |q: &mut Option<CFormatQuantity>| {
1208+
match q {
1209+
Some(CFormatQuantity::FromValuesTuple) => {
1210+
match elements.next() {
1211+
Some(width_obj) => {
1212+
auto_index += 1;
1213+
if !objtype::isinstance(&width_obj, &vm.ctx.int_type())
1214+
{
1215+
Err(vm.new_type_error("* wants int".to_string()))
1216+
} else {
1217+
// TODO: handle errors when truncating BigInt to usize
1218+
*q = Some(CFormatQuantity::Amount(
1219+
objint::get_value(&width_obj)
1220+
.to_usize()
1221+
.unwrap(),
1222+
));
1223+
Ok(())
1224+
}
1225+
}
1226+
None => Err(vm.new_type_error(
1227+
"not enough arguments for format string".to_string(),
1228+
)),
1229+
}
1230+
}
1231+
_ => Ok(()),
1232+
}
1233+
};
1234+
1235+
try_quantity_from_tuple(&mut format_spec.min_field_width)?;
1236+
try_quantity_from_tuple(&mut format_spec.precision)?;
1237+
1238+
let obj = match elements.next() {
12051239
Some(obj) => Ok(obj),
12061240
None => Err(vm.new_type_error(
12071241
"not enough arguments for format string".to_string(),

0 commit comments

Comments
 (0)