diff --git a/src/liballoc/boxed.rs b/src/liballoc/boxed.rs index 13d4a0a1f0a08..e2c1af8a2dafe 100644 --- a/src/liballoc/boxed.rs +++ b/src/liballoc/boxed.rs @@ -17,7 +17,7 @@ use core::default::Default; use core::fmt; use core::intrinsics; use core::mem; -use core::option::Option; +use core::option::{Option, Some}; use core::raw::TraitObject; use core::result::{Ok, Err, Result}; @@ -130,12 +130,22 @@ impl fmt::Show for Box { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { (**self).fmt(f) } + + #[inline] + fn formatter_len_hint(&self) -> Option { + (**self).formatter_len_hint() + } } impl fmt::Show for Box { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.pad("Box") } + + #[inline] + fn formatter_len_hint(&self) -> Option { + Some(8) + } } #[cfg(test)] diff --git a/src/libcollections/string.rs b/src/libcollections/string.rs index 6843996a9e145..1d6a10f6a4c63 100644 --- a/src/libcollections/string.rs +++ b/src/libcollections/string.rs @@ -899,6 +899,11 @@ impl fmt::Show for String { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.as_slice().fmt(f) } + + #[inline] + fn formatter_len_hint(&self) -> Option { + self.as_slice().formatter_len_hint() + } } #[experimental = "waiting on Hash stabilization"] diff --git a/src/libcore/fmt/mod.rs b/src/libcore/fmt/mod.rs index 7bab59960b0fe..9016aa860f203 100644 --- a/src/libcore/fmt/mod.rs +++ b/src/libcore/fmt/mod.rs @@ -14,10 +14,13 @@ use any; use cell::{Cell, Ref, RefMut}; +use cmp; use collections::Collection; +use f32; use iter::{Iterator, range}; use kinds::Copy; use mem; +use num::FPNormal; use option::{Option, Some, None}; use ops::Deref; use result::{Ok, Err}; @@ -171,6 +174,11 @@ impl<'a> Show for Arguments<'a> { pub trait Show { /// Formats the value using the given formatter. fn fmt(&self, &mut Formatter) -> Result; + + /// Returns a conservative estimate of the size of the formatted string. + /// + /// A return of `None` means a close estimate is costly or not feasible. + fn formatter_len_hint(&self) -> Option { None } } /// Format trait for the `b` character @@ -260,7 +268,7 @@ macro_rules! uniform_fn_call_workaround { pub fn $name(x: &T, fmt: &mut Formatter) -> Result { x.fmt(fmt) } - )* + )* } } uniform_fn_call_workaround! { @@ -591,6 +599,7 @@ impl<'a, T: Show> Show for &'a mut T { } impl<'a> Show for &'a Show+'a { fn fmt(&self, f: &mut Formatter) -> Result { (*self).fmt(f) } + fn formatter_len_hint(&self) -> Option { #![inline] (*self).formatter_len_hint() } } impl Bool for bool { @@ -705,20 +714,69 @@ macro_rules! floating(($ty:ident) => { floating!(f32) floating!(f64) +fn formatter_len_hint_string(this: &&str) -> Option { + Some(this.len()) +} + +fn formatter_len_hint_bool(_: &bool) -> Option { + Some(5) +} + +fn formatter_len_hint_char(_: &char) -> Option { + Some(4) +} + +static LOG10_2: f32 = f32::consts::LOG10_E / f32::consts::LOG2_E; +static SHIFTED_LOG10_2: i32 = (LOG10_2 * (1u << 16) as f32) as i32; + +fn formatter_len_hint_float_f32(this: &f32) -> Option { + use num::{Float, Signed}; + + match this.classify() { + FPNormal => { + let (_, exponent, _) = this.integer_decode(); + // A fast approximate log_10. Add a small value at the end for the + // sign and decimal separator. + let log_10 = ((exponent as i32 + 23) * SHIFTED_LOG10_2) >> 16; + Some(cmp::max(log_10, -5).abs() as uint + 4) + } + // Otherwise, "+inf" is the longest string we might print. + _ => Some(4) + } +} + +fn formatter_len_hint_float_f64(this: &f64) -> Option { + use num::{Float, Signed}; + + match this.classify() { + FPNormal => { + let (_, exponent, _) = this.integer_decode(); + let log_10 = ((exponent as i32 + 52) * SHIFTED_LOG10_2) >> 16; + Some(cmp::max(log_10, -5).abs() as uint + 4) + } + // Otherwise, "+inf" is the longest string we might print. + _ => Some(4) + } +} + // Implementation of Show for various core types -macro_rules! delegate(($ty:ty to $other:ident) => { +macro_rules! delegate(($ty:ty to $other:ident $($suffix:ident)*) => { impl<'a> Show for $ty { fn fmt(&self, f: &mut Formatter) -> Result { (concat_idents!(secret_, $other)(self, f)) } + #[inline] + fn formatter_len_hint(&self) -> Option { + (concat_idents!(formatter_len_hint_, $other, $($suffix),*)(self)) + } } }) delegate!(&'a str to string) delegate!(bool to bool) delegate!(char to char) -delegate!(f32 to float) -delegate!(f64 to float) +delegate!(f32 to float _f32) +delegate!(f64 to float _f64) impl Show for *const T { fn fmt(&self, f: &mut Formatter) -> Result { secret_pointer(self, f) } @@ -792,6 +850,11 @@ impl Show for () { fn fmt(&self, f: &mut Formatter) -> Result { f.pad("()") } + + #[inline] + fn formatter_len_hint(&self) -> Option { + Some(2) + } } impl Show for Cell { @@ -804,12 +867,22 @@ impl<'b, T: Show> Show for Ref<'b, T> { fn fmt(&self, f: &mut Formatter) -> Result { (**self).fmt(f) } + + #[inline] + fn formatter_len_hint(&self) -> Option { + (**self).formatter_len_hint() + } } impl<'b, T: Show> Show for RefMut<'b, T> { fn fmt(&self, f: &mut Formatter) -> Result { (*(self.deref())).fmt(f) } + + #[inline] + fn formatter_len_hint(&self) -> Option { + (*(self.deref())).formatter_len_hint() + } } // If you expected tests to be here, look instead at the run-pass/ifmt.rs test, diff --git a/src/libcore/fmt/num.rs b/src/libcore/fmt/num.rs index eb8d684fe50fd..3b990540475dd 100644 --- a/src/libcore/fmt/num.rs +++ b/src/libcore/fmt/num.rs @@ -14,10 +14,13 @@ #![allow(unsigned_negate)] +use clone::Clone; use collections::Collection; use fmt; use iter::DoubleEndedIterator; -use num::{Int, cast, zero}; +use mem::size_of; +use num::{Int, Signed, cast, zero}; +use option::{Option, Some}; use slice::{ImmutableSlice, MutableSlice}; /// A type that represents a specific radix @@ -146,13 +149,54 @@ pub fn radix(x: T, base: u8) -> RadixFmt { RadixFmt(x, Radix::new(base)) } +macro_rules! int_base_hint { + ($Trait:ident for $T:ident as $U:ident -> $Radix:ident; $log2log2base:expr, $abs:ident) => { + impl fmt::$Trait for $T { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + $Radix.fmt_int(*self as $U, f) + } + + fn formatter_len_hint(&self) -> Option { + let num = self.$abs(); + let width = size_of::<$T>() * 8; + // Approximate log_2 of the target base. + let log2base = 1 << $log2log2base; + + // Get the number of digits in the target base. + let binary_digits = width - (num | log2base as $T).leading_zeros(); + Some(binary_digits / log2base) + } + } + }; + // Use `clone` on uints as a noop method in place of abs. + ($Trait:ident for $T:ident as $U:ident -> $Radix:ident; $log2log2base:expr) => { + int_base_hint!($Trait for $T as $U -> $Radix; $log2log2base, clone) + } +} macro_rules! radix_fmt { - ($T:ty as $U:ty, $fmt:ident) => { + ($T:ty as $U:ty, $fmt:ident, $abs:ident) => { impl fmt::Show for RadixFmt<$T, Radix> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { RadixFmt(ref x, radix) => radix.$fmt(*x as $U, f) } + let &RadixFmt(x, radix) = self; + radix.$fmt(x as $U, f) + } + + fn formatter_len_hint(&self) -> Option { + let &RadixFmt(num, radix) = self; + let num = num.$abs(); + let width = size_of::<$T>() * 8; + // Approximate log_2 of the target base. + let log2base = 7 - radix.base().leading_zeros(); + + // Get the number of digits in the target base. + let binary_digits = width - (num | log2base as $T).leading_zeros(); + Some(binary_digits / log2base + 1) } } + }; + // Use `clone` on uints as a noop method in place of abs. + ($T:ty as $U:ty, $fmt:ident) => { + radix_fmt!($T as $U, $fmt, clone) } } macro_rules! int_base { @@ -166,20 +210,20 @@ macro_rules! int_base { } macro_rules! integer { ($Int:ident, $Uint:ident) => { - int_base!(Show for $Int as $Int -> Decimal) int_base!(Signed for $Int as $Int -> Decimal) int_base!(Binary for $Int as $Uint -> Binary) int_base!(Octal for $Int as $Uint -> Octal) int_base!(LowerHex for $Int as $Uint -> LowerHex) int_base!(UpperHex for $Int as $Uint -> UpperHex) - radix_fmt!($Int as $Int, fmt_int) + int_base_hint!(Show for $Int as $Int -> Decimal; 1, abs) + radix_fmt!($Int as $Int, fmt_int, abs) - int_base!(Show for $Uint as $Uint -> Decimal) int_base!(Unsigned for $Uint as $Uint -> Decimal) int_base!(Binary for $Uint as $Uint -> Binary) int_base!(Octal for $Uint as $Uint -> Octal) int_base!(LowerHex for $Uint as $Uint -> LowerHex) int_base!(UpperHex for $Uint as $Uint -> UpperHex) + int_base_hint!(Show for $Uint as $Uint -> Decimal; 1) radix_fmt!($Uint as $Uint, fmt_int) } } diff --git a/src/libcoretest/fmt/mod.rs b/src/libcoretest/fmt/mod.rs index 3f2208e6c2dad..d960afe5cdb2e 100644 --- a/src/libcoretest/fmt/mod.rs +++ b/src/libcoretest/fmt/mod.rs @@ -8,6 +8,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use core::fmt::Show; + mod num; #[test] @@ -16,3 +18,35 @@ fn test_format_flags() { let p = "".as_ptr(); assert_eq!(format!("{:p} {:x}", p, 16u), format!("{:p} 10", p)); } + +#[test] +fn test_len_hints_float() { + let mut f = 5.0f32; + for _ in range(0u, 30) { + let s = format!("{}", f); + let len = f.formatter_len_hint().unwrap(); + assert!(len >= s.len()); + assert!(len <= 128); + f /= 10.0; + } + let mut f = 5.0f32; + for _ in range(0u, 30) { + let s = format!("{}", f); + let len = f.formatter_len_hint().unwrap(); + assert!(len >= s.len()); + assert!(len <= 128); + f *= 10.0; + } +} + +#[test] +fn test_len_hints_u64() { + let mut f = 1u64; + for _ in range(0u, 20) { + let s = format!("{}", f); + let len = f.formatter_len_hint().unwrap(); + assert!(len >= s.len()); + assert!(len <= 128); + f *= 10; + } +} diff --git a/src/librustrt/c_str.rs b/src/librustrt/c_str.rs index 04a4e96ecc40e..97044695c70fe 100644 --- a/src/librustrt/c_str.rs +++ b/src/librustrt/c_str.rs @@ -315,6 +315,11 @@ impl fmt::Show for CString { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { String::from_utf8_lossy(self.as_bytes_no_nul()).fmt(f) } + + #[inline] + fn formatter_len_hint(&self) -> Option { + Some(self.len()) + } } /// A generic trait for converting a value to a CString. diff --git a/src/libstd/ascii.rs b/src/libstd/ascii.rs index fe2b8a15c7375..5ed423ddc0f54 100644 --- a/src/libstd/ascii.rs +++ b/src/libstd/ascii.rs @@ -182,6 +182,10 @@ impl<'a> fmt::Show for Ascii { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { (self.chr as char).fmt(f) } + + fn formatter_len_hint(&self) -> Option { + Some(1) + } } /// Trait for converting into an ascii type. diff --git a/src/libstd/to_string.rs b/src/libstd/to_string.rs index c19fd81b57056..3f3daa9757631 100644 --- a/src/libstd/to_string.rs +++ b/src/libstd/to_string.rs @@ -16,6 +16,8 @@ The `ToString` trait for converting to strings #![experimental] +use io::Writer; +use io; use fmt; use string::String; @@ -33,7 +35,9 @@ pub trait IntoStr { impl ToString for T { fn to_string(&self) -> String { - format!("{}", *self) + let mut output = io::MemWriter::with_capacity(self.formatter_len_hint().unwrap_or(128)); + let _ = write!(&mut output, "{}", *self); + String::from_utf8(output.unwrap()).unwrap() } }