diff --git a/src/fmtf64.rs b/src/fmtf64.rs deleted file mode 100644 index 3e3911b..0000000 --- a/src/fmtf64.rs +++ /dev/null @@ -1,62 +0,0 @@ -use std::fmt::Write; -use std::string::String; - -use types::*; -use formatter::Formatter; - - -/// implement formatting of f64 -impl<'a, 'b> Formatter<'a, 'b> { - /// format the given string onto the buffer - pub fn f64(&mut self, f: f64) -> Result<()> { - let ty = match self.ty() { - None => 'f', - Some(c) => c, - }; - if !self.is_float_type() { - let mut msg = String::new(); - write!(msg, "Unknown format code {:?} for object of type f64", ty).unwrap(); - return Err(FmtError::TypeError(msg)); - } - if self.alternate() { - return Err(FmtError::TypeError("Alternate form (#) not allowed in f64 format \ - specifier" - .to_string())); - } else if self.thousands() { - return Err(FmtError::Invalid("thousands specifier not yet supported".to_string())); - } else if self.fill() == '0' && self.align() == Alignment::Right { - return Err(FmtError::Invalid("sign aware 0 padding not yet supported".to_string())); - } - let mut s = String::new(); - - // handle the sign - if f >= 0_f64 && self.sign_plus() { - self.write_str("+").unwrap(); - } - - match self.precision() { - None => { - match ty { - 'f' => write!(s, "{}", f).unwrap(), - 'e' => write!(s, "{:e}", f).unwrap(), - 'E' => write!(s, "{:E}", f).unwrap(), - _ => unreachable!(), - } - } - Some(p) => { - match ty { - 'f' => write!(s, "{:.*}", p, f).unwrap(), - 'e' => write!(s, "{:.*e}", p, f).unwrap(), - 'E' => write!(s, "{:.*E}", p, f).unwrap(), - _ => unreachable!(), - } - } - } - - let prev_prec = self.precision(); - self.set_precision(None); - let out = self.str_unchecked(s.as_str()); - self.set_precision(prev_prec); - out - } -} diff --git a/src/fmti64.rs b/src/fmti64.rs deleted file mode 100644 index 4340663..0000000 --- a/src/fmti64.rs +++ /dev/null @@ -1,59 +0,0 @@ - -use std::fmt::Write; -use std::string::String; - -use types::*; -use formatter::Formatter; - - -/// implement formatting of f64 -impl<'a, 'b> Formatter<'a, 'b> { - /// format the given string onto the buffer - pub fn i64(&mut self, i: i64) -> Result<()> { - let ty = match self.ty() { - None => ' ', - Some(c) => c, - }; - if !self.is_int_type() { - let mut msg = String::new(); - write!(msg, "Unknown format code {:?} for object of type i64", ty).unwrap(); - return Err(FmtError::TypeError(msg)); - } - if self.precision() != None { - return Err(FmtError::TypeError("precision not allowed for integers".to_string())); - } else if self.thousands() { - return Err(FmtError::Invalid("thousands specifier not yet supported".to_string())); - } else if self.fill() == '0' && self.align() == Alignment::Right { - return Err(FmtError::Invalid("sign aware 0 padding not yet supported".to_string())); - } - let mut s = String::new(); - - // handle the sign - if i >= 0 && self.sign_plus() { - self.write_str("+").unwrap(); - } - if self.alternate() { - match ty { - 'b' => self.write_str("0b").unwrap(), - 'o' => self.write_str("0o").unwrap(), - 'x' | 'X' => self.write_str("0x").unwrap(), - _ => { - let mut msg = String::new(); - write!(msg, "alternate ('#') cannot be used with type {:?}", ty).unwrap(); - return Err(FmtError::Invalid(msg)); - } - } - } - - match ty { - ' ' => write!(s, "{}", i).unwrap(), - 'b' => write!(s, "{:b}", i).unwrap(), - 'o' => write!(s, "{:o}", i).unwrap(), - 'x' => write!(s, "{:x}", i).unwrap(), - 'X' => write!(s, "{:X}", i).unwrap(), - _ => unreachable!(), - } - - self.str_unchecked(s.as_str()) - } -} diff --git a/src/fmtnum.rs b/src/fmtnum.rs new file mode 100644 index 0000000..1f430d7 --- /dev/null +++ b/src/fmtnum.rs @@ -0,0 +1,121 @@ +macro_rules! fmtint { + ($($t:ident)*) => ($( + #[allow(unused_comparisons)] + impl<'a, 'b> Formatter<'a, 'b> { + pub fn $t(&mut self, x: $t) -> Result<()> { + let ty = match self.ty() { + None => ' ', + Some(c) => c, + }; + + if !self.is_int_type() { + let mut msg = String::new(); + write!(msg, "Unknown format code {:?} for type", ty).unwrap(); + return Err(FmtError::TypeError(msg)); + } + + if self.precision() != None { + return Err(FmtError::TypeError("precision not allowed for integers".to_string())); + } + + if self.thousands() { + return Err(FmtError::Invalid("thousands specifier not yet supported".to_string())); + } + + if self.fill() == '0' && self.align() == Alignment::Right { + return Err(FmtError::Invalid("sign aware 0 padding not yet supported".to_string())); + } + + let mut s = String::new(); + + if x >= 0 && self.sign_plus() { + self.write_str("+").unwrap(); + } + + if self.alternate() { + match ty { + 'b' => self.write_str("0b").unwrap(), + 'o' => self.write_str("0o").unwrap(), + 'x' | 'X' => self.write_str("0x").unwrap(), + _ => { + let mut msg = String::new(); + write!(msg, "alternate ('#') cannot be used with type {:?}", ty).unwrap(); + return Err(FmtError::Invalid(msg)); + } + } + } + + match ty { + ' ' => write!(s, "{}", x).unwrap(), + 'b' => write!(s, "{:b}", x).unwrap(), + 'o' => write!(s, "{:o}", x).unwrap(), + 'x' => write!(s, "{:x}", x).unwrap(), + 'X' => write!(s, "{:X}", x).unwrap(), + _ => unreachable!(), + } + + self.str_unchecked(s.as_str()) + } + })*) +} + +macro_rules! fmtfloat { + ($($t:ident)*) => ($( + impl<'a, 'b> Formatter<'a, 'b> { + pub fn $t(&mut self, x: $t) -> Result<()> { + let ty = match self.ty() { + None => 'f', + Some(c) => c, + }; + + if !self.is_float_type() { + let mut msg = String::new(); + write!(msg, "Unknown format code {:?} for type", ty).unwrap(); + return Err(FmtError::TypeError(msg)); + } + + if self.alternate() { + return Err(FmtError::TypeError("Alternate form (#) not allowed for floats".to_string())); + } + + if self.thousands() { + return Err(FmtError::Invalid("thousands specifier not yet supported".to_string())); + } + + if self.fill() == '0' && self.align() == Alignment::Right { + return Err(FmtError::Invalid("sign aware 0 padding not yet supported".to_string())); + } + + let mut s = String::new(); + + if x >= (0 as $t) && self.sign_plus() { + self.write_str("+").unwrap(); + } + + match self.precision() { + None => { + match ty { + 'f' => write!(s, "{}", x).unwrap(), + 'e' => write!(s, "{:e}", x).unwrap(), + 'E' => write!(s, "{:E}", x).unwrap(), + _ => unreachable!(), + } + } + Some(p) => { + match ty { + 'f' => write!(s, "{:.*}", p, x).unwrap(), + 'e' => write!(s, "{:.*e}", p, x).unwrap(), + 'E' => write!(s, "{:.*E}", p, x).unwrap(), + _ => unreachable!(), + } + } + } + + let prev_prec = self.precision(); + self.set_precision(None); + let out = self.str_unchecked(s.as_str()); + self.set_precision(prev_prec); + out + } + })*) +} diff --git a/src/lib.rs b/src/lib.rs index d169046..f9bc4e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,13 +10,17 @@ mod tests; mod types; mod formatter; mod fmtstr; -mod fmtf64; -mod fmti64; + +#[macro_use] +mod fmtnum; pub use types::{Result, FmtError, Alignment, Sign}; pub use fmtstr::strfmt_map; pub use formatter::Formatter; +// u128 & i128 unstable (see https://github.com/rust-lang/rust/issues/35118) +fmtint!(u8 i8 u16 i16 u32 i32 u64 i64 usize isize); +fmtfloat!(f32 f64); /// Rust-style format a string given a `HashMap` of the variables. pub fn strfmt(fmtstr: &str, vars: &HashMap) -> Result { diff --git a/src/tests/strfmt.rs b/src/tests/strfmt.rs index 8e31adc..93809b7 100644 --- a/src/tests/strfmt.rs +++ b/src/tests/strfmt.rs @@ -179,89 +179,150 @@ fn test_ignore_missing() { run_tests(&values, &vars, &strfmt_ignore); } -#[test] -fn test_f64() { - let mut vars: HashMap = HashMap::new(); - vars.insert("x".to_string(), 42.4242); - vars.insert("y".to_string(), -100.11111); - vars.insert("z".to_string(), 0.); - let values: Vec<(&str, &str, u8)> = vec![ - // simple valid - ("{x}", "42.4242", 0), - ("{x:.2}", "42.42", 0), - ("{x:<7.2}", "42.42 ", 0), - ("{x:.2e}", "4.24e1", 0), - ("{x:.2E}", "4.24E1", 0), - ("{x:+}", "+42.4242", 0), - ("{y:.2E}", "-1.00E2", 0), - ("{y:+.2E}", "-1.00E2", 0), - ("{z:+.2E}", "+0.00E0", 0), - - // invalid - ("{x:s}", "", 3), - ("{x:#}", "", 3), - - // TODO - ("{x:+010.2}", "+0042.4242", 1), - ]; - let f = |mut fmt: Formatter| { - match vars.get(fmt.key) { - Some(v) => fmt.f64(*v), - None => panic!(), - } - }; - - let strfmt_f64 = |fmtstr: &str, vars: &HashMap| -> Result { - strfmt_map(fmtstr, &f) - }; - run_tests(&values, &vars, &strfmt_f64); +macro_rules! test_float { + ($($name:ident $t:ident),*) => ($( + #[test] + fn $name() { + let mut vars: HashMap = HashMap::new(); + vars.insert("x".to_string(), 42.4242); + vars.insert("y".to_string(), -100.11111); + vars.insert("z".to_string(), 0.); + let values: Vec<(&str, &str, u8)> = vec![ + // simple valid + ("{x}", "42.4242", 0), + ("{x:.2}", "42.42", 0), + ("{x:<7.2}", "42.42 ", 0), + ("{x:.2e}", "4.24e1", 0), + ("{x:.2E}", "4.24E1", 0), + ("{x:+}", "+42.4242", 0), + ("{y:.2E}", "-1.00E2", 0), + ("{y:+.2E}", "-1.00E2", 0), + ("{z:+.2E}", "+0.00E0", 0), + + // invalid + ("{x:s}", "", 3), + ("{x:#}", "", 3), + + // TODO + ("{x:+010.2}", "+0042.4242", 1), + ]; + let f = |mut fmt: Formatter| { + match vars.get(fmt.key) { + Some(v) => fmt.$t(*v), + None => panic!(), + } + }; + + let strfmt_float = |fmtstr: &str, vars: &HashMap| -> Result { + strfmt_map(fmtstr, &f) + }; + run_tests(&values, &vars, &strfmt_float); + } + )*) } +test_float!(test_f32 f32, test_f64 f64); + +macro_rules! test_uint { + ($($name:ident $t:ident),*) => ($( + #[test] + fn $name() { + let mut vars: HashMap = HashMap::new(); + vars.insert("x".to_string(), 42); + vars.insert("y".to_string(), 0); + let values: Vec<(&str, &str, u8)> = vec![ + ("{x}", "42", 0), + ("{x:<7}", "42 ", 0), + ("{x:>7}", " 42", 0), + ("{x:^7}", " 42 ", 0), + ("{x:x}", "2a", 0), + ("{x:X}", "2A", 0), + ("{x:+x}", "+2a", 0), + ("{x:#x}", "0x2a", 0), + ("{x:#X}", "0x2A", 0), + ("{x:b}", "101010", 0), + ("{x:#b}", "0b101010", 0), + ("{x:o}", "52", 0), + ("{x:#o}", "0o52", 0), + + ("{x:+}", "+42", 0), + ("{y:-}", "0", 0), + ("{y:+}", "+0", 0), + + // invalid + ("{x:.2}", "", 3), + ("{x:s}", "", 3), + + // TODO + ("{x:+010}", "+000000042", 1), + ]; + let f = |mut fmt: Formatter| { + match vars.get(fmt.key) { + Some(v) => fmt.$t(*v), + None => panic!(), + } + }; + + let strfmt_int = |fmtstr: &str, vars: &HashMap| -> Result { + strfmt_map(fmtstr, &f) + }; + run_tests(&values, &vars, &strfmt_int); + } + )*) +} -#[test] -fn test_i64() { - let mut vars: HashMap = HashMap::new(); - vars.insert("x".to_string(), 42); - vars.insert("y".to_string(), -100); - vars.insert("z".to_string(), 0); - let values: Vec<(&str, &str, u8)> = vec![ - // simple valid - ("{x}", "42", 0), - ("{x:<7}", "42 ", 0), - ("{x:X}", "2A", 0), - ("{x:#x}", "0x2a", 0), - ("{x:#X}", "0x2A", 0), - ("{x:b}", "101010", 0), - ("{x:#b}", "0b101010", 0), - ("{x:o}", "52", 0), - ("{x:#o}", "0o52", 0), - - ("{x:+}", "+42", 0), - ("{y}", "-100", 0), - ("{y:+}", "-100", 0), - ("{z}", "0", 0), - ("{z:+}", "+0", 0), - - // invalid - ("{x:.2}", "", 3), - ("{x:s}", "", 3), - - // TODO - ("{x:+010}", "+000000042", 1), - ]; - let f = |mut fmt: Formatter| { - match vars.get(fmt.key) { - Some(v) => fmt.i64(*v), - None => panic!(), +macro_rules! test_int { + ($($name:ident $t:ident),*) => ($( + #[test] + fn $name() { + let mut vars: HashMap = HashMap::new(); + vars.insert("x".to_string(), 42); + vars.insert("y".to_string(), -100); + vars.insert("z".to_string(), 0); + let values: Vec<(&str, &str, u8)> = vec![ + // simple valid + ("{x}", "42", 0), + ("{x:<7}", "42 ", 0), + ("{x:X}", "2A", 0), + ("{x:#x}", "0x2a", 0), + ("{x:#X}", "0x2A", 0), + ("{x:b}", "101010", 0), + ("{x:#b}", "0b101010", 0), + ("{x:o}", "52", 0), + ("{x:#o}", "0o52", 0), + + ("{x:+}", "+42", 0), + ("{y}", "-100", 0), + ("{y:+}", "-100", 0), + ("{z}", "0", 0), + ("{z:-}", "0", 0), + ("{z:+}", "+0", 0), + + // invalid + ("{x:.2}", "", 3), + ("{x:s}", "", 3), + + // TODO + ("{x:+010}", "+000000042", 1), + ]; + let f = |mut fmt: Formatter| { + match vars.get(fmt.key) { + Some(v) => fmt.$t(*v), + None => panic!(), + } + }; + + let strfmt_uint = |fmtstr: &str, vars: &HashMap| -> Result { + strfmt_map(fmtstr, &f) + }; + run_tests(&values, &vars, &strfmt_uint); } - }; - - let strfmt_f64 = |fmtstr: &str, vars: &HashMap| -> Result { - strfmt_map(fmtstr, &f) - }; - run_tests(&values, &vars, &strfmt_f64); + )*) } +test_uint!(test_u8 u8, test_u16 u16, test_u32 u32, test_u64 u64, test_usize usize); +test_int!(test_i8 i8, test_i16 i16, test_i32 i32, test_i64 i64, test_isize isize); + // #[bench] // fn bench_strfmt(b: &mut Bencher) { // let mut vars: HashMap = HashMap::new();