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(unix)]
14pub(crate) fn get_locale_info() -> LocaleInfo {
15 use core::ffi::CStr;
16 unsafe {
17 let lc = libc::localeconv();
18 if lc.is_null() {
19 return LocaleInfo {
20 thousands_sep: String::new(),
21 decimal_point: ".".to_string(),
22 grouping: vec![],
23 };
24 }
25 let thousands_sep = CStr::from_ptr((*lc).thousands_sep)
26 .to_string_lossy()
27 .into_owned();
28 let decimal_point = CStr::from_ptr((*lc).decimal_point)
29 .to_string_lossy()
30 .into_owned();
31 let grouping = parse_grouping((*lc).grouping);
32 LocaleInfo {
33 thousands_sep,
34 decimal_point,
35 grouping,
36 }
37 }
38}
39
40#[cfg(not(unix))]
41pub(crate) fn get_locale_info() -> LocaleInfo {
42 LocaleInfo {
43 thousands_sep: String::new(),
44 decimal_point: ".".to_string(),
45 grouping: vec![],
46 }
47}
48
49#[cfg(unix)]
52unsafe fn parse_grouping(grouping: *const libc::c_char) -> Vec<u8> {
53 let mut result = Vec::new();
54 if grouping.is_null() {
55 return result;
56 }
57 unsafe {
58 let mut ptr = grouping;
59 while ![0, libc::c_char::MAX].contains(&*ptr) {
60 result.push(*ptr as u8);
61 ptr = ptr.add(1);
62 }
63 }
64 if !result.is_empty() {
65 result.push(0);
66 }
67 result
68}
69
70impl IntoPyException for FormatSpecError {
71 fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef {
72 match self {
73 Self::DecimalDigitsTooMany => {
74 vm.new_value_error("Too many decimal digits in format string")
75 }
76 Self::PrecisionTooBig => vm.new_value_error("Precision too big"),
77 Self::InvalidFormatSpecifier => vm.new_value_error("Invalid format specifier"),
78 Self::UnspecifiedFormat(c1, c2) => {
79 let msg = format!("Cannot specify '{c1}' with '{c2}'.");
80 vm.new_value_error(msg)
81 }
82 Self::ExclusiveFormat(c1, c2) => {
83 let msg = format!("Cannot specify both '{c1}' and '{c2}'.");
84 vm.new_value_error(msg)
85 }
86 Self::UnknownFormatCode(c, s) => {
87 let msg = format!("Unknown format code '{c}' for object of type '{s}'");
88 vm.new_value_error(msg)
89 }
90 Self::PrecisionNotAllowed => {
91 vm.new_value_error("Precision not allowed in integer format specifier")
92 }
93 Self::NotAllowed(s) => {
94 let msg = format!("{s} not allowed with integer format specifier 'c'");
95 vm.new_value_error(msg)
96 }
97 Self::UnableToConvert => vm.new_value_error("Unable to convert int to float"),
98 Self::CodeNotInRange => vm.new_overflow_error("%c arg not in range(0x110000)"),
99 Self::ZeroPadding => {
100 vm.new_value_error("Zero padding is not allowed in complex format specifier")
101 }
102 Self::AlignmentFlag => {
103 vm.new_value_error("'=' alignment flag is not allowed in complex format specifier")
104 }
105 Self::NotImplemented(c, s) => {
106 let msg = format!("Format code '{c}' for object of type '{s}' not implemented yet");
107 vm.new_value_error(msg)
108 }
109 }
110 }
111}
112
113impl ToPyException for FormatParseError {
114 fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
115 match self {
116 Self::UnmatchedBracket => vm.new_value_error("expected '}' before end of string"),
117 _ => vm.new_value_error("Unexpected error parsing format string"),
118 }
119 }
120}
121
122fn format_internal(
123 vm: &VirtualMachine,
124 format: &FormatString,
125 field_func: &mut impl FnMut(FieldType) -> PyResult,
126) -> PyResult<Wtf8Buf> {
127 let mut final_string = Wtf8Buf::new();
128 for part in &format.format_parts {
129 let pystr;
130 let result_string: &Wtf8 = match part {
131 FormatPart::Field {
132 field_name,
133 conversion_spec,
134 format_spec,
135 } => {
136 let FieldName { field_type, parts } =
137 FieldName::parse(field_name).map_err(|e| e.to_pyexception(vm))?;
138
139 let mut argument = field_func(field_type)?;
140
141 for name_part in parts {
142 match name_part {
143 FieldNamePart::Attribute(attribute) => {
144 argument = argument.get_attr(&vm.ctx.new_str(attribute), vm)?;
145 }
146 FieldNamePart::Index(index) => {
147 argument = argument.get_item(&index, vm)?;
148 }
149 FieldNamePart::StringIndex(index) => {
150 argument = argument.get_item(&index, vm)?;
151 }
152 }
153 }
154
155 let nested_format =
156 FormatString::from_str(format_spec).map_err(|e| e.to_pyexception(vm))?;
157 let format_spec = format_internal(vm, &nested_format, field_func)?;
158
159 let argument = match conversion_spec.and_then(FormatConversion::from_char) {
160 Some(FormatConversion::Str) => argument.str(vm)?.into(),
161 Some(FormatConversion::Repr) => argument.repr(vm)?.into(),
162 Some(FormatConversion::Ascii) => builtins::ascii(argument, vm)?.into(),
163 Some(FormatConversion::Bytes) => {
164 vm.call_method(&argument, identifier!(vm, decode).as_str(), ())?
165 }
166 None => argument,
167 };
168
169 pystr = vm.format(&argument, vm.ctx.new_str(format_spec))?;
171 pystr.as_wtf8()
172 }
173 FormatPart::Literal(literal) => literal,
174 };
175 final_string.push_wtf8(result_string);
176 }
177 Ok(final_string)
178}
179
180pub(crate) fn format(
181 format: &FormatString,
182 arguments: &FuncArgs,
183 vm: &VirtualMachine,
184) -> PyResult<Wtf8Buf> {
185 let mut auto_argument_index: usize = 0;
186 let mut seen_index = false;
187 format_internal(vm, format, &mut |field_type| match field_type {
188 FieldType::Auto => {
189 if seen_index {
190 return Err(vm.new_value_error(
191 "cannot switch from manual field specification to automatic field numbering",
192 ));
193 }
194 auto_argument_index += 1;
195 arguments
196 .args
197 .get(auto_argument_index - 1)
198 .cloned()
199 .ok_or_else(|| vm.new_index_error("tuple index out of range"))
200 }
201 FieldType::Index(index) => {
202 if auto_argument_index != 0 {
203 return Err(vm.new_value_error(
204 "cannot switch from automatic field numbering to manual field specification",
205 ));
206 }
207 seen_index = true;
208 arguments
209 .args
210 .get(index)
211 .cloned()
212 .ok_or_else(|| vm.new_index_error("tuple index out of range"))
213 }
214 FieldType::Keyword(keyword) => keyword
215 .as_str()
216 .ok()
217 .and_then(|keyword| arguments.get_optional_kwarg(keyword))
218 .ok_or_else(|| vm.new_key_error(vm.ctx.new_str(keyword).into())),
219 })
220}
221
222pub(crate) fn format_map(
223 format: &FormatString,
224 dict: &PyObject,
225 vm: &VirtualMachine,
226) -> PyResult<Wtf8Buf> {
227 format_internal(vm, format, &mut |field_type| match field_type {
228 FieldType::Auto | FieldType::Index(_) => {
229 Err(vm.new_value_error("Format string contains positional fields"))
230 }
231 FieldType::Keyword(keyword) => dict.get_item(&keyword, vm),
232 })
233}