1use std::cell::RefCell;
21use std::collections::HashMap;
22
23use tree_sitter::{Node, Parser, Query, QueryCursor, StreamingIterator};
24
25use crate::error::{Error, Result};
26use crate::lang::Language;
27
28thread_local! {
29 static STRUCT_PARSERS: RefCell<HashMap<Language, Parser>> = RefCell::new(HashMap::new());
30}
31
32const SENTINEL: &str = "GREPLMMV";
35
36const ROOT_CAPTURE: &str = "greplm.match";
39
40const VARIADIC: &str = "GREPLMVARIADIC";
46
47pub struct Compiled {
49 query: Query,
50 pub anchors: Vec<String>,
53 root_capture: Option<u32>,
54}
55
56#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
58pub struct StructCapture {
59 pub name: String,
60 pub text: String,
61 pub line: u32,
62}
63
64#[derive(Debug, Clone)]
66pub struct StructMatch {
67 pub line_start: u32,
68 pub line_end: u32,
69 pub kind: String,
70 pub captures: Vec<StructCapture>,
71}
72
73pub fn compile(lang: Language, pattern: &str) -> Result<Compiled> {
75 let grammar = lang
76 .grammar()
77 .ok_or_else(|| Error::other(format!("language {} is not parseable", lang.id())))?;
78
79 let trimmed = pattern.trim();
80 let (query_src, anchors) = if trimmed.starts_with('(') {
81 (trimmed.to_string(), Vec::new())
83 } else {
84 compile_metavars(lang, trimmed)?
85 };
86
87 let query = Query::new(&grammar, &query_src).map_err(|e| {
88 Error::other(format!(
89 "invalid structural query: {e}\n--- compiled query ---\n{query_src}"
90 ))
91 })?;
92 let root_capture = query
93 .capture_names()
94 .iter()
95 .position(|n| *n == ROOT_CAPTURE)
96 .map(|i| i as u32);
97 Ok(Compiled {
98 query,
99 anchors,
100 root_capture,
101 })
102}
103
104pub fn run(lang: Language, compiled: &Compiled, source: &[u8]) -> Vec<StructMatch> {
106 let grammar = match lang.grammar() {
107 Some(g) => g,
108 None => return Vec::new(),
109 };
110 STRUCT_PARSERS.with(|cell| {
111 let mut map = cell.borrow_mut();
112 let parser = map.entry(lang).or_insert_with(|| {
113 let mut p = Parser::new();
114 let _ = p.set_language(&grammar);
115 p
116 });
117 let tree = match parser.parse(source, None) {
118 Some(t) => t,
119 None => return Vec::new(),
120 };
121 let names = compiled.query.capture_names();
122 let mut cursor = QueryCursor::new();
125 let mut out = Vec::new();
126 let mut b1 = Vec::new();
127 let mut b2 = Vec::new();
128 let mut src = source;
129 let mut matches = cursor.matches(&compiled.query, tree.root_node(), source);
130 while let Some(m) = matches.next() {
131 if !m.satisfies_text_predicates(&compiled.query, &mut b1, &mut b2, &mut src) {
132 continue;
133 }
134 let primary = compiled
137 .root_capture
138 .and_then(|ri| m.captures.iter().find(|c| c.index == ri))
139 .or_else(|| m.captures.first());
140 let node = match primary {
141 Some(c) => c.node,
142 None => continue,
143 };
144 let mut captures = Vec::new();
145 for c in m.captures {
146 let name = names.get(c.index as usize).copied().unwrap_or("");
147 if name == ROOT_CAPTURE {
148 continue;
149 }
150 if let Ok(text) = std::str::from_utf8(&source[c.node.byte_range()]) {
151 captures.push(StructCapture {
152 name: name.to_string(),
153 text: text.to_string(),
154 line: c.node.start_position().row as u32 + 1,
155 });
156 }
157 }
158 out.push(StructMatch {
159 line_start: node.start_position().row as u32 + 1,
160 line_end: node.end_position().row as u32 + 1,
161 kind: node.kind().to_string(),
162 captures,
163 });
164 }
165 out
166 })
167}
168
169fn compile_metavars(lang: Language, pattern: &str) -> Result<(String, Vec<String>)> {
172 let grammar = lang
173 .grammar()
174 .ok_or_else(|| Error::other(format!("language {} is not parseable", lang.id())))?;
175
176 let (substituted, _names) = substitute_metavars(pattern);
178
179 let mut parser = Parser::new();
180 parser
181 .set_language(&grammar)
182 .map_err(|e| Error::other(format!("set_language: {e}")))?;
183 let tree = parser
184 .parse(substituted.as_bytes(), None)
185 .ok_or_else(|| Error::other("failed to parse pattern".to_string()))?;
186 let src = substituted.as_bytes();
187
188 let root = unwrap_root(tree.root_node());
190 if root.is_error() {
191 return Err(Error::other(
192 "pattern did not parse cleanly; check the syntax or use a tree-sitter query"
193 .to_string(),
194 ));
195 }
196
197 let mut out = String::new();
198 let mut anchors = Vec::new();
199 let mut counter = 0usize;
200 emit(root, src, &mut out, &mut anchors, &mut counter);
201 out.push_str(&format!(" @{ROOT_CAPTURE}"));
202 Ok((out, anchors))
203}
204
205fn substitute_metavars(pattern: &str) -> (String, Vec<String>) {
207 let mut out = String::with_capacity(pattern.len());
208 let mut names = Vec::new();
209 let bytes = pattern.as_bytes();
210 let mut i = 0;
211 while i < bytes.len() {
212 if bytes[i] == b'$' {
213 if i + 2 < bytes.len() && bytes[i + 1] == b'$' && bytes[i + 2] == b'$' {
216 let mut j = i + 3;
217 while j < bytes.len() && (bytes[j].is_ascii_alphanumeric() || bytes[j] == b'_') {
218 j += 1;
219 }
220 out.push_str(VARIADIC);
221 i = j;
222 continue;
223 }
224 let mut j = i + 1;
225 while j < bytes.len() && (bytes[j].is_ascii_alphanumeric() || bytes[j] == b'_') {
226 j += 1;
227 }
228 if j > i + 1 {
229 let name = &pattern[i + 1..j];
230 out.push_str(SENTINEL);
231 out.push('_');
232 out.push_str(name);
233 names.push(name.to_string());
234 i = j;
235 continue;
236 }
237 }
238 out.push(bytes[i] as char);
239 i += 1;
240 }
241 (out, names)
242}
243
244fn unwrap_root(node: Node) -> Node {
247 const WRAPPERS: &[&str] = &[
248 "source_file",
249 "program",
250 "translation_unit",
251 "module",
252 "expression_statement",
253 "statement",
254 "compound_statement",
255 "block",
256 ];
257 let mut cur = node;
258 loop {
259 if !WRAPPERS.contains(&cur.kind()) {
260 return cur;
261 }
262 let mut cursor = cur.walk();
263 let children: Vec<Node> = cur.named_children(&mut cursor).collect();
264 if children.len() == 1 {
265 cur = children[0];
266 } else {
267 return cur;
268 }
269 }
270}
271
272fn emit(node: Node, src: &[u8], out: &mut String, anchors: &mut Vec<String>, counter: &mut usize) {
274 let text = node_text(node, src);
275
276 if let Some(t) = &text {
280 if t.trim() == VARIADIC {
281 return;
282 }
283 }
284
285 if let Some(t) = &text {
287 if let Some(name) = t.strip_prefix(&format!("{SENTINEL}_")) {
288 if is_clean_ident(name) {
289 out.push_str(&format!("(_) @{name}"));
290 return;
291 }
292 }
293 }
294
295 let mut cursor = node.walk();
296 let named: Vec<Node> = node.named_children(&mut cursor).collect();
297
298 if node.child_count() == 0 {
299 out.push_str(&format!("({})", node.kind()));
302 if let Some(t) = text {
303 if !t.is_empty() && !t.starts_with(SENTINEL) {
304 *counter += 1;
305 let cap = format!("greplm_a{counter}");
306 out.push_str(&format!(" @{cap}"));
307 out.push_str(&format!(" (#eq? @{cap} \"{}\")", escape(&t)));
308 if t.len() >= 3 && t.chars().all(|c| !c.is_whitespace()) {
309 anchors.push(t);
310 }
311 }
312 }
313 return;
314 }
315
316 if named.is_empty() {
317 out.push_str(&format!("({})", node.kind()));
320 return;
321 }
322
323 out.push('(');
324 out.push_str(node.kind());
325 for child in named {
326 out.push(' ');
327 emit(child, src, out, anchors, counter);
328 }
329 out.push(')');
330}
331
332fn node_text(node: Node, src: &[u8]) -> Option<String> {
333 std::str::from_utf8(src.get(node.start_byte()..node.end_byte())?)
334 .ok()
335 .map(|s| s.to_string())
336}
337
338fn is_clean_ident(s: &str) -> bool {
339 !s.is_empty() && s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
340}
341
342fn escape(s: &str) -> String {
343 s.replace('\\', "\\\\").replace('"', "\\\"")
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349
350 #[test]
351 fn raw_query_matches_calls() {
352 let src = b"fn main() {\n foo();\n bar();\n}\n";
353 let c = compile(
354 Language::Rust,
355 "(call_expression function: (identifier) @fn)",
356 )
357 .unwrap();
358 let m = run(Language::Rust, &c, src);
359 let names: Vec<&str> = m
360 .iter()
361 .flat_map(|mm| mm.captures.iter())
362 .map(|c| c.text.as_str())
363 .collect();
364 assert!(names.contains(&"foo"), "got {names:?}");
365 assert!(names.contains(&"bar"), "got {names:?}");
366 }
367
368 #[test]
369 fn predicate_filters_by_name() {
370 let src = b"fn main() {\n foo();\n bar();\n}\n";
371 let c = compile(
372 Language::Rust,
373 "((call_expression function: (identifier) @fn) (#eq? @fn \"foo\"))",
374 )
375 .unwrap();
376 let m = run(Language::Rust, &c, src);
377 let names: Vec<&str> = m
378 .iter()
379 .flat_map(|mm| mm.captures.iter())
380 .map(|c| c.text.as_str())
381 .collect();
382 assert_eq!(names, vec!["foo"], "predicate should keep only foo");
383 }
384
385 #[test]
386 fn metavar_pattern_compiles_and_matches() {
387 let src = b"struct A;\nfn alpha() {}\nfn beta() {}\n";
388 let c = compile(Language::Rust, "fn $NAME() {}").unwrap();
389 let m = run(Language::Rust, &c, src);
390 let names: Vec<String> = m
392 .iter()
393 .flat_map(|mm| mm.captures.iter())
394 .filter(|c| c.name == "NAME")
395 .map(|c| c.text.clone())
396 .collect();
397 assert!(names.contains(&"alpha".to_string()), "got {names:?}");
398 assert!(names.contains(&"beta".to_string()), "got {names:?}");
399 }
400
401 #[test]
402 fn variadic_metavars_match_any_params_and_body() {
403 let src = b"function noop() {}\nfunction add(a, b) { return a + b; }\n";
404 let c = compile(Language::JavaScript, "function $NAME($$$) { $$$ }").unwrap();
405 let m = run(Language::JavaScript, &c, src);
406 let names: Vec<String> = m
407 .iter()
408 .flat_map(|mm| mm.captures.iter())
409 .filter(|c| c.name == "NAME")
410 .map(|c| c.text.clone())
411 .collect();
412 assert!(names.contains(&"noop".to_string()), "got {names:?}");
413 assert!(
414 names.contains(&"add".to_string()),
415 "variadic params/body should match a function with args, got {names:?}"
416 );
417 }
418}