1use crate::{
2 builtins::PyBaseExceptionRef,
3 convert::{IntoPyException, ToPyException},
4 function::FuncArgs,
5 stdlib::builtins,
6 PyObject, PyResult, VirtualMachine,
7};
8
9use rustpython_format::*;
10
11impl IntoPyException for FormatSpecError {
12 fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef {
13 match self {
14 FormatSpecError::DecimalDigitsTooMany => {
15 vm.new_value_error("Too many decimal digits in format string".to_owned())
16 }
17 FormatSpecError::PrecisionTooBig => vm.new_value_error("Precision too big".to_owned()),
18 FormatSpecError::InvalidFormatSpecifier => {
19 vm.new_value_error("Invalid format specifier".to_owned())
20 }
21 FormatSpecError::UnspecifiedFormat(c1, c2) => {
22 let msg = format!("Cannot specify '{c1}' with '{c2}'.");
23 vm.new_value_error(msg)
24 }
25 FormatSpecError::UnknownFormatCode(c, s) => {
26 let msg = format!("Unknown format code '{c}' for object of type '{s}'");
27 vm.new_value_error(msg)
28 }
29 FormatSpecError::PrecisionNotAllowed => {
30 vm.new_value_error("Precision not allowed in integer format specifier".to_owned())
31 }
32 FormatSpecError::NotAllowed(s) => {
33 let msg = format!("{s} not allowed with integer format specifier 'c'");
34 vm.new_value_error(msg)
35 }
36 FormatSpecError::UnableToConvert => {
37 vm.new_value_error("Unable to convert int to float".to_owned())
38 }
39 FormatSpecError::CodeNotInRange => {
40 vm.new_overflow_error("%c arg not in range(0x110000)".to_owned())
41 }
42 FormatSpecError::NotImplemented(c, s) => {
43 let msg = format!("Format code '{c}' for object of type '{s}' not implemented yet");
44 vm.new_value_error(msg)
45 }
46 }
47 }
48}
49
50impl ToPyException for FormatParseError {
51 fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
52 match self {
53 FormatParseError::UnmatchedBracket => {
54 vm.new_value_error("expected '}' before end of string".to_owned())
55 }
56 _ => vm.new_value_error("Unexpected error parsing format string".to_owned()),
57 }
58 }
59}
60
61fn format_internal(
62 vm: &VirtualMachine,
63 format: &FormatString,
64 field_func: &mut impl FnMut(FieldType) -> PyResult,
65) -> PyResult<String> {
66 let mut final_string = String::new();
67 for part in &format.format_parts {
68 let pystr;
69 let result_string: &str = match part {
70 FormatPart::Field {
71 field_name,
72 conversion_spec,
73 format_spec,
74 } => {
75 let FieldName { field_type, parts } =
76 FieldName::parse(field_name.as_str()).map_err(|e| e.to_pyexception(vm))?;
77
78 let mut argument = field_func(field_type)?;
79
80 for name_part in parts {
81 match name_part {
82 FieldNamePart::Attribute(attribute) => {
83 argument = argument.get_attr(&vm.ctx.new_str(attribute), vm)?;
84 }
85 FieldNamePart::Index(index) => {
86 argument = argument.get_item(&index, vm)?;
87 }
88 FieldNamePart::StringIndex(index) => {
89 argument = argument.get_item(&index, vm)?;
90 }
91 }
92 }
93
94 let nested_format =
95 FormatString::from_str(format_spec).map_err(|e| e.to_pyexception(vm))?;
96 let format_spec = format_internal(vm, &nested_format, field_func)?;
97
98 let argument = match conversion_spec.and_then(FormatConversion::from_char) {
99 Some(FormatConversion::Str) => argument.str(vm)?.into(),
100 Some(FormatConversion::Repr) => argument.repr(vm)?.into(),
101 Some(FormatConversion::Ascii) => {
102 vm.ctx.new_str(builtins::ascii(argument, vm)?).into()
103 }
104 Some(FormatConversion::Bytes) => {
105 vm.call_method(&argument, identifier!(vm, decode).as_str(), ())?
106 }
107 None => argument,
108 };
109
110 pystr = vm.format(&argument, vm.ctx.new_str(format_spec))?;
112 pystr.as_ref()
113 }
114 FormatPart::Literal(literal) => literal,
115 };
116 final_string.push_str(result_string);
117 }
118 Ok(final_string)
119}
120
121pub(crate) fn format(
122 format: &FormatString,
123 arguments: &FuncArgs,
124 vm: &VirtualMachine,
125) -> PyResult<String> {
126 let mut auto_argument_index: usize = 0;
127 let mut seen_index = false;
128 format_internal(vm, format, &mut |field_type| match field_type {
129 FieldType::Auto => {
130 if seen_index {
131 return Err(vm.new_value_error(
132 "cannot switch from manual field specification to automatic field numbering"
133 .to_owned(),
134 ));
135 }
136 auto_argument_index += 1;
137 arguments
138 .args
139 .get(auto_argument_index - 1)
140 .cloned()
141 .ok_or_else(|| vm.new_index_error("tuple index out of range".to_owned()))
142 }
143 FieldType::Index(index) => {
144 if auto_argument_index != 0 {
145 return Err(vm.new_value_error(
146 "cannot switch from automatic field numbering to manual field specification"
147 .to_owned(),
148 ));
149 }
150 seen_index = true;
151 arguments
152 .args
153 .get(index)
154 .cloned()
155 .ok_or_else(|| vm.new_index_error("tuple index out of range".to_owned()))
156 }
157 FieldType::Keyword(keyword) => arguments
158 .get_optional_kwarg(&keyword)
159 .ok_or_else(|| vm.new_key_error(vm.ctx.new_str(keyword).into())),
160 })
161}
162
163pub(crate) fn format_map(
164 format: &FormatString,
165 dict: &PyObject,
166 vm: &VirtualMachine,
167) -> PyResult<String> {
168 format_internal(vm, format, &mut |field_type| match field_type {
169 FieldType::Auto | FieldType::Index(_) => {
170 Err(vm.new_value_error("Format string contains positional fields".to_owned()))
171 }
172 FieldType::Keyword(keyword) => dict.get_item(&keyword, vm),
173 })
174}