use core::ffi::c_char;
use crate::{c, PrintfArgs, PrintfArgsList, PrintfArgument};
#[allow(unconditional_panic)]
#[inline]
pub(crate) const fn is_fmt_valid_for_args<T: PrintfArgs>(
fmt: &[c_char],
panic_on_false: bool,
) -> bool {
if !is_null_terminated(fmt) {
if panic_on_false {
panic!("Candidate format string is not null-terminated!");
}
return false;
}
does_fmt_match_args_list::<T::AsList>(fmt, 0, panic_on_false)
}
struct ConversionSpecification {
width_is_arg: bool,
precision_is_arg: bool,
length_modifier: Option<LengthModifier>,
specifier: ConvSpecifier,
}
enum LengthModifier {
CharLen,
Short,
Long,
LongLong,
LongDouble,
#[cfg(any(feature = "libc", test, all(doc, feature = "doccfg")))]
Max,
#[cfg(any(feature = "libc", test, all(doc, feature = "doccfg")))]
Size,
#[cfg(any(feature = "libc", test, all(doc, feature = "doccfg")))]
Ptrdiff,
}
enum ConvSpecifier {
Integer,
Double,
Char,
String,
Pointer,
}
#[allow(unconditional_panic)]
const fn does_fmt_match_args_list<T: PrintfArgsList>(
fmt: &[c_char],
start_idx: usize,
panic_on_false: bool,
) -> bool {
match (next_conversion_specification(fmt, start_idx), T::IS_EMPTY) {
(None, true) => true,
(Some(conv_start), false) => {
if let Ok((spec, after_conv)) = parse_conversion_specification(fmt, conv_start) {
does_convspec_match_arg::<T>(spec, fmt, after_conv, panic_on_false)
} else {
if panic_on_false {
panic!("Unrecognized conversion specification!");
}
false
}
}
_ => {
if panic_on_false {
panic!("Wrong number of conversions!");
}
false
}
}
}
#[allow(unconditional_panic)]
const fn does_convspec_match_arg<T: PrintfArgsList>(
spec: ConversionSpecification,
fmt: &[c_char],
next_idx: usize,
panic_on_false: bool,
) -> bool {
use ConvSpecifier as CS;
use LengthModifier as LM;
if T::IS_EMPTY {
if panic_on_false {
panic!("Wrong number of arguments for conversion!");
}
return false;
}
if spec.width_is_arg {
if !T::First::IS_INT || !T::First::IS_SIGNED {
if panic_on_false {
panic!("Bad argument for starred width!");
}
return false;
}
return does_convspec_match_arg::<T::Rest>(
ConversionSpecification { width_is_arg: false, ..spec },
fmt,
next_idx,
panic_on_false,
);
}
if spec.precision_is_arg {
if !T::First::IS_INT || !T::First::IS_SIGNED {
if panic_on_false {
panic!("Bad argument for starred precision!");
}
return false;
}
return does_convspec_match_arg::<T::Rest>(
ConversionSpecification { precision_is_arg: false, ..spec },
fmt,
next_idx,
panic_on_false,
);
}
match spec.specifier {
CS::Integer => {
let is_compatible_type = match spec.length_modifier {
None => T::First::IS_INT,
Some(LM::CharLen) => T::First::IS_CHAR,
Some(LM::Short) => T::First::IS_SHORT,
Some(LM::Long) => T::First::IS_LONG,
Some(LM::LongLong) => T::First::IS_LONG_LONG,
#[cfg(any(feature = "libc", test, all(doc, feature = "doccfg")))]
Some(LM::Max) => T::First::IS_MAX,
#[cfg(any(feature = "libc", test, all(doc, feature = "doccfg")))]
Some(LM::Size) => T::First::IS_SIZE,
#[cfg(any(feature = "libc", test, all(doc, feature = "doccfg")))]
Some(LM::Ptrdiff) => T::First::IS_PTRDIFF,
Some(LM::LongDouble) => false,
};
if !is_compatible_type {
if panic_on_false {
panic!("Integer width mismatch in specification!");
}
return false;
}
}
s @ (CS::Double | CS::Char | CS::String | CS::Pointer) => {
let is_compatible_type = match s {
CS::Double => T::First::IS_FLOAT,
CS::Char => T::First::IS_CHAR,
CS::String => T::First::IS_C_STRING,
CS::Pointer => T::First::IS_POINTER,
_ => {
return false;
}
};
if let Some(_) = spec.length_modifier {
if panic_on_false {
panic!("Unsupported length modifier!");
}
return false;
}
if !is_compatible_type {
if panic_on_false {
panic!("printf(3) specifier mismatch!");
}
return false;
}
}
};
does_fmt_match_args_list::<T::Rest>(fmt, next_idx, panic_on_false)
}
const fn next_conversion_specification(fmt: &[c_char], start_idx: usize) -> Option<usize> {
let len = fmt.len();
let mut i: usize = start_idx;
if len == 0 {
return None;
}
while i < len {
if fmt[i] == c(b'%') {
if i < len - 1 && fmt[i + 1] == c(b'%') {
i += 2;
} else {
return Some(i);
}
} else {
i += 1;
}
}
None
}
const fn parse_conversion_specification(
fmt: &[c_char],
start_idx: usize,
) -> Result<(ConversionSpecification, usize), ()> {
use ConvSpecifier::*;
use LengthModifier::*;
let len = fmt.len();
if len < 2 || start_idx > len - 2 {
return Err(());
}
if fmt[start_idx] != c(b'%') {
return Err(());
}
let mut i = start_idx + 1;
while i < len {
match fmt[i] as u8 {
b'\'' | b'-' | b'+' | b'#' | b'0' | b' ' => (),
_ => {
break;
}
};
i += 1;
}
if i >= len {
return Err(());
}
let width_is_arg = match fmt[i] as u8 {
b'*' => {
i += 1;
true
}
_ => {
while i < len && (fmt[i] as u8).is_ascii_digit() {
i += 1;
}
false
}
};
if i >= len {
return Err(());
}
let precision_is_arg = if fmt[i] != c(b'.') {
false
} else {
i += 1;
if i >= len {
return Err(());
}
if fmt[i] == c(b'*') {
i += 1;
true
} else {
while i < len && (fmt[i] as u8).is_ascii_digit() {
i += 1;
}
false
}
};
if i >= len {
return Err(());
}
let length_modifier: Option<LengthModifier> = match fmt[i] as u8 {
b'h' => {
i += 1;
if i < len && fmt[i] == c(b'h') {
i += 1;
Some(CharLen)
} else {
Some(Short)
}
}
b'l' => {
i += 1;
if i < len && fmt[i] == c(b'l') {
i += 1;
Some(LongLong)
} else {
Some(Long)
}
}
#[cfg(any(feature = "libc", test, all(doc, feature = "doccfg")))]
b'j' => {
i += 1;
Some(Max)
}
#[cfg(any(feature = "libc", test, all(doc, feature = "doccfg")))]
b'z' => {
i += 1;
Some(Size)
}
#[cfg(any(feature = "libc", test, all(doc, feature = "doccfg")))]
b't' => {
i += 1;
Some(Ptrdiff)
}
b'L' => {
i += 1;
Some(LongDouble)
}
_ => None,
};
if i >= len {
return Err(());
}
let spec: ConvSpecifier = match fmt[i] as u8 {
b'd' | b'i' | b'o' | b'u' | b'x' | b'X' => Integer,
b'f' | b'F' | b'e' | b'E' | b'g' | b'G' | b'a' | b'A' => Double,
b'c' => Char,
b's' => String,
b'p' => Pointer,
_ => {
return Err(());
}
};
let conv = ConversionSpecification {
width_is_arg: width_is_arg,
precision_is_arg: precision_is_arg,
length_modifier: length_modifier,
specifier: spec,
};
Ok((conv, i + 1))
}
const fn is_null_terminated(s: &[c_char]) -> bool {
let mut i: usize = 0;
while i < s.len() {
if s[i] == c(b'\0') {
return i == (s.len() - 1);
}
i += 1;
}
false
}