use crate::{
PyObject, PyResult, VirtualMachine,
builtins::PyBaseExceptionRef,
convert::{IntoPyException, ToPyException},
function::FuncArgs,
stdlib::builtins,
};
use crate::common::format::*;
use crate::common::wtf8::{Wtf8, Wtf8Buf};
#[cfg(any(unix, windows))]
pub(crate) fn get_locale_info() -> LocaleInfo {
let lc = crate::host_env::locale::localeconv_data();
#[allow(
clippy::unnecessary_cast,
reason = "libc::c_char is not u8 on all platforms"
)]
let mut grouping = lc.grouping.iter().map(|&c| c as u8).collect::<Vec<u8>>();
if !grouping.is_empty() {
grouping.push(0);
}
LocaleInfo {
thousands_sep: String::from_utf8_lossy(&lc.thousands_sep).into_owned(),
decimal_point: if lc.decimal_point.is_empty() {
".".to_string()
} else {
String::from_utf8_lossy(&lc.decimal_point).into_owned()
},
grouping,
}
}
#[cfg(not(any(unix, windows)))]
pub(crate) fn get_locale_info() -> LocaleInfo {
LocaleInfo {
thousands_sep: String::new(),
decimal_point: ".".to_string(),
grouping: vec![],
}
}
impl IntoPyException for FormatSpecError {
fn into_pyexception(self, vm: &VirtualMachine) -> PyBaseExceptionRef {
match self {
Self::DecimalDigitsTooMany => {
vm.new_value_error("Too many decimal digits in format string")
}
Self::PrecisionTooBig => vm.new_value_error("Precision too big"),
Self::PrecisionMissing => vm.new_value_error("Format specifier missing precision"),
Self::InvalidFormatSpecifier => vm.new_value_error("Invalid format specifier"),
Self::UnspecifiedFormat(c1, c2) => {
let msg = format!("Cannot specify '{c1}' with '{c2}'.");
vm.new_value_error(msg)
}
Self::ExclusiveFormat(c1, c2) => {
let msg = format!("Cannot specify both '{c1}' and '{c2}'.");
vm.new_value_error(msg)
}
Self::UnknownFormatCode(c, s) => {
let msg = format!("Unknown format code '{c}' for object of type '{s}'");
vm.new_value_error(msg)
}
Self::PrecisionNotAllowed => {
vm.new_value_error("Precision not allowed in integer format specifier")
}
Self::NotAllowed(s) => {
let msg = format!("{s} not allowed with integer format specifier 'c'");
vm.new_value_error(msg)
}
Self::UnableToConvert => vm.new_value_error("Unable to convert int to float"),
Self::CodeNotInRange => vm.new_overflow_error("%c arg not in range(0x110000)"),
Self::IntTooLargeForCLong => {
vm.new_overflow_error("Python int too large to convert to C long")
}
Self::ZeroPadding => {
vm.new_value_error("Zero padding is not allowed in complex format specifier")
}
Self::AlignmentFlag => {
vm.new_value_error("'=' alignment flag is not allowed in complex format specifier")
}
Self::NegativeZeroCoercionNotAllowed(type_name) => {
let msg = format!(
"Negative zero coercion (z) not allowed in {type_name} format specifier"
);
vm.new_value_error(msg)
}
Self::StringAlignmentFlag => {
vm.new_value_error("'=' alignment not allowed in string format specifier")
}
Self::StringSpecNotAllowed(s) => {
let msg = format!("{s} not allowed in string format specifier");
vm.new_value_error(msg)
}
Self::NotImplemented(c, s) => {
let msg = format!("Format code '{c}' for object of type '{s}' not implemented yet");
vm.new_value_error(msg)
}
}
}
}
impl ToPyException for FormatParseError {
fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
match self {
Self::UnmatchedBracket | Self::MissingRightBracket => {
vm.new_value_error("expected '}' before end of string")
}
Self::MissingStartBracket => {
vm.new_value_error("Single '}' encountered in format string")
}
Self::UnescapedStartBracketInLiteral => {
vm.new_value_error("Single '{' encountered in format string")
}
Self::InvalidFormatSpecifier => vm.new_value_error("unmatched '{' in format spec"),
Self::UnknownConversion => vm.new_value_error("Unexpected error parsing format string"),
Self::EmptyAttribute => vm.new_value_error("Empty attribute in format string"),
Self::InvalidCharacterAfterRightBracket => {
vm.new_value_error("Only '.' or '[' may follow ']' in format field specifier")
}
Self::TooManyDecimalDigits => {
vm.new_value_error("Too many decimal digits in format string")
}
}
}
}
fn format_internal(
vm: &VirtualMachine,
format: &FormatString,
field_func: &mut impl FnMut(FieldType) -> PyResult,
) -> PyResult<Wtf8Buf> {
let mut final_string = Wtf8Buf::new();
for part in &format.format_parts {
let pystr;
let result_string: &Wtf8 = match part {
FormatPart::Field {
field_name,
conversion_spec,
format_spec,
} => {
let FieldName { field_type, parts } =
FieldName::parse(field_name).map_err(|e| e.to_pyexception(vm))?;
let mut argument = field_func(field_type)?;
for name_part in parts {
match name_part {
FieldNamePart::Attribute(attribute) => {
argument = argument.get_attr(&vm.ctx.new_str(attribute), vm)?;
}
FieldNamePart::Index(index) => {
argument = argument.get_item(&index, vm)?;
}
FieldNamePart::StringIndex(index) => {
argument = argument.get_item(&index, vm)?;
}
}
}
let nested_format =
FormatString::from_str(format_spec).map_err(|e| e.to_pyexception(vm))?;
let format_spec = format_internal(vm, &nested_format, field_func)?;
let argument = match conversion_spec {
None => argument,
Some(c) => match FormatConversion::from_char(*c) {
Some(FormatConversion::Str) => argument.str(vm)?.into(),
Some(FormatConversion::Repr) => argument.repr(vm)?.into(),
Some(FormatConversion::Ascii) => builtins::ascii(argument, vm)?.into(),
None => {
return Err(
vm.new_value_error(format!("Unknown conversion specifier {c}"))
);
}
},
};
pystr = vm.format(&argument, vm.ctx.new_str(format_spec))?;
pystr.as_wtf8()
}
FormatPart::Literal(literal) => literal,
};
final_string.push_wtf8(result_string);
}
Ok(final_string)
}
pub(crate) fn format(
format: &FormatString,
arguments: &FuncArgs,
vm: &VirtualMachine,
) -> PyResult<Wtf8Buf> {
let mut auto_argument_index: usize = 0;
let mut seen_index = false;
format_internal(vm, format, &mut |field_type| match field_type {
FieldType::Auto => {
if seen_index {
return Err(vm.new_value_error(
"cannot switch from manual field specification to automatic field numbering",
));
}
auto_argument_index += 1;
let idx = auto_argument_index - 1;
arguments.args.get(idx).cloned().ok_or_else(|| {
vm.new_index_error(format!(
"Replacement index {idx} out of range for positional args tuple"
))
})
}
FieldType::Index(index) => {
if auto_argument_index != 0 {
return Err(vm.new_value_error(
"cannot switch from automatic field numbering to manual field specification",
));
}
seen_index = true;
arguments.args.get(index).cloned().ok_or_else(|| {
vm.new_index_error(format!(
"Replacement index {index} out of range for positional args tuple"
))
})
}
FieldType::Keyword(keyword) => keyword
.as_str()
.ok()
.and_then(|keyword| arguments.get_optional_kwarg(keyword))
.ok_or_else(|| vm.new_key_error(vm.ctx.new_str(keyword).into())),
})
}
pub(crate) fn format_map(
format: &FormatString,
dict: &PyObject,
vm: &VirtualMachine,
) -> PyResult<Wtf8Buf> {
format_internal(vm, format, &mut |field_type| match field_type {
FieldType::Auto | FieldType::Index(_) => {
Err(vm.new_value_error("Format string contains positional fields"))
}
FieldType::Keyword(keyword) => dict.get_item(&keyword, vm),
})
}