1 // Copyright 2018, Alex Huszagh. Unlicensed.
2 // See https://unlicense.org/
3
4 #![allow(non_snake_case)]
5
6 extern crate minimal_lexical;
7 #[macro_use]
8 extern crate serde_derive;
9 extern crate toml;
10
11 use std::env;
12 use std::fs::read_to_string;
13 use std::path::PathBuf;
14
15 // HELPERS
16 // -------
17
18 // These functions are simple, resuable componetns
19
20 /// Find and parse sign and get remaining bytes.
21 #[inline]
parse_sign<'a>(bytes: &'a [u8]) -> (bool, &'a [u8])22 fn parse_sign<'a>(bytes: &'a [u8]) -> (bool, &'a [u8]) {
23 match bytes.get(0) {
24 Some(&b'+') => (true, &bytes[1..]),
25 Some(&b'-') => (false, &bytes[1..]),
26 _ => (true, bytes),
27 }
28 }
29
30 // Convert u8 to digit.
31 #[inline]
to_digit(c: u8) -> Option<u32>32 fn to_digit(c: u8) -> Option<u32> {
33 (c as char).to_digit(10)
34 }
35
36 // Add digit from exponent.
37 #[inline]
add_digit_i32(value: i32, digit: u32) -> Option<i32>38 fn add_digit_i32(value: i32, digit: u32) -> Option<i32> {
39 return value.checked_mul(10)?.checked_add(digit as i32);
40 }
41
42 // Subtract digit from exponent.
43 #[inline]
sub_digit_i32(value: i32, digit: u32) -> Option<i32>44 fn sub_digit_i32(value: i32, digit: u32) -> Option<i32> {
45 return value.checked_mul(10)?.checked_sub(digit as i32);
46 }
47
48 // Convert character to digit.
49 #[inline]
is_digit(c: u8) -> bool50 fn is_digit(c: u8) -> bool {
51 to_digit(c).is_some()
52 }
53
54 // Split buffer at index.
55 #[inline]
split_at_index<'a>(digits: &'a [u8], index: usize) -> (&'a [u8], &'a [u8])56 fn split_at_index<'a>(digits: &'a [u8], index: usize) -> (&'a [u8], &'a [u8]) {
57 (&digits[..index], &digits[index..])
58 }
59
60 /// Consume until a an invalid digit is found.
61 ///
62 /// - `digits` - Slice containing 0 or more digits.
63 #[inline]
consume_digits<'a>(digits: &'a [u8]) -> (&'a [u8], &'a [u8])64 fn consume_digits<'a>(digits: &'a [u8]) -> (&'a [u8], &'a [u8]) {
65 // Consume all digits.
66 let mut index = 0;
67 while index < digits.len() && is_digit(digits[index]) {
68 index += 1;
69 }
70 split_at_index(digits, index)
71 }
72
73 // Trim leading 0s.
74 #[inline]
ltrim_zero<'a>(bytes: &'a [u8]) -> &'a [u8]75 fn ltrim_zero<'a>(bytes: &'a [u8]) -> &'a [u8] {
76 let count = bytes.iter().take_while(|&&si| si == b'0').count();
77 &bytes[count..]
78 }
79
80 // Trim trailing 0s.
81 #[inline]
rtrim_zero<'a>(bytes: &'a [u8]) -> &'a [u8]82 fn rtrim_zero<'a>(bytes: &'a [u8]) -> &'a [u8] {
83 let count = bytes.iter().rev().take_while(|&&si| si == b'0').count();
84 let index = bytes.len() - count;
85 &bytes[..index]
86 }
87
88 // PARSERS
89 // -------
90
91 /// Parse the exponent of the float.
92 ///
93 /// * `exponent` - Slice containing the exponent digits.
94 /// * `is_positive` - If the exponent sign is positive.
parse_exponent(exponent: &[u8], is_positive: bool) -> i3295 fn parse_exponent(exponent: &[u8], is_positive: bool) -> i32 {
96 // Parse the sign bit or current data.
97 let mut value: i32 = 0;
98 match is_positive {
99 true => {
100 for c in exponent {
101 value = match add_digit_i32(value, to_digit(*c).unwrap()) {
102 Some(v) => v,
103 None => return i32::max_value(),
104 };
105 }
106 },
107 false => {
108 for c in exponent {
109 value = match sub_digit_i32(value, to_digit(*c).unwrap()) {
110 Some(v) => v,
111 None => return i32::min_value(),
112 };
113 }
114 },
115 }
116
117 value
118 }
119
120 /// Parse float from input bytes, returning the float and the remaining bytes.
121 ///
122 /// * `bytes` - Array of bytes leading with float-data.
parse_float<'a, F>(bytes: &'a [u8]) -> (F, &'a [u8]) where F: minimal_lexical::Float,123 fn parse_float<'a, F>(bytes: &'a [u8]) -> (F, &'a [u8])
124 where
125 F: minimal_lexical::Float,
126 {
127 // Parse the sign.
128 let (is_positive, bytes) = parse_sign(bytes);
129
130 // Extract and parse the float components:
131 let (integer_slc, bytes) = consume_digits(bytes);
132 let (fraction_slc, bytes) = match bytes.first() {
133 Some(&b'.') => consume_digits(&bytes[1..]),
134 _ => (&bytes[..0], bytes),
135 };
136 let (exponent, bytes) = match bytes.first() {
137 Some(&b'e') | Some(&b'E') => {
138 // Extract and parse the exponent.
139 let (is_positive, bytes) = parse_sign(&bytes[1..]);
140 let (exponent, bytes) = consume_digits(bytes);
141 (parse_exponent(exponent, is_positive), bytes)
142 },
143 _ => (0, bytes),
144 };
145
146 // Trim leading and trailing zeros.
147 let integer_slc = ltrim_zero(integer_slc);
148 let fraction_slc = rtrim_zero(fraction_slc);
149
150 // Create the float and return our data.
151 let mut float: F =
152 minimal_lexical::parse_float(integer_slc.iter(), fraction_slc.iter(), exponent);
153 if !is_positive {
154 float = -float;
155 }
156
157 (float, bytes)
158 }
159
160 // STRUCTS
161 // Derived structs for the Toml parser.
162
163 #[derive(Debug, Deserialize)]
164 struct StrtodTests {
165 negativeFormattingTests: Vec<String>,
166 FormattingTests: Vec<FormattingTest>,
167 ConversionTests: Vec<ConversionTest>,
168 }
169
170 #[derive(Debug, Deserialize)]
171 struct FormattingTest {
172 UID: String,
173 str: String,
174 hex: String,
175 int: String,
176 }
177
178 #[derive(Debug, Deserialize)]
179 struct ConversionTest {
180 UID: String,
181 str: String,
182 hex: String,
183 int: String,
184 }
185
186 // PATH
187
188 /// Return the `target/debug` or `target/release` directory path.
build_dir() -> PathBuf189 pub fn build_dir() -> PathBuf {
190 env::current_exe()
191 .expect("unittest executable path")
192 .parent()
193 .expect("debug/release directory")
194 .to_path_buf()
195 }
196
197 /// Return the `target` directory path.
target_dir() -> PathBuf198 pub fn target_dir() -> PathBuf {
199 build_dir().parent().expect("target directory").to_path_buf()
200 }
201
202 /// Return the project directory path.
project_dir() -> PathBuf203 pub fn project_dir() -> PathBuf {
204 target_dir().parent().expect("project directory").to_path_buf()
205 }
206
run_test(string: &str, hex: &str)207 fn run_test(string: &str, hex: &str) {
208 // This parser doesn't handle literal NaNs/infs.
209 let lower = string.to_lowercase();
210 if !lower.contains("nan") && !lower.contains("inf") {
211 let float: f64 = parse_float(string.as_bytes()).0;
212 let int: u64 = float.to_bits();
213 // Rust literals for NaN are not standard conforming:
214 // Rust uses 0x7ff8000000000000, not 0x7ff0000000000001
215 // We want to pad on the left with 0s, up to 16 characters.
216 if float.is_finite() {
217 assert_eq!(hex, format!("{:0>16x}", int));
218 }
219 }
220 }
221
run_tests(tests: StrtodTests)222 fn run_tests(tests: StrtodTests) {
223 let formatting_tests_count = tests.FormattingTests.len();
224 let conversion_tests_count = tests.ConversionTests.len();
225 for test in tests.FormattingTests {
226 run_test(&test.str, &test.hex)
227 }
228 for test in tests.ConversionTests {
229 run_test(&test.str, &test.hex)
230 }
231 println!("Ran {} formatting tests.", formatting_tests_count);
232 println!("Ran {} conversion tests.", conversion_tests_count);
233 println!("");
234 }
235
parse_tests(name: &str) -> StrtodTests236 fn parse_tests(name: &str) -> StrtodTests {
237 let mut test_path = project_dir();
238 test_path.push("test-parse-unittests");
239 test_path.push(name);
240 let test_data = read_to_string(test_path).unwrap();
241
242 toml::from_str(&test_data).unwrap()
243 }
244
main()245 fn main() {
246 let filenames = ["strtod_tests.toml", "rust_parse_tests.toml"];
247 for filename in filenames.iter() {
248 println!("Running Test: {}", filename);
249 run_tests(parse_tests(filename));
250 }
251 }
252