Skip to content

Commit 481b044

Browse files
committed
Support modulo formatting of numbers and string types
1 parent fe6c148 commit 481b044

File tree

2 files changed

+243
-5
lines changed

2 files changed

+243
-5
lines changed

vm/src/cformat.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,130 @@ pub struct CFormatSpec {
7979
pub format_char: char,
8080
}
8181

82+
impl CFormatSpec {
83+
fn compute_fill_string(fill_char: char, fill_chars_needed: usize) -> String {
84+
(0..fill_chars_needed)
85+
.map(|_| fill_char)
86+
.collect::<String>()
87+
}
88+
89+
pub fn fill_string(&self, string: String, fill_char: char, num_prefix_chars: Option<usize>) -> String {
90+
let mut num_chars = string.len();
91+
if let Some(num_prefix_chars) = num_prefix_chars {
92+
num_chars = num_chars + num_prefix_chars;
93+
}
94+
let num_chars = num_chars;
95+
96+
let width = match self.min_field_width {
97+
Some(width) => cmp::max(width, num_chars),
98+
None => num_chars,
99+
};
100+
let fill_chars_needed = width - num_chars;
101+
let fill_string = CFormatSpec::compute_fill_string(fill_char, fill_chars_needed);
102+
103+
if !fill_string.is_empty() {
104+
if self.flags.contains(CConversionFlags::LEFT_ADJUST) {
105+
format!("{}{}", string, fill_string)
106+
} else {
107+
format!("{}{}", fill_string, string)
108+
}
109+
} else {
110+
string
111+
}
112+
}
113+
114+
pub fn format_string(&self, string: String) -> String {
115+
let mut string = string;
116+
// truncate if needed
117+
if let Some(precision) = self.precision {
118+
if string.len() > precision {
119+
string.truncate(precision);
120+
}
121+
}
122+
self.fill_string(string, ' ', None)
123+
}
124+
125+
pub fn format_number(&self, num: &BigInt) -> String {
126+
use CFormatCase::{Lowercase, Uppercase};
127+
use CNumberType::*;
128+
let fill_char = if self.flags.contains(CConversionFlags::ZERO_PAD) {
129+
'0'
130+
} else {
131+
' '
132+
};
133+
let magnitude = num.abs();
134+
let prefix = if self.flags.contains(CConversionFlags::ALTERNATE_FORM) {
135+
match self.format_type {
136+
CFormatType::Number(Octal) => "0o",
137+
CFormatType::Number(Hex(Lowercase)) => "0x",
138+
CFormatType::Number(Hex(Uppercase)) => "0X",
139+
_ => "",
140+
}
141+
} else {
142+
""
143+
};
144+
145+
let magnitude_string: String = match self.format_type {
146+
CFormatType::Number(Decimal) => magnitude.to_str_radix(10),
147+
CFormatType::Number(Octal) => magnitude.to_str_radix(8),
148+
CFormatType::Number(Hex(Lowercase)) => magnitude.to_str_radix(16),
149+
CFormatType::Number(Hex(Uppercase)) => {
150+
let mut result = magnitude.to_str_radix(16);
151+
result.make_ascii_uppercase();
152+
result
153+
}
154+
_ => unreachable!(), // Should not happen because caller has to make sure that this is a number
155+
};
156+
157+
158+
let sign_string = match num.sign() {
159+
Sign::Minus => "-",
160+
_ => {
161+
if self.flags.contains(CConversionFlags::SIGN_CHAR) {
162+
"+"
163+
} else if self.flags.contains(CConversionFlags::BLANK_SIGN) {
164+
" "
165+
} else {
166+
""
167+
}
168+
}
169+
};
170+
171+
172+
let prefix = format!("{}{}", sign_string, prefix);
173+
let magnitude_filled_string = if !self.flags.contains(CConversionFlags::LEFT_ADJUST) {
174+
// '-' flags overrides '0' flag
175+
self.fill_string(magnitude_string, fill_char, Some(prefix.len()))
176+
} else {
177+
magnitude_string
178+
};
179+
180+
format!("{}{}", prefix, magnitude_filled_string)
181+
}
182+
}
183+
82184
#[derive(Debug, PartialEq)]
83185
pub enum CFormatPart {
84186
Literal(String),
85187
Spec(CFormatSpec),
86188
}
87189

190+
impl CFormatPart {
191+
pub fn is_specifier(&self) -> bool {
192+
match self {
193+
CFormatPart::Spec(_) => true,
194+
_ => false,
195+
}
196+
}
197+
198+
pub fn has_key(&self) -> bool {
199+
match self {
200+
CFormatPart::Spec(s) => s.mapping_key.is_some(),
201+
_ => false,
202+
}
203+
}
204+
}
205+
88206
#[derive(Debug, PartialEq)]
89207
pub struct CFormatString {
90208
pub format_parts: Vec<(usize, CFormatPart)>,

vm/src/obj/objstr.rs

Lines changed: 125 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ use unicode_casing::CharExt;
1111
use unicode_segmentation::UnicodeSegmentation;
1212
use unicode_xid::UnicodeXID;
1313

14-
use crate::cformat::{CFormatString, CFormatErrorType};
14+
use crate::cformat::{
15+
CFormatErrorType, CFormatPart, CFormatPreconversor, CFormatSpec, CFormatString, CFormatType,
16+
CNumberType,
17+
};
1518
use crate::format::{FormatParseError, FormatPart, FormatPreconversor, FormatString};
1619
use crate::function::{single_or_tuple_any, OptionalArg, PyFuncArgs};
1720
use crate::pyhash;
@@ -27,6 +30,7 @@ use super::objint::{self, PyInt};
2730
use super::objnone::PyNone;
2831
use super::objsequence::PySliceableSequence;
2932
use super::objslice::PySlice;
33+
use super::objtuple;
3034
use super::objtype::{self, PyClassRef};
3135

3236
use unicode_categories::UnicodeCategories;
@@ -434,7 +438,7 @@ impl PyString {
434438
fn modulo(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult {
435439
let format_string_text = &self.value;
436440
match CFormatString::from_str(format_string_text) {
437-
Ok(format_string) => perform_clike_format(vm, format_string, values.clone()),
441+
Ok(format_string) => do_cformat(vm, format_string, values.clone()),
438442
Err(err) => match err.typ {
439443
CFormatErrorType::UnsupportedFormatChar(c) => Err(vm.new_value_error(format!(
440444
"unsupported format character '{}' ({:#x}) at index {}",
@@ -1076,6 +1080,10 @@ fn count_char(s: &str, c: char) -> usize {
10761080
s.chars().filter(|x| *x == c).count()
10771081
}
10781082

1083+
fn call_getitem(vm: &VirtualMachine, container: &PyObjectRef, key: &PyObjectRef) -> PyResult {
1084+
vm.call_method(container, "__getitem__", vec![key.clone()])
1085+
}
1086+
10791087
fn call_object_format(vm: &VirtualMachine, argument: PyObjectRef, format_spec: &str) -> PyResult {
10801088
let (preconversor, new_format_spec) = FormatPreconversor::parse_and_consume(format_spec);
10811089
let argument = match preconversor {
@@ -1095,13 +1103,125 @@ fn call_object_format(vm: &VirtualMachine, argument: PyObjectRef, format_spec: &
10951103
Ok(result)
10961104
}
10971105

1098-
fn perform_clike_format(
1106+
fn do_cformat_specifier(
1107+
vm: &VirtualMachine,
1108+
format_spec: &CFormatSpec,
1109+
obj: PyObjectRef,
1110+
) -> Result<String, PyObjectRef> {
1111+
use CNumberType::*;
1112+
// do the formatting by type
1113+
let format_type = &format_spec.format_type;
1114+
1115+
match format_type {
1116+
CFormatType::String(preconversor) => {
1117+
let result = match preconversor {
1118+
CFormatPreconversor::Str => vm.call_method(&obj.clone(), "__str__", vec![])?,
1119+
CFormatPreconversor::Repr => vm.call_method(&obj.clone(), "__repr__", vec![])?,
1120+
CFormatPreconversor::Ascii => vm.call_method(&obj.clone(), "__repr__", vec![])?,
1121+
};
1122+
Ok(format_spec.format_string(get_value(&result)))
1123+
}
1124+
CFormatType::Number(_) => {
1125+
if !objtype::isinstance(&obj, &vm.ctx.int_type()) {
1126+
let required_type_string = match format_type {
1127+
CFormatType::Number(Decimal) => "a number",
1128+
CFormatType::Number(_) => "an integer",
1129+
_ => unreachable!(),
1130+
};
1131+
return Err(vm.new_type_error(format!(
1132+
"%{} format: {} is required, not {}",
1133+
format_spec.format_char,
1134+
required_type_string,
1135+
obj.class()
1136+
)));
1137+
}
1138+
Ok(format_spec.format_number(objint::get_value(&obj)))
1139+
}
1140+
_ => Err(vm.new_not_implemented_error(format!(
1141+
"Not yet implemented for %{}",
1142+
format_spec.format_char
1143+
))),
1144+
}
1145+
}
1146+
1147+
fn do_cformat(
10991148
vm: &VirtualMachine,
11001149
format_string: CFormatString,
11011150
values_obj: PyObjectRef,
11021151
) -> PyResult {
1103-
// TODO
1104-
Err(vm.new_type_error("Not implemented".to_string()))
1152+
let mut final_string = String::new();
1153+
let num_specifiers = format_string
1154+
.format_parts
1155+
.iter()
1156+
.filter(|(_, part)| CFormatPart::is_specifier(part))
1157+
.count();
1158+
let mapping_required = format_string
1159+
.format_parts
1160+
.iter()
1161+
.any(|(_, part)| CFormatPart::has_key(part))
1162+
&& format_string
1163+
.format_parts
1164+
.iter()
1165+
.filter(|(_, part)| CFormatPart::is_specifier(part))
1166+
.all(|(_, part)| CFormatPart::has_key(part));
1167+
1168+
let values_obj = if mapping_required {
1169+
if !objtype::isinstance(&values_obj, &vm.ctx.dict_type()) {
1170+
return Err(vm.new_type_error("format requires a mapping".to_string()));
1171+
}
1172+
values_obj
1173+
} else {
1174+
// check for only literal parts, in which case only empty tuple is allowed
1175+
if 0 == num_specifiers
1176+
&& (!objtype::isinstance(&values_obj, &vm.ctx.tuple_type())
1177+
|| !objtuple::get_value(&values_obj).is_empty())
1178+
{
1179+
return Err(vm.new_type_error(
1180+
"not all arguments converted during string formatting".to_string(),
1181+
));
1182+
}
1183+
1184+
// convert `values_obj` to a new tuple if it's not a tuple
1185+
if !objtype::isinstance(&values_obj, &vm.ctx.tuple_type()) {
1186+
vm.ctx.new_tuple(vec![values_obj.clone()])
1187+
} else {
1188+
values_obj.clone()
1189+
}
1190+
1191+
// if values.len() > num_specifiers {
1192+
// return Err(vm.new_type_error("not all arguments converted during string formatting".to_string()));
1193+
// } else if values.len() < num_specifiers {
1194+
// return Err(vm.new_type_error("not enough arguments for format string".to_string()));
1195+
// }
1196+
};
1197+
1198+
let mut auto_index: usize = 0;
1199+
for (_, part) in &format_string.format_parts {
1200+
let result_string: String = match part {
1201+
CFormatPart::Spec(format_spec) => {
1202+
// try to get the object
1203+
let obj: PyObjectRef = match &format_spec.mapping_key {
1204+
Some(key) => {
1205+
// TODO: change the KeyError message to match the one in cpython
1206+
call_getitem(vm, &values_obj, &vm.ctx.new_str(key.to_string()))?
1207+
}
1208+
None => {
1209+
// TODO: translate exception from IndexError to TypeError
1210+
let obj = call_getitem(vm, &values_obj, &vm.ctx.new_int(auto_index))?;
1211+
auto_index += 1;
1212+
1213+
obj
1214+
}
1215+
};
1216+
1217+
do_cformat_specifier(vm, &format_spec, obj)
1218+
}
1219+
CFormatPart::Literal(literal) => Ok(literal.clone()),
1220+
}?;
1221+
final_string.push_str(&result_string);
1222+
}
1223+
1224+
Ok(vm.ctx.new_str(final_string))
11051225
}
11061226

11071227
fn perform_format(

0 commit comments

Comments
 (0)