fixed-json 0.4.0

No-std, no-allocation JSON parsing into caller-owned fixed storage
Documentation
use crate::{Error, JSON_VAL_MAX, Result};

#[inline]
pub(crate) fn hex_val(b: u8) -> Option<u8> {
    match b {
        b'0'..=b'9' => Some(b - b'0'),
        b'a'..=b'f' => Some(b - b'a' + 10),
        b'A'..=b'F' => Some(b - b'A' + 10),
        _ => None,
    }
}

#[inline]
pub(crate) fn parse_i32_at(bytes: &[u8], start: usize) -> Result<(i32, usize)> {
    let end = number_end(bytes, start)?;
    Ok((parse_i32_bytes(&bytes[start..end])?, end))
}

#[inline]
pub(crate) fn parse_u32_at(bytes: &[u8], start: usize) -> Result<(u32, usize)> {
    let end = number_end(bytes, start)?;
    Ok((parse_u32_bytes(&bytes[start..end])?, end))
}

#[inline]
pub(crate) fn parse_i16_at(bytes: &[u8], start: usize) -> Result<(i16, usize)> {
    let end = number_end(bytes, start)?;
    let v = parse_i64_bytes(&bytes[start..end])?;
    if v < i16::MIN as i64 || v > i16::MAX as i64 {
        return Err(Error::BadNum);
    }
    Ok((v as i16, end))
}

#[inline]
pub(crate) fn parse_u16_at(bytes: &[u8], start: usize) -> Result<(u16, usize)> {
    let end = number_end(bytes, start)?;
    let v = parse_u64_bytes(&bytes[start..end])?;
    if v > u16::MAX as u64 {
        return Err(Error::BadNum);
    }
    Ok((v as u16, end))
}

#[inline]
pub(crate) fn parse_f64_at(bytes: &[u8], start: usize) -> Result<(f64, usize)> {
    let end = number_end(bytes, start)?;
    Ok((parse_f64_bytes(&bytes[start..end])?, end))
}

#[inline]
pub(crate) fn parse_bool_at(bytes: &[u8], start: usize) -> Result<(bool, usize)> {
    if bytes[start..].starts_with(b"true") {
        Ok((true, start + 4))
    } else if bytes[start..].starts_with(b"false") {
        Ok((false, start + 5))
    } else {
        let (v, end) = parse_i32_at(bytes, start)?;
        Ok((v != 0, end))
    }
}

pub(crate) fn number_end(bytes: &[u8], mut i: usize) -> Result<usize> {
    let start = i;
    if i < bytes.len() && bytes[i] == b'-' {
        i += 1;
    }
    let mut saw_digit = false;
    while i < bytes.len() && bytes[i].is_ascii_digit() {
        saw_digit = true;
        i += 1;
    }
    if i < bytes.len() && bytes[i] == b'.' {
        i += 1;
        while i < bytes.len() && bytes[i].is_ascii_digit() {
            saw_digit = true;
            i += 1;
        }
    }
    if i < bytes.len() && matches!(bytes[i], b'e' | b'E') {
        i += 1;
        if i < bytes.len() && matches!(bytes[i], b'+' | b'-') {
            i += 1;
        }
        let exp_start = i;
        while i < bytes.len() && bytes[i].is_ascii_digit() {
            i += 1;
        }
        if i == exp_start {
            return Err(Error::BadNum);
        }
    }
    if !saw_digit || i == start {
        Err(Error::BadNum)
    } else {
        Ok(i)
    }
}

#[inline]
pub(crate) fn parse_i32_token(token: &[u8]) -> Result<i32> {
    parse_i32_bytes(&token[..token_len(token)])
}

#[inline]
pub(crate) fn parse_u32_token(token: &[u8]) -> Result<u32> {
    parse_u32_bytes(&token[..token_len(token)])
}

#[inline]
pub(crate) fn parse_i16_token(token: &[u8]) -> Result<i16> {
    let v = parse_i64_token(token)?;
    if v < i16::MIN as i64 || v > i16::MAX as i64 {
        return Err(Error::BadNum);
    }
    Ok(v as i16)
}

#[inline]
pub(crate) fn parse_u16_token(token: &[u8]) -> Result<u16> {
    let v = parse_u64_bytes(&token[..token_len(token)])?;
    if v > u16::MAX as u64 {
        return Err(Error::BadNum);
    }
    Ok(v as u16)
}

#[inline]
pub(crate) fn parse_i64_token(token: &[u8]) -> Result<i64> {
    parse_i64_bytes(&token[..token_len(token)])
}

#[inline]
pub(crate) fn parse_f64_token(token: &[u8]) -> Result<f64> {
    parse_f64_bytes(&token[..token_len(token)])
}

#[inline]
pub(crate) fn parse_i32_bytes(bytes: &[u8]) -> Result<i32> {
    let v = parse_i64_bytes(bytes)?;
    if v < i32::MIN as i64 || v > i32::MAX as i64 {
        return Err(Error::BadNum);
    }
    Ok(v as i32)
}

#[inline]
pub(crate) fn parse_u32_bytes(bytes: &[u8]) -> Result<u32> {
    let v = parse_u64_bytes(bytes)?;
    if v > u32::MAX as u64 {
        return Err(Error::BadNum);
    }
    Ok(v as u32)
}

pub(crate) fn parse_i64_bytes(bytes: &[u8]) -> Result<i64> {
    let mut i = 0usize;
    let neg = bytes.first() == Some(&b'-');
    if neg {
        i = 1;
    }
    if i >= bytes.len() || bytes[i] == b'+' {
        return Err(Error::BadNum);
    }
    let mut v: i64 = 0;
    while i < bytes.len() {
        let b = bytes[i];
        if !b.is_ascii_digit() {
            return Err(Error::BadNum);
        }
        v = v.checked_mul(10).ok_or(Error::BadNum)?;
        v = v.checked_add((b - b'0') as i64).ok_or(Error::BadNum)?;
        i += 1;
    }
    if neg {
        v.checked_neg().ok_or(Error::BadNum)
    } else {
        Ok(v)
    }
}

pub(crate) fn parse_u64_bytes(bytes: &[u8]) -> Result<u64> {
    if bytes.is_empty() || bytes[0] == b'-' || bytes[0] == b'+' {
        return Err(Error::BadNum);
    }
    let mut v: u64 = 0;
    for &b in bytes {
        if !b.is_ascii_digit() {
            return Err(Error::BadNum);
        }
        v = v.checked_mul(10).ok_or(Error::BadNum)?;
        v = v.checked_add((b - b'0') as u64).ok_or(Error::BadNum)?;
    }
    Ok(v)
}

pub(crate) fn parse_f64_bytes(bytes: &[u8]) -> Result<f64> {
    if bytes.is_empty() {
        return Err(Error::BadNum);
    }
    let mut i = 0usize;
    let neg = bytes[i] == b'-';
    if neg {
        i += 1;
    }
    if i >= bytes.len() {
        return Err(Error::BadNum);
    }
    let mut value = 0f64;
    let int_start = i;
    if bytes[i] == b'0' {
        i += 1;
        if i < bytes.len() && bytes[i].is_ascii_digit() {
            return Err(Error::BadNum);
        }
    } else {
        while i < bytes.len() && bytes[i].is_ascii_digit() {
            value = value * 10.0 + (bytes[i] - b'0') as f64;
            i += 1;
        }
    }
    if i == int_start {
        return Err(Error::BadNum);
    }
    if i < bytes.len() && bytes[i] == b'.' {
        i += 1;
        let frac_start = i;
        let mut scale = 0.1f64;
        while i < bytes.len() && bytes[i].is_ascii_digit() {
            value += (bytes[i] - b'0') as f64 * scale;
            scale *= 0.1;
            i += 1;
        }
        if i == frac_start {
            return Err(Error::BadNum);
        }
    }
    if i < bytes.len() && matches!(bytes[i], b'e' | b'E') {
        i += 1;
        let exp_neg = if i < bytes.len() && matches!(bytes[i], b'+' | b'-') {
            let n = bytes[i] == b'-';
            i += 1;
            n
        } else {
            false
        };
        let exp_start = i;
        let mut exp = 0i32;
        while i < bytes.len() && bytes[i].is_ascii_digit() {
            exp = exp
                .saturating_mul(10)
                .saturating_add((bytes[i] - b'0') as i32);
            i += 1;
        }
        if i == exp_start {
            return Err(Error::BadNum);
        }
        value *= pow10(if exp_neg { -exp } else { exp });
    }
    if i != bytes.len() {
        return Err(Error::BadNum);
    }
    Ok(if neg { -value } else { value })
}

#[inline]
pub(crate) fn pow10(exp: i32) -> f64 {
    let mut n = if exp < 0 { -exp } else { exp };
    let mut base = 10.0f64;
    let mut result = 1.0f64;
    while n > 0 {
        if n & 1 == 1 {
            result *= base;
        }
        base *= base;
        n >>= 1;
    }
    if exp < 0 { 1.0 / result } else { result }
}

#[inline]
pub(crate) fn json_number_is_integer(token: &[u8]) -> bool {
    let bytes = &token[..token_len(token)];
    match_json_number(bytes).is_some_and(|is_integer| is_integer)
}

#[inline]
pub(crate) fn json_number_is_real(token: &[u8]) -> bool {
    let bytes = &token[..token_len(token)];
    match_json_number(bytes).is_some_and(|is_integer| !is_integer)
}

pub(crate) fn match_json_number(bytes: &[u8]) -> Option<bool> {
    if bytes.is_empty() {
        return None;
    }
    let mut i = 0usize;
    let mut saw_fraction = false;
    let mut saw_exponent = false;
    if bytes[i] == b'-' {
        i += 1;
    }
    if i >= bytes.len() || !bytes[i].is_ascii_digit() {
        return None;
    }
    if bytes[i] == b'0' {
        i += 1;
        if i < bytes.len() && bytes[i].is_ascii_digit() {
            return None;
        }
    } else {
        while i < bytes.len() && bytes[i].is_ascii_digit() {
            i += 1;
        }
    }
    if i < bytes.len() && bytes[i] == b'.' {
        saw_fraction = true;
        i += 1;
        if i >= bytes.len() || !bytes[i].is_ascii_digit() {
            return None;
        }
        while i < bytes.len() && bytes[i].is_ascii_digit() {
            i += 1;
        }
    }
    if i < bytes.len() && matches!(bytes[i], b'e' | b'E') {
        saw_exponent = true;
        i += 1;
        if i < bytes.len() && matches!(bytes[i], b'+' | b'-') {
            i += 1;
        }
        if i >= bytes.len() || !bytes[i].is_ascii_digit() {
            return None;
        }
        while i < bytes.len() && bytes[i].is_ascii_digit() {
            i += 1;
        }
    }
    if i == bytes.len() {
        Some(!(saw_fraction || saw_exponent))
    } else {
        None
    }
}

pub(crate) fn write_i32_token(mut value: i32, out: &mut [u8; JSON_VAL_MAX + 1]) {
    clear_cbuf(out);
    if value == 0 {
        out[0] = b'0';
        return;
    }
    let mut tmp = [0u8; 12];
    let neg = value < 0;
    let mut i = tmp.len();
    while value != 0 {
        let digit = (value % 10).unsigned_abs() as u8;
        i -= 1;
        tmp[i] = b'0' + digit;
        value /= 10;
    }
    if neg {
        i -= 1;
        tmp[i] = b'-';
    }
    let n = tmp.len() - i;
    out[..n].copy_from_slice(&tmp[i..]);
}

#[inline]
fn token_len(token: &[u8]) -> usize {
    token.iter().position(|&b| b == 0).unwrap_or(token.len())
}

#[inline]
fn clear_cbuf(buf: &mut [u8]) {
    for b in buf {
        *b = 0;
    }
}