// pest-typed. A statically typed version of pest. // Copyright (c) 2023 黄博奕 // // Licensed under the Apache License, Version 2.0 // or the MIT // license , at your // option. All files in the project carrying such notice may not be copied, // modified, or distributed except according to those terms. extern crate alloc; #[cfg(test)] mod tests { use pest_typed::{ atomic_rule, choices::Choice2, compound_atomic_rule, iterators::{Pair, PairTree, Pairs}, non_atomic_rule, normal_rule, predefined_node::*, rule_eoi, sequence::{Seq2, Seq3}, silent_rule, BoundWrapper, ParsableTypedNode, RuleStruct, RuleWrapper, Storage, StringArrayWrapper, StringWrapper, TypeWrapper, }; use std::{fmt::Write, ops::Deref, string::String}; #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] enum Rule { Foo, RepFoo, NotFooBar, Life, WHITESPACE, COMMENT, EOI, String, Quote, } #[derive(Clone, Hash, PartialEq, Eq)] pub struct Foo; impl StringWrapper for Foo { const CONTENT: &'static str = "foo"; } impl RuleWrapper for Foo { const RULE: Rule = Rule::Foo; type Rule = Rule; } atomic_rule!( WHITESPACE, "White space.", Rule, Rule::WHITESPACE, CharRange::<' ', ' '> ); compound_atomic_rule!( COMMENT, "Comment.", Rule, Rule::COMMENT, CharRange::<'\t', '\t'>, false ); normal_rule!( StrFoo, "String \"Foo\".", Rule, Rule::Foo, Str::, AtomicRepeat, COMMENT<'i>>>, false ); rule_eoi!(EOI, Rule); #[test] fn string() { assert_eq!( as TypeWrapper>::Inner::CONTENT, Foo::CONTENT); StrFoo::try_check("foo").unwrap(); let s = StrFoo::try_parse("foo").unwrap(); assert_eq!(s.content.get_content(), "foo"); assert_eq!( format!("{:?}", s), "StrFoo { content: Str, span: Span { str: \"foo\", start: 0, end: 3 } }" ) } #[test] fn range() { WHITESPACE::try_check(" ").unwrap(); let whitespace = WHITESPACE::try_parse(" ").unwrap(); assert_eq!( format!("{:?}", whitespace), "WHITESPACE { span: Span { str: \" \", start: 0, end: 1 } }" ); COMMENT::try_check("\t").unwrap(); let comment = COMMENT::try_parse("\t").unwrap(); assert_eq!( format!("{:?}", comment), "COMMENT { content: CharRange { content: '\\t' }, span: Span { str: \"\\t\", start: 0, end: 1 } }" ); } type Ignore<'i> = AtomicRepeat, COMMENT<'i>>>; #[test] fn ignore() { silent_rule!( tmp, "Temporary rule.", Rule, Rule::RepFoo, Ignore<'i>, Empty<'i>, false ); tmp::try_check(" ").unwrap(); tmp::try_parse(" \t ").unwrap(); } #[test] fn repetition() { type REP<'i> = Rep, Ignore<'i>, 1>; non_atomic_rule!( R, "Repetion of [StrFoo].", Rule, Rule::RepFoo, REP<'i>, Ignore<'i>, false ); R::try_check("foo").unwrap(); let inputs = [ "foofoofoo", "foo foo foo", "foo foo\tfoo", "foofoofoo", "foo foo foo", "foo foo\tfoo", ]; for input in inputs { R::try_check(input).unwrap(); } let [rep1, rep2, rep3, ..] = inputs.map(|s| R::try_parse(s).unwrap()); let _ = R::try_parse("").unwrap(); assert_ne!(rep1, rep2); assert_ne!(rep1, rep3); let format = |rep: &R<'_, 1>| -> String { rep.iter_matched() .map(|e| e.get_content()) .collect::>() .concat() }; assert_eq!(format(&rep1), format(&rep2)); assert_eq!(format(&rep1), format(&rep3)); for i in rep1.clone().children() { assert_eq!(i.rule, Rule::Foo); } // White spaces and comments aren't filtered out. assert_ne!( rep1.iter_matched().collect::>(), rep2.iter_matched().collect::>() ); assert_ne!( rep1.iter_matched().collect::>(), rep3.iter_matched().collect::>() ); assert_eq!(REP::MIN, 0); assert_eq!(rep1.deref().get_min_len(), 0); assert_eq!(rep1.deref().get_max_len(), usize::MAX); assert_eq!( as TypeWrapper>::Inner::MIN, 0); } #[test] fn repetition_at_least_once() { type REP<'i> = RepOnce, Ignore<'i>, 1>; non_atomic_rule!( R, "Repetion of [StrFoo].", Rule, Rule::RepFoo, REP<'i>, Ignore<'i>, false ); let inputs = ["fooFoofoo", "foo Foo foo", "Foo foo\tfoo", "Foofoofoo"]; let [rep1, rep2, rep3, rep4] = inputs.map(|s| R::try_parse(s).unwrap()); assert_ne!(rep1, rep2); assert_ne!(rep1, rep3); assert_ne!(rep1, rep4); let collect = |r: &R<'_, 1>| { r.iter_matched() .map(|r| r.get_content()) .collect::>() .concat() }; assert_eq!(collect(&rep1), collect(&rep2)); assert_eq!(collect(&rep1), collect(&rep3)); assert_eq!(collect(&rep1), collect(&rep4)); assert_eq!(rep1.clone().self_or_children().len(), 1); assert_eq!(rep1.clone().self_or_children()[0].rule, Rule::RepFoo); { let mut buf = String::new(); rep3.iterate_level_order(|p, _depth| writeln!(buf, "{}", p.span.as_str())) .unwrap(); assert_eq!(buf, "Foo foo\tfoo\n \n\t\n"); assert_eq!( rep3.format_as_tree().unwrap(), "RepFoo WHITESPACE \" \" COMMENT \"\\t\" " ); } assert_eq!(REP::MIN, 1); assert_eq!(rep1.deref().get_min_len(), 1); assert_eq!(rep1.deref().get_max_len(), usize::MAX); assert_eq!( as TypeWrapper>::Inner::MIN, 1); } #[test] fn skip() { #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct NewLine; impl StringArrayWrapper for NewLine { const CONTENT: &'static [&'static str] = &["\r\n", "\n", "\r"]; } compound_atomic_rule!( QuotedString, "Quoted string.", Rule, Rule::String, Seq2, Ignore<'i>, 0>, Skipped, 0>>, false ); let s1 = QuotedString::<1>::try_parse("2\r\n").unwrap(); let s2 = QuotedString::<1>::try_parse("\r\n").unwrap(); assert_ne!(s1, s2); let new_line = NewLine; assert_eq!(new_line.get_content(), NewLine::CONTENT); } #[test] fn skip_char() { compound_atomic_rule!( three, "Skip 3 characters.", Rule, Rule::Foo, SkipChar<'i, 3>, false ); three::<1>::try_parse("foo").unwrap(); three::<1>::try_parse("foobar").unwrap_err(); } #[test] fn positive_predicate() { atomic_rule!(Quote,"A single `'`.", Rule,Rule::Quote,CharRange<'\'', '\''>); compound_atomic_rule!( Lifetime, "A simplified example of rust lifetime specifier.", Rule, Rule::Life, Seq3< Skipped, Ignore<'i>, 0>, Skipped< Positive< Seq2< Skipped, 0>, Skipped>, Ignore<'i>, 0>, >, >, Ignore<'i>, 0, >, Skipped>, Ignore<'i>, 0>, >, false ); let l = Lifetime::<1>::try_parse("'i").unwrap(); let (quote, peeked, name) = l.as_ref(); assert_eq!(quote.span.as_str(), "'"); let (any, _) = peeked.as_ref(); assert_eq!(any.content, 'i'); assert_eq!(name.iter().map(|c| c.content).collect::(), "i"); assert_eq!( name.deref() .into_iter() .map(|c| c.content) .collect::(), "i" ); let l = Lifetime::<1>::try_parse("'input").unwrap(); let (_, peeked, name) = l.as_ref(); let (any, _) = peeked.as_ref(); assert_eq!(any.content, 'i'); assert_eq!(name.iter().map(|c| c.content).collect::(), "input"); assert_eq!( name.deref() .clone() .into_iter() .map(|c| c.content) .collect::(), "input" ); Lifetime::<1>::try_parse("'i'").unwrap_err(); } #[test] fn negative_predicate() { #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct StrFoo; impl StringWrapper for StrFoo { const CONTENT: &'static str = "foo"; } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct StrBar; impl StringWrapper for StrBar { const CONTENT: &'static str = "bar"; } compound_atomic_rule!( not_foo_bar, "Any character except \"foo\" or \"bar\".", Rule, Rule::NotFooBar, AtomicRepeat<(Negative, Str>>, ANY)>, false ); let _ = not_foo_bar::<1>::try_parse("").unwrap(); let baz = not_foo_bar::<1>::try_parse("baz").unwrap(); for i in baz.iter() { let (neg, any) = i; assert_eq!( as Pairs<'_, Rule>>::self_or_children(neg).len(), 0 ); assert_eq!(>::self_or_children(any).len(), 0); } let _ = not_foo_bar::<1>::try_parse("Foofoo").unwrap_err(); let _ = not_foo_bar::<1>::try_parse("bazfoo").unwrap_err(); } #[test] fn peek() { compound_atomic_rule!( Rep_1_3, "Repeat previously matched expression 1 to 3 times", Rule, Rule::RepFoo, Seq2< Skipped>, Ignore<'i>, 0>, Skipped, Ignore<'i>, 0>, 1, 3>, Ignore<'i>, 0>, >, false ); let r = Rep_1_3::<1>::try_parse("foOfoO").unwrap(); assert_eq!( format!("{:#?}", r), "Rep_1_3 { content: Seq2( Push { content: Insens { content: \"foO\", }, }, RepeatMinMax { content: [ PEEK { span: Span { str: \"foO\", start: 3, end: 6, }, }, ], }, ), span: Span { str: \"foOfoO\", start: 0, end: 6, }, }" ); let (head, following) = r.as_ref(); assert_eq!(head.deref().content, "foO"); assert_eq!(head.get_content(), Foo::CONTENT); for i in following.iter_matched() { assert_eq!(i.span.as_str(), head.deref().content); } Rep_1_3::try_parse("fooFoo").unwrap_err(); Rep_1_3::try_parse("Foo").unwrap_err(); Rep_1_3::try_parse("FooFooFooFooFoo").unwrap_err(); let (pos, _) = Rep_1_3::try_parse_partial("FooFooFooFooFoo").unwrap(); assert_eq!(pos.pos(), 12); { use std::collections::HashSet; let mut set = HashSet::new(); for input in [ "foofoo", "FooFoo", "fOofOo", "foOfoO", "foofoo", "foofoofoo", ] { set.insert(Rep_1_3::try_parse(input).unwrap()); } assert_eq!(set.len(), 5); } } #[test] fn rep() { compound_atomic_rule!( Rep_0_3, "Repeat previously matched expression 0 to 3 times", Rule, Rule::RepFoo, RepeatMin, Ignore<'i>, 0>, 0>, false ); let x = Rep_0_3::try_parse("").unwrap(); assert_eq!(x.ref_inner(), &RepeatMin::default()); use std::collections::HashSet; let mut set = HashSet::new(); let foos = ["foofoo", "foo", "foofoofoo"]; for input in foos { Rep_0_3::try_check(input).unwrap(); let r = Rep_0_3::try_parse(input).unwrap(); for s in r.iter_matched() { set.insert(s.clone()); } for s in r.deref().clone().into_iter_matched() { set.insert(s); } } assert_eq!(set.len(), 1); let mut set = HashSet::new(); for input in ["foo", "foofoofoo", "foofoo", "foofoofoo"] { set.insert(Rep_0_3::try_parse(input).unwrap()); } assert_eq!(set.len(), 3); compound_atomic_rule!( Rep_2, "Repeat \"foo\" 2 times", Rule, Rule::RepFoo, [Insens<'i, Foo>; 2], false ); for input in ["", "foo", "foofoofoo"] { Rep_2::try_check(input).unwrap_err(); Rep_2::try_parse(input).unwrap_err(); } let arr = Rep_2::try_parse("foofoo").unwrap(); assert_eq!( format!("{:?}", arr), "Rep_2 { content: [Insens { content: \"foo\" }, Insens { content: \"foo\" }], span: Span { str: \"foofoo\", start: 0, end: 6 } }" ); let mut set = HashSet::new(); for input in ["foofoo", "fooFOO", "FooFoo", "foofoo"] { set.insert(Rep_2::try_parse(input).unwrap()); } assert_eq!(set.len(), 3); } }