summaryrefslogtreecommitdiffstats
path: root/third_party/rust/minimal-lexical/tests/integration_tests.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/minimal-lexical/tests/integration_tests.rs')
-rw-r--r--third_party/rust/minimal-lexical/tests/integration_tests.rs228
1 files changed, 228 insertions, 0 deletions
diff --git a/third_party/rust/minimal-lexical/tests/integration_tests.rs b/third_party/rust/minimal-lexical/tests/integration_tests.rs
new file mode 100644
index 0000000000..a8f2ff8a0e
--- /dev/null
+++ b/third_party/rust/minimal-lexical/tests/integration_tests.rs
@@ -0,0 +1,228 @@
+/// Find and parse sign and get remaining bytes.
+#[inline]
+fn parse_sign<'a>(bytes: &'a [u8]) -> (bool, &'a [u8]) {
+ match bytes.get(0) {
+ Some(&b'+') => (true, &bytes[1..]),
+ Some(&b'-') => (false, &bytes[1..]),
+ _ => (true, bytes),
+ }
+}
+
+// Convert u8 to digit.
+#[inline]
+fn to_digit(c: u8) -> Option<u32> {
+ (c as char).to_digit(10)
+}
+
+// Add digit from exponent.
+#[inline]
+fn add_digit_i32(value: i32, digit: u32) -> Option<i32> {
+ return value.checked_mul(10)?.checked_add(digit as i32);
+}
+
+// Subtract digit from exponent.
+#[inline]
+fn sub_digit_i32(value: i32, digit: u32) -> Option<i32> {
+ return value.checked_mul(10)?.checked_sub(digit as i32);
+}
+
+// Convert character to digit.
+#[inline]
+fn is_digit(c: u8) -> bool {
+ to_digit(c).is_some()
+}
+
+// Split buffer at index.
+#[inline]
+fn split_at_index<'a>(digits: &'a [u8], index: usize) -> (&'a [u8], &'a [u8]) {
+ (&digits[..index], &digits[index..])
+}
+
+/// Consume until a an invalid digit is found.
+///
+/// - `digits` - Slice containing 0 or more digits.
+#[inline]
+fn consume_digits<'a>(digits: &'a [u8]) -> (&'a [u8], &'a [u8]) {
+ // Consume all digits.
+ let mut index = 0;
+ while index < digits.len() && is_digit(digits[index]) {
+ index += 1;
+ }
+ split_at_index(digits, index)
+}
+
+// Trim leading 0s.
+#[inline]
+fn ltrim_zero<'a>(bytes: &'a [u8]) -> &'a [u8] {
+ let count = bytes.iter().take_while(|&&si| si == b'0').count();
+ &bytes[count..]
+}
+
+// Trim trailing 0s.
+#[inline]
+fn rtrim_zero<'a>(bytes: &'a [u8]) -> &'a [u8] {
+ let count = bytes.iter().rev().take_while(|&&si| si == b'0').count();
+ let index = bytes.len() - count;
+ &bytes[..index]
+}
+
+// PARSERS
+// -------
+
+/// Parse the exponent of the float.
+///
+/// * `exponent` - Slice containing the exponent digits.
+/// * `is_positive` - If the exponent sign is positive.
+fn parse_exponent(exponent: &[u8], is_positive: bool) -> i32 {
+ // Parse the sign bit or current data.
+ let mut value: i32 = 0;
+ match is_positive {
+ true => {
+ for c in exponent {
+ value = match add_digit_i32(value, to_digit(*c).unwrap()) {
+ Some(v) => v,
+ None => return i32::max_value(),
+ };
+ }
+ },
+ false => {
+ for c in exponent {
+ value = match sub_digit_i32(value, to_digit(*c).unwrap()) {
+ Some(v) => v,
+ None => return i32::min_value(),
+ };
+ }
+ },
+ }
+
+ value
+}
+
+pub fn case_insensitive_starts_with<'a, 'b, Iter1, Iter2>(mut x: Iter1, mut y: Iter2) -> bool
+where
+ Iter1: Iterator<Item = &'a u8>,
+ Iter2: Iterator<Item = &'b u8>,
+{
+ // We use a faster optimization here for ASCII letters, which NaN
+ // and infinite strings **must** be. [A-Z] is 0x41-0x5A, while
+ // [a-z] is 0x61-0x7A. Therefore, the xor must be 0 or 32 if they
+ // are case-insensitive equal, but only if at least 1 of the inputs
+ // is an ASCII letter.
+ loop {
+ let yi = y.next();
+ if yi.is_none() {
+ return true;
+ }
+ let yi = *yi.unwrap();
+ let is_not_equal = x.next().map_or(true, |&xi| {
+ let xor = xi ^ yi;
+ xor != 0 && xor != 0x20
+ });
+ if is_not_equal {
+ return false;
+ }
+ }
+}
+
+/// Parse float from input bytes, returning the float and the remaining bytes.
+///
+/// * `bytes` - Array of bytes leading with float-data.
+pub fn parse_float<'a, F>(bytes: &'a [u8]) -> (F, &'a [u8])
+where
+ F: minimal_lexical::Float,
+{
+ let start = bytes;
+
+ // Parse the sign.
+ let (is_positive, bytes) = parse_sign(bytes);
+
+ // Check NaN, Inf, Infinity
+ if case_insensitive_starts_with(bytes.iter(), b"NaN".iter()) {
+ let mut float = F::from_bits(F::EXPONENT_MASK | (F::HIDDEN_BIT_MASK >> 1));
+ if !is_positive {
+ float = -float;
+ }
+ return (float, &bytes[3..]);
+ } else if case_insensitive_starts_with(bytes.iter(), b"Infinity".iter()) {
+ let mut float = F::from_bits(F::EXPONENT_MASK);
+ if !is_positive {
+ float = -float;
+ }
+ return (float, &bytes[8..]);
+ } else if case_insensitive_starts_with(bytes.iter(), b"inf".iter()) {
+ let mut float = F::from_bits(F::EXPONENT_MASK);
+ if !is_positive {
+ float = -float;
+ }
+ return (float, &bytes[3..]);
+ }
+
+ // Extract and parse the float components:
+ // 1. Integer
+ // 2. Fraction
+ // 3. Exponent
+ let (integer_slc, bytes) = consume_digits(bytes);
+ let (fraction_slc, bytes) = match bytes.first() {
+ Some(&b'.') => consume_digits(&bytes[1..]),
+ _ => (&bytes[..0], bytes),
+ };
+ let (exponent, bytes) = match bytes.first() {
+ Some(&b'e') | Some(&b'E') => {
+ // Extract and parse the exponent.
+ let (is_positive, bytes) = parse_sign(&bytes[1..]);
+ let (exponent, bytes) = consume_digits(bytes);
+ (parse_exponent(exponent, is_positive), bytes)
+ },
+ _ => (0, bytes),
+ };
+
+ if bytes.len() == start.len() {
+ return (F::from_u64(0), bytes);
+ }
+
+ // Note: You may want to check and validate the float data here:
+ // 1). Many floats require integer or fraction digits, if a fraction
+ // is present.
+ // 2). All floats require either integer or fraction digits.
+ // 3). Some floats do not allow a '+' sign before the significant digits.
+ // 4). Many floats require exponent digits after the exponent symbol.
+ // 5). Some floats do not allow a '+' sign before the exponent.
+
+ // We now need to trim leading and trailing 0s from the integer
+ // and fraction, respectively. This is required to make the
+ // fast and moderate paths more efficient, and for the slow
+ // path.
+ let integer_slc = ltrim_zero(integer_slc);
+ let fraction_slc = rtrim_zero(fraction_slc);
+
+ // Create the float and return our data.
+ let mut float: F =
+ minimal_lexical::parse_float(integer_slc.iter(), fraction_slc.iter(), exponent);
+ if !is_positive {
+ float = -float;
+ }
+
+ (float, bytes)
+}
+
+macro_rules! b {
+ ($x:literal) => {
+ $x.as_bytes()
+ };
+}
+
+#[test]
+fn f32_test() {
+ assert_eq!(
+ (184467440000000000000.0, b!("\x00\x00006")),
+ parse_float::<f32>(b"000184467440737095516150\x00\x00006")
+ );
+}
+
+#[test]
+fn f64_test() {
+ assert_eq!(
+ (184467440737095500000.0, b!("\x00\x00006")),
+ parse_float::<f64>(b"000184467440737095516150\x00\x00006")
+ );
+}