1use itertools::Itertools;
7use num_traits::cast::ToPrimitive;
8
9use crate::{
10 AsObject, PyObject, PyObjectRef, PyResult, TryFromBorrowedObject, TryFromObject,
11 VirtualMachine,
12 builtins::{
13 PyBaseExceptionRef, PyByteArray, PyBytes, PyFloat, PyInt, PyStr,
14 int::check_int_to_str_digits, try_f64_to_bigint, tuple,
15 },
16 common::{
17 cformat::{
18 CCharacterType, CConversionFlags, CFormatBytes, CFormatConversion, CFormatPart,
19 CFormatPrecision, CFormatQuantity, CFormatSpec, CFormatSpecKeyed, CFormatType,
20 CFormatWtf8, CNumberType,
21 },
22 wtf8::{CodePoint, Wtf8, Wtf8Buf},
23 },
24 function::ArgIntoFloat,
25 protocol::{BufferFlags, PyBuffer},
26 stdlib::builtins,
27};
28
29fn format_decimal_object(
30 vm: &VirtualMachine,
31 spec: &CFormatSpec,
32 obj: &PyObject,
33) -> PyResult<String> {
34 let type_error = || {
35 vm.new_type_error(format!(
36 "%{} format: a real number is required, not {}",
37 spec.format_type.to_char(),
38 obj.class().slot_name()
39 ))
40 };
41 let i = obj
43 .number()
44 .int(vm)
45 .or_else(|| obj.try_index_opt(vm))
46 .ok_or_else(type_error)?
47 .map_err(|error| {
48 if error.fast_isinstance(vm.ctx.exceptions.type_error) {
49 type_error()
50 } else {
51 error
52 }
53 })?;
54 check_int_to_str_digits(i.as_bigint(), vm)?;
55 Ok(spec.format_number(i.as_bigint()))
56}
57
58fn spec_format_bytes(
59 vm: &VirtualMachine,
60 spec: &CFormatSpec,
61 obj: PyObjectRef,
62) -> PyResult<Vec<u8>> {
63 match &spec.format_type {
64 CFormatType::Unsupported { ch, index } => Err(vm.new_value_error(format!(
65 "unsupported format character '{}' ({:#x}) at index {index}",
66 ch.to_char_lossy(),
67 ch.to_u32(),
68 ))),
69 CFormatType::String(CFormatConversion::Repr | CFormatConversion::Ascii) => {
72 Ok(spec.format_bytes(builtins::ascii(obj, vm)?.as_bytes()))
73 }
74 CFormatType::Bytes | CFormatType::String(CFormatConversion::Str) => {
77 if let Some(bytes) = obj.downcast_ref::<PyBytes>() {
78 return Ok(spec.format_bytes(bytes.as_bytes()));
79 }
80 if let Some(bytearray) = obj.downcast_ref::<PyByteArray>() {
81 return Ok(spec.format_bytes(&bytearray.borrow_buf()));
82 }
83 if let Some(method) = vm.get_special_method(&obj, identifier!(vm, __bytes__))? {
84 let bytes = method.invoke((), vm)?;
85 let bytes = PyBytes::try_from_borrowed_object(vm, &bytes)?;
86 return Ok(spec.format_bytes(bytes.as_bytes()));
87 }
88 if obj.check_buffer() {
89 let buffer = PyBuffer::from_object(vm, &obj, BufferFlags::FULL_RO)?;
90 return Ok(buffer.contiguous_or_collect(|bytes| spec.format_bytes(bytes)));
91 }
92 let msg = format!(
93 "%b requires a bytes-like object, or an object that \
94 implements __bytes__, not '{}'",
95 obj.class().name()
96 );
97 Err(vm.new_type_error(msg))
98 }
99 CFormatType::Number(number_type) => match number_type {
100 CNumberType::DecimalD | CNumberType::DecimalI | CNumberType::DecimalU => {
101 if let Some(i) = obj.downcast_ref::<PyInt>() {
102 check_int_to_str_digits(i.as_bigint(), vm)?;
103 Ok(spec.format_number(i.as_bigint()).into_bytes())
104 } else if let Some(f) = obj.downcast_ref_if_exact::<PyFloat>(vm) {
105 let bigint = try_f64_to_bigint(f.to_f64(), vm)?;
106 check_int_to_str_digits(&bigint, vm)?;
107 Ok(spec.format_number(&bigint).into_bytes())
108 } else {
109 format_decimal_object(vm, spec, &obj).map(String::into_bytes)
110 }
111 }
112 _ => {
113 if let Some(i) = obj.downcast_ref::<PyInt>() {
116 Ok(spec.format_number(i.as_bigint()).into_bytes())
117 } else if let Some(int_result) = obj.try_index_opt(vm) {
118 let i = int_result?;
119 Ok(spec.format_number(i.as_bigint()).into_bytes())
120 } else {
121 Err(vm.new_type_error(format!(
122 "%{} format: an integer is required, not {}",
123 spec.format_type.to_char(),
124 obj.class().name()
125 )))
126 }
127 }
128 },
129 CFormatType::Float(_) => {
130 let class = obj.class().to_owned();
131 let value = ArgIntoFloat::try_from_object(vm, obj).map_err(|e| {
132 if e.fast_isinstance(vm.ctx.exceptions.type_error) {
133 vm.new_type_error(format!("float argument required, not {}", class.name()))
136 } else {
137 e
138 }
139 })?;
140 Ok(spec.format_float(value.into()).into_bytes())
141 }
142 CFormatType::Character(CCharacterType::Character) => {
143 if let Some(b) = obj.downcast_ref::<PyBytes>() {
146 if b.as_bytes().len() == 1 {
147 return Ok(spec.format_char(b.as_bytes()[0]));
148 }
149 } else if let Some(ba) = obj.downcast_ref::<PyByteArray>() {
150 let buf = ba.borrow_buf();
151 if buf.len() == 1 {
152 return Ok(spec.format_char(buf[0]));
153 }
154 }
155 let int = if let Some(i) = obj.downcast_ref::<PyInt>() {
156 i.to_owned()
157 } else if let Some(int_result) = obj.try_index_opt(vm) {
158 int_result?
159 } else {
160 let what = if let Some(b) = obj.downcast_ref::<PyBytes>() {
163 format!("a bytes object of length {}", b.as_bytes().len())
164 } else if let Some(ba) = obj.downcast_ref::<PyByteArray>() {
165 format!("a bytearray object of length {}", ba.borrow_buf().len())
166 } else {
167 obj.class().name().to_string()
168 };
169 return Err(vm.new_type_error(format!(
170 "%c requires an integer in range(256) or a single byte, not {what}"
171 )));
172 };
173 let ch = int
174 .try_to_primitive::<u8>(vm)
175 .map_err(|_| vm.new_overflow_error("%c arg not in range(256)"))?;
176 Ok(spec.format_char(ch))
177 }
178 }
179}
180
181fn spec_format_string(
182 vm: &VirtualMachine,
183 spec: &CFormatSpec,
184 obj: PyObjectRef,
185) -> PyResult<Wtf8Buf> {
186 match &spec.format_type {
187 CFormatType::Unsupported { ch, index } => Err(vm.new_value_error(format!(
188 "unsupported format character '{}' ({:#x}) at index {index}",
189 ch.to_char_lossy(),
190 ch.to_u32(),
191 ))),
192 CFormatType::String(conversion) => {
193 let result = match conversion {
194 CFormatConversion::Ascii => builtins::ascii(obj, vm)?.as_wtf8().to_owned(),
195 CFormatConversion::Str => obj.str(vm)?.as_wtf8().to_owned(),
196 CFormatConversion::Repr => obj.repr(vm)?.as_wtf8().to_owned(),
197 };
198 Ok(spec.format_string(result))
199 }
200 CFormatType::Bytes => {
201 unreachable!("%b cannot be parsed in a str format string")
203 }
204 CFormatType::Number(number_type) => match number_type {
205 CNumberType::DecimalD | CNumberType::DecimalI | CNumberType::DecimalU => {
206 if let Some(i) = obj.downcast_ref::<PyInt>() {
207 check_int_to_str_digits(i.as_bigint(), vm)?;
208 Ok(spec.format_number(i.as_bigint()).into())
209 } else if let Some(f) = obj.downcast_ref_if_exact::<PyFloat>(vm) {
210 let bigint = try_f64_to_bigint(f.to_f64(), vm)?;
211 check_int_to_str_digits(&bigint, vm)?;
212 Ok(spec.format_number(&bigint).into())
213 } else {
214 format_decimal_object(vm, spec, &obj).map(Into::into)
215 }
216 }
217 _ => {
218 if let Some(i) = obj.downcast_ref::<PyInt>() {
221 Ok(spec.format_number(i.as_bigint()).into())
222 } else if let Some(int_result) = obj.try_index_opt(vm) {
223 let i = int_result?;
224 Ok(spec.format_number(i.as_bigint()).into())
225 } else {
226 Err(vm.new_type_error(format!(
227 "%{} format: an integer is required, not {}",
228 spec.format_type.to_char(),
229 obj.class().name()
230 )))
231 }
232 }
233 },
234 CFormatType::Float(_) => {
235 let value = ArgIntoFloat::try_from_object(vm, obj)?;
236 Ok(spec.format_float(value.into()).into())
237 }
238 CFormatType::Character(CCharacterType::Character) => {
239 if let Some(s) = obj.downcast_ref::<PyStr>()
242 && let Ok(ch) = s.as_wtf8().code_points().exactly_one()
243 {
244 return Ok(spec.format_char(ch));
245 }
246 let int = if let Some(i) = obj.downcast_ref::<PyInt>() {
247 i.to_owned()
248 } else if let Some(int_result) = obj.try_index_opt(vm) {
249 int_result?
250 } else {
251 let what = match obj.downcast_ref::<PyStr>() {
254 Some(s) => format!("a string of length {}", s.char_len()),
255 None => obj.class().name().to_string(),
256 };
257 return Err(vm.new_type_error(format!(
258 "%c requires an int or a unicode character, not {what}"
259 )));
260 };
261 let ch = int
262 .as_bigint()
263 .to_u32()
264 .and_then(CodePoint::from_u32)
265 .ok_or_else(|| vm.new_overflow_error("%c arg not in range(0x110000)"))?;
266 Ok(spec.format_char(ch))
267 }
268 }
269}
270
271fn get_star_arg(vm: &VirtualMachine, element: Option<&PyObject>) -> PyResult<i32> {
272 match element {
273 Some(obj) => {
274 if let Some(i) = obj.downcast_ref::<PyInt>() {
275 i.try_to_primitive::<i32>(vm)
276 } else {
277 Err(vm.new_type_error("* wants int"))
278 }
279 }
280 None => Err(vm.new_type_error("not enough arguments for format string")),
281 }
282}
283
284fn try_update_quantity_from_tuple<'a, I: Iterator<Item = &'a PyObject>>(
285 vm: &VirtualMachine,
286 elements: &mut I,
287 q: &mut Option<CFormatQuantity>,
288 f: &mut CConversionFlags,
289) -> PyResult<()> {
290 let Some(CFormatQuantity::FromValuesTuple) = q else {
291 return Ok(());
292 };
293
294 let width = get_star_arg(vm, elements.next())?;
295 if width < 0 {
296 f.insert(CConversionFlags::LEFT_ADJUST);
297 }
298 *q = Some(CFormatQuantity::Amount(width.unsigned_abs() as usize));
299 Ok(())
300}
301
302fn try_update_precision_from_tuple<'a, I: Iterator<Item = &'a PyObject>>(
303 vm: &VirtualMachine,
304 elements: &mut I,
305 p: &mut Option<CFormatPrecision>,
306) -> PyResult<()> {
307 let Some(CFormatPrecision::Quantity(CFormatQuantity::FromValuesTuple)) = p else {
308 return Ok(());
309 };
310
311 let precision = get_star_arg(vm, elements.next())?.max(0) as usize;
312 *p = Some(CFormatPrecision::Quantity(CFormatQuantity::Amount(
313 precision,
314 )));
315 Ok(())
316}
317
318fn specifier_error(vm: &VirtualMachine) -> PyBaseExceptionRef {
319 vm.new_type_error("format requires a mapping")
320}
321
322pub(crate) fn cformat_bytes(
323 vm: &VirtualMachine,
324 format_string: &[u8],
325 values_obj: &PyObject,
326) -> PyResult<Vec<u8>> {
327 let mut format = CFormatBytes::parse_from_bytes(format_string)
328 .map_err(|err| vm.new_value_error(err.to_string()))?;
329 let (num_specifiers, mapping_required) = format
330 .check_specifiers()
331 .ok_or_else(|| specifier_error(vm))?;
332
333 let mut result = vec![];
334
335 let is_mapping = values_obj.class().has_attr(identifier!(vm, __getitem__))
336 && !values_obj.fast_isinstance(vm.ctx.types.tuple_type)
337 && !values_obj.fast_isinstance(vm.ctx.types.bytes_type)
338 && !values_obj.fast_isinstance(vm.ctx.types.bytearray_type);
339
340 if num_specifiers == 0 {
341 if !is_mapping
342 && values_obj
343 .downcast_ref::<tuple::PyTuple>()
344 .is_none_or(|e| !e.as_slice().is_empty())
345 {
346 return Err(vm.new_type_error("not all arguments converted during bytes formatting"));
347 }
348
349 for (_, part) in format.iter_mut() {
351 if let CFormatPart::Literal(literal) = part {
352 result.append(literal)
353 } else {
354 unreachable!()
355 }
356 }
357
358 return Ok(result);
359 }
360
361 if mapping_required {
362 if !is_mapping {
363 return Err(vm.new_type_error("format requires a mapping"));
364 }
365
366 for (_, part) in format {
368 match part {
369 CFormatPart::Literal(literal) => result.extend(literal),
370 CFormatPart::Spec(CFormatSpecKeyed { mapping_key, spec }) => {
371 let key = mapping_key.unwrap();
372 let value = values_obj.get_item(&key, vm)?;
373 let part_result = spec_format_bytes(vm, &spec, value)?;
374 result.extend(part_result);
375 }
376 }
377 }
378
379 return Ok(result);
380 }
381
382 let mut slice_iter;
384 let mut once_iter;
385 let mut value_iter: &mut dyn Iterator<Item = &PyObject> =
386 if let Some(tup) = values_obj.downcast_ref::<tuple::PyTuple>() {
387 slice_iter = tup.as_slice().iter().map(|v| &**v);
388 &mut slice_iter
389 } else {
390 once_iter = core::iter::once(values_obj);
391 &mut once_iter
392 };
393
394 for (_, part) in format {
395 match part {
396 CFormatPart::Literal(literal) => result.extend(literal),
397 CFormatPart::Spec(CFormatSpecKeyed { mut spec, .. }) => {
398 try_update_quantity_from_tuple(
399 vm,
400 &mut value_iter,
401 &mut spec.min_field_width,
402 &mut spec.flags,
403 )?;
404 try_update_precision_from_tuple(vm, &mut value_iter, &mut spec.precision)?;
405
406 let Some(value) = value_iter.next() else {
407 return Err(vm.new_type_error("not enough arguments for format string"));
408 };
409
410 let part_result = spec_format_bytes(vm, &spec, value.to_owned())?;
411 result.extend(part_result);
412 }
413 }
414 }
415
416 if !is_mapping && value_iter.next().is_some() {
418 Err(vm.new_type_error("not all arguments converted during bytes formatting"))
419 } else {
420 Ok(result)
421 }
422}
423
424pub(crate) fn cformat_string(
425 vm: &VirtualMachine,
426 format_string: &Wtf8,
427 values_obj: &PyObject,
428) -> PyResult<Wtf8Buf> {
429 let format = CFormatWtf8::parse_from_wtf8(format_string)
430 .map_err(|err| vm.new_value_error(err.to_string()))?;
431 let (num_specifiers, mapping_required) = format
432 .check_specifiers()
433 .ok_or_else(|| specifier_error(vm))?;
434
435 let mut result = Wtf8Buf::new();
436
437 let is_mapping = values_obj.class().has_attr(identifier!(vm, __getitem__))
438 && !values_obj.fast_isinstance(vm.ctx.types.tuple_type)
439 && !values_obj.fast_isinstance(vm.ctx.types.str_type);
440
441 if num_specifiers == 0 {
442 if !is_mapping
443 && values_obj
444 .downcast_ref::<tuple::PyTuple>()
445 .is_none_or(|e| !e.as_slice().is_empty())
446 {
447 return Err(vm.new_type_error("not all arguments converted during string formatting"));
448 }
449
450 for (_, part) in format.iter() {
452 if let CFormatPart::Literal(literal) = part {
453 result.push_wtf8(literal)
454 } else {
455 unreachable!()
456 }
457 }
458
459 return Ok(result);
460 }
461
462 if mapping_required {
463 if !is_mapping {
464 return Err(vm.new_type_error("format requires a mapping"));
465 }
466
467 for (_, part) in format {
469 match part {
470 CFormatPart::Literal(literal) => result.push_wtf8(&literal),
471 CFormatPart::Spec(CFormatSpecKeyed { mapping_key, spec }) => {
472 let value = values_obj.get_item(&mapping_key.unwrap(), vm)?;
473 let part_result = spec_format_string(vm, &spec, value)?;
474 result.push_wtf8(&part_result);
475 }
476 }
477 }
478
479 return Ok(result);
480 }
481
482 let mut slice_iter;
484 let mut once_iter;
485 let mut value_iter: &mut dyn Iterator<Item = &PyObject> =
486 if let Some(tup) = values_obj.downcast_ref::<tuple::PyTuple>() {
487 slice_iter = tup.as_slice().iter().map(|v| &**v);
488 &mut slice_iter
489 } else {
490 once_iter = core::iter::once(values_obj);
491 &mut once_iter
492 };
493
494 for (_, part) in format {
495 match part {
496 CFormatPart::Literal(literal) => result.push_wtf8(&literal),
497 CFormatPart::Spec(CFormatSpecKeyed { mut spec, .. }) => {
498 try_update_quantity_from_tuple(
499 vm,
500 &mut value_iter,
501 &mut spec.min_field_width,
502 &mut spec.flags,
503 )?;
504 try_update_precision_from_tuple(vm, &mut value_iter, &mut spec.precision)?;
505
506 let Some(value) = value_iter.next() else {
507 return Err(vm.new_type_error("not enough arguments for format string"));
508 };
509
510 let part_result = spec_format_string(vm, &spec, value.to_owned())?;
511 result.push_wtf8(&part_result);
512 }
513 }
514 }
515
516 if !is_mapping && value_iter.next().is_some() {
518 Err(vm.new_type_error("not all arguments converted during string formatting"))
519 } else {
520 Ok(result)
521 }
522}