Skip to content

Commit

Permalink
Add anchoring to user documentation and examples
Browse files Browse the repository at this point in the history
  • Loading branch information
ggiraldez committed Jul 3, 2024
1 parent 50a20a5 commit 6708dac
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 7 deletions.
5 changes: 5 additions & 0 deletions crates/metaslang/cst/src/query/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,9 @@ 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.
let (mut template, last_anchor) = matcher.children.iter().enumerate().fold(
(Vec::new(), false),
|(mut acc, last_anchor), (index, child)| {
Expand Down Expand Up @@ -553,6 +556,8 @@ impl<T: KindTypes + 'static> Matcher<T> for OneOrMoreMatcher<T> {
}
}

/// Matches any number of sibling nodes and is used in between other matchers
/// when matching sequences, unless an explicit anchor is added.
struct EllipsisMatcher<T: KindTypes> {
cursor: Cursor<T>,
has_returned_initial_empty_value: bool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,7 @@ fn capturing_nodes() {
@contract_name name:[Identifier]
members:[ContractMembers
[ContractMember
[EventDefinition
@event_name name:[Identifier]
]
[EventDefinition @event_name name:[Identifier]]
]
]
]
Expand Down Expand Up @@ -275,3 +273,105 @@ fn alternations() {
"break"
);
}

#[test]
fn anchoring() {
let query = Query::parse(
&r#"
// --8<-- [start:anchoring-1]
[FunctionDefinition
[ParametersDeclaration
[Parameters . @first_param [Parameter]]
]
]
// --8<-- [end:anchoring-1]
"#
.remove_mkdoc_snippet_markers(),
)
.unwrap();

let iter = assert_matches(
&query,
NonterminalKind::FunctionDefinition,
"function test(int x, int y);",
);

let matches: Vec<_> = iter.collect();
assert_eq!(matches.len(), 1);
assert_eq!(
matches[0].captures.get("first_param").unwrap()[0]
.node()
.unparse(),
"int x"
);

let query = Query::parse(
&r#"
// --8<-- [start:anchoring-2]
[FunctionDefinition
[ParametersDeclaration
[Parameters @last_param [Parameter] .]
]
]
// --8<-- [end:anchoring-2]
"#
.remove_mkdoc_snippet_markers(),
)
.unwrap();

let iter = assert_matches(
&query,
NonterminalKind::FunctionDefinition,
"function test(int x, int y);",
);

let matches: Vec<_> = iter.collect();
assert_eq!(matches.len(), 1);
assert_eq!(
matches[0].captures.get("last_param").unwrap()[0]
.node()
.unparse(),
" int y"
);

let query = Query::parse(
&r#"
// --8<-- [start:anchoring-3]
[Statements
@stmt1 [Statement] . @stmt2 [Statement]
]
// --8<-- [end:anchoring-3]
"#
.remove_mkdoc_snippet_markers(),
)
.unwrap();

let iter = assert_matches(&query, NonterminalKind::Statements, "int x; int y; x + y;");

let matches: Vec<_> = iter.collect();
assert_eq!(matches.len(), 2);
assert_eq!(
matches[0].captures.get("stmt1").unwrap()[0]
.node()
.unparse(),
"int x;"
);
assert_eq!(
matches[0].captures.get("stmt2").unwrap()[0]
.node()
.unparse(),
" int y;"
);
assert_eq!(
matches[1].captures.get("stmt1").unwrap()[0]
.node()
.unparse(),
" int y;"
);
assert_eq!(
matches[1].captures.get("stmt2").unwrap()[0]
.node()
.unparse(),
" x + y;"
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ fn test_fails_single_anchor() {
let result = Query::parse(r#"[_ .]"#);
match result {
Ok(_) => panic!("Expected parse failure"),
Err(e) => assert_eq!(e.message, "Parse error:\nexpected ']' at: .]\nAlt at: [_ .]\n"),
Err(e) => assert_eq!(
e.message,
"Parse error:\nexpected ']' at: .]\nAlt at: [_ .]\n"
),
}
}
31 changes: 28 additions & 3 deletions documentation/public/user-guide/tree-query-language.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ A _query_ is a pattern that matches a
certain set of nodes in a tree. The expression to match a given node
consists of a pair of brackets (`[]`) containing two things: the node's kind, and
optionally, a series of other patterns that match the node's children. For
example, this pattern would match any `MultiplicativeExpression` node whose children
are exactly two `Expression` nodes, with an `Asterisk` node in between (no whitespace):
example, this pattern would match any `MultiplicativeExpression` node that has
two children `Expression` nodes, with an `Asterisk` node in between:

```{ .scheme }
--8<-- "crates/solidity/outputs/cargo/tests/src/doc_examples/tree_query_language.rs:query-syntax-1"
Expand Down Expand Up @@ -36,7 +36,7 @@ node with two children, one of any kind labeled `left_operand` and one of any ki
--8<-- "crates/solidity/outputs/cargo/tests/src/doc_examples/tree_query_language.rs:query-syntax-4"
```

Children can also be elided. For example, this would produce multiple matches for a
Children can be elided. For example, this would produce multiple matches for a
`MultiplicativeExpression` where at least _one_ of the children is an expression of a `StringExpression` variant, where each match
is associated with each of the `StringExpression` children:

Expand Down Expand Up @@ -107,3 +107,28 @@ This pattern would match a set of possible keyword terminals, capturing them as
```{ .scheme }
--8<-- "crates/solidity/outputs/cargo/tests/src/doc_examples/tree_query_language.rs:alternations-2"
```

### Anchoring

By using anchors '.', you can constrain a pattern to only match the first or the last child nodes.

For example, the following pattern would match only the first parameter
declaration in a function definition:

```{ .scheme }
--8<-- "crates/solidity/outputs/cargo/tests/src/doc_examples/tree_query_language.rs:anchoring-1"
```

And conversely the following will match only the last parameter:

```{ .scheme }
--8<-- "crates/solidity/outputs/cargo/tests/src/doc_examples/tree_query_language.rs:anchoring-2"
```

If the anchor is used in between two patterns it constrains matches on both
patterns to occur consecutively, ie. without any other sibling node in between. For
example, this pattern matches pairs of consecutive statements:

```{ .scheme }
--8<-- "crates/solidity/outputs/cargo/tests/src/doc_examples/tree_query_language.rs:anchoring-3"
```

0 comments on commit 6708dac

Please sign in to comment.