use crate::error::{FormatError, Result};
use crate::fmt::violence;
use crate::format_spec::{self, Argument, Count, FormatSpec, Precision};
use crate::prelude::*;
pub struct Parameter<'ident, 'formattable> {
pub identifier: &'ident str,
pub formattable: Formattable<'formattable>,
}
#[macro_export]
macro_rules! _param {
($param_name:ident = $param:expr) => {
$crate::fmt::format::Parameter {
identifier: ::core::stringify!($param_name),
formattable: $crate::formattable::into_formattable!($param),
}
};
($param:expr) => {
$crate::fmt::format::Parameter {
identifier: ::core::stringify!($param),
formattable: $crate::formattable::into_formattable!($param),
}
};
}
pub use _param as param;
#[macro_export]
macro_rules! _params {
() => ([]);
($name:ident = $value:expr) => (
[$crate::fmt::format::param!($name = $value)]
);
($value:expr) => (
[$crate::fmt::format::param!($value)]
);
($($name:ident = $value:expr),*) => (
[$($crate::fmt::format::param!($name = $value)),*]
);
(@acc [$($acc:tt)*];) => ([$($acc)*]);
(@acc [$($acc:tt)*]; $name:ident = $value:expr) => (
[$($acc)*, $crate::fmt::format::param!($name = $value)]
);
(@acc [$($acc:tt)*]; $value:expr) => (
[$($acc)*, $crate::fmt::format::param!($value)]
);
(@acc [$($acc:tt)*]; $name:ident = $value:expr, $($tail:tt)*) => (
$crate::fmt::format::params!(@acc [$($acc)*, $crate::fmt::format::param!($name = $value)]; $($tail)*)
);
(@acc [$($acc:tt)*]; $value:expr, $($tail:tt)*) => (
$crate::fmt::format::params!(@acc [$($acc)*, $crate::fmt::format::param!($value)]; $($tail)*)
);
($name:ident = $value:expr, $($tail:tt)*) => (
$crate::fmt::format::params!(@acc [$crate::fmt::format::param!($name = $value)]; $($tail)*)
);
($value:expr, $($tail:tt)*) => (
$crate::fmt::format::params!(@acc [$crate::fmt::format::param!($value)]; $($tail)*)
);
}
pub use _params as params;
pub fn format_string(fmt: &str, params: &[Parameter]) -> Result<String> {
let mut result = String::new();
let mut param_iter = params.iter();
let mut chars = fmt.char_indices().peekable();
while let Some((i, c)) = chars.next() {
if c == '{' {
if let Some((_, '{')) = chars.peek() {
chars.next();
result.push('{');
} else {
let mut format = String::new();
let mut found = false;
for (_, next) in chars.by_ref() {
if next == '}' {
found = true;
break;
}
format.push(next);
}
if !found {
return Err(FormatError::UnmatchedOpeningBrace(i, fmt.to_string()));
}
format_and_write_value(&mut result, &format, params, &mut param_iter)?;
}
} else if c == '}' {
if let Some((_, '}')) = chars.peek() {
chars.next();
result.push('}');
} else {
return Err(FormatError::UnmatchedClosingBrace(i, fmt.to_string()));
}
} else {
result.push(c);
}
}
Ok(result)
}
#[derive(Debug, PartialEq)]
struct Format<'format_string> {
argument: Option<Argument<'format_string>>,
format_spec: FormatSpec<'format_string>,
}
fn format_and_write_value<
'param,
'ident,
'formattable,
W: std::fmt::Write,
I: Iterator<Item = &'param Parameter<'ident, 'formattable>>,
>(
dst: &mut W,
format: &str,
app_params: &'param [Parameter<'ident, 'formattable>],
params_iter: &mut I,
) -> Result<()>
where
'ident: 'param,
'formattable: 'param,
{
let format = parse_format(format)?;
let format_spec = &format.format_spec;
let precision = resolve_precision_value(&format_spec.precision, app_params, params_iter)?;
let parameter = get_parameter_for_formatting(&format.argument, app_params, params_iter)?;
let width = resolve_width_value(format_spec, app_params)?.unwrap_or(0);
let out = match format_spec.r#type {
format_spec::Type::Binary => {
violence::format_binary(format_spec, precision, width, parameter)
}
format_spec::Type::Custom(_) => {
let as_custom = parameter.formattable.try_as_custom().ok_or_else(|| {
FormatError::TraitNotImplemented(parameter.identifier.to_string(), "Custom")
})?;
as_custom.format(format_spec, precision, width, parameter)
}
format_spec::Type::Debug => {
violence::format_debug(format_spec, precision, width, parameter)
}
format_spec::Type::DebugLowerHex => {
violence::format_debug_lower_hex(format_spec, precision, width, parameter)
}
format_spec::Type::DebugUpperHex => {
violence::format_debug_upper_hex(format_spec, precision, width, parameter)
}
format_spec::Type::Display => {
violence::format_display(format_spec, precision, width, parameter)
}
format_spec::Type::LowerExp => {
violence::format_lower_exp(format_spec, precision, width, parameter)
}
format_spec::Type::LowerHex => {
violence::format_lower_hex(format_spec, precision, width, parameter)
}
format_spec::Type::Octal => {
violence::format_octal(format_spec, precision, width, parameter)
}
format_spec::Type::Pointer => {
violence::format_pointer(format_spec, precision, width, parameter)
}
format_spec::Type::UpperExp => {
violence::format_upper_exp(format_spec, precision, width, parameter)
}
format_spec::Type::UpperHex => {
violence::format_upper_hex(format_spec, precision, width, parameter)
}
}?;
let mut fill = [0; 4];
let out = out.replace("\u{001A}", format_spec.fill.encode_utf8(&mut fill));
write!(dst, "{out}")?;
Ok(())
}
fn resolve_precision_value<
'param,
'ident,
'formattable,
I: Iterator<Item = &'param Parameter<'ident, 'formattable>>,
>(
precision: &Option<Precision>,
params: &'param [Parameter<'ident, 'formattable>],
params_iter: &mut I,
) -> Result<Option<usize>>
where
'ident: 'param,
'formattable: 'param,
{
if let Some(precision) = precision {
let precision = match precision {
Precision::Star => params_iter.next().ok_or(FormatError::NotEnoughParameters)?,
Precision::Count(Count::Argument(a)) => lookup_parameter_by_argument(a, params)?,
Precision::Count(Count::Integer(i)) => return Ok(Some(*i)),
};
let param = precision
.formattable
.try_as_usize()
.ok_or(FormatError::PrecisionNotUsize)?
.as_usize();
Ok(Some(param))
} else {
Ok(None)
}
}
fn resolve_width_value<'param, 'ident, 'formattable>(
format_spec: &FormatSpec,
params: &'param [Parameter<'ident, 'formattable>],
) -> Result<Option<usize>>
where
'ident: 'param,
'formattable: 'param,
{
Ok(match &format_spec.width {
Some(w) => {
let param = match w {
Count::Argument(a) => lookup_parameter_by_argument(a, params)?,
Count::Integer(i) => return Ok(Some(*i)),
};
Some(
param
.formattable
.try_as_usize()
.ok_or(FormatError::WidthNotUsize)?
.as_usize(),
)
}
None => None,
})
}
fn get_parameter_for_formatting<
'param,
'ident,
'formattable,
I: Iterator<Item = &'param Parameter<'ident, 'formattable>>,
>(
argument: &Option<Argument>,
params: &'param [Parameter<'ident, 'formattable>],
params_iter: &mut I,
) -> Result<&'param Parameter<'ident, 'formattable>>
where
'ident: 'param,
'formattable: 'param,
{
Ok(match argument {
None => params_iter.next().ok_or(FormatError::NotEnoughParameters)?,
Some(a) => lookup_parameter_by_argument(a, params)?,
})
}
fn lookup_parameter_by_argument<'param, 'ident, 'formattable>(
argument: &Argument,
parameters: &'param [Parameter<'ident, 'formattable>],
) -> Result<&'param Parameter<'ident, 'formattable>> {
match argument {
Argument::Identifier(i) => parameters
.iter()
.find(|a| a.identifier == *i)
.ok_or_else(|| FormatError::NoNamedParameter(i.to_string())),
Argument::Integer(i) => parameters
.get(*i)
.ok_or(FormatError::NoPositionalParameter(*i)),
}
}
fn parse_format(format: &str) -> Result<Format> {
let (argument, format_spec) = format.split_once(':').unwrap_or((format, ""));
let argument = if argument.is_empty() {
None
} else if argument.chars().all(|c| c.is_ascii_digit()) {
Some(Argument::Integer(argument.parse::<usize>()?))
} else {
Some(Argument::Identifier(argument))
};
let format_spec = format_spec::parse_format_spec(format_spec)?;
Ok(Format {
argument,
format_spec,
})
}
#[cfg(test)]
mod tests {
use std::f64::consts::PI;
use crate::{
fmt::{Custom, format::Parameter},
format_spec::FormatSpec,
prelude::*,
};
macro_rules! assert_format {
($fmt:expr) => {
::core::assert_eq!(
::std::format!($fmt),
$crate::prelude::rformat!($fmt).unwrap()
)
};
($fmt:expr, $($args:tt)*) => {
::core::assert_eq!(
::std::format!($fmt, $($args)*),
$crate::prelude::rformat!($fmt, $($args)*).unwrap()
)
};
}
#[test]
fn format_parameters() {
assert_format!("{}{}{}", 5, 6, 7);
}
#[test]
fn format_positional_parameters() {
assert_format!("{2}{1}{0}", 5, 6, 7);
}
#[test]
fn format_named_parameters() {
let param_1 = 5;
let param_2 = 6;
let param_3 = 7;
assert_eq!(
format!("{param_2}{param_1}{param_3}"),
rformat!("{param_2}{param_1}{param_3}", param_1, param_2, param_3).unwrap()
);
}
#[test]
fn format_width() {
let width = 10;
assert_format!("{:5}{:3$}{:w$}", 5, 6, 7, 3, w = width);
}
#[test]
fn format_precision() {
let precision: usize = 3;
assert_format!("{0:.5} {0:.1$} {0:.p$}", 123.456789, 2, p = precision);
}
#[test]
fn test_alignment_specifications() {
assert_format!("{:<5}", 42);
assert_format!("{:>5}", 42);
assert_format!("{:^5}", 42);
let array = [1, 2, 3];
assert_format!("{:<5?}", array);
assert_format!("{:>5?}", array);
assert_format!("{:^5?}", array);
}
#[test]
fn test_fill_character() {
assert_format!("{:*>7}", 42);
assert_format!("{:A<7}", 42);
assert_format!("{:🎉^7}", 42);
let array = [1, 2, 3];
assert_format!("{:*<5?}", array);
assert_format!("{:A>5?}", array);
assert_format!("{:🎉^5?}", array);
}
#[test]
fn test_number_formatting() {
assert_format!("{:+}", 42);
assert_format!("{:+}", -42i64);
assert_format!("{:-}", 42);
assert_format!("{:-}", -42i64);
assert_format!("{}", 42i64);
assert_format!("{}", -42i64);
assert_format!("{:#o}", 42);
assert_format!("{:#x}", 42);
assert_format!("{:04}", 42);
assert_format!("{:.2}", 42.5);
}
#[test]
fn test_all_format_types() {
let integer = 42;
let negative = -42i32;
let float = PI;
let string = "hello";
let character = 'A';
let boolean = true;
let array = [1, 2, 3];
assert_format!("{:}", integer);
assert_format!("{}", boolean);
assert_format!("{:?}", integer);
assert_format!("{:?}", string);
assert_format!("{:?}", array);
assert_format!("{:x}", integer);
assert_format!("{:x}", negative);
assert_format!("{:X}", integer);
assert_format!("{:X}", negative);
assert_format!("{:o}", integer);
assert_format!("{:b}", integer);
assert_format!("{:e}", float);
assert_format!("{:E}", float);
let ptr = &character;
assert_format!("{:p}", ptr);
}
#[test]
fn test_precision_variations() {
let float = PI;
let string = "hello";
assert_format!("{:.3}", float);
assert_format!("{:.1}", float);
assert_format!("{:.0}", float);
assert_format!("{:.2}", string);
assert_format!("{:.10}", string);
assert_format!("{:6.2}", float);
assert_format!("{:<6.3}", string);
let precision = 2;
assert_format!("{:.1$}", float, precision);
}
#[test]
fn test_complex_combinations() {
assert_format!("{:+05}", 42);
assert_format!("{:*^+7}", 42);
assert_format!("{:7.2}", PI);
assert_format!("{:<7.2}", PI);
assert_format!("{:#06x}", 42);
assert_format!("{:#6x}", 42);
assert_format!("{:#06X}", 42);
assert_format!("{:06.2}", PI);
assert_format!("{:#>6.2}", PI);
assert_format!("{:>+5}", 42);
assert_format!("{:^+5}", 42);
assert_format!("{:^5}", -42i32);
}
#[test]
fn test_sign_options() {
assert_format!("{}", 42);
assert_format!("{:+}", 42);
assert_format!("{:-}", 42);
assert_format!("{}", -42i64);
assert_format!("{:+}", -42i64);
assert_format!("{:-}", -42i64);
assert_format!("{}", 0);
assert_format!("{:+}", 0);
assert_format!("{:-}", 0);
}
#[test]
fn test_nested_formats() {
let width = 10;
let precision = 2;
let number = PI;
assert_format!("{:1$.2$}", number, width, precision);
assert_format!("{:>wi$.pre$}", number, wi = width, pre = precision);
}
#[test]
fn test_alternate_forms() {
assert_format!("{:#b}", 42);
assert_format!("{:#o}", 42);
assert_format!("{:#x}", 42);
assert_format!("{:#X}", 42);
assert_format!("{:#}", 3.0);
assert_format!("{:.0}", 3.0);
assert_format!("{:#.0}", 3.0);
}
#[test]
fn test_debug_formatting() {
#[derive(Debug)]
struct TestStruct {
_field: i32,
}
#[derive(Debug)]
struct TestStruct2 {
_struct: TestStruct,
_field: i32,
}
let test_struct = TestStruct2 {
_struct: TestStruct { _field: 24 },
_field: 42,
};
let test_struct_ref = &test_struct;
assert_format!("{:?}", test_struct_ref);
assert_format!("{:#?}", test_struct_ref);
assert_format!("{:❌^#20?}", test_struct_ref);
}
#[test]
fn test_unicode_fill() {
assert_format!("{:♥>7}", 42);
assert_format!("{:♥<7}", 42);
assert_format!("{:♥^7}", 42);
assert_format!("{:😊^7}", 42);
}
#[test]
fn test_format_errors() {
assert!(rformat!("}", 42).is_err()); assert!(rformat!("{", 42).is_err()); assert!(rformat!("{} {}", 42).is_err()); assert!(rformat!("{1}", 42).is_err()); assert!(rformat!("{unknown}", 42).is_err()); assert!(rformat!("{:p}", 42).is_err()); }
#[test]
fn test_pointer() {
let param = 42;
let param_ref = ¶m;
assert_format!("{:p}", param_ref);
}
#[test]
#[should_panic]
fn test_exception() {
assert_format!("{:6.3}", "hi");
}
impl Custom for i32 {
fn format(
&self,
format_spec: &FormatSpec,
_precision: Option<usize>,
_width: usize,
_parameter: &Parameter,
) -> crate::error::Result<String> {
match format_spec.r#type {
crate::format_spec::Type::Custom("custom") => Ok(format!("custom: {}", self)),
_ => Err(crate::error::FormatError::UnsupportedFormatType(
format_spec.r#type.to_str().to_string(),
)),
}
}
}
#[test]
fn test_custom() {
assert_eq!(rformat!("{:custom}", 42i32).unwrap(), "custom: 42");
}
#[test]
fn test_custom_unsupported() {
assert!(rformat!("{:unsupported}", 42i32).is_err());
}
#[test]
fn test_custom_notimplemented() {
assert!(rformat!("{:custom}", 42u32).is_err());
}
}