Skip to main content

rustpython_vm/
format.rs

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/// Get locale information from C `localeconv()` for the 'n' format specifier.
13#[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        // Matched exhaustively on purpose: a catch-all sent most of these to
107        // "Unexpected error parsing format string", which tells the reader nothing
108        // about the format string they mistyped.
109        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            // A brace in literal text that was not doubled, i.e. a stray `{` with
117            // nothing after it. `parse_spec` reports this rather than
118            // UnmatchedBracket so that "{" and "a{" are separated from "{0" and
119            // "a{b", which are fields left open.
120            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            // Reached both for a conversion that is not one character (`{0!xy}`,
125            // where CPython says "expected ':' after conversion specifier") and for
126            // an empty one (`{0!}`, "unmatched '{' in format spec"). Telling those
127            // apart needs the parser to say which, so the generic message stays.
128            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                // FIXME: compiler can intern specs using parser tree. Then this call can be interned_str
192                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}