• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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