jjpwrgem-parse 0.14.0

JSON parser and formatter with rich errors
Documentation
mod array;
mod object;

use core::range::Range;

use crate::{
    Error, ErrorKind, Result,
    tokens::{ErrorToken, Token, TokenStream, TokenWithContext},
    traverse::{array::parse_array, object::parse_object},
};

pub trait Visitor<'a> {
    fn on_object_open(&mut self, range: Range<usize>);
    fn on_object_key(&mut self, range: Range<usize>, key: &'a str);
    fn on_object_key_val_delim(&mut self);
    fn on_object_close(&mut self, range: Range<usize>);
    fn on_array_open(&mut self, range: Range<usize>);
    fn on_array_close(&mut self, range: Range<usize>);
    fn on_null(&mut self, range: Range<usize>);
    fn on_string(&mut self, range: Range<usize>, value: &'a str);
    fn on_mantissa(&mut self, range: Range<usize>, mantissa: &'a str);
    fn on_exponent(&mut self, range: Range<usize>, exponent: &'a str);
    fn on_boolean(&mut self, range: Range<usize>, value: bool);
    fn on_item_delim(&mut self);
}

const MAX_DEPTH: usize = 128;

pub fn parse_tokens<'a>(
    tokens: &mut TokenStream<'a>,
    text: &'a str,
    fail_on_multiple_value: bool,
    visitor: &mut impl Visitor<'a>,
) -> Result<Range<usize>> {
    parse_tokens_at_depth(tokens, text, fail_on_multiple_value, visitor, 0)
}

fn parse_tokens_at_depth<'a>(
    tokens: &mut TokenStream<'a>,
    text: &'a str,
    fail_on_multiple_value: bool,
    visitor: &mut impl Visitor<'a>,
    depth: usize,
) -> Result<Range<usize>> {
    if depth >= MAX_DEPTH {
        return Err(Error::new(
            ErrorKind::NestingTooDeep(MAX_DEPTH),
            0..text.len(),
            text,
        ));
    }
    let peeked = tokens.peek_token()?.copied();
    let Some(peeked) = peeked else {
        let pos = tokens.pos();
        return Err(Error::new(
            ErrorKind::ExpectedValue(None, None.into()),
            pos..pos,
            text,
        ));
    };
    let range = match peeked.token {
        Token::OpenCurlyBrace => parse_object(tokens, text, depth, visitor)?,
        Token::OpenSquareBracket => parse_array(tokens, text, depth, visitor)?,
        t if t.is_scalar() => {
            let token_ctx = tokens
                .next_token()?
                .expect("peek guaranteed a value for scalar token");
            let range = token_ctx.range;

            match t {
                Token::String => {
                    let body = token_ctx.content_range();
                    visitor.on_string(body, &text[body]);
                }
                Token::Mantissa => {
                    visitor.on_mantissa(range, &text[range]);
                    if let Some(TokenWithContext {
                        token: Token::Exponent,
                        range: er,
                    }) = tokens.peek_token()?.copied()
                    {
                        let exp_ctx = tokens.next_token()?.expect("peek guaranteed");
                        debug_assert_eq!(
                            exp_ctx.range, er,
                            "peeked exponent range must match the consumed token"
                        );
                        visitor.on_exponent(er, &text[er]);
                    }
                }
                Token::Null => visitor.on_null(range),
                Token::True => visitor.on_boolean(range, true),
                Token::False => visitor.on_boolean(range, false),
                _ => unreachable!("guard prevents non scalars"),
            }

            range
        }
        t => {
            return Err(Error::new(
                ErrorKind::ExpectedValue(None, Some(t).into()),
                peeked.range,
                text,
            ));
        }
    };

    if fail_on_multiple_value && let Some(twc) = tokens.peek_token()?.copied() {
        return Err(Error::new(
            ErrorKind::TokenAfterEnd(ErrorToken::new(twc.token, twc.range, text)),
            twc.range,
            text,
        ));
    }

    Ok(range)
}

pub(crate) fn validate_start_of_value(
    text: &str,
    expect_ctx: TokenWithContext,
    maybe_token: Option<TokenWithContext>,
) -> Result<()> {
    if !maybe_token.is_some_and(|ctx| ctx.token.is_start_of_value()) {
        Err(Error::from_maybe_token_with_context(
            |tok| ErrorKind::ExpectedValue(Some(expect_ctx), tok),
            maybe_token,
            text,
        ))
    } else {
        Ok(())
    }
}

fn join<V, T>(
    visitor: &mut V,
    items: impl IntoIterator<Item = T>,
    mut item_fmt: impl FnMut(&mut V, &T),
    mut delim_fmt: impl FnMut(&mut V, &T),
) {
    let mut iter = items.into_iter();
    if let Some(first) = iter.next() {
        item_fmt(visitor, &first);
        for item in iter {
            delim_fmt(visitor, &item);
            item_fmt(visitor, &item);
        }
    }
}

pub fn visit_document<'a, S: AsRef<str>>(
    doc: &'a crate::ast::Document<S>,
    visitor: &mut impl Visitor<'a>,
) {
    visit_value(doc.source.as_ref(), doc.root(), visitor);
}

pub(crate) fn visit_value<'a>(
    source: &'a str,
    val: &'a crate::ast::Value,
    visitor: &mut impl Visitor<'a>,
) {
    use crate::ast::{ObjectEntries, Value};
    match val {
        Value::Null(r) => visitor.on_null(*r),
        Value::String(r) => visitor.on_string(*r, &source[*r]),
        Value::Number { mantissa, exponent } => {
            visitor.on_mantissa(*mantissa, &source[*mantissa]);
            if let Some(exponent) = exponent {
                visitor.on_exponent(*exponent, &source[*exponent]);
            }
        }
        Value::Boolean(r, b) => visitor.on_boolean(*r, *b),
        Value::Object(r, ObjectEntries(items)) => {
            visitor.on_object_open(r.start..r.start + 1);
            join(
                visitor,
                items,
                |visitor, (kr, v)| {
                    visitor.on_object_key(*kr, &source[*kr]);
                    visitor.on_object_key_val_delim();
                    visit_value(source, v, visitor);
                },
                |visitor, _| visitor.on_item_delim(),
            );
            visitor.on_object_close(r.end - 1..r.end);
        }
        Value::Array(r, items) => {
            visitor.on_array_open(r.start..r.start + 1);
            join(
                visitor,
                items,
                |visitor, val| visit_value(source, val, visitor),
                |visitor, _| visitor.on_item_delim(),
            );
            visitor.on_array_close(r.end - 1..r.end);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ast::Document;

    #[derive(Debug, PartialEq, Eq)]
    enum Event {
        ObjectOpen(Range<usize>),
        ObjectKey(Range<usize>),
        ObjectKeyValDelim,
        ObjectClose(Range<usize>),
        ArrayOpen(Range<usize>),
        ArrayClose(Range<usize>),
        Null(Range<usize>),
        String(Range<usize>),
        Mantissa(Range<usize>),
        Exponent(Range<usize>),
        Boolean(Range<usize>, bool),
        ItemDelim,
    }

    #[derive(Default)]
    struct RecordingVisitor {
        events: Vec<Event>,
    }

    impl<'a> Visitor<'a> for RecordingVisitor {
        fn on_object_open(&mut self, range: Range<usize>) {
            self.events.push(Event::ObjectOpen(range));
        }

        fn on_object_key(&mut self, range: Range<usize>, _key: &'a str) {
            self.events.push(Event::ObjectKey(range));
        }

        fn on_object_key_val_delim(&mut self) {
            self.events.push(Event::ObjectKeyValDelim);
        }

        fn on_object_close(&mut self, range: Range<usize>) {
            self.events.push(Event::ObjectClose(range));
        }

        fn on_array_open(&mut self, range: Range<usize>) {
            self.events.push(Event::ArrayOpen(range));
        }

        fn on_array_close(&mut self, range: Range<usize>) {
            self.events.push(Event::ArrayClose(range));
        }

        fn on_null(&mut self, range: Range<usize>) {
            self.events.push(Event::Null(range));
        }

        fn on_string(&mut self, range: Range<usize>, _value: &'a str) {
            self.events.push(Event::String(range));
        }

        fn on_mantissa(&mut self, range: Range<usize>, _mantissa: &'a str) {
            self.events.push(Event::Mantissa(range));
        }

        fn on_exponent(&mut self, range: Range<usize>, _exponent: &'a str) {
            self.events.push(Event::Exponent(range));
        }

        fn on_boolean(&mut self, range: Range<usize>, value: bool) {
            self.events.push(Event::Boolean(range, value));
        }

        fn on_item_delim(&mut self) {
            self.events.push(Event::ItemDelim);
        }
    }

    #[test]
    fn visit_document_matches_token_traversal_events() {
        let json = r#"{"a":["b",{"c":1e5}],"d":true}"#;
        let expected = vec![
            Event::ObjectOpen(0..1),
            Event::ObjectKey(2..3),
            Event::ObjectKeyValDelim,
            Event::ArrayOpen(5..6),
            Event::String(7..8),
            Event::ItemDelim,
            Event::ObjectOpen(10..11),
            Event::ObjectKey(12..13),
            Event::ObjectKeyValDelim,
            Event::Mantissa(15..16),
            Event::Exponent(17..18),
            Event::ObjectClose(18..19),
            Event::ArrayClose(19..20),
            Event::ItemDelim,
            Event::ObjectKey(22..23),
            Event::ObjectKeyValDelim,
            Event::Boolean(25..29, true),
            Event::ObjectClose(29..30),
        ];

        let mut from_tokens: RecordingVisitor = RecordingVisitor::default();
        parse_tokens(&mut TokenStream::new(json), json, true, &mut from_tokens).unwrap();

        let doc = Document::parse(json).unwrap();
        let mut from_ast = RecordingVisitor::default();
        visit_document(&doc, &mut from_ast);

        assert_eq!(from_tokens.events, expected);
        assert_eq!(from_ast.events, expected);
        assert_eq!(from_tokens.events, from_ast.events);
    }
}