1use crate::{
2 PyObject, PyResult, VirtualMachine,
3 builtins::PyBaseExceptionRef,
4 convert::{IntoPyException, ToPyException},
5 function::FuncArgs,
6 stdlib::builtins,
7};
8
9use crate::common::format::*;
10use crate::common::wtf8::{Wtf8, Wtf8Buf};
11
12#[cfg(any(unix, windows))]
14pub(crate) fn get_locale_info() -> LocaleInfo {
15 let lc = crate::host_env::locale::localeconv_data();
16 #[allow(
17 clippy::unnecessary_cast,
18 reason = "libc::c_char is not u8 on all platforms"
19 )]
20 let mut grouping = lc.grouping.iter().map(|&c| c as u8).collect::<Vec<u8>>();
21 if !grouping.is_empty() {
22 grouping.push(0);
23 }
24 LocaleInfo {
25 thousands_sep: String::from_utf8_lossy(&lc.thousands_sep).into_owned(),
26 decimal_point: if lc.decimal_point.is_empty() {
27 ".".to_string()
28 } else {
29 String::from_utf8_lossy(&lc.decimal_point).into_owned()
30 },
31 grouping,
32 }
33}
34
35#[cfg(not(any(unix, windows)))]
36pub(crate) fn get_locale_info() -> LocaleInfo {
37 LocaleInfo {
38 thousands_sep: String::new(),
39 decimal_point: ".".to_string(),
40 grouping: vec![],
41 }
42}
43
44impl IntoPyException for FormatSpecError {
45 fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef {
46 match self {
47 Self::DecimalDigitsTooMany => {
48 vm.new_value_error("Too many decimal digits in format string")
49 }
50 Self::PrecisionTooBig => vm.new_value_error("Precision too big"),
51 Self::PrecisionMissing => vm.new_value_error("Format specifier missing precision"),
52 Self::InvalidFormatSpecifier => vm.new_value_error("Invalid format specifier"),
53 Self::UnspecifiedFormat(c1, c2) => {
54 let msg = format!("Cannot specify '{c1}' with '{c2}'.");
55 vm.new_value_error(msg)
56 }
57 Self::ExclusiveFormat(c1, c2) => {
58 let msg = format!("Cannot specify both '{c1}' and '{c2}'.");
59 vm.new_value_error(msg)
60 }
61 Self::UnknownFormatCode(c, s) => {
62 let msg = format!("Unknown format code '{c}' for object of type '{s}'");
63 vm.new_value_error(msg)
64 }
65 Self::PrecisionNotAllowed => {
66 vm.new_value_error("Precision not allowed in integer format specifier")
67 }
68 Self::NotAllowed(s) => {
69 let msg = format!("{s} not allowed with integer format specifier 'c'");
70 vm.new_value_error(msg)
71 }
72 Self::UnableToConvert => vm.new_value_error("Unable to convert int to float"),
73 Self::CodeNotInRange => vm.new_overflow_error("%c arg not in range(0x110000)"),
74 Self::IntTooLargeForCLong => {
75 vm.new_overflow_error("Python int too large to convert to C long")
76 }
77 Self::ZeroPadding => {
78 vm.new_value_error("Zero padding is not allowed in complex format specifier")
79 }
80 Self::AlignmentFlag => {
81 vm.new_value_error("'=' alignment flag is not allowed in complex format specifier")
82 }
83 Self::NegativeZeroCoercionNotAllowed(type_name) => {
84 let msg = format!(
85 "Negative zero coercion (z) not allowed in {type_name} format specifier"
86 );
87 vm.new_value_error(msg)
88 }
89 Self::StringAlignmentFlag => {
90 vm.new_value_error("'=' alignment not allowed in string format specifier")
91 }
92 Self::StringSpecNotAllowed(s) => {
93 let msg = format!("{s} not allowed in string format specifier");
94 vm.new_value_error(msg)
95 }
96 Self::NotImplemented(c, s) => {
97 let msg = format!("Format code '{c}' for object of type '{s}' not implemented yet");
98 vm.new_value_error(msg)
99 }
100 }
101 }
102}
103
104impl ToPyException for FormatParseError {
105 fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
106 match self {
110 Self::UnmatchedBracket | Self::MissingRightBracket => {
111 vm.new_value_error("expected '}' before end of string")
112 }
113 Self::MissingStartBracket => {
114 vm.new_value_error("Single '}' encountered in format string")
115 }
116 Self::UnescapedStartBracketInLiteral => {
121 vm.new_value_error("Single '{' encountered in format string")
122 }
123 Self::InvalidFormatSpecifier => vm.new_value_error("unmatched '{' in format spec"),
124 Self::UnknownConversion => vm.new_value_error("Unexpected error parsing format string"),
129 Self::EmptyAttribute => vm.new_value_error("Empty attribute in format string"),
130 Self::InvalidCharacterAfterRightBracket => {
131 vm.new_value_error("Only '.' or '[' may follow ']' in format field specifier")
132 }
133 Self::TooManyDecimalDigits => {
134 vm.new_value_error("Too many decimal digits in format string")
135 }
136 }
137 }
138}
139
140fn format_internal(
141 vm: &VirtualMachine,
142 format: &FormatString,
143 field_func: &mut impl FnMut(FieldType) -> PyResult,
144) -> PyResult<Wtf8Buf> {
145 let mut final_string = Wtf8Buf::new();
146 for part in &format.format_parts {
147 let pystr;
148 let result_string: &Wtf8 = match part {
149 FormatPart::Field {
150 field_name,
151 conversion_spec,
152 format_spec,
153 } => {
154 let FieldName { field_type, parts } =
155 FieldName::parse(field_name).map_err(|e| e.to_pyexception(vm))?;
156
157 let mut argument = field_func(field_type)?;
158
159 for name_part in parts {
160 match name_part {
161 FieldNamePart::Attribute(attribute) => {
162 argument = argument.get_attr(&vm.ctx.new_str(attribute), vm)?;
163 }
164 FieldNamePart::Index(index) => {
165 argument = argument.get_item(&index, vm)?;
166 }
167 FieldNamePart::StringIndex(index) => {
168 argument = argument.get_item(&index, vm)?;
169 }
170 }
171 }
172
173 let nested_format =
174 FormatString::from_str(format_spec).map_err(|e| e.to_pyexception(vm))?;
175 let format_spec = format_internal(vm, &nested_format, field_func)?;
176
177 let argument = match conversion_spec {
178 None => argument,
179 Some(c) => match FormatConversion::from_char(*c) {
180 Some(FormatConversion::Str) => argument.str(vm)?.into(),
181 Some(FormatConversion::Repr) => argument.repr(vm)?.into(),
182 Some(FormatConversion::Ascii) => builtins::ascii(argument, vm)?.into(),
183 None => {
184 return Err(
185 vm.new_value_error(format!("Unknown conversion specifier {c}"))
186 );
187 }
188 },
189 };
190
191 pystr = vm.format(&argument, vm.ctx.new_str(format_spec))?;
193 pystr.as_wtf8()
194 }
195 FormatPart::Literal(literal) => literal,
196 };
197 final_string.push_wtf8(result_string);
198 }
199 Ok(final_string)
200}
201
202pub(crate) fn format(
203 format: &FormatString,
204 arguments: &FuncArgs,
205 vm: &VirtualMachine,
206) -> PyResult<Wtf8Buf> {
207 let mut auto_argument_index: usize = 0;
208 let mut seen_index = false;
209 format_internal(vm, format, &mut |field_type| match field_type {
210 FieldType::Auto => {
211 if seen_index {
212 return Err(vm.new_value_error(
213 "cannot switch from manual field specification to automatic field numbering",
214 ));
215 }
216 auto_argument_index += 1;
217 let idx = auto_argument_index - 1;
218 arguments.args.get(idx).cloned().ok_or_else(|| {
219 vm.new_index_error(format!(
220 "Replacement index {idx} out of range for positional args tuple"
221 ))
222 })
223 }
224 FieldType::Index(index) => {
225 if auto_argument_index != 0 {
226 return Err(vm.new_value_error(
227 "cannot switch from automatic field numbering to manual field specification",
228 ));
229 }
230 seen_index = true;
231 arguments.args.get(index).cloned().ok_or_else(|| {
232 vm.new_index_error(format!(
233 "Replacement index {index} out of range for positional args tuple"
234 ))
235 })
236 }
237 FieldType::Keyword(keyword) => keyword
238 .as_str()
239 .ok()
240 .and_then(|keyword| arguments.get_optional_kwarg(keyword))
241 .ok_or_else(|| vm.new_key_error(vm.ctx.new_str(keyword).into())),
242 })
243}
244
245pub(crate) fn format_map(
246 format: &FormatString,
247 dict: &PyObject,
248 vm: &VirtualMachine,
249) -> PyResult<Wtf8Buf> {
250 format_internal(vm, format, &mut |field_type| match field_type {
251 FieldType::Auto | FieldType::Index(_) => {
252 Err(vm.new_value_error("Format string contains positional fields"))
253 }
254 FieldType::Keyword(keyword) => dict.get_item(&keyword, vm),
255 })
256}