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(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/// Parse C `lconv.grouping` into a `Vec<u8>`.
50/// Reads bytes until 0 or CHAR_MAX, then appends 0 (meaning "repeat last group").
51#[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                // FIXME: compiler can intern specs using parser tree. Then this call can be interned_str
170                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}