Skip to content

Commit e1c0d10

Browse files
committed
Add sprintf support.
Requires a C file as variadic functions aren't stable in Rust.
1 parent 6ce526e commit e1c0d10

File tree

13 files changed

+931
-34
lines changed

13 files changed

+931
-34
lines changed

Cargo.toml

100644100755
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,16 @@ license-file = "LICENCES.md"
88
readme = "README.md"
99

1010
[dependencies]
11+
12+
[build-dependencies]
13+
cc = "1.0"
14+
15+
[features]
16+
# Set this feature to assume a C `long` is 64-bits (i.e. the C compiler uses
17+
# the LP64 model, rather than the LLP64, ILP32 or LP32 models). See
18+
# https://en.cppreference.com/w/cpp/language/types for more details.
19+
lp64 = []
20+
# Set this feature to assume a C `int` is 16-bits (i.e. the C compiler uses
21+
# the LP32 model, rather than the LP64, LLP64 or ILP32 models). See
22+
# https://en.cppreference.com/w/cpp/language/types for more details.
23+
lp32 = []

README.md

100644100755
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@ This crate basically came about so that the [nrfxlib](https://github.com/NordicP
99
## Implemented so far
1010

1111
* strol
12+
* atoi
13+
* strcmp
14+
* strncmp
15+
* strlen
16+
* strtol
17+
* strstr
18+
* snprintf
19+
* vsnprintf
20+
21+
## Non-standard helper functions
22+
23+
* itoa
24+
* utoa
1225

1326
## To Do
1427

build.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
use cc;
2+
3+
fn main() {
4+
// Build our snprintf substitute (which has to be C as Rust doesn't do varargs)
5+
cc::Build::new()
6+
.warnings(true)
7+
.extra_warnings(true)
8+
.file("./src/snprintf.c")
9+
.compile("clocal");
10+
}

src/atoi.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//! Copyright (c) Jonathan 'theJPster' Pallant 2019
44
//! Licensed under the Blue Oak Model Licence 1.0.0
55
6-
use crate::strtol;
6+
use crate::{strtol, CChar, CInt, CLong};
77

88
/// Converts a null-terminated string representing a decimal integer, into an
99
/// integer. No indication of error.
@@ -15,13 +15,13 @@ use crate::strtol;
1515
/// assert_eq!(unsafe { atoi(b"".as_ptr()) }, 0);
1616
/// ```
1717
#[no_mangle]
18-
pub unsafe extern "C" fn atoi(s: *const crate::CChar) -> crate::CInt {
18+
pub unsafe extern "C" fn atoi(s: *const CChar) -> CInt {
1919
let result = strtol(s);
20-
if result > crate::CInt::max_value() {
21-
crate::CInt::max_value()
22-
} else if result < crate::CInt::min_value() {
23-
crate::CInt::min_value()
20+
if result > CInt::max_value() as CLong {
21+
CInt::max_value()
22+
} else if result < CInt::min_value() as CLong {
23+
CInt::min_value()
2424
} else {
25-
result as crate::CInt
25+
result as CInt
2626
}
2727
}

src/itoa.rs

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
//! Rust implementation of C library function `itoa`
2+
//!
3+
//! This function isn't part of POSIX, or the C standard library, but it is
4+
//! mentioned in K&R, and it useful when writing a `sprintf` implementation.
5+
//!
6+
//! Copyright (c) Jonathan 'theJPster' Pallant 2019
7+
//! Licensed under the Blue Oak Model Licence 1.0.0
8+
9+
use crate::CChar;
10+
11+
#[no_mangle]
12+
/// Formats the given value `i`, with the given radix, into the given buffer (of the given length).
13+
///
14+
/// No prefixes (like 0x or 0b) are generated. Only radix values in the range
15+
/// 2..=16 are supported.
16+
///
17+
/// Returns the number of bytes written on success (not including the null),
18+
/// or -1 if the buffer wasn't large enough.
19+
pub unsafe extern "C" fn itoa(i: i64, s: *mut CChar, s_len: usize, radix: u8) -> i32 {
20+
let (is_negative, pos_i) = if i < 0 {
21+
(true, (-i) as u64)
22+
} else {
23+
(false, i as u64)
24+
};
25+
26+
if is_negative && (s_len > 0) {
27+
core::ptr::write(s, b'-');
28+
utoa(pos_i, s.offset(1), s_len - 1, radix)
29+
} else {
30+
utoa(pos_i, s, s_len, radix)
31+
}
32+
}
33+
34+
#[no_mangle]
35+
/// Formats the given value `u`, with the given radix, into the given buffer (of the given length).
36+
///
37+
/// No prefixes (like 0x or 0b) are generated. Only radix values in the range
38+
/// 2..=16 are supported. Negative numbers are not supported.
39+
///
40+
/// Returns the number of bytes written on success (not including the null),
41+
/// or -1 if the buffer wasn't large enough.
42+
pub unsafe extern "C" fn utoa(mut u: u64, s: *mut CChar, s_len: usize, radix: u8) -> i32 {
43+
let buffer_slice = core::slice::from_raw_parts_mut(s, s_len);
44+
45+
// Build the number up in buffer s in reverse order
46+
let mut index = 0usize;
47+
for slot in buffer_slice.iter_mut() {
48+
let digit = (u % radix as u64) as u8;
49+
if digit <= 9 {
50+
*slot = digit + b'0';
51+
} else {
52+
*slot = digit - 10 + b'a';
53+
}
54+
index += 1;
55+
u /= radix as u64;
56+
if u == 0 {
57+
break;
58+
}
59+
}
60+
61+
if u != 0 {
62+
return -1;
63+
}
64+
65+
// Null-terminate
66+
if index < buffer_slice.len() {
67+
buffer_slice[index] = b'\0';
68+
}
69+
70+
// Reverse buffer into correct order
71+
buffer_slice[0..index].reverse();
72+
73+
index as i32
74+
}
75+
76+
#[cfg(test)]
77+
mod test {
78+
use super::{itoa, utoa};
79+
use crate::strcmp::strcmp;
80+
81+
#[test]
82+
fn zero() {
83+
let mut buf = [b'\0'; 32];
84+
assert_eq!(unsafe { itoa(0, buf.as_mut_ptr(), buf.len(), 10) }, 1);
85+
assert_eq!(
86+
unsafe { strcmp(buf.as_ptr() as *const u8, b"0\0" as *const u8) },
87+
0
88+
);
89+
}
90+
91+
#[test]
92+
fn one() {
93+
let mut buf = [b'\0'; 32];
94+
assert_eq!(unsafe { itoa(1, buf.as_mut_ptr(), buf.len(), 10) }, 1);
95+
assert_eq!(
96+
unsafe { strcmp(buf.as_ptr() as *const u8, b"1\0" as *const u8) },
97+
0
98+
);
99+
}
100+
101+
#[test]
102+
fn hundredish() {
103+
let mut buf = [b'\0'; 32];
104+
assert_eq!(unsafe { itoa(123, buf.as_mut_ptr(), buf.len(), 10) }, 3);
105+
assert_eq!(
106+
unsafe { strcmp(buf.as_ptr() as *const u8, b"123\0" as *const u8) },
107+
0
108+
);
109+
}
110+
111+
#[test]
112+
fn too_small() {
113+
let mut buf = [b'\0'; 1];
114+
assert_eq!(unsafe { itoa(10, buf.as_mut_ptr(), buf.len(), 10) }, -1);
115+
}
116+
117+
#[test]
118+
fn hex() {
119+
let mut buf = [b'\0'; 32];
120+
assert_eq!(
121+
unsafe { itoa(0xDEADBEEF, buf.as_mut_ptr(), buf.len(), 16) },
122+
8
123+
);
124+
assert_eq!(
125+
unsafe { strcmp(buf.as_ptr() as *const u8, b"deadbeef\0" as *const u8) },
126+
0
127+
);
128+
}
129+
130+
#[test]
131+
fn octal() {
132+
let mut buf = [b'\0'; 32];
133+
assert_eq!(unsafe { itoa(0o774, buf.as_mut_ptr(), buf.len(), 8) }, 3);
134+
assert_eq!(
135+
unsafe { strcmp(buf.as_ptr() as *const u8, b"774\0" as *const u8) },
136+
0
137+
);
138+
}
139+
140+
#[test]
141+
fn binary() {
142+
let mut buf = [b'\0'; 32];
143+
assert_eq!(
144+
unsafe { itoa(0b11100010, buf.as_mut_ptr(), buf.len(), 2) },
145+
8
146+
);
147+
assert_eq!(
148+
unsafe { strcmp(buf.as_ptr() as *const u8, b"11100010\0" as *const u8) },
149+
0
150+
);
151+
}
152+
153+
#[test]
154+
fn negative() {
155+
let mut buf = [b'\0'; 32];
156+
unsafe { itoa(-123, buf.as_mut_ptr(), buf.len(), 10) };
157+
assert_eq!(
158+
unsafe { strcmp(buf.as_ptr() as *const u8, b"-123\0" as *const u8) },
159+
0
160+
);
161+
}
162+
163+
#[test]
164+
fn unsigned_zero() {
165+
let mut buf = [b'\0'; 32];
166+
assert_eq!(unsafe { utoa(0, buf.as_mut_ptr(), buf.len(), 10) }, 1);
167+
assert_eq!(
168+
unsafe { strcmp(buf.as_ptr() as *const u8, b"0\0" as *const u8) },
169+
0
170+
);
171+
}
172+
173+
#[test]
174+
fn unsigned_one() {
175+
let mut buf = [b'\0'; 32];
176+
assert_eq!(unsafe { utoa(1, buf.as_mut_ptr(), buf.len(), 10) }, 1);
177+
assert_eq!(
178+
unsafe { strcmp(buf.as_ptr() as *const u8, b"1\0" as *const u8) },
179+
0
180+
);
181+
}
182+
183+
#[test]
184+
fn unsigned_hundredish() {
185+
let mut buf = [b'\0'; 32];
186+
assert_eq!(unsafe { utoa(123, buf.as_mut_ptr(), buf.len(), 10) }, 3);
187+
assert_eq!(
188+
unsafe { strcmp(buf.as_ptr() as *const u8, b"123\0" as *const u8) },
189+
0
190+
);
191+
}
192+
193+
#[test]
194+
fn unsigned_too_small() {
195+
let mut buf = [b'\0'; 1];
196+
assert_eq!(unsafe { utoa(10, buf.as_mut_ptr(), buf.len(), 10) }, -1);
197+
}
198+
199+
#[test]
200+
fn unsigned_hex() {
201+
let mut buf = [b'\0'; 32];
202+
assert_eq!(
203+
unsafe { utoa(0xDEADBEEF, buf.as_mut_ptr(), buf.len(), 16) },
204+
8
205+
);
206+
assert_eq!(
207+
unsafe { strcmp(buf.as_ptr() as *const u8, b"deadbeef\0" as *const u8) },
208+
0
209+
);
210+
}
211+
212+
#[test]
213+
fn unsigned_octal() {
214+
let mut buf = [b'\0'; 32];
215+
assert_eq!(unsafe { utoa(0o774, buf.as_mut_ptr(), buf.len(), 8) }, 3);
216+
assert_eq!(
217+
unsafe { strcmp(buf.as_ptr() as *const u8, b"774\0" as *const u8) },
218+
0
219+
);
220+
}
221+
222+
#[test]
223+
fn unsigned_binary() {
224+
let mut buf = [b'\0'; 32];
225+
assert_eq!(
226+
unsafe { utoa(0b11100010, buf.as_mut_ptr(), buf.len(), 2) },
227+
8
228+
);
229+
assert_eq!(
230+
unsafe { strcmp(buf.as_ptr() as *const u8, b"11100010\0" as *const u8) },
231+
0
232+
);
233+
}
234+
}

src/lib.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,51 @@ pub use self::strstr::strstr;
3131
mod atoi;
3232
pub use self::atoi::atoi;
3333

34-
// TODO: Add cfg defines / work these out for platforms other than armv6/7/8m
34+
mod itoa;
35+
pub use self::itoa::itoa;
3536

37+
mod snprintf;
38+
39+
/// `long long int` is always 64-bits long.
3640
pub type CLongLong = i64;
41+
42+
/// `unsigned long long int` is always 64-bits long.
43+
pub type CULongLong = i64;
44+
45+
#[cfg(feature = "lp64")]
46+
/// The `lp64` feature means we assume `long int` is 64-bits.
47+
pub type CLong = i64;
48+
49+
#[cfg(feature = "lp64")]
50+
/// The `lp64` feature means we assume `unsigned long int` is 64-bits.
51+
pub type CULong = u64;
52+
53+
#[cfg(not(feature = "lp64"))]
54+
/// We assume `long int` is 32-bits.
3755
pub type CLong = i32;
56+
57+
#[cfg(not(feature = "lp64"))]
58+
/// We assume `unsigned long int` is 32-bits.
59+
pub type CULong = u32;
60+
61+
#[cfg(feature = "lp32")]
62+
/// The `lp32` feature means we assume `int` is 16-bits.
63+
pub type CInt = i16;
64+
65+
#[cfg(feature = "lp32")]
66+
/// The `lp32` feature means we assume `unsigned int` is 16-bits.
67+
pub type CUInt = u16;
68+
69+
#[cfg(not(feature = "lp32"))]
70+
/// We assume `int` is 32-bits.
3871
pub type CInt = i32;
72+
73+
#[cfg(not(feature = "lp32"))]
74+
/// We assume `unsigned int` is 32-bits.
75+
pub type CUInt = u32;
76+
77+
/// Represents an 8-bit `char`. Rust does not (and will never) support
78+
/// platforms where `char` is not 8-bits long.
3979
pub type CChar = u8;
4080

4181
/// This allows you to iterate a null-terminated string in a relatively simple

0 commit comments

Comments
 (0)