diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 59e1c61..bd71834 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.64.0 # clippy is too much of a moving target at the moment + toolchain: 1.75.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/Cargo.toml b/Cargo.toml index 1dc9f50..241d30c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,11 +24,20 @@ all = [ "strlen", "strtol", "strtoul", + "strtoll", + "strtoull", + "strtoimax", + "strtoumax", "strstr", "strchr", "atoi", + "utoa", "itoa", "snprintf", + "isspace", + "isdigit", + "isalpha", + "isupper", ] abs = [] strcmp = [] @@ -38,8 +47,17 @@ strncpy = [] strlen = [] strtol = [] strtoul = [] +strtoll = [] +strtoull = [] +strtoimax = [] +strtoumax = [] strstr = [] strchr = [] atoi = [] +utoa = [] itoa = [] snprintf = [] +isspace = [] +isdigit = [] +isalpha = [] +isupper = [] diff --git a/README.md b/README.md index e834105..c7ec328 100644 --- a/README.md +++ b/README.md @@ -11,13 +11,21 @@ This crate basically came about so that the [nrfxlib](https://github.com/NordicP * abs * strol * atoi +* isspace +* isdigit +* isalpha +* isupper * strcmp * strncmp * strcpy * strncpy * strlen * strtol +* strtoll * strtoul +* strtoull +* strtoimax +* strtoumax * strstr * strchr * snprintf diff --git a/build.rs b/build.rs index 46130ba..57017a5 100644 --- a/build.rs +++ b/build.rs @@ -1,11 +1,30 @@ fn main() { if cfg!(feature = "snprintf") { // Build our snprintf substitute (which has to be C as Rust doesn't do varargs) - cc::Build::new() + let mut build = cc::Build::new(); + + build .warnings(true) .extra_warnings(true) .flag("-std=c99") - .file("./src/snprintf.c") - .compile("clocal"); + .file("./src/snprintf.c"); + + #[cfg(not(feature = "itoa"))] + { + build.define("itoa", "tinyrlibc_itoa"); + } + #[cfg(not(feature = "utoa"))] + { + build.define("utoa", "tinyrlibc_utoa"); + } + #[cfg(not(feature = "strtoul"))] + { + build.define("strtoul", "tinyrlibc_strtoul"); + } + + build.compile("clocal"); } + + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=src/snprintf.c"); } diff --git a/src/abs.rs b/src/abs.rs index c6d257b..f42f4ec 100644 --- a/src/abs.rs +++ b/src/abs.rs @@ -4,13 +4,8 @@ use crate::CInt; -/// Calculates the integer absolute value -/// -/// ``` -/// use tinyrlibc::abs; -/// assert_eq!(abs(-2), 2); -/// ``` -#[no_mangle] +/// Rust implementation of C library function `abs` +#[cfg_attr(feature = "abs", no_mangle)] pub extern "C" fn abs(i: CInt) -> CInt { i.abs() } diff --git a/src/atoi.rs b/src/atoi.rs deleted file mode 100644 index 56642f5..0000000 --- a/src/atoi.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! Rust implementation of C library function `atoi` -//! -//! Copyright (c) Jonathan 'theJPster' Pallant 2019 -//! Licensed under the Blue Oak Model Licence 1.0.0 - -use crate::{strtol, CChar, CInt, CLong}; - -/// Converts a null-terminated string representing a decimal integer, into an -/// integer. No indication of error. -/// -/// ``` -/// use tinyrlibc::atoi; -/// assert_eq!(unsafe { atoi(b"123\0".as_ptr()) }, 123); -/// assert_eq!(unsafe { atoi(b"123x\0".as_ptr()) }, 123); -/// assert_eq!(unsafe { atoi(b"\0".as_ptr()) }, 0); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn atoi(s: *const CChar) -> CInt { - let result = strtol(s); - if result > CInt::max_value() as CLong { - CInt::max_value() - } else if result < CInt::min_value() as CLong { - CInt::min_value() - } else { - result as CInt - } -} diff --git a/src/ctype.rs b/src/ctype.rs new file mode 100644 index 0000000..de79efa --- /dev/null +++ b/src/ctype.rs @@ -0,0 +1,67 @@ +//! A tiny C library, written in Rust. +//! +//! See README.md for more details. +//! +//! This file is Copyright (c) Jonathan 'theJPster' Pallant 2019 +//! Licensed under the Blue Oak Model Licence 1.0.0 +//! +//! See each module for its respective licence. + +/// `void` +pub type CVoid = ::core::ffi::c_void; + +/// `long long int` +pub type CLongLong = ::core::ffi::c_longlong; + +/// `unsigned long long int` +pub type CULongLong = ::core::ffi::c_ulonglong; + +/// `intmax_t` +pub type CUIntMax = CULongLong; + +/// `uintmax_t` +pub type CIntMax = CLongLong; + +/// `long int` +pub type CLong = ::core::ffi::c_long; + +/// `unsigned long int` +pub type CULong = ::core::ffi::c_ulong; + +/// `int` +pub type CInt = ::core::ffi::c_int; + +/// `unsigned int` +pub type CUInt = ::core::ffi::c_uint; + +/// Represents an 8-bit `char`. Rust does not (and will never) support +/// platforms where `char` is not 8-bits long. +pub type CChar = u8; + +/// This allows you to iterate a null-terminated string in a relatively simple +/// way. +pub struct CStringIter { + ptr: *const CChar, + idx: isize, +} + +impl CStringIter { + /// Create a new iterator from a pointer to a null-terminated string. The + /// behaviour is undefined if the string is not null-terminated. + pub fn new(s: *const CChar) -> CStringIter { + CStringIter { ptr: s, idx: 0 } + } +} + +impl core::iter::Iterator for CStringIter { + type Item = CChar; + fn next(&mut self) -> Option { + let c = unsafe { *self.ptr.offset(self.idx) }; + if c == 0 { + None + } else { + self.idx += 1; + Some(c) + } + } +} diff --git a/src/itoa.rs b/src/itoa.rs index ed3dc94..9d484ed 100644 --- a/src/itoa.rs +++ b/src/itoa.rs @@ -8,7 +8,6 @@ use crate::CChar; -#[no_mangle] /// Formats the given value `i`, with the given radix, into the given buffer (of the given length). /// /// No prefixes (like 0x or 0b) are generated. Only radix values in the range @@ -16,6 +15,8 @@ use crate::CChar; /// /// Returns the number of bytes written on success (not including the null), /// or -1 if the buffer wasn't large enough. +#[cfg_attr(not(feature = "itoa"), export_name = "tinyrlibc_itoa")] +#[cfg_attr(feature = "itoa", no_mangle)] pub unsafe extern "C" fn itoa(i: i64, s: *mut CChar, s_len: usize, radix: u8) -> i32 { let (is_negative, pos_i) = if i < 0 { (true, (-i) as u64) @@ -31,7 +32,6 @@ pub unsafe extern "C" fn itoa(i: i64, s: *mut CChar, s_len: usize, radix: u8) -> } } -#[no_mangle] /// Formats the given value `u`, with the given radix, into the given buffer (of the given length). /// /// No prefixes (like 0x or 0b) are generated. Only radix values in the range @@ -39,6 +39,8 @@ pub unsafe extern "C" fn itoa(i: i64, s: *mut CChar, s_len: usize, radix: u8) -> /// /// Returns the number of bytes written on success (not including the null), /// or -1 if the buffer wasn't large enough. +#[cfg_attr(not(feature = "utoa"), export_name = "tinyrlibc_utoa")] +#[cfg_attr(feature = "utoa", no_mangle)] pub unsafe extern "C" fn utoa(mut u: u64, s: *mut CChar, s_len: usize, radix: u8) -> i32 { let buffer_slice = core::slice::from_raw_parts_mut(s, s_len); diff --git a/src/lib.rs b/src/lib.rs index ef74727..6214010 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,123 +5,74 @@ //! This file is Copyright (c) Jonathan 'theJPster' Pallant 2019 //! Licensed under the Blue Oak Model Licence 1.0.0 //! -//! See each module for its respective licence. +//! See each module for its respective license. #![cfg_attr(not(test), no_std)] +#![allow(clippy::missing_safety_doc)] #[cfg(test)] #[allow(unused_imports)] use std as core; -#[cfg(feature = "abs")] +mod itoa; + mod abs; #[cfg(feature = "abs")] pub use self::abs::abs; -#[cfg(feature = "strcmp")] mod strcmp; #[cfg(feature = "strcmp")] pub use self::strcmp::strcmp; -#[cfg(feature = "strncmp")] mod strncmp; #[cfg(feature = "strncmp")] pub use self::strncmp::strncmp; -#[cfg(feature = "strcpy")] mod strcpy; #[cfg(feature = "strcpy")] pub use self::strcpy::strcpy; -#[cfg(feature = "strncpy")] mod strncpy; #[cfg(feature = "strncpy")] pub use self::strncpy::strncpy; -#[cfg(feature = "strlen")] mod strlen; #[cfg(feature = "strlen")] pub use self::strlen::strlen; -#[cfg(feature = "strtol")] mod strtol; +#[cfg(feature = "atoi")] +pub use self::strtol::atoi; +#[cfg(feature = "isalpha")] +pub use self::strtol::isalpha; +#[cfg(feature = "isdigit")] +pub use self::strtol::isdigit; +#[cfg(feature = "isspace")] +pub use self::strtol::isspace; +#[cfg(feature = "isupper")] +pub use self::strtol::isupper; +#[cfg(feature = "strtoimax")] +pub use self::strtol::strtoimax; #[cfg(feature = "strtol")] pub use self::strtol::strtol; - +#[cfg(feature = "strtoll")] +pub use self::strtol::strtoll; #[cfg(feature = "strtoul")] -mod strtoul; -#[cfg(feature = "strtoul")] -pub use self::strtoul::strtoul; +pub use self::strtol::strtoul; +#[cfg(feature = "strtoull")] +pub use self::strtol::strtoull; +#[cfg(feature = "strtoumax")] +pub use self::strtol::strtoumax; -#[cfg(feature = "strstr")] mod strstr; #[cfg(feature = "strstr")] pub use self::strstr::strstr; -#[cfg(feature = "strchr")] mod strchr; #[cfg(feature = "strchr")] pub use self::strchr::strchr; -#[cfg(feature = "atoi")] -mod atoi; -#[cfg(feature = "atoi")] -pub use self::atoi::atoi; - -#[cfg(feature = "itoa")] -mod itoa; -#[cfg(feature = "itoa")] -pub use self::itoa::itoa; - -#[cfg(feature = "snprintf")] mod snprintf; -/// `long long int` -pub type CLongLong = ::core::ffi::c_longlong; - -/// `unsigned long long int` -pub type CULongLong = ::core::ffi::c_ulonglong; - -/// `long int` -pub type CLong = ::core::ffi::c_long; - -/// `unsigned long int` -pub type CULong = ::core::ffi::c_ulong; - -/// `int` -pub type CInt = ::core::ffi::c_int; - -/// `unsigned int` -pub type CUInt = ::core::ffi::c_uint; - -/// Represents an 8-bit `char`. Rust does not (and will never) support -/// platforms where `char` is not 8-bits long. -pub type CChar = u8; - -/// This allows you to iterate a null-terminated string in a relatively simple -/// way. -pub struct CStringIter { - ptr: *const CChar, - idx: isize, -} - -impl CStringIter { - /// Create a new iterator from a pointer to a null-terminated string. The - /// behaviour is undefined if the string is not null-terminated. - pub fn new(s: *const CChar) -> CStringIter { - CStringIter { ptr: s, idx: 0 } - } -} - -impl core::iter::Iterator for CStringIter { - type Item = CChar; - fn next(&mut self) -> Option { - let c = unsafe { *self.ptr.offset(self.idx) }; - if c == 0 { - None - } else { - self.idx += 1; - Some(c) - } - } -} +mod ctype; +pub use self::ctype::*; diff --git a/src/snprintf.c b/src/snprintf.c index 8c88091..9a06757 100644 --- a/src/snprintf.c +++ b/src/snprintf.c @@ -65,7 +65,7 @@ extern int32_t utoa(uint64_t i, char* s, size_t s_len, uint8_t radix); /** * This is provided by `strtoul.rs`. It converts a string to a long. */ -extern unsigned long int strtoul(const char* str, const char* restrict* endptr, int base); +extern unsigned long int strtoul(const char* str, char** endptr, int base); /* ======================================================================== * * @@ -106,7 +106,7 @@ extern unsigned long int strtoul(const char* str, const char* restrict* endptr, * @return the number of characters written to `str` */ int vsnprintf( - char* restrict str, size_t size, const char* restrict fmt, va_list ap ) + char* restrict str, size_t size, const char* fmt, va_list ap ) { size_t written = 0; bool is_escape = false; @@ -347,7 +347,7 @@ int vsnprintf( } else { - precision = strtoul(fmt, &fmt, 10); + precision = 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--; diff --git a/src/snprintf.rs b/src/snprintf.rs index ac4be00..354b4a2 100644 --- a/src/snprintf.rs +++ b/src/snprintf.rs @@ -3,6 +3,7 @@ //! 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" { diff --git a/src/strchr.rs b/src/strchr.rs index 5b77ed6..063c96a 100644 --- a/src/strchr.rs +++ b/src/strchr.rs @@ -6,7 +6,7 @@ use crate::{CChar, CInt}; /// Rust implementation of C library function `strchr` -#[no_mangle] +#[cfg_attr(feature = "strchr", no_mangle)] pub unsafe extern "C" fn strchr(haystack: *const CChar, needle: CInt) -> *const CChar { for idx in 0.. { let ptr = haystack.offset(idx); diff --git a/src/strcmp.rs b/src/strcmp.rs index bd5c50f..c170463 100644 --- a/src/strcmp.rs +++ b/src/strcmp.rs @@ -6,7 +6,7 @@ use crate::{CChar, CInt}; /// Rust implementation of C library function `strcmp` -#[no_mangle] +#[cfg_attr(feature = "strcmp", no_mangle)] pub unsafe extern "C" fn strcmp(s1: *const CChar, s2: *const CChar) -> CInt { for i in 0.. { let s1_i = s1.offset(i); @@ -19,3 +19,33 @@ pub unsafe extern "C" fn strcmp(s1: *const CChar, s2: *const CChar) -> CInt { } 0 } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test1() { + assert!(unsafe { strcmp(b"Hello\0" as *const CChar, b"Hello\0" as *const CChar) } == 0); + } + + #[test] + fn test2() { + assert!(unsafe { strcmp(b"Hello\0" as *const CChar, b"Hello1\0" as *const CChar) } < 0); + } + + #[test] + fn test3() { + assert!(unsafe { strcmp(b"Hello1\0" as *const CChar, b"Hello\0" as *const CChar) } > 0); + } + + #[test] + fn test4() { + assert!(unsafe { strcmp(b"\0" as *const CChar, b"Hello\0" as *const CChar) } < 0); + } + + #[test] + fn test5() { + assert!(unsafe { strcmp(b"Hello\0" as *const CChar, b"\0" as *const CChar) } > 0); + } +} diff --git a/src/strcpy.rs b/src/strcpy.rs index a902ca6..d6deced 100644 --- a/src/strcpy.rs +++ b/src/strcpy.rs @@ -6,7 +6,7 @@ use crate::CChar; /// Rust implementation of C library function `strcpy`. Passing NULL /// (core::ptr::null()) gives undefined behaviour. -#[no_mangle] +#[cfg_attr(feature = "strcpy", no_mangle)] pub unsafe extern "C" fn strcpy(dest: *mut CChar, src: *const CChar) -> *const CChar { let mut i = 0; loop { diff --git a/src/strlen.rs b/src/strlen.rs index fc2683d..a81ed3d 100644 --- a/src/strlen.rs +++ b/src/strlen.rs @@ -6,7 +6,7 @@ use crate::CChar; /// Rust implementation of C library function `strlen` -#[no_mangle] +#[cfg_attr(feature = "strlen", no_mangle)] pub unsafe extern "C" fn strlen(mut s: *const CChar) -> usize { let mut result = 0; while *s != 0 { diff --git a/src/strncmp.rs b/src/strncmp.rs index 498ab03..d081a52 100644 --- a/src/strncmp.rs +++ b/src/strncmp.rs @@ -7,7 +7,7 @@ use crate::{CChar, CInt}; /// Rust implementation of C library function `strncmp`. Passing NULL /// (core::ptr::null()) gives undefined behaviour. -#[no_mangle] +#[cfg_attr(feature = "strncmp", no_mangle)] pub unsafe extern "C" fn strncmp(s1: *const CChar, s2: *const CChar, n: usize) -> crate::CInt { for i in 0..n as isize { let s1_i = s1.offset(i); diff --git a/src/strncpy.rs b/src/strncpy.rs index 4b325fc..d911c26 100644 --- a/src/strncpy.rs +++ b/src/strncpy.rs @@ -7,7 +7,7 @@ use crate::CChar; /// Rust implementation of C library function `strncmp`. Passing NULL /// (core::ptr::null()) gives undefined behaviour. -#[no_mangle] +#[cfg_attr(feature = "strncpy", no_mangle)] pub unsafe extern "C" fn strncpy( dest: *mut CChar, src: *const CChar, diff --git a/src/strstr.rs b/src/strstr.rs index cb4ac9e..692962f 100644 --- a/src/strstr.rs +++ b/src/strstr.rs @@ -6,7 +6,7 @@ use crate::{CChar, CStringIter}; /// Rust implementation of C library function `strstr` -#[no_mangle] +#[cfg_attr(feature = "strstr", no_mangle)] pub unsafe extern "C" fn strstr(haystack: *const CChar, needle: *const CChar) -> *const CChar { if *needle.offset(0) == 0 { return haystack; diff --git a/src/strtol.rs b/src/strtol.rs index 602c7e3..1de8973 100644 --- a/src/strtol.rs +++ b/src/strtol.rs @@ -1,67 +1,244 @@ //! Rust implementation of C library function `strtol` //! -//! Copyright (c) Jonathan 'theJPster' Pallant 2019 -//! Licensed under the Blue Oak Model Licence 1.0.0 - -use crate::{CChar, CLong, CStringIter}; - -/// Rust implementation of C library function `strtol`. -/// -/// Takes a null-terminated string and interprets it as a decimal integer. -/// This integer is returned as a `CLong`. Parsing stops when the first -/// non-digit ASCII byte is seen. If no valid ASCII digit bytes are seen, this -/// function returns zero. -#[no_mangle] -pub unsafe extern "C" fn strtol(s: *const CChar) -> CLong { - let mut result: CLong = 0; - for c in CStringIter::new(s) { - if (b'0'..=b'9').contains(&c) { - result *= 10; - result += (c - b'0') as CLong; +//! Original code from the `c-ward` project. +//! Licensed under the MIT license. + +use crate::{CChar, CInt, CIntMax, CLong, CLongLong, CUIntMax, CULong, CULongLong}; + +/// Rust implementation of C library function `atoi` +#[cfg_attr(feature = "atoi", no_mangle)] +pub unsafe extern "C" fn atoi(s: *const CChar) -> CInt { + strtol(s, core::ptr::null_mut(), 10) as CInt +} + +/// Rust implementation of C library function `atol` +#[cfg_attr(feature = "strtol", no_mangle)] +pub unsafe extern "C" fn strtol(s: *const CChar, endptr: *mut *const CChar, base: CInt) -> CLong { + strtox(s, endptr, base, CLong::MIN as _, CLong::MAX as _) as CLong +} + +/// Rust implementation of C library function `strtoul` +#[cfg_attr(not(feature = "strtoul"), export_name = "tinyrlibc_strtoul")] +#[cfg_attr(feature = "strtoul", no_mangle)] +pub unsafe extern "C" fn strtoul(s: *const CChar, endptr: *mut *const CChar, base: CInt) -> CULong { + strtox(s, endptr, base, 0, CULong::MAX as _) as CULong +} + +/// Rust implementation of C library function `strtoll` +#[cfg_attr(feature = "strtoll", no_mangle)] +pub unsafe extern "C" fn strtoll( + s: *const CChar, + endptr: *mut *const CChar, + base: CInt, +) -> CLongLong { + strtox(s, endptr, base, CLongLong::MIN, CLongLong::MAX as _) as CLongLong +} + +/// Rust implementation of C library function `strtoull` +#[cfg_attr(feature = "strtoull", no_mangle)] +pub unsafe extern "C" fn strtoull( + s: *const CChar, + endptr: *mut *const CChar, + base: CInt, +) -> CULongLong { + strtox(s, endptr, base, 0, CULongLong::MAX) as CULongLong +} + +/// Rust implementation of C library function `strtoimax` +#[cfg_attr(feature = "strtoimax", no_mangle)] +pub unsafe extern "C" fn strtoimax( + s: *const CChar, + endptr: *mut *const CChar, + base: CInt, +) -> CIntMax { + strtox(s, endptr, base, CIntMax::MIN, CIntMax::MAX as _) as CIntMax +} + +/// Rust implementation of C library function `strtoumax` +#[cfg_attr(feature = "strtoumax", no_mangle)] +pub unsafe extern "C" fn strtoumax( + s: *const CChar, + endptr: *mut *const CChar, + base: CInt, +) -> CUIntMax { + strtox(s, endptr, base, 0, CUIntMax::MAX) as CUIntMax +} + +pub unsafe fn strtox( + s: *const CChar, + endptr: *mut *const CChar, + base: CInt, + min: CIntMax, + max: CUIntMax, +) -> CUIntMax { + if !(0..=36).contains(&base) { + // TODO: set errno to EINVAL + return 0; + } + + // Skip leading whitespace. + let mut s = s; + while isspace(CInt::from(*s)) != 0 { + s = s.add(1); + } + + // Parse an optional +/- sign. + let mut negate = false; + if *s == b'+' as CChar { + s = s.add(1); + } else if *s == b'-' as CChar { + negate = true; + s = s.add(1); + } + + // Parse an optional base prefix. + let mut base: CUIntMax = base as CUIntMax; + if base == 0 { + if *s == b'0' as CChar { + s = s.add(1); + if (*s == b'x' as CChar || *s == b'X' as CChar) && (*s.add(1)).is_ascii_hexdigit() { + s = s.add(1); + base = 16; + } else { + base = 8; + } } else { - break; + base = 10; } + } else if base == 16 + && *s == b'0' as CChar + && (*s.add(1) == b'x' as CChar || *s.add(1) == b'X' as CChar) + && (*s.add(2)).is_ascii_hexdigit() + { + s = s.add(2); } - result -} -#[cfg(test)] -mod test { - use super::strtol; + // Parse the digits. + let mut overflow = false; + let mut num: CUIntMax = 0; + loop { + let digit: CUIntMax = match *s { + x @ b'0'..=b'9' => x - b'0', + x @ b'a'..=b'z' => x - b'a' + 10, + x @ b'A'..=b'Z' => x - b'A' + 10, + _ => break, + } + .into(); + if digit >= base { + break; + } - #[test] - fn empty() { - let result = unsafe { strtol(b"\0".as_ptr()) }; - assert_eq!(result, 0); + if negate && min != 0 { + if (num as CIntMax) < min / base as CIntMax { + overflow = true; + } + } else if num > max / base { + overflow = true; + } + num = num.wrapping_mul(base); + + if negate && min != 0 { + if (num as CIntMax) < min + digit as CIntMax { + overflow = true; + } + num = num.wrapping_sub(digit); + } else { + if num > max - digit { + overflow = true; + } + num = num.wrapping_add(digit); + } + + s = s.add(1); } - #[test] - fn non_digit() { - let result = unsafe { strtol(b"1234x\0".as_ptr()) }; - assert_eq!(result, 1234); + // If requested, report the end position. + if !endptr.is_null() { + *endptr = s.cast_mut(); } - #[test] - fn bad_number() { - let result = unsafe { strtol(b"x\0".as_ptr()) }; - assert_eq!(result, 0); + // Report overflow. + if overflow { + // TODO: set errno to ERANGE + return if negate && min != 0 { + min as CUIntMax + } else { + max + }; } - #[test] - fn one() { - let result = unsafe { strtol(b"1\0".as_ptr()) }; - assert_eq!(result, 1); + // Perform negation if requested. + if negate && min == 0 { + num = num.wrapping_neg(); + } + + // Return a successful result. + num as CUIntMax +} + +/// Rust implementation of C library function `isspace` +#[cfg_attr(feature = "isspace", no_mangle)] +pub extern "C" fn isspace(argument: CInt) -> CInt { + match argument as CChar { + b' ' | b'\t' | b'\n' | b'\r' | 0x0b | 0x0c => 1, + _ => 0, } +} + +/// Rust implementation of C library function `isdigit` +#[cfg_attr(feature = "isdigit", no_mangle)] +pub extern "C" fn isdigit(argument: CInt) -> CInt { + (argument as CChar).is_ascii_digit() as CInt +} + +/// Rust implementation of C library function `isalpha` +#[cfg_attr(feature = "isalpha", no_mangle)] +pub extern "C" fn isalpha(argument: CInt) -> CInt { + (argument as CChar).is_ascii_alphabetic() as CInt +} + +/// Rust implementation of C library function `isupper` +#[cfg_attr(feature = "isupper", no_mangle)] +pub extern "C" fn isupper(argument: CInt) -> CInt { + (argument as CChar).is_ascii_uppercase() as CInt +} + +#[cfg(test)] +mod tests { + use core::ptr::null_mut; + + use super::*; #[test] - fn hundredish() { - let result = unsafe { strtol(b"123\0".as_ptr()) }; - assert_eq!(result, 123); + fn parse_multi_string() { + let string = b"10 200000000000000000000000000000 30 -40\0"; + + let mut s = string.as_ptr(); + let results = [ + (10, unsafe { s.offset(2) }), + (CULong::MAX, unsafe { s.offset(33) }), + (30, unsafe { s.offset(36) }), + (-40i32 as CULong, unsafe { s.offset(40) }), + ]; + + for (result_number, result_ptr) in results { + let number = unsafe { strtoul(s, &mut s as *mut _, 10) }; + + assert_eq!(s, result_ptr); + assert_eq!(number, result_number); + } } #[test] - fn big_long() { - let result = unsafe { strtol(b"2147483647\0".as_ptr()) }; - assert_eq!(result, 2147483647); + fn parse_hex() { + assert_eq!( + unsafe { strtoul(b"0xAA123\0".as_ptr(), null_mut(), 0) }, + 0xAA123 + ); + assert_eq!(unsafe { strtoul(b"0X00\0".as_ptr(), null_mut(), 0) }, 0x00); + assert_eq!( + unsafe { strtoul(b"-0x123456F\0".as_ptr(), null_mut(), 0) }, + (-0x123456Fi32) as _ + ); } } diff --git a/src/strtoul.rs b/src/strtoul.rs deleted file mode 100644 index ba6fc36..0000000 --- a/src/strtoul.rs +++ /dev/null @@ -1,191 +0,0 @@ -//! Copyright (c) 1990 Regents of the University of California. -//! All rights reserved. -//! -//! Redistribution and use in source and binary forms, with or without -//! modification, are permitted provided that the following conditions -//! are met: -//! 1. Redistributions of source code must retain the above copyright -//! notice, this list of conditions and the following disclaimer. -//! 2. Redistributions in binary form must reproduce the above copyright -//! notice, this list of conditions and the following disclaimer in the -//! documentation and/or other materials provided with the distribution. -//! 3. [rescinded 22 July 1999] -//! 4. Neither the name of the University nor the names of its contributors -//! may be used to endorse or promote products derived from this software -//! without specific prior written permission. -//! -//! THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND -//! ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -//! IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -//! ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE -//! FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -//! DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -//! OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -//! HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -//! LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -//! OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -//! SUCH DAMAGE. -//! -//! Translated from https://github.com/gcc-mirror/gcc/blob/97d1ed67fc6a5773c8c00875bfa3616a457cf5f9/libiberty/strtoul.c - -use crate::{CChar, CInt, CLong, CULong}; - -/// Rust implementation of C library function [`strtoul`](https://cplusplus.com/reference/cstdlib/strtoul/). -/// -/// Passing NULL (core::ptr::null()) gives undefined behaviour. -/// -/// Convert a string to an unsigned long integer. -/// -/// Ignores `locale' stuff. Assumes that the upper and lower case -/// alphabets and digits are each contiguous. -#[no_mangle] -pub unsafe extern "C" fn strtoul( - nptr: *const CChar, - endptr: *mut *const CChar, - mut base: CInt, -) -> CULong { - let mut s = nptr; - - let mut c = *s; - s = s.offset(1); - while isspace(c) { - c = *s; - s = s.offset(1); - } - - let neg = if c == b'-' { - c = *s; - s = s.offset(1); - true - } else { - if c == b'+' { - c = *s; - s = s.offset(1); - } - false - }; - - if (base == 0 || base == 16) && c == b'0' && (*s == b'x' || *s == b'X') { - c = *s.offset(1); - s = s.offset(2); - base = 16; - } - - if base == 0 { - base = if c == b'0' { 8 } else { 10 }; - } - - let cutoff = CULong::MAX / base as CULong; - let cutlim = CULong::MAX % base as CULong; - - let mut acc = 0; - let mut any = 0; - - loop { - if isdigit(c) { - c -= b'0'; - } else if isalpha(c) { - c -= if isupper(c) { b'A' - 10 } else { b'a' - 10 }; - } else { - break; - } - - if c as CInt >= base { - break; - } - - if any < 0 { - c = *s; - s = s.offset(1); - continue; - } - - if acc > cutoff || (acc == cutoff && c as CULong > cutlim) { - any = -1; - acc = CULong::MAX; - } else { - any = 1; - acc *= base as CULong; - acc += c as CULong; - } - - c = *s; - s = s.offset(1); - } - if neg && any > 0 { - acc = -(acc as CLong) as _; - } - - if !endptr.is_null() { - (*endptr) = if any != 0 { - s.offset(-1) - } else { - core::ptr::null() - }; - } - - acc -} - -fn isspace(argument: CChar) -> bool { - // Rust doesn't support "\v" - const VERTICAL_TAB: u8 = 0x0B; - // Rust doesn't support "\f" - const FEED: u8 = 0x0C; - const SPACE_CHARACTERS: [u8; 6] = [b' ', b'\n', b'\t', VERTICAL_TAB, FEED, b'\r']; - - SPACE_CHARACTERS.contains(&argument) -} - -fn isdigit(argument: CChar) -> bool { - (b'0'..=b'9').contains(&argument) -} - -fn isalpha(argument: CChar) -> bool { - (b'a'..=b'z').contains(&argument) || (b'A'..=b'Z').contains(&argument) -} - -fn isupper(argument: CChar) -> bool { - (b'A'..=b'Z').contains(&argument) -} - -#[cfg(test)] -mod tests { - use core::ptr::null_mut; - - use super::*; - - #[test] - fn parse_multi_string() { - let string = b"10 200000000000000000000000000000 30 -40\0"; - - let mut s = string.as_ptr(); - - let results = [ - (10, unsafe { s.offset(2) }), - (CULong::MAX, unsafe { s.offset(33) }), - (30, unsafe { s.offset(36) }), - (-40i32 as CULong, unsafe { s.offset(40) }), - ]; - - for (result_number, result_ptr) in results { - let number = unsafe { strtoul(s, &mut s as *mut _, 10) }; - - assert_eq!(number, result_number); - assert_eq!(s, result_ptr); - } - } - - #[test] - fn parse_hex() { - assert_eq!( - unsafe { strtoul(b"0xAA123\0".as_ptr(), null_mut(), 0) }, - 0xAA123 - ); - assert_eq!(unsafe { strtoul(b"0X00\0".as_ptr(), null_mut(), 0) }, 0x00); - assert_eq!( - unsafe { strtoul(b"-0x123456F\0".as_ptr(), null_mut(), 0) }, - (-0x123456Fi32) as _ - ); - } -}