rexact-check 0.2.0

Independent checker for Rexact equivalence certificates (shares no code with the engine)
Documentation
//! Minimal JSON reader (objects, arrays, strings, unsigned integers, bools, null).

use crate::{malformed, CheckError};

#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum J {
    Null,
    Bool(bool),
    Num(u64),
    Str(String),
    Arr(Vec<J>),
    Obj(Vec<(String, J)>),
}

impl J {
    pub fn get(&self, key: &str) -> Option<&J> {
        match self {
            J::Obj(v) => v.iter().find(|(k, _)| k == key).map(|(_, x)| x),
            _ => None,
        }
    }
    pub fn as_str(&self) -> Option<&str> {
        if let J::Str(s) = self {
            Some(s)
        } else {
            None
        }
    }
    pub fn as_num(&self) -> Option<u64> {
        if let J::Num(n) = self {
            Some(*n)
        } else {
            None
        }
    }
    pub fn as_arr(&self) -> Option<&[J]> {
        if let J::Arr(a) = self {
            Some(a)
        } else {
            None
        }
    }
}

struct P<'a> {
    b: &'a [u8],
    i: usize,
}

pub fn parse(text: &str) -> Result<J, CheckError> {
    let mut p = P {
        b: text.as_bytes(),
        i: 0,
    };
    let v = p.value()?;
    p.ws();
    if p.i != p.b.len() {
        return Err(malformed("trailing data after JSON value"));
    }
    Ok(v)
}

impl P<'_> {
    fn ws(&mut self) {
        while self.i < self.b.len() && matches!(self.b[self.i], b' ' | b'\n' | b'\r' | b'\t') {
            self.i += 1;
        }
    }
    fn peek(&self) -> Option<u8> {
        self.b.get(self.i).copied()
    }
    fn lit(&mut self, s: &str, v: J) -> Result<J, CheckError> {
        if self.b[self.i..].starts_with(s.as_bytes()) {
            self.i += s.len();
            Ok(v)
        } else {
            Err(malformed("invalid JSON literal"))
        }
    }
    fn value(&mut self) -> Result<J, CheckError> {
        self.ws();
        match self.peek() {
            Some(b'{') => {
                self.i += 1;
                let mut out = Vec::new();
                self.ws();
                if self.peek() == Some(b'}') {
                    self.i += 1;
                    return Ok(J::Obj(out));
                }
                loop {
                    self.ws();
                    let k = self.string()?;
                    self.ws();
                    if self.peek() != Some(b':') {
                        return Err(malformed("expected ':' in object"));
                    }
                    self.i += 1;
                    let v = self.value()?;
                    out.push((k, v));
                    self.ws();
                    match self.peek() {
                        Some(b',') => self.i += 1,
                        Some(b'}') => {
                            self.i += 1;
                            return Ok(J::Obj(out));
                        }
                        _ => return Err(malformed("expected ',' or '}' in object")),
                    }
                }
            }
            Some(b'[') => {
                self.i += 1;
                let mut out = Vec::new();
                self.ws();
                if self.peek() == Some(b']') {
                    self.i += 1;
                    return Ok(J::Arr(out));
                }
                loop {
                    out.push(self.value()?);
                    self.ws();
                    match self.peek() {
                        Some(b',') => self.i += 1,
                        Some(b']') => {
                            self.i += 1;
                            return Ok(J::Arr(out));
                        }
                        _ => return Err(malformed("expected ',' or ']' in array")),
                    }
                }
            }
            Some(b'"') => Ok(J::Str(self.string()?)),
            Some(b't') => self.lit("true", J::Bool(true)),
            Some(b'f') => self.lit("false", J::Bool(false)),
            Some(b'n') => self.lit("null", J::Null),
            Some(c) if c.is_ascii_digit() => {
                let mut n: u64 = 0;
                while let Some(d) = self.peek().filter(u8::is_ascii_digit) {
                    n = n
                        .checked_mul(10)
                        .and_then(|x| x.checked_add(u64::from(d - b'0')))
                        .ok_or_else(|| malformed("number too large"))?;
                    self.i += 1;
                }
                Ok(J::Num(n))
            }
            _ => Err(malformed("unexpected character in JSON")),
        }
    }
    fn string(&mut self) -> Result<String, CheckError> {
        if self.peek() != Some(b'"') {
            return Err(malformed("expected string"));
        }
        self.i += 1;
        let mut out = Vec::new();
        loop {
            let Some(c) = self.peek() else {
                return Err(malformed("unterminated string"));
            };
            self.i += 1;
            match c {
                b'"' => break,
                b'\\' => {
                    let Some(e) = self.peek() else {
                        return Err(malformed("bad escape"));
                    };
                    self.i += 1;
                    match e {
                        b'"' | b'\\' | b'/' => out.push(e),
                        b'n' => out.push(b'\n'),
                        b't' => out.push(b'\t'),
                        b'r' => out.push(b'\r'),
                        _ => return Err(malformed("unsupported escape")),
                    }
                }
                _ => out.push(c),
            }
        }
        String::from_utf8(out).map_err(|_| malformed("invalid UTF-8 in string"))
    }
}