use super::ExtendedBigDecimal;
use crate::builtins::generated::format::spec::ArgumentLocation;
use crate::builtins::generated::format_support::QuotingStyle;
use crate::builtins::generated::format_support::locale_aware_escape_name;
use crate::builtins::generated::format_support::os_str_as_bytes;
use crate::builtins::generated::format_support::set_exit_code;
use crate::builtins::generated::format_support::show_error;
use crate::builtins::generated::format_support::show_warning;
use crate::builtins::generated::num_parser::ExtendedParser;
use crate::builtins::generated::num_parser::ExtendedParserError;
use os_display::Quotable;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::num::NonZero;
#[derive(Clone, Debug, PartialEq)]
pub enum FormatArgument {
Char(char),
String(OsString),
UnsignedInt(u64),
SignedInt(i64),
Float(ExtendedBigDecimal),
Unparsed(OsString),
}
#[derive(Debug, PartialEq)]
pub struct FormatArguments<'a> {
args: &'a [FormatArgument],
next_arg_position: usize,
highest_arg_position: Option<usize>,
current_offset: usize,
}
impl<'a> FormatArguments<'a> {
pub fn new(args: &'a [FormatArgument]) -> Self {
Self {
args,
next_arg_position: 0,
highest_arg_position: None,
current_offset: 0,
}
}
pub fn peek_arg(&self) -> Option<&'a FormatArgument> {
self.args.get(self.next_arg_position)
}
pub fn is_exhausted(&self) -> bool {
self.current_offset >= self.args.len()
}
pub fn start_next_batch(&mut self) {
self.current_offset = self
.next_arg_position
.max(self.highest_arg_position.map_or(0, |x| x.saturating_add(1)));
self.next_arg_position = self.current_offset;
}
pub fn next_char(&mut self, position: ArgumentLocation) -> u8 {
match self.next_arg(position) {
Some(FormatArgument::Char(c)) => *c as u8,
Some(FormatArgument::Unparsed(os)) => match os_str_as_bytes(os) {
Ok(bytes) => bytes.first().copied().unwrap_or(b'\0'),
Err(_) => b'\0',
},
_ => b'\0',
}
}
pub fn next_string(&mut self, position: ArgumentLocation) -> &'a OsStr {
match self.next_arg(position) {
Some(FormatArgument::Unparsed(os) | FormatArgument::String(os)) => os,
_ => "".as_ref(),
}
}
pub fn next_i64(&mut self, position: ArgumentLocation) -> i64 {
match self.next_arg(position) {
Some(FormatArgument::SignedInt(n)) => *n,
Some(FormatArgument::Unparsed(os)) => Self::get_num::<i64>(os),
_ => 0,
}
}
pub fn next_u64(&mut self, position: ArgumentLocation) -> u64 {
match self.next_arg(position) {
Some(FormatArgument::UnsignedInt(n)) => *n,
Some(FormatArgument::Unparsed(os)) => Self::get_num::<u64>(os),
_ => 0,
}
}
pub fn next_extended_big_decimal(&mut self, position: ArgumentLocation) -> ExtendedBigDecimal {
match self.next_arg(position) {
Some(FormatArgument::Float(n)) => n.clone(),
Some(FormatArgument::Unparsed(os)) => Self::get_num::<ExtendedBigDecimal>(os),
_ => ExtendedBigDecimal::zero(),
}
}
fn parse_quote_start<T>(os: &OsStr) -> Result<T, ExtendedParserError<T>>
where
T: ExtendedParser + From<u8> + From<u32> + Default,
{
let Ok(s) = os_str_as_bytes(os) else {
return Err(ExtendedParserError::NotNumeric);
};
let (Some((b'"', bytes)) | Some((b'\'', bytes))) = s.split_first() else {
debug_assert!(false);
return Err(ExtendedParserError::NotNumeric);
};
if bytes.is_empty() {
return Err(ExtendedParserError::NotNumeric);
}
let (val, len) = if let Some(c) = bytes
.utf8_chunks()
.next()
.expect("bytes should not be empty")
.valid()
.chars()
.next()
{
((c as u32).into(), c.len_utf8())
} else {
(bytes[0].into(), 1)
};
if bytes.len() > len {
return Err(ExtendedParserError::PartialMatch(
val,
String::from_utf8_lossy(&bytes[len..]).into_owned(),
));
}
Ok(val)
}
fn get_num<T>(os: &OsStr) -> T
where
T: ExtendedParser + From<u8> + From<u32> + Default,
{
let s = os.to_string_lossy();
let first = s.as_bytes().first().copied();
let quote_start = first == Some(b'"') || first == Some(b'\'');
let parsed = if quote_start {
Self::parse_quote_start(os)
} else {
T::extended_parse(&s)
};
extract_value(parsed, &s, quote_start)
}
fn get_at_relative_position(&mut self, pos: NonZero<usize>) -> Option<&'a FormatArgument> {
let pos: usize = pos.into();
let pos = (pos - 1).saturating_add(self.current_offset);
self.highest_arg_position = Some(self.highest_arg_position.map_or(pos, |x| x.max(pos)));
self.args.get(pos)
}
fn next_arg(&mut self, position: ArgumentLocation) -> Option<&'a FormatArgument> {
match position {
ArgumentLocation::NextArgument => {
let arg = self.args.get(self.next_arg_position);
self.next_arg_position += 1;
arg
}
ArgumentLocation::Position(pos) => self.get_at_relative_position(pos),
}
}
}
fn extract_value<T: Default>(
p: Result<T, ExtendedParserError<T>>,
input: &str,
quote_start: bool,
) -> T {
match p {
Ok(v) => v,
Err(e) => {
set_exit_code(1);
let input = locale_aware_escape_name(OsStr::new(input), QuotingStyle::C_NO_QUOTES);
match e {
ExtendedParserError::Overflow(v) => {
show_error!("{}: Numerical result out of range", input.quote());
v
}
ExtendedParserError::Underflow(v) => {
show_error!("{}: Numerical result out of range", input.quote());
v
}
ExtendedParserError::NotNumeric => {
show_error!("{}: expected a numeric value", input.quote());
Default::default()
}
ExtendedParserError::PartialMatch(v, rest) => {
if quote_start {
set_exit_code(0);
show_warning!(
"{rest}: character(s) following character constant have been ignored"
);
} else {
show_error!("{}: value not completely converted", input.quote());
}
v
}
}
}
}
}