bunny-codec 0.4.0

Zero-copy mesh format adapters for Bunny.
Documentation
const MAX_MANTISSA_DIGITS: i32 = 19;

#[allow(clippy::cast_possible_truncation)]
pub(super) fn parse_ascii_float(text: &str) -> Option<f32> {
    if text.eq_ignore_ascii_case("nan") {
        return Some(f32::NAN);
    }
    if text.eq_ignore_ascii_case("inf")
        || text.eq_ignore_ascii_case("+inf")
        || text.eq_ignore_ascii_case("infinity")
        || text.eq_ignore_ascii_case("+infinity")
    {
        return Some(f32::INFINITY);
    }
    if text.eq_ignore_ascii_case("-inf") || text.eq_ignore_ascii_case("-infinity") {
        return Some(f32::NEG_INFINITY);
    }

    let bytes = text.as_bytes();
    let mut index = 0;
    let sign = parse_float_sign(bytes, &mut index);
    let mut mantissa = ParsedMantissa::default();

    while let Some(digit) = decimal_digit(bytes.get(index).copied()) {
        mantissa.push_integer_digit(digit);
        index += 1;
    }

    if bytes.get(index) == Some(&b'.') {
        index += 1;
        while let Some(digit) = decimal_digit(bytes.get(index).copied()) {
            mantissa.push_fractional_digit(digit);
            index += 1;
        }
    }

    if !mantissa.saw_digit {
        return None;
    }

    let exponent = mantissa.adjust_exponent(parse_float_exponent(bytes, &mut index)?);
    if index == bytes.len() {
        let value = if mantissa.value == 0.0 {
            sign * 0.0
        } else {
            sign * mantissa.value * 10_f64.powi(exponent)
        };
        Some(value as f32)
    } else {
        None
    }
}

#[derive(Clone, Copy, Debug, Default)]
struct ParsedMantissa {
    value: f64,
    saw_digit: bool,
    kept_digits: i32,
    skipped_digits: i32,
    fractional_digits: i32,
}

impl ParsedMantissa {
    fn push_integer_digit(&mut self, digit: u8) {
        self.push_digit(digit);
    }

    fn push_fractional_digit(&mut self, digit: u8) {
        self.fractional_digits = self.fractional_digits.saturating_add(1);
        self.push_digit(digit);
    }

    fn push_digit(&mut self, digit: u8) {
        self.saw_digit = true;
        if self.kept_digits == 0 && digit == 0 {
            return;
        }
        if self.kept_digits < MAX_MANTISSA_DIGITS {
            self.value = self.value.mul_add(10.0, f64::from(digit));
            self.kept_digits += 1;
        } else {
            self.skipped_digits = self.skipped_digits.saturating_add(1);
        }
    }

    const fn adjust_exponent(self, exponent: i32) -> i32 {
        exponent
            .saturating_add(self.skipped_digits)
            .saturating_sub(self.fractional_digits)
    }
}

fn parse_float_sign(bytes: &[u8], index: &mut usize) -> f64 {
    match bytes.get(*index) {
        Some(b'-') => {
            *index += 1;
            -1.0
        }
        Some(b'+') => {
            *index += 1;
            1.0
        }
        _ => 1.0,
    }
}

fn parse_float_exponent(bytes: &[u8], index: &mut usize) -> Option<i32> {
    if !matches!(bytes.get(*index), Some(b'e' | b'E')) {
        return Some(0);
    }
    *index += 1;

    let sign = match bytes.get(*index) {
        Some(b'-') => {
            *index += 1;
            -1
        }
        Some(b'+') => {
            *index += 1;
            1
        }
        _ => 1,
    };

    let mut exponent = 0_i32;
    let mut saw_digit = false;
    while let Some(digit) = decimal_digit(bytes.get(*index).copied()) {
        exponent = exponent.saturating_mul(10).saturating_add(i32::from(digit));
        saw_digit = true;
        *index += 1;
    }
    saw_digit.then_some(sign * exponent)
}

const fn decimal_digit(byte: Option<u8>) -> Option<u8> {
    match byte {
        Some(value @ b'0'..=b'9') => Some(value - b'0'),
        _ => None,
    }
}