Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description = "printf reimplemented in Rust"
version = "0.2.1"
repository = "https://github.com/lights0123/printf-compat"
authors = ["lights0123 <developer@lights0123.com>"]
edition = "2021"
edition = "2024"
license = "MIT OR Apache-2.0"
readme = "README.md"

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ Now, add your function signature:
```rust
use core::ffi::{c_char, c_int};

#[no_mangle]
#[unsafe(no_mangle)]
unsafe extern "C" fn c_library_print(str: *const c_char, args: ...) -> c_int {
todo!()
}
Expand All @@ -78,13 +78,13 @@ unsafe extern "C" fn c_library_print(str: *const c_char, args: ...) -> c_int {
Think about what you're doing:

- If you're implenting `printf` *because you don't have one*, you'll want to
call it `printf` and add `#[no_mangle]`.
call it `printf` and add `#[unsafe(no_mangle)]`.
- Likewise, if you're creating a custom log function for a C library and it
expects to call a globally-defined function, keep `#[no_mangle]` and
expects to call a globally-defined function, keep `#[unsafe(no_mangle)]` and
rename the function to what it expects.
- On the other hand, if your C library expects you to call a function to
register a callback ([example 1][sigrok-log], [example 2][libusb-log]),
remove `#[no_mangle]`.
remove `#[unsafe(no_mangle)]`.

Now, add your logic:

Expand Down
10 changes: 5 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
//! # #![feature(c_variadic)]
//! use core::ffi::{c_char, c_int};
//!
//! #[no_mangle]
//! #[unsafe(no_mangle)]
//! unsafe extern "C" fn c_library_print(str: *const c_char, args: ...) -> c_int {
//! todo!()
//! }
Expand All @@ -74,20 +74,20 @@
//! Think about what you're doing:
//!
//! - If you're implenting `printf` *because you don't have one*, you'll want to
//! call it `printf` and add `#[no_mangle]`.
//! call it `printf` and add `#[unsafe(no_mangle)]`.
//! - Likewise, if you're creating a custom log function for a C library and it
//! expects to call a globally-defined function, keep `#[no_mangle]` and
//! expects to call a globally-defined function, keep `#[unsafe(no_mangle)]` and
//! rename the function to what it expects.
//! - On the other hand, if your C library expects you to call a function to
//! register a callback ([example 1][sigrok-log], [example 2][libusb-log]),
//! remove `#[no_mangle]`.
//! remove `#[unsafe(no_mangle)]`.
//!
//! Now, add your logic:
//!
//! ```rust
//! # #![feature(c_variadic)]
//! # use core::ffi::{c_char, c_int};
//! # #[no_mangle]
//! # #[unsafe(no_mangle)]
//! # unsafe extern "C" fn c_library_print(str: *const c_char, args: ...) -> c_int {
//! use printf_compat::{format, output};
//! let mut s = String::new();
Expand Down
10 changes: 3 additions & 7 deletions src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,9 +323,9 @@ pub unsafe fn display<'a>(format: *const c_char, va_list: VaList<'a>) -> VaListD
///
/// use core::ffi::{c_char, c_int};
///
/// #[no_mangle]
/// #[unsafe(no_mangle)]
/// unsafe extern "C" fn c_library_print(str: *const c_char, args: ...) -> c_int {
/// let format = printf_compat::output::display(str, args);
/// let format = unsafe { printf_compat::output::display(str, args) };
/// println!("{}", format);
/// format.bytes_written()
/// }
Expand All @@ -348,11 +348,7 @@ impl<'a> fmt::Display for VaListDisplay<'a> {
unsafe {
let bytes = crate::format(self.format, self.va_list.clone(), fmt_write(f));
self.written.set(bytes);
if bytes < 0 {
Err(fmt::Error)
} else {
Ok(())
}
if bytes < 0 { Err(fmt::Error) } else { Ok(()) }
}
}
}
Expand Down
64 changes: 33 additions & 31 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ fn parse_flags(mut sub: &[u8]) -> (Flags, &[u8]) {
unsafe fn parse_width<'a>(mut sub: &'a [u8], args: &mut VaList) -> (c_int, &'a [u8]) {
let mut width: c_int = 0;
if sub.first() == Some(&b'*') {
return (args.arg(), next_char(sub));
return (unsafe { args.arg() }, next_char(sub));
}
while let Some(&ch) = sub.first() {
match ch {
Expand All @@ -46,7 +46,7 @@ unsafe fn parse_width<'a>(mut sub: &'a [u8], args: &mut VaList) -> (c_int, &'a [
unsafe fn parse_precision<'a>(sub: &'a [u8], args: &mut VaList) -> (Option<c_int>, &'a [u8]) {
match sub.first() {
Some(&b'.') => {
let (prec, sub) = parse_width(next_char(sub), args);
let (prec, sub) = unsafe { parse_width(next_char(sub), args) };
(Some(prec), sub)
}
_ => (None, sub),
Expand All @@ -73,24 +73,24 @@ enum Length {
impl Length {
unsafe fn parse_signed(self, args: &mut VaList) -> SignedInt {
match self {
Length::Int => SignedInt::Int(args.arg()),
Length::Char => SignedInt::Char(args.arg::<c_int>() as c_schar),
Length::Short => SignedInt::Short(args.arg::<c_int>() as c_short),
Length::Long => SignedInt::Long(args.arg()),
Length::LongLong => SignedInt::LongLong(args.arg()),
Length::Int => SignedInt::Int(unsafe { args.arg() }),
Length::Char => SignedInt::Char(unsafe { args.arg::<c_int>() } as c_schar),
Length::Short => SignedInt::Short(unsafe { args.arg::<c_int>() } as c_short),
Length::Long => SignedInt::Long(unsafe { args.arg() }),
Length::LongLong => SignedInt::LongLong(unsafe { args.arg() }),
// for some reason, these exist as different options, yet produce the same output
Length::Usize | Length::Isize => SignedInt::Isize(args.arg()),
Length::Usize | Length::Isize => SignedInt::Isize(unsafe { args.arg() }),
}
}
unsafe fn parse_unsigned(self, args: &mut VaList) -> UnsignedInt {
match self {
Length::Int => UnsignedInt::Int(args.arg()),
Length::Char => UnsignedInt::Char(args.arg::<c_uint>() as c_uchar),
Length::Short => UnsignedInt::Short(args.arg::<c_uint>() as c_ushort),
Length::Long => UnsignedInt::Long(args.arg()),
Length::LongLong => UnsignedInt::LongLong(args.arg()),
Length::Int => UnsignedInt::Int(unsafe { args.arg() }),
Length::Char => UnsignedInt::Char(unsafe { args.arg::<c_uint>() } as c_uchar),
Length::Short => UnsignedInt::Short(unsafe { args.arg::<c_uint>() } as c_ushort),
Length::Long => UnsignedInt::Long(unsafe { args.arg() }),
Length::LongLong => UnsignedInt::LongLong(unsafe { args.arg() }),
// for some reason, these exist as different options, yet produce the same output
Length::Usize | Length::Isize => UnsignedInt::Isize(args.arg()),
Length::Usize | Length::Isize => UnsignedInt::Isize(unsafe { args.arg() }),
}
}
}
Expand Down Expand Up @@ -122,7 +122,7 @@ pub unsafe fn format(
mut args: VaList,
mut handler: impl FnMut(Argument) -> c_int,
) -> c_int {
let str = CStr::from_ptr(format).to_bytes();
let str = unsafe { CStr::from_ptr(format).to_bytes() };
let mut iter = str.split(|&c| c == b'%');
let mut written = 0;

Expand Down Expand Up @@ -151,8 +151,8 @@ pub unsafe fn format(
continue;
}
let (flags, sub) = parse_flags(sub);
let (width, sub) = parse_width(sub, &mut args);
let (precision, sub) = parse_precision(sub, &mut args);
let (width, sub) = unsafe { parse_width(sub, &mut args) };
let (precision, sub) = unsafe { parse_precision(sub, &mut args) };
let (length, sub) = parse_length(sub);
let ch = sub
.first()
Expand All @@ -166,35 +166,35 @@ pub unsafe fn format(
last_was_percent = true;
Specifier::Percent
}
b'd' | b'i' => Specifier::Int(length.parse_signed(&mut args)),
b'x' => Specifier::Hex(length.parse_unsigned(&mut args)),
b'X' => Specifier::UpperHex(length.parse_unsigned(&mut args)),
b'u' => Specifier::Uint(length.parse_unsigned(&mut args)),
b'o' => Specifier::Octal(length.parse_unsigned(&mut args)),
b'd' | b'i' => Specifier::Int(unsafe { length.parse_signed(&mut args) }),
b'x' => Specifier::Hex(unsafe { length.parse_unsigned(&mut args) }),
b'X' => Specifier::UpperHex(unsafe { length.parse_unsigned(&mut args) }),
b'u' => Specifier::Uint(unsafe { length.parse_unsigned(&mut args) }),
b'o' => Specifier::Octal(unsafe { length.parse_unsigned(&mut args) }),
b'f' | b'F' => Specifier::Double {
value: args.arg(),
value: unsafe { args.arg() },
format: DoubleFormat::Normal.set_upper(ch.is_ascii_uppercase()),
},
b'e' | b'E' => Specifier::Double {
value: args.arg(),
value: unsafe { args.arg() },
format: DoubleFormat::Scientific.set_upper(ch.is_ascii_uppercase()),
},
b'g' | b'G' => Specifier::Double {
value: args.arg(),
value: unsafe { args.arg() },
format: DoubleFormat::Auto.set_upper(ch.is_ascii_uppercase()),
},
b'a' | b'A' => Specifier::Double {
value: args.arg(),
value: unsafe { args.arg() },
format: DoubleFormat::Hex.set_upper(ch.is_ascii_uppercase()),
},
b's' => {
let arg: *mut c_char = args.arg();
let arg: *mut c_char = unsafe { args.arg() };
// As a common extension supported by glibc, musl, and
// others, format a NULL pointer as "(null)".
if arg.is_null() {
Specifier::Bytes(b"(null)")
} else {
Specifier::String(CStr::from_ptr(arg))
Specifier::String(unsafe { CStr::from_ptr(arg) })
}
}
b'c' => {
Expand All @@ -210,10 +210,12 @@ pub unsafe fn format(
type IntType = c_uint;
}

Specifier::Char(args.arg::<<c_char as CharToInt>::IntType>() as c_char)
Specifier::Char(
unsafe { args.arg::<<c_char as CharToInt>::IntType>() } as c_char
)
}
b'p' => Specifier::Pointer(args.arg()),
b'n' => Specifier::WriteBytesWritten(written, args.arg()),
b'p' => Specifier::Pointer(unsafe { args.arg() }),
b'n' => Specifier::WriteBytesWritten(written, unsafe { args.arg() }),
_ => return -1,
},
}));
Expand Down
19 changes: 10 additions & 9 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@

use core::{ffi::*, ptr::null_mut};

extern "C" {
unsafe extern "C" {
fn asprintf(s: *mut *mut c_char, format: *const c_char, ...) -> c_int;
fn free(p: *mut c_void);
}

unsafe extern "C" fn rust_fmt(str: *const c_char, args: ...) -> Box<(c_int, String)> {
let mut s = String::new();
let bytes_written =
printf_compat::format(str, args.clone(), printf_compat::output::fmt_write(&mut s));
let bytes_written = unsafe {
printf_compat::format(str, args.clone(), printf_compat::output::fmt_write(&mut s))
};
assert!(bytes_written >= 0);
let mut s2 = std::io::Cursor::new(vec![]);
assert_eq!(
bytes_written,
printf_compat::format(str, args, printf_compat::output::io_write(&mut s2),)
);
assert_eq!(bytes_written, unsafe {
printf_compat::format(str, args, printf_compat::output::io_write(&mut s2))
});
assert_eq!(s.as_bytes(), s2.get_ref());
Box::new((bytes_written, s))
}
Expand Down Expand Up @@ -61,8 +61,9 @@ fn assert_fmt_err(fmt: &CStr) {

unsafe extern "C" fn format(str: *const c_char, args: ...) -> c_int {
let mut s = String::new();
let bytes_written =
printf_compat::format(str, args.clone(), printf_compat::output::fmt_write(&mut s));
let bytes_written = unsafe {
printf_compat::format(str, args.clone(), printf_compat::output::fmt_write(&mut s))
};
bytes_written
}
let bytes_written = unsafe { format(fmt.as_ptr()) };
Expand Down