Skip to content

Commit a62c857

Browse files
authored
hide Query and QueryMatch private APIs (#1431)
also fixed a minor bug where CST query matches used to return `undefined` instead of empty arrays for unmatched named captures.
1 parent d82cc0b commit a62c857

File tree

19 files changed

+241
-206
lines changed

19 files changed

+241
-206
lines changed

.changeset/tall-towns-tan.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@nomicfoundation/slang": patch
3+
---
4+
5+
fix CST query matches to return an empty array for unmatched named captures, instead of `undefined`.

crates/codegen/runtime/cargo/crate/src/runtime/cst/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ pub use terminal_kind::TerminalKind;
2323

2424
/// The base type of all nonterminals, terminals, and edges.
2525
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
26-
pub enum KindTypes {
26+
pub struct KindTypes {
2727
// These derives are because default #[derive(...)] on a generic type implements only the trait
2828
// with default bounds also implied for the generic types as well, i.e.
2929
//
@@ -79,6 +79,10 @@ pub type AncestorsIterator = metaslang_cst::cursor::AncestorsIterator<KindTypes>
7979
/// for detailed information about the query syntax and how to use queries to find matches.
8080
pub type Query = metaslang_cst::query::Query<KindTypes>;
8181

82+
/// A single capture matched by a query, containing the capture name,
83+
/// and a list of [`Cursor`]s to the location of each captured node in the parse tree.
84+
pub type Capture<'a> = metaslang_cst::query::Capture<'a, KindTypes>;
85+
8286
pub use metaslang_cst::query::QueryError;
8387
/// Represents a match found by executing queries on a cursor.
8488
pub type QueryMatch = metaslang_cst::query::QueryMatch<KindTypes>;

crates/metaslang/cst/generated/public_api.txt

Lines changed: 9 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/metaslang/cst/src/query/engine.rs

Lines changed: 62 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use super::model::{
88
OptionalASTNode, Query, SequenceASTNode,
99
};
1010
use crate::kinds::{KindTypes, NodeKind, TerminalKindExtensions};
11-
use crate::query::CaptureQuantifier;
1211

1312
impl<T: KindTypes + 'static> Cursor<T> {
1413
/// Returns an iterator over all matches of the given queries in the syntax tree.
@@ -145,14 +144,10 @@ impl<T: KindTypes + 'static> ASTNode<T> {
145144

146145
/// Represents a match found by executing queries on a cursor.
147146
pub struct QueryMatch<T: KindTypes> {
148-
/// The queries that were matched.
149-
pub queries: Rc<Vec<Query<T>>>,
150-
/// The index of the matched query within the list of queries.
151-
pub query_index: usize,
152-
/// The cursor that was used to find the match.
153-
pub root_cursor: Cursor<T>,
154-
/// The capture definitions in the query
155-
pub captures: BTreeMap<String, Vec<Cursor<T>>>,
147+
queries: Rc<Vec<Query<T>>>,
148+
query_index: usize,
149+
root_cursor: Cursor<T>,
150+
captures: BTreeMap<String, Vec<Cursor<T>>>,
156151
}
157152

158153
impl<T: KindTypes> QueryMatch<T> {
@@ -161,46 +156,70 @@ impl<T: KindTypes> QueryMatch<T> {
161156
&self.queries[self.query_index]
162157
}
163158

159+
/// The index of the matched query within the list of queries.
160+
pub fn query_index(&self) -> usize {
161+
self.query_index
162+
}
163+
164+
/// The cursor that was used to find the match.
165+
pub fn root_cursor(&self) -> &Cursor<T> {
166+
&self.root_cursor
167+
}
168+
164169
/// Returns an iterator over all of the capture names matched by this query.
165170
pub fn capture_names(&self) -> impl Iterator<Item = &String> {
166-
self.query().capture_quantifiers.keys()
171+
self.query().capture_quantifiers().keys()
167172
}
168173

169-
/// Returns an iterator over all of the captures matched by this query. The iterator item
170-
/// is a 3-tuple containing the capture name, a [`CaptureQuantifier`], and an iterator yielding
171-
/// a [`Cursor`] to the location of each capture in the parse tree.
172-
pub fn captures(
173-
&self,
174-
) -> impl Iterator<Item = (&String, CaptureQuantifier, impl Iterator<Item = Cursor<T>>)> {
175-
let query = self.query();
176-
query.capture_quantifiers.iter().map(|(name, quantifier)| {
177-
let captures = self
178-
.captures
179-
.get(name)
180-
.unwrap_or(&vec![])
181-
.clone()
182-
.into_iter();
183-
(name, *quantifier, captures)
174+
/// Returns an iterator over all of the captures matched by this query. Each [`Capture`] contains the capture name,
175+
/// and a list of [`Cursor`]s to the location of each captured node in the parse tree.
176+
pub fn captures(&self) -> impl Iterator<Item = Capture<'_, T>> {
177+
self.query().capture_quantifiers().keys().map(|name| {
178+
let cursors = match self.captures.get(name) {
179+
Some(cursors) => &cursors[..],
180+
None => &[],
181+
};
182+
183+
Capture { name, cursors }
184184
})
185185
}
186186

187-
/// Try to find a single capture matched by this query. If no captures exist with the name `name`,
188-
/// this will return `None`. If captures do exist, then this will return a tuple containing a [`CaptureQuantifier`]
189-
/// and an iterator yielding a [`Cursor`] to the location of each capture in the parse tree.
190-
pub fn capture(
191-
&self,
192-
name: &str,
193-
) -> Option<(CaptureQuantifier, impl Iterator<Item = Cursor<T>>)> {
194-
let query = self.query();
195-
query.capture_quantifiers.get(name).map(|quantifier| {
196-
let captures = self
197-
.captures
198-
.get(name)
199-
.unwrap_or(&vec![])
200-
.clone()
201-
.into_iter();
202-
(*quantifier, captures)
203-
})
187+
/// Try to find a single capture matched by this query.
188+
/// If no captures exist with the name `name`, this will return `None`.
189+
/// If a capture does exist, then this will return a [`Capture`] containing the capture name,
190+
/// and a list of [`Cursor`]s to the location of each captured node in the parse tree.
191+
pub fn capture(&self, name: &str) -> Option<Capture<'_, T>> {
192+
let name = self
193+
.query()
194+
.capture_quantifiers()
195+
.keys()
196+
.find(|key| key == &name)?;
197+
198+
let cursors = match self.captures.get(name) {
199+
Some(cursors) => &cursors[..],
200+
None => &[],
201+
};
202+
203+
Some(Capture { name, cursors })
204+
}
205+
}
206+
207+
/// A single capture matched by a query, containing the capture name,
208+
/// and a list of [`Cursor`]s to the location of each captured node in the parse tree.
209+
pub struct Capture<'a, T: KindTypes> {
210+
name: &'a str,
211+
cursors: &'a [Cursor<T>],
212+
}
213+
214+
impl<T: KindTypes> Capture<'_, T> {
215+
/// The name of the capture.
216+
pub fn name(&self) -> &str {
217+
self.name
218+
}
219+
220+
/// A list of cursors to the location of each captured node in the parse tree.
221+
pub fn cursors(&self) -> &[Cursor<T>] {
222+
self.cursors
204223
}
205224
}
206225

@@ -225,7 +244,7 @@ impl<T: KindTypes + 'static> QueryMatchIterator<T> {
225244
fn advance_to_next_possible_matching_query(&mut self) {
226245
while !self.cursor.is_completed() {
227246
while self.query_index < self.queries.len() {
228-
let ast_node = &self.queries[self.query_index].ast_node;
247+
let ast_node = &self.queries[self.query_index].ast_node();
229248
if ast_node.can_match(&self.cursor) {
230249
// The first matcher in the query should allow implicit matches
231250
self.matcher = Some(ast_node.create_matcher(self.cursor.clone(), false));

crates/metaslang/cst/src/query/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ mod engine;
44
mod model;
55
mod parser;
66

7-
pub use engine::{QueryMatch, QueryMatchIterator};
7+
pub use engine::{Capture, QueryMatch, QueryMatchIterator};
88
pub use model::Query;
99
pub use parser::{CaptureQuantifier, QueryError};

crates/metaslang/cst/src/query/model.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@ use crate::text_index::TextIndex;
1717
/// for detailed information about the query syntax and how to use queries to find matches.
1818
#[derive(Clone, Debug)]
1919
pub struct Query<T: KindTypes> {
20-
/// The abstract syntax tree (AST) representation of the query.
21-
pub ast_node: ASTNode<T>,
22-
/// A map of capture names to their quantifiers, used to define how many times a capture can occur.
23-
pub capture_quantifiers: BTreeMap<String, CaptureQuantifier>,
20+
ast_node: ASTNode<T>,
21+
capture_quantifiers: BTreeMap<String, CaptureQuantifier>,
2422
}
2523

2624
impl<T: KindTypes> Query<T> {
@@ -107,6 +105,16 @@ impl<T: KindTypes> Query<T> {
107105
capture_quantifiers,
108106
})
109107
}
108+
109+
/// The abstract syntax tree (AST) representation of the query.
110+
pub(crate) fn ast_node(&self) -> &ASTNode<T> {
111+
&self.ast_node
112+
}
113+
114+
/// A map of capture names to their quantifiers, used to define how many times a capture can occur.
115+
pub fn capture_quantifiers(&self) -> &BTreeMap<String, CaptureQuantifier> {
116+
&self.capture_quantifiers
117+
}
110118
}
111119

112120
impl<T: KindTypes> fmt::Display for Query<T> {

crates/metaslang/graph_builder/generated/public_api.txt

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/metaslang/graph_builder/src/checker.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ impl<KT: KindTypes> ast::Stanza<KT> {
163163

164164
let all_captures = self
165165
.query
166-
.capture_quantifiers
166+
.capture_quantifiers()
167167
.keys()
168168
.into_iter()
169169
.map(|cn| Identifier::from(cn.as_str()))
@@ -607,7 +607,7 @@ impl ast::Capture {
607607
ctx: &mut StanzaCheckContext<'_, KT>,
608608
) -> Result<ExpressionResult, CheckError> {
609609
let name = self.name.to_string();
610-
if let Some(quantifier) = ctx.stanza_query.capture_quantifiers.get(&name) {
610+
if let Some(quantifier) = ctx.stanza_query.capture_quantifiers().get(&name) {
611611
self.quantifier = *quantifier;
612612
ctx.used_captures.insert(self.name.clone());
613613
Ok(ExpressionResult {

crates/metaslang/graph_builder/src/execution.rs

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details.
66
// ------------------------------------------------------------------------------------------------
77

8+
use metaslang_cst::cursor::Cursor;
89
use metaslang_cst::kinds::KindTypes;
910
use metaslang_cst::query::{CaptureQuantifier, QueryMatch};
1011
use thiserror::Error;
@@ -143,30 +144,16 @@ pub struct Match<KT: KindTypes> {
143144
impl<KT: KindTypes> Match<KT> {
144145
/// Return the top-level matched node.
145146
pub fn full_capture(&self) -> metaslang_cst::cursor::Cursor<KT> {
146-
self.mat.root_cursor.clone()
147+
self.mat.root_cursor().clone()
147148
}
148149

149150
/// Return the matched nodes for a named capture.
150-
pub fn named_captures(
151-
&self,
152-
) -> impl Iterator<
153-
Item = (
154-
&String,
155-
CaptureQuantifier,
156-
impl Iterator<Item = metaslang_cst::cursor::Cursor<KT>>,
157-
),
158-
> {
151+
pub fn named_captures(&self) -> impl Iterator<Item = metaslang_cst::query::Capture<'_, KT>> {
159152
self.mat.captures()
160153
}
161154

162155
/// Return the matched nodes for a named capture.
163-
pub fn named_capture(
164-
&self,
165-
name: &str,
166-
) -> Option<(
167-
CaptureQuantifier,
168-
impl Iterator<Item = metaslang_cst::cursor::Cursor<KT>>,
169-
)> {
156+
pub fn named_capture(&self, name: &str) -> Option<metaslang_cst::query::Capture<'_, KT>> {
170157
self.mat.capture(name)
171158
}
172159

@@ -248,27 +235,28 @@ impl CancellationFlag for NoCancellation {
248235
pub struct CancellationError(pub &'static str);
249236

250237
impl Value {
251-
pub fn from_nodes<KT: KindTypes, CI: IntoIterator<Item = metaslang_cst::cursor::Cursor<KT>>>(
238+
pub fn from_nodes<KT: KindTypes>(
252239
graph: &mut Graph<KT>,
253-
cursors: CI,
240+
cursors: &[Cursor<KT>],
254241
quantifier: CaptureQuantifier,
255242
) -> Value {
256243
let mut cursors = cursors.into_iter();
257244
match quantifier {
258245
CaptureQuantifier::One => {
259-
let syntax_node = graph.add_syntax_node(cursors.next().expect("missing capture"));
246+
let cursor = cursors.next().expect("missing capture").clone();
247+
let syntax_node = graph.add_syntax_node(cursor);
260248
syntax_node.into()
261249
}
262250
CaptureQuantifier::ZeroOrMore | CaptureQuantifier::OneOrMore => {
263251
let syntax_nodes = cursors
264-
.map(|c| graph.add_syntax_node(c).into())
252+
.map(|c| graph.add_syntax_node(c.clone()).into())
265253
.collect::<Vec<Value>>();
266254
syntax_nodes.into()
267255
}
268256
CaptureQuantifier::ZeroOrOne => match cursors.next() {
269257
None => Value::Null.into(),
270258
Some(cursor) => {
271-
let syntax_node = graph.add_syntax_node(cursor);
259+
let syntax_node = graph.add_syntax_node(cursor.clone());
272260
syntax_node.into()
273261
}
274262
},

0 commit comments

Comments
 (0)