Skip to content

Commit

Permalink
Allow sequences as alt options or grouped quantified matchers
Browse files Browse the repository at this point in the history
  • Loading branch information
ggiraldez committed Jul 3, 2024
1 parent 6708dac commit 6087c38
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 14 deletions.
11 changes: 7 additions & 4 deletions crates/metaslang/cst/src/query/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,10 +341,13 @@ struct SequenceMatcher<T: KindTypes> {
impl<T: KindTypes + 'static> SequenceMatcher<T> {
fn new(matcher: Rc<SequenceASTNode<T>>, cursor: Cursor<T>) -> Self {
// Produce a template of instructions to create the matchers for the
// sequence by inserting ellipsis matchers in between each of the child
// matchers, unless it's explicitly disabled by an anchor token.
// sequence by inserting ellipsis matchers at the start, end, and in
// between each of the child matchers, unless it's explicitly disabled
// by an anchor token.
// If the sequence is anchored (eg. option in alt or quantified
// group sequence) then the starting and ending anchors are implicit.
let (mut template, last_anchor) = matcher.children.iter().enumerate().fold(
(Vec::new(), false),
(Vec::new(), matcher.anchored),
|(mut acc, last_anchor), (index, child)| {
if matches!(child, ASTNode::Anchor) {
if last_anchor {
Expand All @@ -360,7 +363,7 @@ impl<T: KindTypes + 'static> SequenceMatcher<T> {
}
},
);
if !last_anchor {
if !last_anchor && !matcher.anchored {
template.push(SequenceItem::Ellipsis);
}
Self {
Expand Down
2 changes: 2 additions & 0 deletions crates/metaslang/cst/src/query/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ pub struct NodeMatchASTNode<T: KindTypes> {
#[derive(Debug)]
pub struct SequenceASTNode<T: KindTypes> {
pub children: Vec<ASTNode<T>>,
// if true, the sequence has implicit beginning and ending anchors
pub anchored: bool,
}

#[derive(Debug)]
Expand Down
49 changes: 40 additions & 9 deletions crates/metaslang/cst/src/query/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ pub(super) fn parse_query<T: KindTypes>(input: &str) -> Result<ASTNode<T>, Query
pub(super) fn parse_matcher_alternatives<T: KindTypes>(
i: &str,
) -> IResult<&str, ASTNode<T>, VerboseError<&str>> {
separated_list1(token('|'), parse_quantified_matcher::<T>)
separated_list1(token('|'), parse_matcher_alt_sequence::<T>)
.map(|mut children| {
if children.len() == 1 {
children.pop().unwrap()
Expand All @@ -74,21 +74,45 @@ pub(super) fn parse_matcher_sequence<T: KindTypes>(
children.len() > 1 || !matches!(children[0], ASTNode::Anchor)
},
)
.map(|children| ASTNode::Sequence(Rc::new(SequenceASTNode { children })))
.map(|children| {
ASTNode::Sequence(Rc::new(SequenceASTNode {
children,
anchored: false,
}))
})
.parse(i)
}

pub(super) fn parse_sequence_item<T: KindTypes>(
pub(super) fn parse_matcher_alt_sequence<T: KindTypes>(
i: &str,
) -> IResult<&str, ASTNode<T>, VerboseError<&str>> {
alt((parse_anchor::<T>, parse_quantified_matcher::<T>)).parse(i)
verify(
many1(parse_sequence_item::<T>),
|children: &[ASTNode<T>]| {
// Alternative sequences cannot start or end with an anchor, because
// those anchors are implicit
!matches!(children[0], ASTNode::Anchor)
&& !matches!(children[children.len() - 1], ASTNode::Anchor)
},
)
.map(|mut children| {
if children.len() == 1 {
// Alternative sequences of length 1 can be simplified to the child pattern
children.pop().unwrap()
} else {
ASTNode::Sequence(Rc::new(SequenceASTNode {
children,
anchored: true,
}))
}
})
.parse(i)
}

pub(super) fn parse_anchor<T: KindTypes>(i: &str) -> IResult<&str, ASTNode<T>, VerboseError<&str>> {
// An anchor is a single '.' character, and cannot be followed by another anchor
pair(token('.'), cut(peek(none_of(". \t\r\n"))))
.map(|_| ASTNode::Anchor)
.parse(i)
pub(super) fn parse_sequence_item<T: KindTypes>(
i: &str,
) -> IResult<&str, ASTNode<T>, VerboseError<&str>> {
alt((anchor::<T>, parse_quantified_matcher::<T>)).parse(i)
}

pub(super) fn parse_quantified_matcher<T: KindTypes>(
Expand Down Expand Up @@ -304,3 +328,10 @@ fn text_token(i: &str) -> IResult<&str, String, VerboseError<&str>> {
fn token<'input>(c: char) -> impl Parser<&'input str, char, VerboseError<&'input str>> {
terminated(char(c), multispace0)
}

fn anchor<T: KindTypes>(i: &str) -> IResult<&str, ASTNode<T>, VerboseError<&str>> {
// An anchor is a single '.' character, and cannot be followed by another anchor
pair(token('.'), cut(peek(none_of(". \t\r\n"))))
.map(|_| ASTNode::Anchor)
.parse(i)
}
15 changes: 15 additions & 0 deletions crates/testlang/outputs/cargo/tests/src/query/engine_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,18 @@ fn test_nested() {
},
);
}

#[test]
fn test_alternatives() {
run_query_test(
&common_test_tree(),
"(@x node:[_] | @y [DelimitedIdentifier] . @z [DelimitedIdentifier])",
query_matches! {
{x: ["A"]}
{y: ["A"], z: ["B"]}
{y: ["B"], z: ["C"]}
{y: ["D"], z: ["E"]}
{x: ["E"]}
},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ fn test_fails_parsing_ellipsis() {

#[test]
fn test_fails_consecutive_anchors() {
let result = Query::parse(r#"[_ . .]"#);
let result = Query::parse(r#"[_ [DelimitedIdentifier] . .]"#);
match result {
Ok(_) => panic!("Expected parse failure"),
Err(e) => assert_eq!(e.message, "Parse error:\nNoneOf at: .]\n"),
Expand Down

0 comments on commit 6087c38

Please sign in to comment.