diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 788f566..6c64040 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -103,7 +103,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.75.0 # clippy is too much of a moving target at the moment + toolchain: 1.77.0 # clippy is too much of a moving target at the moment override: true components: clippy - uses: actions-rs/clippy-check@v1 diff --git a/src/snprintf.c b/src/snprintf.c index 9a06757..8ae2ac2 100644 --- a/src/snprintf.c +++ b/src/snprintf.c @@ -29,6 +29,9 @@ static void write_output( char out, char* restrict str, size_t max, size_t* written ); +static void write_padding( + char* restrict str, size_t size, size_t* written, size_t len, unsigned long width, unsigned long precision, bool zero_pad, bool is_negative ); + static char upcase(char c); /* ======================================================================== * @@ -84,7 +87,7 @@ extern unsigned long int strtoul(const char* str, char** endptr, int base); * - c (char) * - s (null-terminated string) * - % (literal percent sign) - * - qualifiers: l, ll, z + * - qualifiers: l, ll, z, width, (non-string) precision, left-space-pad, zero-pad * * Does not support: * @@ -96,7 +99,7 @@ extern unsigned long int strtoul(const char* str, char** endptr, int base); * - f (decimal floating point) * - g (the shorter of %e and %f) * - G (the shorter of %E and %f) - * - qualifiers: L, width, (non-string) precision, -, +, space-pad, zero-pad, etc + * - qualifiers: L, -, +, right-pad, etc * * @param str the output buffer to write to * @param size the size of the output buffer @@ -113,6 +116,8 @@ int vsnprintf( int is_long = 0; bool is_size_t = false; unsigned long precision = -1; + unsigned long width = 0; + bool zero_pad = false; while ( *fmt ) { @@ -138,173 +143,93 @@ int vsnprintf( is_escape = true; break; case 'u': - if ( is_size_t ) { - // Render %zu char s[MAXIMUM_NUMBER_LENGTH] = { 0 }; - size_t su = va_arg( ap, size_t ); - utoa( su, s, sizeof(s), 10 ); - for ( const char* p = s; *p != '\0'; p++ ) + unsigned long long ll = 0; + if ( is_size_t ) { - write_output( *p, str, size, &written ); + // Render %zu + ll = va_arg( ap, size_t ); } - } - else if ( is_long == 2 ) - { - // Render %lu - char s[MAXIMUM_NUMBER_LENGTH] = { 0 }; - unsigned long long ll = va_arg( ap, unsigned long long ); - utoa( ll, s, sizeof(s), 10 ); - for ( const char* p = s; *p != '\0'; p++ ) + else if ( is_long == 2 ) { - write_output( *p, str, size, &written ); + // Render %llu + ll = va_arg( ap, unsigned long long ); } - } - else if ( is_long == 1 ) - { - // Render %lu - char s[MAXIMUM_NUMBER_LENGTH] = { 0 }; - unsigned long l = va_arg( ap, unsigned long ); - utoa( l, s, sizeof(s), 10 ); - for ( const char* p = s; *p != '\0'; p++ ) + else if ( is_long == 1 ) { - write_output( *p, str, size, &written ); + // Render %lu + ll = va_arg( ap, unsigned long ); } - } - else - { - // Render %u - unsigned int i = va_arg( ap, unsigned int ); - char s[MAXIMUM_NUMBER_LENGTH] = { 0 }; - utoa( i, s, sizeof(s), 10 ); - for ( const char* p = s; *p != '\0'; p++ ) + else { - write_output( *p, str, size, &written ); + // Render %u + ll = va_arg( ap, unsigned int ); } - } - break; - case 'x': - if ( is_size_t ) - { - // Render %zu - char s[MAXIMUM_NUMBER_LENGTH] = { 0 }; - size_t su = va_arg( ap, size_t ); - utoa( su, s, sizeof(s), 16 ); + utoa( ll, s, sizeof(s), 10 ); + write_padding( str, size, &written, strlen(s), width, precision, zero_pad, false ); for ( const char* p = s; *p != '\0'; p++ ) { write_output( *p, str, size, &written ); } } - else if ( is_long == 2 ) + break; + case 'x': + case 'X': + // Render %x and %X { - // Render %llu + unsigned long long ll; char s[MAXIMUM_NUMBER_LENGTH] = { 0 }; - unsigned long long ll = va_arg( ap, unsigned long long ); - utoa( ll, s, sizeof(s), 16 ); - for ( const char* p = s; *p != '\0'; p++ ) + if ( is_size_t ) { - write_output( *p, str, size, &written ); + ll = va_arg( ap, size_t ); } - } - else if ( is_long == 1 ) - { - // Render %lu - char s[MAXIMUM_NUMBER_LENGTH] = { 0 }; - unsigned long l = va_arg( ap, unsigned long ); - utoa( l, s, sizeof(s), 16 ); - for ( const char* p = s; *p != '\0'; p++ ) + else if ( is_long == 2 ) { - write_output( *p, str, size, &written ); + ll = va_arg( ap, unsigned long long ); } - } - else - { - // Render %u - unsigned int i = va_arg( ap, unsigned int ); - char s[MAXIMUM_NUMBER_LENGTH] = { 0 }; - utoa( i, s, sizeof(s), 16 ); - for ( const char* p = s; *p != '\0'; p++ ) + else if ( is_long == 1 ) { - write_output( *p, str, size, &written ); + ll = va_arg( ap, unsigned long ); } - } - break; - case 'X': - if ( is_size_t ) - { - // Render %zu - char s[MAXIMUM_NUMBER_LENGTH] = { 0 }; - size_t su = va_arg( ap, size_t ); - utoa( su, s, sizeof(s), 16 ); - for ( const char* p = s; *p != '\0'; p++ ) + else { - write_output( upcase(*p), str, size, &written ); + ll = va_arg( ap, unsigned int ); } - } - else if ( is_long == 2 ) - { - // Render %llu - char s[MAXIMUM_NUMBER_LENGTH] = { 0 }; - unsigned long long ll = va_arg( ap, unsigned long long ); utoa( ll, s, sizeof(s), 16 ); - for ( const char* p = s; *p != '\0'; p++ ) + write_padding( str, size, &written, strlen(s), width, precision, zero_pad, false ); + for (const char* p = s; *p != '\0'; p++) { - write_output( upcase(*p), str, size, &written ); - } - } - else if ( is_long == 1 ) - { - // Render %lu - char s[MAXIMUM_NUMBER_LENGTH] = { 0 }; - unsigned long l = va_arg( ap, unsigned long ); - utoa( l, s, sizeof(s), 16 ); - for ( const char* p = s; *p != '\0'; p++ ) - { - write_output( upcase(*p), str, size, &written ); - } - } - else - { - // Render %u - unsigned int i = va_arg( ap, unsigned int ); - char s[MAXIMUM_NUMBER_LENGTH] = { 0 }; - utoa( i, s, sizeof(s), 16 ); - for ( const char* p = s; *p != '\0'; p++ ) - { - write_output( upcase(*p), str, size, &written ); + char output_char = (*fmt == 'X') ? upcase(*p) : *p; + write_output( output_char, str, size, &written ); } } break; case 'i': case 'd': - if ( is_long == 2 ) { - // Render %ld char s[MAXIMUM_NUMBER_LENGTH] = { 0 }; - signed long long ll = va_arg( ap, signed long long ); - itoa( ll, s, sizeof(s), 10 ); - for ( const char* p = s; *p != '\0'; p++ ) + signed long long ll = 0; + unsigned long long ull = 0; + if ( is_long == 2 ) { - write_output( *p, str, size, &written ); + // Render %lld + ll = va_arg( ap, signed long long ); } - } - else if ( is_long == 1 ) - { - // Render %ld - char s[MAXIMUM_NUMBER_LENGTH] = { 0 }; - signed long l = va_arg( ap, signed long ); - itoa( l, s, sizeof(s), 10 ); - for ( const char* p = s; *p != '\0'; p++ ) + else if ( is_long == 1 ) { - write_output( *p, str, size, &written ); + // Render %ld + ll = va_arg( ap, signed long ); } - } - else - { - // Render %d - signed int i = va_arg( ap, signed int ); - char s[MAXIMUM_NUMBER_LENGTH] = { 0 }; - itoa( i, s, sizeof(s), 10 ); + else + { + // Render %d + ll = va_arg( ap, signed int ); + } + bool is_negative = ll < 0; + ull = is_negative ? -ll : ll; + utoa( ull, s, sizeof(s), 10 ); + write_padding( str, size, &written, strlen(s), width, precision, zero_pad, is_negative ); for ( const char* p = s; *p != '\0'; p++ ) { write_output( *p, str, size, &written ); @@ -323,6 +248,12 @@ int vsnprintf( { const char *s = va_arg( ap, const char* ); unsigned long count = precision; + + size_t len = strlen(s); + if (precision != (unsigned long)-1 && precision < len) { + len = precision; + } + write_padding( str, size, &written, len, width, precision, false, false ); while (count > 0 && *s != '\0') { @@ -336,7 +267,7 @@ int vsnprintf( } break; case '.': - // Render a precision specifier + // Parse a precision specifier { // Next up is either a number or a '*' that signifies that the number is in the arguments list char next = *++fmt; @@ -356,6 +287,27 @@ int vsnprintf( is_escape = true; } break; + case '0': + // Parse zero padding specifier + zero_pad = true; + fmt++; + /* fall through */ + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + // Parse padding specifier + width = strtoul(fmt, (char**) &fmt, 10); + // Strtoul sets the fmt pointer to the char after the number, + // however the code expects the char before that. + fmt--; + is_escape = true; + break; case '%': write_output( '%', str, size, &written ); break; @@ -378,6 +330,9 @@ int vsnprintf( is_escape = true; is_long = 0; is_size_t = false; + zero_pad = false; + width = 0; + precision = -1; break; default: write_output( *fmt, str, size, &written ); @@ -451,6 +406,58 @@ static void write_output( } } +/** + * write_padding - Write padding to the output buffer. + * + * @param str the buffer to write to + * @param size the total size of `str` + * @param written pass in the number of characters in the buffer; is increased + * by one regardless of whether we wrote to the buffer + * @param len the length of the string to write + * @param width the total width of the padding + * @param precision the precision of the padding + * @param zero_pad whether to zero pad the string + * @param is_negative whether the number is negative + */ +static void write_padding(char* restrict str, size_t size, size_t* written, size_t len, unsigned long width, unsigned long precision, bool zero_pad, bool is_negative) { + if ( is_negative ) + { + len++; + } + unsigned long pad_len = width > len ? width - len : 0; + unsigned long zero_pad_len = 0; + if ( precision != 0 && precision != (unsigned long)-1 ) + { + if ( is_negative ) + { + zero_pad_len = precision >= len ? precision - len + 1 : 0; + } + else + { + zero_pad_len = precision >= len ? precision - len : 0; + } + } + else if ( zero_pad && precision == (unsigned long)-1 ) + { + zero_pad_len = pad_len; + } + // Apply whitespace padding if needed + pad_len = (zero_pad_len > pad_len) ? 0 : pad_len - zero_pad_len; + for (unsigned long i = 0; i < pad_len; i++) + { + write_output( ' ', str, size, written ); + } + // Apply zero padding if needed + if (is_negative) + { + write_output( '-', str, size, written ); + } + for (unsigned long i = 0; i < zero_pad_len; i++) + { + write_output( '0', str, size, written ); + } +} + /** * Converts 'a'..'z' to 'A'..'Z', leaving all other characters unchanged. */ diff --git a/src/snprintf.rs b/src/snprintf.rs index 354b4a2..ec1031c 100644 --- a/src/snprintf.rs +++ b/src/snprintf.rs @@ -3,82 +3,83 @@ //! Copyright (c) Jonathan 'theJPster' Pallant 2019 //! Licensed under the Blue Oak Model Licence 1.0.0 -#[cfg(feature = "snprintf")] #[cfg(test)] mod test { extern "C" { fn snprintf(buf: *mut CChar, len: usize, fmt: *const CChar, ...) -> i32; } + use core::{ffi::CStr, fmt}; + use std::fmt::format; + use crate::{strcmp::strcmp, CChar, CInt, CLong, CLongLong, CUInt, CULong, CULongLong}; - #[test] - fn plain_string() { - let mut buf = [b'\0'; 32]; - assert_eq!( - unsafe { snprintf(buf.as_mut_ptr(), buf.len(), "Hi\0".as_ptr()) }, - 2, - "{}", - String::from_utf8_lossy(&buf).escape_debug(), - ); + /// Make it easier to turn `c"Hello"` into a `*const CChar` + trait ToByte { + fn cp(&self) -> *const CChar; + } + + impl ToByte for &std::ffi::CStr { + fn cp(&self) -> *const CChar { + self.as_ptr().cast() + } + } + + /// Handle the buffer that `snprintf` needs + #[track_caller] + fn asprintf(fmt: &str, expected: &str, f: F) + where + F: FnOnce(*mut CChar, usize, *const CChar) -> i32, + { + let mut buf = vec![0u8; 128]; + let cfmt = std::ffi::CString::new(fmt).unwrap(); + let res = f(buf.as_mut_ptr(), buf.len(), cfmt.as_ptr().cast()); + if res < 0 { + panic!("closure returned {}", res); + } + // res does not include the trailing NUL that snprintf always outputs (if there's space) + buf.truncate((res + 1) as usize); + let cs = std::ffi::CString::from_vec_with_nul(buf) + .expect("failed to make CString from closure output"); + let cs = cs.to_str().expect("was not UTF-8").to_string(); assert_eq!( - unsafe { strcmp(buf.as_ptr() as *const u8, b"Hi\0" as *const u8) }, - 0 + cs, expected, + "fmt '{}', expected '{}', got '{}'", + fmt, expected, cs ); } + #[test] + fn plain_string() { + asprintf("Hi", "Hi", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt) + }); + } + #[test] fn strings() { - let mut buf = [b'\0'; 32]; - assert_eq!( - unsafe { - snprintf( - buf.as_mut_ptr(), - buf.len(), - "%s, %s!\0".as_ptr(), - "Hello\0".as_ptr(), - "World\0".as_ptr(), - ) - }, - 13, - "{}", - String::from_utf8_lossy(&buf).escape_debug(), - ); - assert_eq!( - unsafe { strcmp(buf.as_ptr() as *const u8, b"Hello, World!\0" as *const u8) }, - 0 - ); + asprintf("%s, %s!", "Hello, World!", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, c"Hello".cp(), c"World".cp()) + }); } #[test] fn size() { - let mut buf = [b'\0'; 32]; - assert_eq!( - unsafe { - snprintf( - buf.as_mut_ptr(), - buf.len(), - "%zx\0".as_ptr(), - 0x1000_0000usize, - ) - }, - 8 - ); - assert_eq!( - unsafe { strcmp(buf.as_ptr() as *const u8, b"10000000\0" as *const u8) }, - 0 - ); + asprintf("%zx", "10000000", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, 0x1000_0000usize) + }); } #[test] fn numbers() { - let mut buf = [b'\0'; 64]; - assert_eq!( - unsafe { + asprintf( + "%u %lu %llu %d %ld %lld %x %lx %llX", + "100 100 100 -100 -100 -100 cafe1234 cafe1234 CAFE1234", + |buf, len, fmt| unsafe { snprintf( - buf.as_mut_ptr(), - buf.len(), - "%u %lu %llu %d %ld %lld %x %lx %llX\0".as_ptr(), + buf, + len, + fmt, CUInt::from(100u8), CULong::from(100u8), CULongLong::from(100u8), @@ -90,58 +91,198 @@ mod test { CULongLong::from(0xcafe1234u32), ) }, - 53, - "{}", - String::from_utf8_lossy(&buf).escape_debug(), - ); - assert_eq!( - unsafe { - strcmp( - buf.as_ptr() as *const u8, - b"100 100 100 -100 -100 -100 cafe1234 cafe1234 CAFE1234\0" as *const u8, - ) - }, - 0 ); } #[test] - fn non_null_terminated_with_length() { - let mut buf = [b'\0'; 64]; - assert_eq!( - unsafe { - snprintf( - buf.as_mut_ptr(), - buf.len(), - "%.*s\0".as_ptr(), - 5, - "01234567890123456789\0".as_ptr(), - ) - }, - 5, - "{}", - String::from_utf8_lossy(&buf).escape_debug(), + fn int_min() { + asprintf( + "%d", + &format!("{}", CInt::min_value()), + |buf, len, fmt| unsafe { snprintf(buf, len, fmt, CInt::min_value()) }, ); - assert_eq!( - unsafe { strcmp(buf.as_ptr() as *const u8, b"01234\0" as *const u8,) }, - 0 + asprintf( + "%lld", + &format!("{}", CLongLong::min_value()), + |buf, len, fmt| unsafe { snprintf(buf, len, fmt, CLongLong::min_value()) }, ); - assert_eq!( - unsafe { - snprintf( - buf.as_mut_ptr(), - buf.len(), - "%.10s\0".as_ptr(), - "01234567890123456789\0".as_ptr(), - ) - }, - 10, - "{}", - String::from_utf8_lossy(&buf).escape_debug(), + } + + #[test] + fn int_max() { + asprintf( + "%d", + &format!("{}", CInt::max_value()), + |buf, len, fmt| unsafe { snprintf(buf, len, fmt, CInt::max_value()) }, ); - assert_eq!( - unsafe { strcmp(buf.as_ptr() as *const u8, b"0123456789\0" as *const u8,) }, - 0 + asprintf( + "%lld", + &format!("{}", CLongLong::max_value()), + |buf, len, fmt| unsafe { snprintf(buf, len, fmt, CLongLong::max_value()) }, ); } + + #[test] + fn non_null_terminated_with_length() { + asprintf("%.*s", "01234", |buf, len, fmt: *const u8| unsafe { + snprintf(buf, len, fmt, 5, c"01234567890123456789".cp()) + }); + asprintf("%.10s", "0123456789", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, c"01234567890123456789".cp()) + }); + } + + #[test] + fn number_with_padding() { + asprintf("%5u", " 123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CUInt::from(123u8)) + }); + asprintf("%5lu", " 123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CULong::from(123u8)) + }); + asprintf("%5llu", " 123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CULongLong::from(123u8)) + }); + asprintf("%5d", " -123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CInt::from(-123i8)) + }); + asprintf("%5ld", " -123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CLong::from(-123i8)) + }); + asprintf("%5lld", " -123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CLongLong::from(-123i8)) + }); + asprintf("%10x", " cafe1234", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CUInt::from(0xcafe1234u32)) + }); + asprintf("%10lx", " cafe1234", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CULong::from(0xcafe1234u32)) + }); + asprintf("%10llX", " CAFE1234", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CULongLong::from(0xcafe1234u32)) + }); + } + + #[test] + fn number_with_zero_padding() { + asprintf("%05u", "00123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CUInt::from(123u8)) + }); + asprintf("%05lu", "00123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CULong::from(123u8)) + }); + asprintf("%05llu", "00123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CULongLong::from(123u8)) + }); + asprintf("%05d", "-0123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CInt::from(-123i8)) + }); + asprintf("%05ld", "-0123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CLong::from(-123i8)) + }); + asprintf("%05lld", "-0123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CLongLong::from(-123i8)) + }); + asprintf("%010x", "00cafe1234", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CUInt::from(0xcafe1234u32)) + }); + asprintf("%010lx", "00cafe1234", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CULong::from(0xcafe1234u32)) + }); + asprintf("%010llX", "00CAFE1234", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CULongLong::from(0xcafe1234u32)) + }); + } + + #[test] + fn number_with_precision() { + asprintf("%.5u", "00123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CUInt::from(123u8)) + }); + asprintf("%.5d", "-00123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CInt::from(-123i8)) + }); + asprintf("%.10x", "00cafe1234", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CUInt::from(0xcafe1234u32)) + }); + asprintf("%.4d", "-0123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CInt::from(-123i8)) + }); + asprintf("%.3d", "-123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CInt::from(-123i8)) + }); + asprintf("%.2u", "123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CUInt::from(123u8)) + }); + asprintf("%.0u", "123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CUInt::from(123u8)) + }); + } + + #[test] + fn number_with_width_and_precision() { + asprintf("%10.5u", " 00123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CUInt::from(123u8)) + }); + asprintf("%10.5d", " -00123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CInt::from(-123i8)) + }); + asprintf("%15.10x", " 00cafe1234", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CUInt::from(0xcafe1234u32)) + }); + + asprintf("%5.5u", "00123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CUInt::from(123u8)) + }); + asprintf("%4.5u", "00123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CUInt::from(123u8)) + }); + asprintf("%2.5u", "00123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CUInt::from(123u8)) + }); + asprintf("%0.5u", "00123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CUInt::from(123u8)) + }); + asprintf("%5.4u", " 0123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CUInt::from(123u8)) + }); + asprintf("%5.3u", " 123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CUInt::from(123u8)) + }); + asprintf("%5.0u", " 123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CUInt::from(123u8)) + }); + + asprintf("%5.5d", "-00123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CInt::from(-123i8)) + }); + asprintf("%4.5d", "-00123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CInt::from(-123i8)) + }); + asprintf("%2.5d", "-00123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CInt::from(-123i8)) + }); + asprintf("%0.5d", "-00123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CInt::from(-123i8)) + }); + asprintf("%5.4d", "-0123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CInt::from(-123i8)) + }); + asprintf("%5.3d", " -123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CInt::from(-123i8)) + }); + asprintf("%5.0d", " -123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CInt::from(-123i8)) + }); + + asprintf("%05.4d", "-0123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CInt::from(-123i8)) + }); + asprintf("%05.3d", " -123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CInt::from(-123i8)) + }); + asprintf("%05.0d", " -123", |buf, len, fmt| unsafe { + snprintf(buf, len, fmt, CInt::from(-123i8)) + }); + } }