2
0
Эх сурвалжийг харах

Implement almost all of printf

jD91mZM2 6 жил өмнө
parent
commit
63882684b2

+ 498 - 82
src/header/stdio/printf.rs

@@ -1,120 +1,536 @@
+use alloc::string::String;
+use alloc::string::ToString;
+use alloc::vec::Vec;
 use core::fmt::Write as CoreWrite;
-use core::{ptr, slice, str};
+use core::ops::Range;
+use core::{fmt, ptr, slice, str};
 
 use c_str::CStr;
+use io::{self, Write};
 use platform::types::*;
 use platform::{self, WriteByte};
-use va_list::VaList;
+use va_list::{VaList, VaPrimitive};
 
-pub unsafe fn printf<W: WriteByte>(w: W, format: *const c_char, mut ap: VaList) -> c_int {
-    let mut w = platform::CountingWriter::new(w);
+#[derive(PartialEq, Eq)]
+enum IntKind {
+    Byte,
+    Short,
+    Int,
+    Long,
+    LongLong,
+    IntMax,
+    PtrDiff,
+    Size,
+}
 
-    let format = slice::from_raw_parts(format as *const u8, usize::max_value());
+trait IntoUsize {
+    fn into_usize(self) -> usize;
+    fn from_usize(i: usize) -> Self;
+}
+macro_rules! impl_intousize {
+    ($($kind:tt;)*) => {
+        $(impl IntoUsize for $kind {
+            fn into_usize(self) -> usize {
+                self as usize
+            }
+            fn from_usize(i: usize) -> Self {
+                i as Self
+            }
+        })*
+    }
+}
+impl_intousize! {
+    i32;
+    u32;
+    i64;
+    u64;
+    isize;
+    usize;
+}
+impl<T> IntoUsize for *const T {
+    fn into_usize(self) -> usize {
+        self as usize
+    }
+    fn from_usize(i: usize) -> Self {
+        i as Self
+    }
+}
+impl<T> IntoUsize for *mut T {
+    fn into_usize(self) -> usize {
+        self as usize
+    }
+    fn from_usize(i: usize) -> Self {
+        i as Self
+    }
+}
+impl IntoUsize for f32 {
+    fn into_usize(self) -> usize {
+        self.to_bits() as usize
+    }
+    fn from_usize(i: usize) -> Self {
+        Self::from_bits(i as u32)
+    }
+}
+impl IntoUsize for f64 {
+    fn into_usize(self) -> usize {
+        self.to_bits() as usize
+    }
+    fn from_usize(i: usize) -> Self {
+        Self::from_bits(i as u64)
+    }
+}
 
-    let mut found_percent = false;
-    for &b in format.iter() {
-        // check for NUL
-        if b == 0 {
-            break;
+struct BufferedVaList {
+    list: VaList,
+    buf: Vec<usize>,
+    i: usize
+}
+impl BufferedVaList {
+    fn new(list: VaList) -> Self {
+        Self {
+            list,
+            buf: Vec::new(),
+            i: 0
         }
+    }
+    unsafe fn get<T: VaPrimitive + IntoUsize>(&mut self, i: Option<usize>) -> T {
+        match i {
+            None => self.next(),
+            Some(i) => self.index(i),
+        }
+    }
+    unsafe fn next<T: VaPrimitive + IntoUsize>(&mut self) -> T {
+        if self.i >= self.buf.len() {
+            self.buf.push(self.list.get::<T>().into_usize());
+        }
+        let arg = T::from_usize(self.buf[self.i]);
+        self.i += 1;
+        arg
+    }
+    unsafe fn index<T: VaPrimitive + IntoUsize>(&mut self, i: usize) -> T {
+        while self.buf.len() < i {
+            // Getting a usize here most definitely isn't sane, however,
+            // there's no way to know the type!
+            // Just take this for example:
+            //
+            // printf("%*4$d\n", "hi", 0, "hello", 10);
+            //
+            // This chooses the width 10. How does it know the type of 0 and "hello"?
+            // It clearly can't.
 
-        if found_percent {
-            if match b as char {
-                '%' => {
-                    found_percent = false;
-                    w.write_char('%')
-                }
-                'c' => {
-                    let a = ap.get::<u32>();
+            self.buf.push(self.list.get::<usize>());
+        }
+        T::from_usize(self.buf[i - 1])
+    }
+}
 
-                    found_percent = false;
+unsafe fn pop_int_raw(format: &mut *const u8) -> Option<usize> {
+    let mut int = None;
+    while let Some(digit) = (**format as char).to_digit(10) {
+        *format = format.offset(1);
+        if int.is_none() {
+            int = Some(0);
+        }
+        *int.as_mut().unwrap() *= 10;
+        *int.as_mut().unwrap() += digit as usize;
+    }
+    int
+}
+unsafe fn pop_int(format: &mut *const u8, ap: &mut BufferedVaList) -> Option<usize> {
+    if **format == b'*' {
+        *format = format.offset(1);
 
-                    w.write_u8(a as u8)
-                }
-                'd' | 'i' => {
-                    let a = ap.get::<c_int>();
+        // Peek ahead for a positional argument:
+        let mut format2 = *format;
+        if let Some(i) = pop_int_raw(&mut format2) {
+            if *format2 == b'$' {
+                *format = format2.offset(1);
+                return Some(ap.index::<usize>(i))
+            }
+        }
+
+        Some(ap.next::<usize>())
+    } else {
+        pop_int_raw(format)
+    }
+}
+unsafe fn fmt_int<I>(fmt: u8, i: I) -> String
+    where I: fmt::Display + fmt::Octal + fmt::LowerHex + fmt::UpperHex
+{
+    match fmt {
+        b'o' => format!("{:o}", i),
+        b'u' => i.to_string(),
+        b'x' => format!("{:x}", i),
+        b'X' => format!("{:X}", i),
+        _ => panic!("fmt_int should never be called with the fmt {}", fmt)
+    }
+}
+fn pad<W: Write>(w: &mut W, current_side: bool, pad_char: u8, range: Range<usize>) -> io::Result<()> {
+    if current_side {
+        for _ in range {
+            w.write_all(&[pad_char])?;
+        }
+    }
+    Ok(())
+}
+fn float_string(float: c_double, precision: usize, trim: bool) -> String {
+    let mut string = format!("{:.p$}", float, p = precision);
+    if trim {
+        let truncate = {
+            let mut slice = string.trim_right_matches('0');
+            if slice.ends_with('.') {
+                slice.len() - 1
+            } else {
+                slice.len()
+            }
+        };
+        string.truncate(truncate);
+    }
+    string
+}
+fn fmt_float_exp<W: Write>(
+    w: &mut W,
+    exp_fmt: u8,
+    range: Option<(isize, isize)>,
+    trim: bool,
+    precision: usize,
+    mut float: c_double,
+    left: bool,
+    pad_space: usize,
+    pad_zero: usize,
+) -> io::Result<bool> {
+    let mut exp: isize = 0;
+    while float >= 10.0 || float <= -10.0 {
+        float /= 10.0;
+        exp += 1;
+    }
+    while (float > 0.0 && float < 1.0) || (float > -1.0 && float < 0.0) {
+        float *= 10.0;
+        exp -= 1;
+    }
+
+    if range.map(|(start, end)| exp >= start && exp < end).unwrap_or(false) {
+        return Ok(false);
+    }
+
+    let mut exp2 = exp;
+    let mut exp_len = 1;
+    while exp2 >= 10 {
+        exp2 /= 10;
+        exp_len += 1;
+    }
+
+    let string = float_string(float, precision, trim);
+    let mut len = string.len() + 2 + 2.max(exp_len);
 
-                    found_percent = false;
+    pad(w, !left, b' ', len..pad_space)?;
+    let bytes = if string.starts_with('-') {
+        w.write_all(&[b'-'])?;
+        &string.as_bytes()[1..]
+    } else {
+        string.as_bytes()
+    };
+    pad(w, !left, b'0', len..pad_zero)?;
+    w.write_all(bytes)?;
+    write!(w, "{}{:+03}", exp_fmt as char, exp)?;
+    pad(w, left, b' ', len..pad_space)?;
+
+    Ok(true)
+}
+fn fmt_float_normal<W: Write>(
+    w: &mut W,
+    trim: bool,
+    precision: usize,
+    float: c_double,
+    left: bool,
+    pad_space: usize,
+    pad_zero: usize,
+) -> io::Result<usize> {
+    let string = float_string(float, precision, trim);
+
+    pad(w, !left, b' ', string.len()..pad_space)?;
+    let bytes = if string.starts_with('-') {
+        w.write_all(&[b'-'])?;
+        &string.as_bytes()[1..]
+    } else {
+        string.as_bytes()
+    };
+    pad(w, true, b'0', string.len()..pad_zero)?;
+    w.write_all(bytes)?;
+    pad(w, left, b' ', string.len()..pad_space)?;
+
+    Ok(string.len())
+}
+unsafe fn inner_printf<W: Write>(w: W, format: *const c_char, mut ap: VaList) -> io::Result<c_int> {
+    let mut w = &mut platform::CountingWriter::new(w);
+    let mut ap = BufferedVaList::new(ap);
+    let mut format = format as *const u8;
 
-                    w.write_fmt(format_args!("{}", a))
+    while *format != 0 {
+        if *format != b'%' {
+            w.write_all(&[*format])?;
+        } else {
+            format = format.offset(1);
+
+            // Peek ahead to maybe specify argument to fetch from
+            let mut index = None;
+            let mut format2 = format;
+            if let Some(i) = pop_int_raw(&mut format2) {
+                if *format2 == b'$' {
+                    format = format2.offset(1);
+                    index = Some(i);
                 }
-                'f' | 'F' => {
-                    let a = ap.get::<f64>();
+            }
 
-                    found_percent = false;
+            // Flags:
+            let mut alternate = false;
+            let mut zero = false;
+            let mut left = false;
+            let mut sign_reserve = false;
+            let mut sign_always = false;
 
-                    w.write_fmt(format_args!("{}", a))
+            loop {
+                match *format {
+                    b'#' => alternate = true,
+                    b'0' => zero = true,
+                    b'-' => left = true,
+                    b' ' => sign_reserve = true,
+                    b'+' => sign_always = true,
+                    _ => break
                 }
-                'n' => {
-                    let _a = ap.get::<c_int>();
+                format = format.offset(1);
+            }
 
-                    found_percent = false;
-                    Ok(())
+            // Width and precision:
+            let min_width = pop_int(&mut format, &mut ap).unwrap_or(0);
+            let precision = if *format == b'.' {
+                format = format.offset(1);
+                match pop_int(&mut format, &mut ap) {
+                    int@Some(_) => int,
+                    None => return Ok(-1)
                 }
-                'p' => {
-                    let a = ap.get::<usize>();
+            } else {
+                None
+            };
 
-                    found_percent = false;
+            let pad_space = if zero { 0 } else { min_width };
+            let pad_zero  = if zero { min_width } else { 0 };
 
-                    w.write_fmt(format_args!("0x{:x}", a))
-                }
-                's' => {
-                    let a = ap.get::<*const c_char>();
+            // Integer size:
+            let mut kind = IntKind::Int;
+            loop {
+                kind = match *format {
+                    b'h' => if kind == IntKind::Short || kind == IntKind::Byte {
+                        IntKind::Byte
+                    } else {
+                        IntKind::Short
+                    },
+                    b'j' => IntKind::IntMax,
+                    b'l' => if kind == IntKind::Long || kind == IntKind::LongLong {
+                        IntKind::LongLong
+                    } else {
+                        IntKind::Long
+                    },
+                    b'q' | b'L' => IntKind::LongLong,
+                    b't' => IntKind::PtrDiff,
+                    b'z' => IntKind::Size,
+                    _ => break,
+                };
+
+                format = format.offset(1);
+            }
+
+            // Finally, type:
+            match *format {
+                b'%' => w.write_all(&[b'%'])?,
+                b'd' | b'i' => {
+                    let string = match kind {
+                        // VaList does not seem to support these two:
+                        //   IntKind::Byte     => ap.get::<c_char>(index).to_string(),
+                        //   IntKind::Short    => ap.get::<c_short>(index).to_string(),
+                        IntKind::Byte     => (ap.get::<c_int>(index) as c_char).to_string(),
+                        IntKind::Short    => (ap.get::<c_int>(index) as c_short).to_string(),
+                        IntKind::Int      => ap.get::<c_int>(index).to_string(),
+                        IntKind::Long     => ap.get::<c_long>(index).to_string(),
+                        IntKind::LongLong => ap.get::<c_longlong>(index).to_string(),
+                        IntKind::PtrDiff  => ap.get::<ptrdiff_t>(index).to_string(),
+                        IntKind::Size     => ap.get::<ssize_t>(index).to_string(),
+                        IntKind::IntMax   => ap.get::<intmax_t>(index).to_string(),
+                    };
+                    let positive = !string.starts_with('-');
+                    let zero = precision == Some(0) && string == "0";
+
+                    let mut len = string.len();
+                    let mut final_len = string.len().max(precision.unwrap_or(0));
+                    if positive && (sign_reserve || sign_always) {
+                        final_len += 1;
+                    }
+                    if zero {
+                        len = 0;
+                        final_len = 0;
+                    }
 
-                    found_percent = false;
-                    if a != ptr::null() {
-                        let a_cstr = CStr::from_ptr(a);
-                        w.write_str(str::from_utf8_unchecked(a_cstr.to_bytes()))
+                    pad(w, !left, b' ', final_len..pad_space)?;
+
+                    let bytes = if positive {
+                        if sign_reserve {
+                            w.write_all(&[b' '])?;
+                        } else if sign_always {
+                            w.write_all(&[b'+'])?;
+                        }
+                        string.as_bytes()
                     } else {
-                        w.write_str("NULL")
+                        w.write_all(&[b'-'])?;
+                        &string.as_bytes()[1..]
+                    };
+                    pad(w, true, b'0', len..precision.unwrap_or(pad_zero))?;
+
+                    if !zero {
+                        w.write_all(bytes)?;
                     }
-                }
-                'u' => {
-                    let a = ap.get::<c_uint>();
 
-                    found_percent = false;
+                    pad(w, left, b' ', final_len..pad_space)?;
+                },
+                b'o' | b'u' | b'x' | b'X' => {
+                    let fmt = *format;
+                    let string = match kind {
+                        // VaList does not seem to support these two:
+                        //   IntKind::Byte     => fmt_int(kind ap.get::<c_char>(index)),
+                        //   IntKind::Short     => fmt_int(kind ap.get::<c_short>(index)),
+                        IntKind::Byte     => fmt_int(fmt, ap.get::<c_uint>(index) as c_uchar),
+                        IntKind::Short    => fmt_int(fmt, ap.get::<c_uint>(index) as c_ushort),
+                        IntKind::Int      => fmt_int(fmt, ap.get::<c_uint>(index)),
+                        IntKind::Long     => fmt_int(fmt, ap.get::<c_ulong>(index)),
+                        IntKind::LongLong => fmt_int(fmt, ap.get::<c_ulonglong>(index)),
+                        IntKind::PtrDiff  => fmt_int(fmt, ap.get::<ptrdiff_t>(index)),
+                        IntKind::Size     => fmt_int(fmt, ap.get::<size_t>(index)),
+                        IntKind::IntMax   => fmt_int(fmt, ap.get::<uintmax_t>(index)),
+                    };
+                    let zero = precision == Some(0) && string == "0";
 
-                    w.write_fmt(format_args!("{}", a))
-                }
-                'x' => {
-                    let a = ap.get::<c_uint>();
+                    // If this int is padded out to be larger than it is, don't
+                    // add an extra zero if octal.
+                    let no_precision = precision.map(|pad| pad < string.len()).unwrap_or(true);
 
-                    found_percent = false;
+                    let mut len = string.len();
+                    let mut final_len = string.len().max(precision.unwrap_or(0)) +
+                        if alternate && string != "0" {
+                            match fmt {
+                                b'o' if no_precision => 1,
+                                b'x' |
+                                b'X' => 2,
+                                _ => 0
+                            }
+                        } else { 0 };
 
-                    w.write_fmt(format_args!("{:x}", a))
-                }
-                'X' => {
-                    let a = ap.get::<c_uint>();
+                    if zero {
+                        len = 0;
+                        final_len = 0;
+                    }
 
-                    found_percent = false;
+                    pad(w, !left, b' ', final_len..pad_space)?;
 
-                    w.write_fmt(format_args!("{:X}", a))
-                }
-                'o' => {
-                    let a = ap.get::<c_uint>();
+                    if alternate && string != "0" {
+                        match fmt {
+                            b'o' if no_precision => w.write_all(&[b'0'])?,
+                            b'x' => w.write_all(&[b'0', b'x'])?,
+                            b'X' => w.write_all(&[b'0', b'X'])?,
+                            _ => ()
+                        }
+                    }
+                    pad(w, true, b'0', len..precision.unwrap_or(pad_zero))?;
+
+                    if !zero {
+                        w.write_all(string.as_bytes())?;
+                    }
 
-                    found_percent = false;
+                    pad(w, left, b' ', final_len..pad_space)?;
+                },
+                b'e' | b'E' => {
+                    let exp_fmt = *format;
+                    let mut float = ap.get::<c_double>(index);
+                    let precision = precision.unwrap_or(6);
 
-                    w.write_fmt(format_args!("{:o}", a))
+                    fmt_float_exp(w, exp_fmt, None, false, precision, float, left, pad_space, pad_zero)?;
+                },
+                b'f' | b'F' => {
+                    let mut float = ap.get::<c_double>(index);
+                    let precision = precision.unwrap_or(6);
+
+                    fmt_float_normal(w, false, precision, float, left, pad_space, pad_zero)?;
+                },
+                b'g' | b'G' => {
+                    let exp_fmt = b'E' | (*format & 32);
+                    let mut float = ap.get::<c_double>(index);
+                    let precision = precision.unwrap_or(6);
+
+                    if !fmt_float_exp(w, exp_fmt, Some((-4, precision as isize)), true,
+                            precision, float, left, pad_space, pad_zero)? {
+                        fmt_float_normal(w, true, precision, float, left, pad_space, pad_zero)?;
+                    }
                 }
-                '-' => Ok(()),
-                '+' => Ok(()),
-                ' ' => Ok(()),
-                '#' => Ok(()),
-                '0'...'9' => Ok(()),
-                _ => Ok(()),
-            }.is_err()
-            {
-                return -1;
-            }
-        } else if b == b'%' {
-            found_percent = true;
-        } else {
-            if w.write_u8(b).is_err() {
-                return -1;
+                b's' => {
+                    // if kind == IntKind::Long || kind == IntKind::LongLong, handle *const wchar_t
+
+                    let ptr = ap.get::<*const c_char>(index);
+
+                    if ptr.is_null() {
+                        w.write_all(b"(null)")?;
+                    } else {
+                        let mut len = 0;
+                        while *ptr.offset(len) != 0 {
+                            len += 1;
+                        }
+
+                        let len = (len as usize).min(precision.unwrap_or(::core::usize::MAX));
+
+                        pad(w, !left, b' ', len..pad_space)?;
+                        w.write_all(slice::from_raw_parts(ptr as *const u8, len))?;
+                        pad(w, left, b' ', len..pad_space)?;
+                    }
+                },
+                b'c' => {
+                    // if kind == IntKind::Long || kind == IntKind::LongLong, handle wint_t
+
+                    let c = ap.get::<c_int>(index) as c_char;
+
+                    pad(w, !left, b' ', 1..pad_space)?;
+                    w.write_all(&[c as u8])?;
+                    pad(w, left, b' ', 1..pad_space)?;
+                },
+                b'p' => {
+                    let ptr = ap.get::<*const c_void>(index);
+
+                    let mut len = 1;
+                    if ptr.is_null() {
+                        len = "(nil)".len();
+                    } else {
+                        let mut ptr = ptr as usize;
+                        while ptr >= 10 {
+                            ptr /= 10;
+                            len += 1;
+                        }
+                    }
+
+                    pad(w, !left, b' ', len..pad_space)?;
+                    if ptr.is_null() {
+                        write!(w, "(nil)")?;
+                    } else {
+                        write!(w, "0x{:x}", ptr as usize)?;
+                    }
+                    pad(w, left, b' ', len..pad_space)?;
+                },
+                b'n' => {
+                    let ptr = ap.get::<*mut c_int>(index);
+                    *ptr = w.written as c_int;
+                },
+                _ => return Ok(-1)
             }
         }
+        format = format.offset(1);
     }
-
-    w.written as c_int
+    Ok(w.written as c_int)
+}
+pub unsafe fn printf<W: Write>(w: W, format: *const c_char, ap: VaList) -> c_int {
+    inner_printf(w, format, ap).unwrap_or(-1)
 }

+ 7 - 3
src/header/stdio/scanf.rs

@@ -39,7 +39,7 @@ unsafe fn inner_scanf<R: Read>(
 
     macro_rules! read {
         () => {{
-            let mut buf = &mut [byte];
+            let buf = &mut [byte];
             match r.read(buf) {
                 Ok(0) => false,
                 Ok(_) => {
@@ -117,13 +117,17 @@ unsafe fn inner_scanf<R: Read>(
             let mut kind = IntKind::Int;
             loop {
                 kind = match c {
-                    b'h' => if kind == IntKind::Short {
+                    b'h' => if kind == IntKind::Short || kind == IntKind::Byte {
                         IntKind::Byte
                     } else {
                         IntKind::Short
                     },
                     b'j' => IntKind::IntMax,
-                    b'l' => IntKind::Long,
+                    b'l' => if kind == IntKind::Long || kind == IntKind::LongLong {
+                        IntKind::LongLong
+                    } else {
+                        IntKind::Long
+                    },
                     b'q' | b'L' => IntKind::LongLong,
                     b't' => IntKind::PtrDiff,
                     b'z' => IntKind::Size,

+ 72 - 31
src/platform/mod.rs

@@ -1,6 +1,6 @@
 use alloc::vec::Vec;
 use core::{fmt, ptr};
-use io::{self, Read};
+use io::{self, Read, Write};
 
 pub use self::allocator::*;
 
@@ -95,68 +95,88 @@ impl Read for FileReader {
 }
 
 pub struct StringWriter(pub *mut u8, pub usize);
-
-impl StringWriter {
-    pub unsafe fn write(&mut self, buf: &[u8]) {
+impl Write for StringWriter {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
         if self.1 > 1 {
             let copy_size = buf.len().min(self.1 - 1);
-            ptr::copy_nonoverlapping(
-                buf.as_ptr(),
-                self.0,
-                copy_size
-            );
-            self.1 -= copy_size;
+            unsafe {
+                ptr::copy_nonoverlapping(
+                    buf.as_ptr(),
+                    self.0,
+                    copy_size
+                );
+                self.1 -= copy_size;
+
+                self.0 = self.0.offset(copy_size as isize);
+                *self.0 = 0;
+            }
 
-            self.0 = self.0.offset(copy_size as isize);
-            *self.0 = 0;
+            Ok(copy_size)
+        } else {
+            Ok(0)
         }
     }
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
 }
-
 impl fmt::Write for StringWriter {
     fn write_str(&mut self, s: &str) -> fmt::Result {
-        unsafe { self.write(s.as_bytes()) };
+        unsafe {
+            // can't fail
+            self.write(s.as_bytes()).unwrap();
+        };
         Ok(())
     }
 }
-
 impl WriteByte for StringWriter {
     fn write_u8(&mut self, byte: u8) -> fmt::Result {
-        unsafe { self.write(&[byte]) };
+        unsafe {
+            // can't fail
+            self.write(&[byte]).unwrap();
+        }
         Ok(())
     }
 }
 
 pub struct UnsafeStringWriter(pub *mut u8);
-
-impl UnsafeStringWriter {
-    pub unsafe fn write(&mut self, buf: &[u8]) {
-        ptr::copy_nonoverlapping(
-            buf.as_ptr(),
-            self.0,
-            buf.len()
-        );
-        *self.0.offset(buf.len() as isize) = b'\0';
-        self.0 = self.0.offset(buf.len() as isize);
+impl Write for UnsafeStringWriter {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        unsafe {
+            ptr::copy_nonoverlapping(
+                buf.as_ptr(),
+                self.0,
+                buf.len()
+            );
+            *self.0.offset(buf.len() as isize) = b'\0';
+            self.0 = self.0.offset(buf.len() as isize);
+        }
+        Ok(buf.len())
+    }
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
     }
 }
-
 impl fmt::Write for UnsafeStringWriter {
     fn write_str(&mut self, s: &str) -> fmt::Result {
-        unsafe { self.write(s.as_bytes()) };
+        unsafe {
+            // can't fail
+            self.write(s.as_bytes()).unwrap();
+        }
         Ok(())
     }
 }
-
 impl WriteByte for UnsafeStringWriter {
     fn write_u8(&mut self, byte: u8) -> fmt::Result {
-        unsafe { self.write(&[byte]) };
+        unsafe {
+            // can't fail
+            self.write(&[byte]).unwrap();
+        }
         Ok(())
     }
 }
 
 pub struct UnsafeStringReader(pub *const u8);
-
 impl Read for UnsafeStringReader {
     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
         unsafe {
@@ -197,3 +217,24 @@ impl<T: WriteByte> WriteByte for CountingWriter<T> {
         self.inner.write_u8(byte)
     }
 }
+impl<T: Write> Write for CountingWriter<T> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        let res = self.inner.write(buf);
+        if let Ok(written) = res {
+            self.written += written;
+        }
+        res
+    }
+    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
+        match self.inner.write_all(&buf) {
+            Ok(()) => (),
+            Err(ref err) if err.kind() == io::ErrorKind::WriteZero => (),
+            Err(err) => return Err(err)
+        }
+        self.written += buf.len();
+        Ok(())
+    }
+    fn flush(&mut self) -> io::Result<()> {
+        self.inner.flush()
+    }
+}

+ 1 - 1
tests/expected/math.stdout

@@ -1 +1 @@
-cos(3.14) = -0.9999987483024597
+cos(3.140000) = -0.999999

+ 33 - 1
tests/expected/stdio/printf.stdout

@@ -7,4 +7,36 @@ uint: 32
 hex: beef
 HEX: C0FFEE
 string: end
-len of previous write: 94
+%n returned 51, total len of write: 94
+
+Padding madness:
+ 001   +2
+   c
+ 0x00000ff
+ 001
+0 0x1
+ 1
+(00123) (  123)
+(-0123) ( -123)
+(             )
+0xabcdef
+(nil)
+
+Positional madness:
+4 3 2
+00002
+|Fiz     |Buz     |     Fiz|     Tot|
+
+Float madness:
+        1.234568e+02
+        1.000000E-05
+          123.456789
+            0.000010
+       -1.234568e+02
+-00000001.234568e+02
+100000
+1e+06
+1.000000e+06
+0.0001
+1E-05
+1.000000E-05

+ 9 - 9
tests/expected/stdio/scanf.stdout

@@ -1,9 +1,9 @@
-2, { sa: 12, ia: 345, ib: 0, ic: 0, fa: 0, da: 0, ptr: 0x0, char: a, string:  }
-3, { sa: 12, ia: 18, ib: 837, ic: 8, fa: 0, da: 0, ptr: 0x0, char: a, string:  }
-2, { sa: 12, ia: 18, ib: 837, ic: 8, fa: 0.10000000149011612, da: 0.2, ptr: 0x0, char: a, string:  }
-1, { sa: 12, ia: 18, ib: 837, ic: 8, fa: 0.10000000149011612, da: 0.2, ptr: 0xabcdef, char: a, string:  }
-1, { sa: 12, ia: 18, ib: 837, ic: 8, fa: 0.10000000149011612, da: 0.2, ptr: 0xabcdef, char: a, string: Hello }
-1, { sa: 12, ia: 15, ib: 837, ic: 8, fa: 0.10000000149011612, da: 0.2, ptr: 0xabcdef, char: a, string: Hello }
-2, { sa: 12, ia: 15, ib: 837, ic: 8, fa: 0.10000000149011612, da: 0.2, ptr: 0xabcdef, char: h, string: elllo }
-1, { sa: 12, ia: 0, ib: 8, ic: 8, fa: 0.10000000149011612, da: 0.2, ptr: 0xabcdef, char: h, string: elllo }
-0, { sa: 12, ia: 0, ib: 8, ic: 8, fa: 0.10000000149011612, da: 0.2, ptr: 0xabcdef, char: h, string: elllo }
+2, { sa: 12, ia: 345, ib: 0, ic: 0, fa: 0.000000, da: 0.000000, ptr: (nil), char: a, string:  }
+3, { sa: 12, ia: 18, ib: 837, ic: 8, fa: 0.000000, da: 0.000000, ptr: (nil), char: a, string:  }
+2, { sa: 12, ia: 18, ib: 837, ic: 8, fa: 0.100000, da: 0.200000, ptr: (nil), char: a, string:  }
+1, { sa: 12, ia: 18, ib: 837, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: a, string:  }
+1, { sa: 12, ia: 18, ib: 837, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: a, string: Hello }
+1, { sa: 12, ia: 15, ib: 837, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: a, string: Hello }
+2, { sa: 12, ia: 15, ib: 837, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string: elllo }
+1, { sa: 12, ia: 0, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string: elllo }
+0, { sa: 12, ia: 0, ib: 8, ic: 8, fa: 0.100000, da: 0.200000, ptr: 0xabcdef, char: h, string: elllo }

+ 1 - 1
tests/expected/stdlib/atof.stdout

@@ -1 +1 @@
--3.14
+-3.140000

+ 9 - 9
tests/expected/stdlib/strtod.stdout

@@ -1,9 +1,9 @@
-d: 0 Endptr: "a 1 hello"
-d: 1 Endptr: " hello"
-d: 1 Endptr: " hello 2"
-d: 10.123 Endptr: ""
-d: 10.123 Endptr: ""
-d: -5.3 Endptr: ""
-d: 16.071044921875 Endptr: ""
-d: 1.13671875 Endptr: ""
-d: 3.12890625 Endptr: ""
+d: 0.000000 Endptr: "a 1 hello"
+d: 1.000000 Endptr: " hello"
+d: 1.000000 Endptr: " hello 2"
+d: 10.123000 Endptr: ""
+d: 10.123000 Endptr: ""
+d: -5.300000 Endptr: ""
+d: 16.071045 Endptr: ""
+d: 1.136719 Endptr: ""
+d: 3.128906 Endptr: ""

+ 2 - 2
tests/expected/string/strstr.stdout

@@ -1,5 +1,5 @@
 rust
 libc we trust
-NULL
-NULL
+(null)
+(null)
 RUST

+ 9 - 9
tests/expected/unistd/getopt_long.stdout

@@ -1,20 +1,20 @@
 --- Running: test --test0 -a
-getopt_long returned 1, argument test0=NULL
-Option -a with value NULL
+getopt_long returned 1, argument test0=(null)
+Option -a with value (null)
 --- Running: test --test1 -a
-getopt_long returned 0, set flag to 2, argument test1=NULL
-Option -a with value NULL
+getopt_long returned 0, set flag to 2, argument test1=(null)
+Option -a with value (null)
 --- Running: test --test2 -a
-getopt_long returned 3, argument test2=NULL
-Option -a with value NULL
+getopt_long returned 3, argument test2=(null)
+Option -a with value (null)
 --- Running: test --test2=arg -a
 getopt_long returned 3, argument test2=arg
-Option -a with value NULL
+Option -a with value (null)
 --- Running: test --test3 -a
 getopt_long returned 4, argument test3=-a
 --- Running: test --test3=arg -a
 getopt_long returned 4, argument test3=arg
-Option -a with value NULL
+Option -a with value (null)
 --- Running: test --test3 arg -a
 getopt_long returned 4, argument test3=arg
-Option -a with value NULL
+Option -a with value (null)

+ 2 - 2
tests/expected/wchar/mbrtowc.stdout

@@ -1,2 +1,2 @@
-Processing 11 UTF-8 code units: [ 7a c3 9f e6 b0 b4 f0 9f 8d 8c 0 ]
-into 5 wchar_t units: [ 7a df 6c34 1f34c 0 ]
+Processing 11 UTF-8 code units: [ 0x7a 0xc3 0x9f 0xe6 0xb0 0xb4 0xf0 0x9f 0x8d 0x8c 0 ]
+into 5 wchar_t units: [ 0x7a 0xdf 0x6c34 0x1f34c 0 ]

+ 2 - 2
tests/expected/wchar/wcrtomb.stdout

@@ -1,2 +1,2 @@
-Processing 5 wchar_t units: [ 7a df 6c34 1f34c 0 ]
-into 11 UTF-8 code units: [ 7a c3 9f e6 b0 b4 f0 9f 8d 8c 0 ]
+Processing 5 wchar_t units: [ 0x7a 0xdf 0x6c34 0x1f34c 0 ]
+into 11 UTF-8 code units: [ 0x7a 0xc3 0x9f 0xe6 0xb0 0xb4 0xf0 0x9f 0x8d 0x8c 0 ]

+ 37 - 2
tests/stdio/printf.c

@@ -1,17 +1,52 @@
 #include <stdio.h>
 
 int main(int argc, char ** argv) {
+    int sofar = 0;
     int len = printf(
-        "percent: %%\nstring: %s\nchar: %c\nchar: %c\nint: %d\nuint: %u\nhex: %x\nHEX: %X\nstring: %s\n",
+        "percent: %%\nstring: %s\nchar: %c\nchar: %c\nint: %d\n%nuint: %u\nhex: %x\nHEX: %X\nstring: %s\n",
         "String",
         'c',
         254,
         -16,
+        &sofar,
         32,
         0xbeef,
         0xC0FFEE,
         "end"
     );
-    printf("len of previous write: %d\n", len);
+    printf("%%n returned %d, total len of write: %d\n", sofar, len);
+
+    puts("\nPadding madness:");
+    printf("% -5.3d %+3d\n", 1, 2);
+    printf("%4c\n", 'c');
+    printf("%#10.7x\n", 0xFF);
+    printf("%#4.3o\n", 01);
+    printf("%#x %#x\n", 0, 1);
+    printf("%.0d %.0d\n", 0, 1);
+    printf("(%05d) (%5d)\n", 123, 123);
+    printf("(%05d) (%5d)\n", -123, -123);
+    printf("(%13.0d)\n", 0);
+    printf("%p\n", (void*) 0xABCDEF);
+    printf("%p\n", (void*) 0);
+
+    puts("\nPositional madness:");
+    printf("%3$d %2$d %1$d\n", 2, 3, 4);
+    printf("%.*3$d\n", 2, 0, 5);
+    printf("|%-*6$.*5$s|%-*6$.*5$s|%*6$.*5$s|%*6$.*5$s|\n", "Fizz", "Buzz", "FizzBuzz", "TotalBuzz", 3, 8);
+
+    puts("\nFloat madness:");
+    printf("%20e\n", 123.456789123);
+    printf("%20E\n", 0.00001);
+    printf("%20f\n", 123.456789123);
+    printf("%20F\n", 0.00001);
+    printf("%20e\n", -123.456789123);
+    printf("%020e\n", -123.456789123);
+
+    printf("%g\n", 100000.0);
+    printf("%g\n", 1000000.0);
+    printf("%e\n", 1000000.0);
+    printf("%G\n", 0.0001);
+    printf("%G\n", 0.00001);
+    printf("%E\n", 0.00001);
     return 0;
 }