use crate::{
Pos,
lexical::{self, Token},
pointer::{Group, Pointer, state},
syntax::{Context, Error, Parser, StructKind},
};
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
pub enum Event<P> {
Nil(Token),
Enter(Token, P),
Exit(Token, P),
Match(Token, P),
}
impl<P> Event<P> {
pub fn token(&self) -> Token {
match self {
Self::Nil(t) | Self::Enter(t, _) | Self::Exit(t, _) | Self::Match(t, _) => *t,
}
}
pub fn is_nil(&self) -> bool {
matches!(self, Self::Nil(_))
}
pub fn is_enter(&self) -> bool {
matches!(self, Self::Enter(_, _))
}
pub fn is_exit(&self) -> bool {
matches!(self, Self::Exit(_, _))
}
pub fn is_match(&self) -> bool {
matches!(self, Self::Match(_, _))
}
}
impl Event<&Pointer> {
pub fn pointer(&self) -> Option<&Pointer> {
match self {
Self::Enter(_, p) | Self::Exit(_, p) | Self::Match(_, p) => Some(p),
Self::Nil(_) => None,
}
}
pub fn to_owned(&self) -> Event<Pointer> {
match *self {
Self::Nil(t) => Event::Nil(t),
Self::Enter(t, p) => Event::Enter(t, p.clone()),
Self::Exit(t, p) => Event::Exit(t, p.clone()),
Self::Match(t, p) => Event::Match(t, p.clone()),
}
}
}
impl Event<Pointer> {
pub fn pointer(&self) -> Option<&Pointer> {
match self {
Self::Enter(_, p) | Self::Exit(_, p) | Self::Match(_, p) => Some(p),
Self::Nil(_) => None,
}
}
pub fn as_ref(&self) -> Event<&Pointer> {
match self {
Self::Nil(t) => Event::Nil(*t),
Self::Enter(t, p) => Event::Enter(*t, p),
Self::Exit(t, p) => Event::Exit(*t, p),
Self::Match(t, p) => Event::Match(*t, p),
}
}
}
impl From<Event<&Pointer>> for Event<Pointer> {
fn from(value: Event<&Pointer>) -> Self {
value.to_owned()
}
}
impl PartialEq<Event<Pointer>> for Event<&Pointer> {
fn eq(&self, other: &Event<Pointer>) -> bool {
match (self, other) {
(Self::Nil(t), Event::Nil(u)) => t == u,
(Self::Enter(t, p), Event::Enter(u, q))
| (Self::Exit(t, p), Event::Exit(u, q))
| (Self::Match(t, p), Event::Match(u, q)) => t == u && **p == *q,
_ => false,
}
}
}
impl PartialEq<Event<&Pointer>> for Event<Pointer> {
fn eq(&self, other: &Event<&Pointer>) -> bool {
other == self
}
}
pub struct Evaluator<L, G> {
parser: Parser<L>,
mach: state::Machine<G>,
skip_level: usize,
expect_member: bool,
}
impl<L, G> Evaluator<L, G>
where
L: lexical::Analyzer,
L::Error: 'static,
G: AsRef<Group>,
{
pub fn new(parser: Parser<L>, group: G, unescape: bool) -> Self {
Self {
parser,
mach: state::Machine::new(group, unescape),
skip_level: usize::MAX,
expect_member: false,
}
}
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Event<&Pointer> {
let token = self.parser.next();
self.eventify(token)
}
pub fn next_non_white(&mut self) -> Event<&Pointer> {
let token = self.parser.next_non_white();
self.eventify(token)
}
pub fn next_meaningful(&mut self) -> Event<&Pointer> {
let token = self.parser.next_meaningful();
self.eventify(token)
}
#[inline(always)]
pub fn content(&self) -> L::Content {
self.parser.content()
}
#[inline(always)]
pub fn err(&self) -> Error {
self.parser.err()
}
#[inline(always)]
pub fn pos(&self) -> &Pos {
self.parser.pos()
}
#[inline(always)]
pub fn try_content(&self) -> Result<L::Content, Error> {
self.parser.try_content()
}
#[inline(always)]
pub fn context(&self) -> &Context {
self.parser.context()
}
#[inline(always)]
pub fn level(&self) -> usize {
self.parser.level()
}
pub fn into_parts(self) -> (Parser<L>, G) {
(self.parser, self.mach.into_inner())
}
fn eventify(&mut self, token: Token) -> Event<&Pointer> {
if self.parser.level() > self.skip_level {
return Event::Nil(token);
} else {
self.skip_level = usize::MAX;
}
match token {
Token::Eof | Token::Err | Token::White | Token::NameSep | Token::ValueSep => {
Event::Nil(token)
}
Token::ArrBegin => {
let (action, entered_pointer) = self.mach.arr_begin();
if action == state::StructAction::Skip {
self.skip_level = self.parser.level() - 1;
}
if let Some(p) = entered_pointer {
Event::Enter(token, p)
} else {
Event::Nil(token)
}
}
Token::ArrEnd => {
self.expect_member = self.is_obj();
if let Some(p) = self.mach.arr_end() {
Event::Exit(token, p)
} else {
Event::Nil(token)
}
}
Token::ObjBegin => {
self.expect_member = true;
let (action, entered_pointer) = self.mach.obj_begin();
if action == state::StructAction::Skip {
self.skip_level = self.parser.level() - 1;
}
if let Some(p) = entered_pointer {
Event::Enter(token, p)
} else {
Event::Nil(token)
}
}
Token::ObjEnd => {
self.expect_member = self.is_obj();
if let Some(p) = self.mach.obj_end() {
Event::Exit(token, p)
} else {
Event::Nil(token)
}
}
Token::Str if self.expect_member => {
self.expect_member = false;
self.mach.member_name(self.content());
Event::Nil(token)
}
Token::Str | Token::Num | Token::LitNull | Token::LitFalse | Token::LitTrue => {
self.expect_member = self.is_obj();
match self.mach.primitive() {
Some(p) => Event::Match(token, p),
None => Event::Nil(token),
}
}
}
}
fn is_obj(&self) -> bool {
matches!(self.parser.context().struct_kind(), Some(StructKind::Obj))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{lexical::fixed::FixedAnalyzer, syntax};
use rstest::rstest;
use std::fmt;
#[test]
fn test_event() {
fn validate(e: Event<&Pointer>, others: [Event<&Pointer>; 3]) {
assert_eq!(e, e);
assert_eq!(e.to_owned(), e);
assert_eq!(e, e.to_owned());
assert_eq!(e.to_owned().as_ref(), e);
assert_eq!(e, e.to_owned().as_ref());
assert_eq!(e.token(), e.to_owned().token());
assert_eq!(e.pointer(), e.to_owned().pointer());
let f: Event<Pointer> = e.into();
assert_eq!(e, f);
for o in others.into_iter() {
assert_ne!(e, o);
assert_ne!(e.to_owned(), o);
assert_ne!(o, e.to_owned());
assert_ne!(e, o.to_owned());
assert_ne!(o.to_owned(), e);
}
}
let pointer = Pointer::default();
let nil = Event::Nil(Token::Eof);
let enter = Event::Enter(Token::ArrBegin, &pointer);
let exit = Event::Exit(Token::ArrEnd, &pointer);
let match_ = Event::Match(Token::Str, &pointer);
assert!(nil.is_nil());
assert!(enter.is_enter());
assert!(exit.is_exit());
assert!(match_.is_match());
validate(nil, [enter, exit, match_]);
validate(enter, [nil, exit, match_]);
validate(exit, [nil, enter, match_]);
validate(match_, [nil, enter, exit]);
}
#[rstest]
#[case::no_pointers::primitive(NO_POINTERS, "true", NO_EVENTS)]
#[case::no_pointers::empty_array(NO_POINTERS, "[]", NO_EVENTS)]
#[case::no_pointers::empty_object(NO_POINTERS, "{}", NO_EVENTS)]
#[case::root_pointer::primitive(
Some(Pointer::default()),
" 123 ",
Some(Event::Match(Token::Num, Pointer::default()))
)]
#[case::root_pointer::empty_array(
Some(Pointer::default()),
"[]",
enter_exit(Token::ArrBegin, Token::ArrEnd, "")
)]
#[case::root_pointer::empty_object(
Some(Pointer::default()),
"{}",
enter_exit(Token::ObjBegin, Token::ObjEnd, "")
)]
#[case::level1_index0_pointer_primitive(Some("/0"), r#""a""#, NO_EVENTS)]
#[case::level1_index0_pointer_empty_array(Some("/0"), "[\t]", NO_EVENTS)]
#[case::level1_index0_pointer_empty_object(Some("/0"), "{ }", NO_EVENTS)]
#[case::level1_index0_pointer_singleton_array_primitive(
Some("/0"),
"[null]",
match1(Token::LitNull, "/0")
)]
#[case::level1_index0_pointer_singleton_array_empty_array(
Some("/0"),
"[[]]",
enter_exit(Token::ArrBegin, Token::ArrEnd, "/0")
)]
#[case::level1_index0_pointer_singleton_array_empty_object(
Some("/0"),
"[{}]",
enter_exit(Token::ObjBegin, Token::ObjEnd, "/0")
)]
#[case::level1_index0_pointer_singleton_array_singleton_array(
Some("/0"),
"[[false]]",
enter_exit(Token::ArrBegin, Token::ArrEnd, "/0")
)]
#[case::level1_index0_pointer_singleton_array_singleton_object(
Some("/0"),
r#"[{"foo":{}}]"#,
enter_exit(Token::ObjBegin, Token::ObjEnd, "/0")
)]
#[case::level1_index0_pointer_multi_array_primitive(
Some("/0"),
"[true,false]",
match1(Token::LitTrue, "/0")
)]
#[case::level1_index1_pointer_primitive(Some("/1"), r#""a""#, NO_EVENTS)]
#[case::level1_index1_pointer_empty_array(Some("/1"), "[\t]", NO_EVENTS)]
#[case::level1_index1_pointer_empty_object(Some("/1"), "{ }", NO_EVENTS)]
#[case::level1_index1_pointer_singleton_array_primitive(Some("/1"), "[null]", NO_EVENTS)]
#[case::level1_index1_pointer_singleton_array_empty_array(Some("/1"), "[[]]", NO_EVENTS)]
#[case::level1_index1_pointer_singleton_array_empty_object(Some("/1"), "[{}]", NO_EVENTS)]
#[case::level1_index1_pointer_multi_array_primitive(
Some("/1"),
"[null,true,false]",
match1(Token::LitTrue, "/1")
)]
#[case::level1_index1_pointer_multi_array_empty_array(
Some("/1"),
"[null,[]]",
enter_exit(Token::ArrBegin, Token::ArrEnd, "/1")
)]
#[case::level1_index1_pointer_multi_array_empty_object(
Some("/1"),
"[[],{},123]",
enter_exit(Token::ObjBegin, Token::ObjEnd, "/1")
)]
#[case::level1_index1_pointer_multi_array_non_empty_array(
Some("/1"),
r#"[{},[true,false],123]"#,
enter_exit(Token::ArrBegin, Token::ArrEnd, "/1")
)]
#[case::level1_index1_pointer_multi_array_non_empty_object(
Some("/1"),
r#"[null,{"a":true,"b":false}]"#,
enter_exit(Token::ObjBegin, Token::ObjEnd, "/1")
)]
#[case::level1_name_num_pointer_object_primitive(
Some("/0"),
r#"{"a":1,"b":true,"0":null}"#,
match1(Token::LitNull, "/0")
)]
#[case::level1_name_num_pointer_object_empty_array(
Some("/0"),
r#"{"a":1,"0":[],"b":true}"#,
enter_exit(Token::ArrBegin, Token::ArrEnd, "/0")
)]
#[case::level1_name_num_pointer_object_empty_object(
Some("/0"),
r#"{"0":{},"a":1,"b":true}"#,
enter_exit(Token::ObjBegin, Token::ObjEnd, "/0")
)]
#[case::level1_name_str_pointer_primitive(Some("/a"), "1.0", NO_EVENTS)]
#[case::level1_name_str_pointer_empty_array(Some("/a"), "[]", NO_EVENTS)]
#[case::level1_name_str_pointer_empty_array(Some("/a"), "{}", NO_EVENTS)]
#[case::level1_name_str_pointer_singleton_array_primitive(Some("/a"), "[-1]", NO_EVENTS)]
#[case::level1_name_str_pointer_singleton_array_empty_array(Some("/a"), "[[]]", NO_EVENTS)]
#[case::level1_name_str_pointer_singleton_array_empty_object(Some("/a"), "[{}]", NO_EVENTS)]
#[case::level1_name_str_pointer_object_primitive(
Some("/a"),
r#"{"a":1.0}"#,
match1(Token::Num, "/a")
)]
#[case::level1_name_str_pointer_object_empty_array(
Some("/a"),
r#"{"a":[]}"#,
enter_exit(Token::ArrBegin, Token::ArrEnd, "/a")
)]
#[case::level1_name_str_pointer_object_empty_object(
Some("/a"),
r#"{"a":{}}"#,
enter_exit(Token::ObjBegin, Token::ObjEnd, "/a")
)]
#[case::level2_index0_0_pointer_primitive(Some("/0/0"), r#""a""#, NO_EVENTS)]
#[case::level2_index0_0_pointer_empty_array(Some("/0/0"), "[\t]", NO_EVENTS)]
#[case::level2_index0_0_pointer_empty_object(Some("/0/0"), "{ }", NO_EVENTS)]
#[case::level2_index0_0_pointer_singleton_array_primitive(Some("/0/0"), "[null]", NO_EVENTS)]
#[case::level2_index0_0_pointer_singleton_array_empty_array(Some("/0/0"), "[[]]", NO_EVENTS)]
#[case::level2_index0_0_pointer_singleton_array_empty_object(Some("/0/0"), "[{}]", NO_EVENTS)]
#[case::level2_index0_0_pointer_singleton_array_singleton_array(
Some("/0/0"),
"[[false]]",
match1(Token::LitFalse, "/0/0")
)]
#[case::level2_index0_0_pointer_singleton_array_singleton_object(
Some("/0/0"),
r#"[{"foo":{}}]"#,
NO_EVENTS
)]
#[case::level2_index0_0_pointer_multi_array_primitive(Some("/0/0"), "[true,false]", NO_EVENTS)]
#[case::level2_index_0_1_pointer_singleton_array_singleton_array(
Some("/0/1"),
"[[null]]",
NO_EVENTS
)]
#[case::level2_index_0_1_pointer_singleton_array_multi_array_primitive(
Some("/0/1"),
r#"[[{}, "match here", true]]"#,
match1(Token::Str, "/0/1")
)]
#[case::level2_index_0_1_pointer_singleton_array_multi_array_array(
Some("/0/1"),
"[[{}, [0, 1, 2], null]]",
enter_exit(Token::ArrBegin, Token::ArrEnd, "/0/1")
)]
#[case::level2_index_0_1_pointer_singleton_array_multi_array_object(
Some("/0/1"),
r#"[[{}, {"hello": "world"}, true]]"#,
enter_exit(Token::ObjBegin, Token::ObjEnd, "/0/1")
)]
#[case::level2_index_1_0_pointer_singleton_array_singleton_array(
Some("/1/0"),
"[[null]]",
NO_EVENTS
)]
#[case::level2_index_1_0_pointer_multi_array_singleton_array_primitive(
Some("/1/0"),
r#"[["zero"],[1]]"#,
match1(Token::Num, "/1/0")
)]
#[case::level2_index_1_0_pointer_multi_array_singleton_array_array(
Some("/1/0"),
r#"[["zero"],[[true, false, null]]]"#,
enter_exit(Token::ArrBegin, Token::ArrEnd, "/1/0")
)]
#[case::level2_index_1_0_pointer_multi_array_singleton_array_object(
Some("/1/0"),
r#"[["zero"],[{"foo":"bar"}]]"#,
enter_exit(Token::ObjBegin, Token::ObjEnd, "/1/0")
)]
#[case::level2_index_1_1_pointer_multi_array_singleton_array_primitive(
Some("/1/1"),
"[0, [1]]",
NO_EVENTS
)]
#[case::level2_index_1_1_pointer_multi_array_multi_array_primitive(
Some("/1/1"),
r#"[0, [0, "one"]]"#,
match1(Token::Str, "/1/1")
)]
#[case::level2_index_1_1_pointer_multi_array_multi_array_array(
Some("/1/1"),
r#"[0, [0, ["one"]]]"#,
enter_exit(Token::ArrBegin, Token::ArrEnd, "/1/1")
)]
#[case::level2_index_1_1_pointer_multi_array_multi_array_object(
Some("/1/1"),
r#"[0, [0, {"one":2,"three":4}]]"#,
enter_exit(Token::ObjBegin, Token::ObjEnd, "/1/1")
)]
#[case::level2_name_num_pointer_object_object_primitive(
Some("/1/1"),
r#"{"1":{"1":"one"}}"#,
match1(Token::Str, "/1/1")
)]
#[case::level2_name_num_pointer_object_object_empty_array(
Some("/1/1"),
r#"{"1":{"1":[]}}"#,
enter_exit(Token::ArrBegin, Token::ArrEnd, "/1/1")
)]
#[case::level2_name_num_pointer_object_object_empty_object(
Some("/1/1"),
r#"{"1":{"1":{}}}"#,
enter_exit(Token::ObjBegin, Token::ObjEnd, "/1/1")
)]
#[case::level2_name_num_pointer_object_array_primitive(
Some("/1/1"),
r#"{"1":["zero", 1]}"#,
match1(Token::Num, "/1/1")
)]
#[case::level2_name_num_pointer_object_array_empty_array(
Some("/1/1"),
r#"{"1":[0, []]}"#,
enter_exit(Token::ArrBegin, Token::ArrEnd, "/1/1")
)]
#[case::level2_name_num_pointer_object_array_empty_object(
Some("/1/1"),
r#"{"1":[0, {}]}"#,
enter_exit(Token::ObjBegin, Token::ObjEnd, "/1/1")
)]
#[case::level2_name_num_pointer_array_object_primitive(
Some("/1/1"),
r#"[0, {"1":true}]"#,
match1(Token::LitTrue, "/1/1")
)]
#[case::level2_name_num_pointer_array_object_empty_array(
Some("/1/1"),
r#"[0, {"1":[]}]"#,
enter_exit(Token::ArrBegin, Token::ArrEnd, "/1/1")
)]
#[case::level2_name_num_pointer_array_object_empty_object(
Some("/1/1"),
r#"[0, {"1": {}}]"#,
enter_exit(Token::ObjBegin, Token::ObjEnd, "/1/1")
)]
#[case::index_leading_zero(Some("/00"), "[true]", NO_EVENTS)]
#[case::index_many(
["", "/0", "/1", "/1/1", "/1/3", "/3", "/3/0", "/10"],
r#"[true, [{}, 1, 2, 3], false, null, 4, 5, 6, 7, 8, 9, "ten"]"#,
[
Event::Enter(Token::ArrBegin, Pointer::default()),
Event::Match(Token::LitTrue, Pointer::from_static("/0")),
Event::Enter(Token::ArrBegin, Pointer::from_static("/1")),
Event::Match(Token::Num, Pointer::from_static("/1/1")),
Event::Match(Token::Num, Pointer::from_static("/1/3")),
Event::Exit(Token::ArrEnd, Pointer::from_static("/1")),
Event::Match(Token::LitNull, Pointer::from_static("/3")),
Event::Match(Token::Str, Pointer::from_static("/10")),
Event::Exit(Token::ArrEnd, Pointer::default()),
]
)]
#[case::name_diverges_after_common_prefix_1(Some("/foo"), r#"{"fox":"🦊"}"#, NO_EVENTS)]
#[case::name_diverges_after_common_prefix_2(
["/a", "/b", "/foo"],
r#"{"fox":"🦊"}"#,
NO_EVENTS)
]
#[case::name_diverges_after_common_prefix_3(
["/a", "/b", "/c", "/d", "/e", "/foo", "/g"],
r#"{"fox":"🦊"}"#,
NO_EVENTS)
]
#[case::name_diverges_after_common_prefix_4(
["/a", "/b", "/c", "/foo", "/foolish"],
r#"{"fox":"🦊"}"#,
NO_EVENTS)
]
#[case::name_diverges_after_common_prefix_4(
["/a", "/b", "/c", "/fog", "/foo", "/for"],
r#"{"fox":"🦊"}"#,
NO_EVENTS)
]
#[case::name_many_no_trie(
["/a", "/a/b", "/b", "/corge", "/foo", "/foo/1", "/garply", "/hello", "/qux", "/y", "/z"],
r#"{"a":{"b":[]},"foo":["bar","baz"],"garply":[1,2,3,{}],"hell":"no","hello":"world","helloo":false,"qux":123}"#,
[
Event::Enter(Token::ObjBegin, Pointer::from_static("/a")),
Event::Enter(Token::ArrBegin, Pointer::from_static("/a/b")),
Event::Exit(Token::ArrEnd, Pointer::from_static("/a/b")),
Event::Exit(Token::ObjEnd, Pointer::from_static("/a")),
Event::Enter(Token::ArrBegin, Pointer::from_static("/foo")),
Event::Match(Token::Str, Pointer::from_static("/foo/1")),
Event::Exit(Token::ArrEnd, Pointer::from_static("/foo")),
Event::Enter(Token::ArrBegin, Pointer::from_static("/garply")),
Event::Exit(Token::ArrEnd, Pointer::from_static("/garply")),
Event::Match(Token::Str, Pointer::from_static("/hello")),
Event::Match(Token::Num, Pointer::from_static("/qux")),
]
)]
#[case::name_trie_basic_1(["/a", "/ab/c"], r#"{"ab":{"c":1}}"#, match1(Token::Num, "/ab/c"))]
#[case::name_trie_basic_2(
["/a", "/ab", "/ab/c", "/ab/cd"],
r#"{"ab": {"c": null}}"#,
[
Event::Enter(Token::ObjBegin, Pointer::from_static("/ab")),
Event::Match(Token::LitNull, Pointer::from_static("/ab/c")),
Event::Exit(Token::ObjEnd, Pointer::from_static("/ab")),
]
)]
#[case::name::trie::long(
["/fog", "/folly", "/foo", "/fool", "/foolery", "/foolhardy", "/fooling", "/foolish", "/foolishness", "/foolishness/knows/0", "/foolishly", "/foolproof", "/foolscap"],
r#"{"foolishness":{"knows":["bounds"]}}"#,
[
Event::Enter(Token::ObjBegin, Pointer::from_static("/foolishness")),
Event::Match(Token::Str, Pointer::from_static("/foolishness/knows/0")),
Event::Exit(Token::ObjEnd, Pointer::from_static("/foolishness")),
],
)]
fn test_next_ascii_no_unescape<P, I, E>(
#[case] pointers: I,
#[case] input: &'static str,
#[case] expect: E,
) where
P: TryInto<Pointer>,
<P as TryInto<Pointer>>::Error: fmt::Debug,
I: IntoIterator<Item = P>,
E: IntoIterator<Item = Event<Pointer>>,
{
let group = Group::from_pointers(pointers.into_iter().map(|p| p.try_into().unwrap()));
let expect = expect.into_iter().collect::<Vec<_>>();
let (terminal, events, group) = collect_events(group, input, NO_UNESCAPE, Evaluator::next);
assert_eq!(Token::Eof, terminal);
assert_eq!(expect, events);
let (terminal, events, group) =
collect_events(group, input, NO_UNESCAPE, Evaluator::next_non_white);
assert_eq!(Token::Eof, terminal);
assert_eq!(expect, events);
let (terminal, events, _) =
collect_events(group, input, NO_UNESCAPE, Evaluator::next_meaningful);
assert_eq!(Token::Eof, terminal);
assert_eq!(expect, events);
}
#[rstest]
#[case::no_unescape(false)]
#[case::unescape(true)]
fn test_other_methods(#[case] unescape: bool) {
let parser = FixedAnalyzer::new(&br#"{"fo\u1234":1"#[..]).into_parser();
let pointer = Pointer::from_static("/foo");
let group = Group::from_pointer(pointer);
let mut evaluator = Evaluator::new(parser, group, unescape);
assert_eq!(0, evaluator.level());
assert_eq!(Event::<&Pointer>::Nil(Token::ObjBegin), evaluator.next());
assert_eq!(1, evaluator.level());
assert_eq!(
Some(syntax::StructKind::Obj),
evaluator.context().struct_kind()
);
assert_eq!(Event::<&Pointer>::Nil(Token::Str), evaluator.next());
assert_eq!(r#""fo\u1234""#, evaluator.content().literal());
assert_eq!(Event::<&Pointer>::Nil(Token::NameSep), evaluator.next());
assert_eq!(&Pos::new(11, 1, 12), evaluator.pos());
assert_eq!(Event::<&Pointer>::Nil(Token::Num), evaluator.next());
assert!(matches!(evaluator.try_content(), Ok(c) if "1" == c.literal()));
assert_eq!(Event::<&Pointer>::Nil(Token::Err), evaluator.next());
assert!(
matches!(evaluator.err().kind(), syntax::ErrorKind::Syntax { context: c, token: Token::Eof } if c.expect() == syntax::Expect::ObjValueSepOrEnd)
);
let (parser, _) = evaluator.into_parts();
assert_eq!(1, parser.level());
assert_eq!(
Some(syntax::StructKind::Obj),
parser.context().struct_kind()
);
assert!(
matches!(parser.err().kind(), syntax::ErrorKind::Syntax { context: c, token: Token::Eof } if c.expect() == syntax::Expect::ObjValueSepOrEnd)
);
}
fn collect_events<M>(
group: Group,
input: &'static str,
unescape: bool,
method: M,
) -> (Token, Vec<Event<Pointer>>, Group)
where
M: for<'a> Fn(&'a mut Evaluator<FixedAnalyzer<&'static [u8]>, Group>) -> Event<&'a Pointer>,
{
let parser = FixedAnalyzer::new(input.as_bytes()).into_parser();
let mut evaluator = Evaluator::new(parser, group, unescape);
let mut events = Vec::new();
let terminal = loop {
let event = method(&mut evaluator);
if event.token().is_terminal() {
assert!(event.is_nil());
break event.token();
} else if !event.is_nil() {
assert!(!event.token().is_pseudo() && !event.token().is_punct());
events.push(event.to_owned());
}
};
let (_, group) = evaluator.into_parts();
(terminal, events, group)
}
const NO_EVENTS: Option<Event<Pointer>> = None;
const NO_POINTERS: Option<Pointer> = None;
const NO_UNESCAPE: bool = false;
const fn match1(token: Token, pointer: &'static str) -> Option<Event<Pointer>> {
Some(Event::Match(token, Pointer::from_static(pointer)))
}
const fn enter_exit(enter: Token, exit: Token, pointer: &'static str) -> [Event<Pointer>; 2] {
[
Event::Enter(enter, Pointer::from_static(pointer)),
Event::Exit(exit, Pointer::from_static(pointer)),
]
}
}