rustpython_vm/
format.rs

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                // FIXME: compiler can intern specs using parser tree. Then this call can be interned_str
111                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}