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"))
}
}