1use std::collections::{HashMap, HashSet};
2
3use rlua::Lua;
4use tree_sitter::{Language, Node, Tree};
5
6use crate::{
7 env::{Env, Metavar},
8 lua::{eval_lua, eval_lua_scope, node::LuaNode, pattern::LuaPattern, LuaData},
9 node_types::NodeTypes,
10};
11
12pub(crate) fn parse(language: Language, code: &str) -> Tree {
13 let mut parser = tree_sitter::Parser::new();
14 parser
15 .set_language(language)
16 .expect("Failed to set tree-sitter parser language");
17 parser.parse(code, None).expect("Failed to parse code")
18}
19
20#[derive(Clone, Debug, Eq, Hash, PartialEq)]
21pub struct LuaCode(pub(crate) String);
22
23#[derive(Clone, Debug, Eq, Hash, PartialEq)]
24pub struct TmpVar(String);
25
26#[derive(Clone, Debug, Eq, Hash, PartialEq)]
27pub enum FindExpr {
28 Anonymous,
29 Ellipsis,
30 Metavar(Metavar),
31 Lua(LuaCode),
32}
33
34impl FindExpr {
35 const ANONYMOUS: &str = "_";
36 const ELLIPSIS: &str = "..";
37
38 pub fn parse(s: String) -> Self {
39 if s == Self::ANONYMOUS {
40 return Self::Anonymous;
41 }
42 if s == Self::ELLIPSIS {
43 return Self::Ellipsis;
44 }
45 Self::Metavar(Metavar(s))
46 }
47}
48
49#[derive(Clone, Debug)]
50pub struct Pattern<'nts> {
51 exprs: HashMap<TmpVar, FindExpr>,
52 lang: Language,
53 node_types: &'nts NodeTypes<'nts>,
54 root_id: usize,
55 text: String,
56 tree: Tree,
57 r#where: Vec<LuaCode>,
58}
59
60#[derive(Copy, Clone)]
61struct Goal<'tree> {
62 node: Node<'tree>,
63 text: &'tree str,
64}
65
66impl<'tree> Goal<'tree> {
67 fn as_str(&self) -> &'tree str {
68 self.node.utf8_text(self.text.as_bytes()).unwrap().trim()
69 }
70
71 fn child(&self, i: usize) -> Self {
72 Self {
73 node: self.node.child(i).unwrap(),
74 text: self.text,
75 }
76 }
77
78 fn _next_named_sibling(&self) -> Option<Self> {
79 self.node.next_sibling().map(|node| Self {
80 node,
81 text: self.text,
82 })
83 }
84
85 fn next_sibling(&self) -> Option<Self> {
86 self.node.next_sibling().map(|node| Self {
87 node,
88 text: self.text,
89 })
90 }
91
92 fn _parent(&self) -> Option<Self> {
93 self.node.parent().map(|node| Self {
94 node,
95 text: self.text,
96 })
97 }
98}
99
100#[derive(Copy, Clone)]
101pub struct Candidate<'tree> {
102 node: Node<'tree>,
103 text: &'tree str,
104}
105
106impl<'tree> Candidate<'tree> {
107 fn as_str(&self) -> &'tree str {
108 self.node.utf8_text(self.text.as_bytes()).unwrap().trim()
109 }
110
111 fn child(&self, i: usize) -> Self {
112 Self {
113 node: self.node.child(i).unwrap(),
114 text: self.text,
115 }
116 }
117
118 fn next_sibling(&self) -> Option<Self> {
119 self.node.next_sibling().map(|node| Self {
120 node,
121 text: self.text,
122 })
123 }
124}
125
126#[derive(Clone, Debug, Eq, PartialEq)]
127pub struct Match<'tree> {
128 pub(crate) env: Env<'tree>,
129 pub root: Node<'tree>,
130}
131
132impl<'nts> Pattern<'nts> {
133 fn meta(i: usize) -> TmpVar {
134 TmpVar(format!("mogglo_tmp_var_{i}"))
135 }
136
137 fn parse_from(
139 lang: Language,
140 node_types: &'nts NodeTypes<'nts>,
141 pat: String,
142 mut vars: usize,
143 unwrap_until: Option<&str>,
144 ) -> Self {
145 let mut peek = pat.chars().peekable();
146 let mut nest = 0;
147 let mut code = String::new();
148 let mut text = String::new();
149 let mut exprs = HashMap::new();
150 while let Some(current) = peek.next() {
151 if current == '$' {
152 if peek.next_if_eq(&'{').is_some() && peek.next_if_eq(&'{').is_some() {
154 if nest > 0 {
155 code += "${{"
156 }
157 nest += 1;
158 continue;
159 } else if nest > 0 {
160 code += &String::from(current);
161 continue;
162 }
163
164 if peek.next_if_eq(&'_').is_some() {
166 let tvar = Self::meta(vars);
167 vars += 1;
168 text += &tvar.0;
169 exprs.insert(tvar, FindExpr::Anonymous);
170 }
171
172 if peek.next_if_eq(&'.').is_some() && peek.next_if_eq(&'.').is_some() {
174 let tvar = Self::meta(vars);
175 vars += 1;
176 text += &tvar.0;
177 exprs.insert(tvar, FindExpr::Ellipsis);
178 }
179
180 let mvar_name: String =
182 peek.clone().take_while(char::is_ascii_alphabetic).collect();
183 if !mvar_name.is_empty() {
184 peek.nth(mvar_name.len() - 1);
185 }
186 if !mvar_name.is_empty() {
187 let tvar = Self::meta(vars);
188 vars += 1;
189 text += &tvar.0;
190 exprs.insert(tvar, FindExpr::Metavar(Metavar(mvar_name)));
191 continue;
192 }
193 } else if current == '}' && peek.next_if_eq(&'}').is_some() {
194 nest -= 1;
195 if nest == 0 {
196 let tvar = Self::meta(vars);
197 vars += 1;
198 text += &tvar.0;
199 exprs.insert(tvar, FindExpr::Lua(LuaCode(code)));
200 code = String::new();
201 } else {
202 code += "}}"
203 }
204 } else if nest > 0 {
205 code += &String::from(current);
206 continue;
207 } else {
208 text += &String::from(current);
209 continue;
210 }
211 }
212
213 let mut tree = parse(lang, &text);
222 if tree.root_node().has_error() {
223 text = format!("{{ {text} }}");
224 tree = parse(lang, &text);
225 if tree.root_node().has_error() {
226 text = format!("{text};");
227 tree = parse(lang, &text);
228 if tree.root_node().has_error() {
229 eprintln!("[WARN] Parse error in pattern!");
230 }
231 }
232 }
233 let mut root = tree.root_node();
234 if root.child_count() == 1 {
236 root = root.child(0).unwrap();
237 }
238 while root.named_child_count() == 1 {
240 if Some(root.kind()) == unwrap_until {
241 break;
242 }
243 root = root.named_child(0).unwrap();
244 }
245
246 Self {
247 exprs,
248 lang,
249 node_types,
250 root_id: root.id(),
251 text,
252 tree,
253 r#where: Vec::new(),
254 }
255 }
256
257 pub fn parse_kind(
258 lang: Language,
259 node_types: &'nts NodeTypes<'nts>,
260 pat: String,
261 kind: &str,
262 ) -> Self {
263 Self::parse_from(lang, node_types, pat, 0, Some(kind))
264 }
265
266 pub fn parse(lang: Language, node_types: &'nts NodeTypes<'nts>, pat: String) -> Self {
267 Self::parse_from(lang, node_types, pat, 0, None)
268 }
269
270 fn match_leaf_node(goal: Goal, candidate: Candidate) -> bool {
271 debug_assert!(goal.node.child_count() == 0);
272 goal.as_str() == candidate.as_str()
273 }
274
275 fn match_plain_node<'tree>(
276 &self,
277 lua: &Lua,
278 mut env: Env<'tree>,
279 goal: Goal,
280 candidate: Candidate<'tree>,
281 ) -> Option<Match<'tree>> {
282 let goal_count = goal.node.child_count();
283 let candidate_count = candidate.node.child_count();
284
285 if goal_count == 0 {
286 if candidate_count != 0 {
290 return None;
291 }
292 if Self::match_leaf_node(goal, candidate) {
296 return Some(Match {
297 env,
298 root: candidate.node,
299 });
300 }
301 return None;
305 }
306
307 if goal.node.kind_id() == candidate.node.kind_id() {
308 let mut goal_child = goal.child(0);
309 let mut candidate_child = candidate.child(0);
310 loop {
311 if let Some(FindExpr::Ellipsis) =
312 self.exprs.get(&TmpVar(goal_child.as_str().to_string()))
313 {
314 return Some(Match {
315 env,
316 root: candidate.node,
317 });
318 }
319 if let Some(m) =
320 self.match_node_internal(lua, env.clone(), goal_child, candidate_child)
321 {
322 env.extend(m.env);
323 match (goal_child.next_sibling(), candidate_child.next_sibling()) {
324 (Some(gnext), Some(cnext)) => {
325 goal_child = gnext;
326 candidate_child = cnext;
327 }
328 (None, Some(_)) => {
329 return Some(Match {
330 env,
331 root: candidate.node,
332 })
333 }
334 (Some(gnext), None) => {
335 goal_child = gnext;
337 }
338 (None, None) => {
339 return Some(Match {
340 env,
341 root: candidate.node,
342 })
343 }
344 }
345 } else {
346 match candidate_child.next_sibling() {
347 None => return None,
348 Some(cnext) => candidate_child = cnext,
349 }
350 }
351 }
352 } else {
353 for i in 0..candidate.node.child_count() {
355 if let Some(m) =
357 self.match_node_internal(lua, env.clone(), goal, candidate.child(i))
358 {
359 return Some(m);
360 }
361 }
362 None
363 }
364 }
365
366 fn match_expr<'tree>(
367 &self,
368 lua: &Lua,
369 mut env: Env<'tree>,
370 expr: &FindExpr,
371 candidate: Candidate<'tree>,
372 ) -> Option<Match<'tree>> {
373 match expr {
374 FindExpr::Anonymous => Some(Match {
375 env,
376 root: candidate.node,
377 }),
378 FindExpr::Ellipsis => panic!("Unhandled ellipsis"),
379 FindExpr::Metavar(m) => match env.0.get(m) {
380 None => {
381 env.insert(m.clone(), candidate.node);
382 Some(Match {
383 env,
384 root: candidate.node,
385 })
386 }
387 Some(goals) => {
388 let mut extended = env.clone();
389 for goal in goals {
390 let goal = Goal {
392 node: *goal,
393 text: candidate.text,
394 };
395 let mch = self.match_plain_node(lua, extended.clone(), goal, candidate)?;
396 extended.insert(m.clone(), mch.root);
397 }
398 Some(Match {
399 env: extended,
400 root: candidate.node,
401 })
402 }
403 },
404 FindExpr::Lua(LuaCode(code)) => {
405 let data = LuaData {
406 env: &env,
407 node_types: self.node_types,
408 text: candidate.text,
409 };
410 let mut binds = Env::default();
411 let matched = lua.context(|lua_ctx| {
413 let loaded = match lua_ctx.load(code).set_name("lua code") {
414 Err(e) => {
415 eprintln!("Bad Lua code: {code}");
416 return Err(e);
417 }
418 Ok(l) => l,
419 };
420 lua_ctx.scope(|scope| {
421 let globals = lua_ctx.globals();
422 globals.set("focus", LuaNode::new(candidate.node, candidate.text))?;
423 globals.set("t", candidate.as_str())?;
424 globals.set(
425 "bind",
426 scope.create_function_mut(|_, m: String| {
427 binds.insert(Metavar(m), candidate.node);
428 Ok(())
429 })?,
430 )?;
431 globals.set(
433 "match",
434 scope.create_function(|_, p: String| {
435 let pat = Pattern::parse_from(
436 self.lang,
437 self.node_types,
438 p,
439 self.exprs.len(),
440 None,
441 );
442 Ok(pat
443 .match_node_internal(lua, env.clone(), pat.to_goal(), candidate)
444 .is_some())
445 })?,
446 )?;
447
448 globals.set(
449 "pat",
450 scope.create_function(|_, p: String| {
451 let pat = Pattern::parse_from(
452 self.lang,
453 self.node_types,
454 p,
455 self.exprs.len(),
456 None,
457 );
458 Ok(LuaPattern::new(pat))
459 })?,
460 )?;
461
462 globals.set(
463 "pmatch",
464 scope.create_function(|_, (p, n): (LuaPattern, LuaNode)| {
465 Ok(p.0
466 .match_node_internal(
467 lua,
468 env.clone(),
469 p.0.to_goal(),
470 Candidate {
471 node: n.node,
472 text: n.text,
473 },
474 )
475 .is_some())
476 })?,
477 )?;
478
479 globals.set(
481 "rec",
482 scope.create_function(|_, p: String| {
483 let pat = Pattern::parse_from(
484 self.lang,
485 self.node_types,
486 p,
487 self.exprs.len(),
488 None,
489 );
490 Ok(!pat
491 .matches_internal(
492 candidate.text,
493 candidate.node,
494 &env,
495 true,
496 Some(1),
497 )
498 .is_empty())
499 })?,
500 )?;
501 eval_lua_scope::<bool>(lua_ctx, scope, loaded, &data)
502 })
503 });
504 env.extend(binds);
506 match matched {
507 Ok(true) => Some(Match {
508 env,
509 root: candidate.node,
510 }),
511 Ok(false) => None,
512 Err(e) => {
513 eprintln!("{e}");
514 None
515 }
516 }
517 }
518 }
519 }
520
521 fn match_node_internal<'tree>(
522 &self,
523 lua: &Lua,
524 env: Env<'tree>,
525 goal: Goal,
526 candidate: Candidate<'tree>,
527 ) -> Option<Match<'tree>> {
528 match self.exprs.get(&TmpVar(goal.as_str().to_string())) {
530 None => self.match_plain_node(lua, env, goal, candidate),
531 Some(expr) => self.match_expr(lua, env, expr, candidate),
532 }
533 }
534
535 pub fn match_node<'s, 'tree>(
536 &'s self,
537 env: Env<'tree>,
538 candidate: Candidate<'tree>,
539 ) -> Option<Match<'tree>>
540 where
541 'tree: 's,
542 {
543 let lua = Lua::new();
544 if let Some(m) = self.match_node_internal(&lua, env, self.to_goal(), candidate) {
545 for LuaCode(c) in &self.r#where {
546 let data = LuaData {
547 env: &m.env,
548 node_types: self.node_types,
549 text: candidate.text,
550 };
551 match eval_lua::<bool>(&lua, c, &data) {
552 Ok(b) if b => (),
553 Ok(_) => return None,
554 Err(e) => {
555 eprintln!("Error in Lua: {c}");
556 eprintln!("{e}");
557 return None;
558 }
559 }
560 }
561 Some(m)
562 } else {
563 None
564 }
565 }
566
567 fn matches_internal<'tree>(
570 &self,
571 text: &'tree str,
572 node: Node<'tree>,
573 env: &Env<'tree>,
574 recursive: bool,
575 limit: Option<usize>,
576 ) -> Vec<Match<'tree>> {
577 let mut cursor = node.walk();
578 let mut nodes: Vec<_> = node.children(&mut cursor).collect();
579 let mut ms = Vec::new();
580 let mut ranges = HashSet::new();
581 while !nodes.is_empty() {
582 let mut next = Vec::with_capacity(nodes.len()); for node in nodes {
584 let candidate = Candidate { node, text };
585 if let Some(m) = self.match_node(env.clone(), candidate) {
586 if ranges.contains(&m.root.byte_range()) {
587 continue;
588 }
589 ranges.insert(m.root.byte_range());
590 ms.push(m);
591 if limit.map(|l| ms.len() >= l).unwrap_or(false) {
592 return ms;
593 }
594 if !recursive {
595 continue;
596 }
597 }
598 let mut child_cursor = node.walk();
599 for child in node.children(&mut child_cursor) {
600 next.push(child);
601 }
602 }
603 nodes = next;
604 }
605 ms
606 }
607
608 pub fn matches<'tree>(
609 &self,
610 tree: &'tree Tree,
611 text: &'tree str,
612 env: &Env<'tree>,
613 recursive: bool,
614 limit: Option<usize>,
615 ) -> Vec<Match<'tree>> {
616 self.matches_internal(text, tree.root_node(), env, recursive, limit)
617 }
618
619 fn to_goal(&self) -> Goal {
620 let mut goal = self.tree.root_node();
621 while goal.id() != self.root_id {
623 if goal.child_count() == 1 {
624 goal = goal.child(0).unwrap();
625 } else {
626 debug_assert_eq!(goal.named_child_count(), 1);
627 goal = goal.named_child(0).unwrap();
628 }
629 }
630 Goal {
631 node: goal,
632 text: &self.text,
633 }
634 }
635
636 pub fn replacement(&self, m: &Match, text: &str) -> String {
637 let mut replacement = self
639 .to_goal()
640 .node
641 .utf8_text(self.text.as_bytes())
642 .unwrap()
643 .to_string();
644
645 for (tvar, expr) in &self.exprs {
646 match expr {
647 FindExpr::Anonymous => {
648 eprintln!("`$_` is not valid in replacements");
649 return String::new();
650 }
651 FindExpr::Ellipsis => {
652 eprintln!("`$..` is not valid in replacements");
653 return String::new();
654 }
655 FindExpr::Metavar(mvar @ Metavar(mtxt)) => match m.env.0.get(mvar) {
656 Some(matching_nodes) => {
657 if let Some(node) = matching_nodes.iter().next() {
658 replacement = replacement
659 .replace(&tvar.0, node.utf8_text(text.as_bytes()).unwrap());
660 }
661 }
662 None => {
663 eprintln!("Bad metavariable in replacement: {mtxt}");
664 return String::new();
665 }
666 },
667 FindExpr::Lua(LuaCode(code)) => {
668 let lua = Lua::new();
669 let data = LuaData {
670 env: &m.env,
671 node_types: self.node_types,
672 text,
673 };
674 match eval_lua::<String>(&lua, code, &data) {
675 Ok(evaled) => replacement = replacement.replace(&tvar.0, &evaled),
676 Err(e) => {
677 eprintln!("{e}")
678 }
679 };
680 }
681 }
682 }
683 replacement
684 }
685
686 pub fn replace(&self, matches: Vec<Match>, mut text: String) -> String {
687 for m in matches {
688 text = text.replace(
689 m.root.utf8_text(text.as_bytes()).unwrap(),
690 &self.replacement(&m, &text),
691 )
692 }
693 text
694 }
695
696 pub fn r#where(&mut self, iter: &mut impl Iterator<Item = LuaCode>) {
697 self.r#where.extend(iter);
698 }
699}
700
701#[cfg(test)]
702mod tests {
703 use std::collections::{HashMap, HashSet};
704
705 use tree_sitter::Tree;
706 use tree_sitter_rust::language;
707
708 use crate::node_types::NodeTypes;
709
710 use super::{Candidate, Env, FindExpr, LuaCode, Match, Metavar, Pattern};
711
712 lazy_static::lazy_static! {
713 static ref NODE_TYPES: NodeTypes<'static> = NodeTypes::new(tree_sitter_rust::NODE_TYPES).unwrap();
715 }
716
717 fn pat(s: &str) -> Pattern {
718 Pattern::parse(language(), &NODE_TYPES, s.to_string())
719 }
720
721 fn match_one<'tree>(s: &str, tree: &'tree Tree, text: &'tree str) -> Option<Env<'tree>> {
722 let candidate = Candidate {
723 node: tree.root_node(),
724 text,
725 };
726 Pattern::parse(language(), &NODE_TYPES, s.to_string())
727 .match_node(Env::default(), candidate)
728 .map(|m| m.env)
729 }
730
731 fn matches<'tree>(
732 s: &str,
733 tree: &'tree Tree,
734 text: &'tree str,
735 ) -> Option<HashMap<Metavar, HashSet<&'tree str>>> {
736 match_one(s, tree, text).map(|m| {
737 m.0.into_iter()
738 .map(|(k, v)| {
739 (
740 k,
741 v.into_iter()
742 .map(|n| n.utf8_text(text.as_bytes()).unwrap())
743 .collect(),
744 )
745 })
746 .collect()
747 })
748 }
749
750 fn match_all<'tree>(s: &str, tree: &'tree Tree, text: &'tree str) -> Vec<Match<'tree>> {
751 Pattern::parse(language(), &NODE_TYPES, s.to_string()).matches(
752 tree,
753 text,
754 &Env::default(),
755 false,
756 None,
757 )
758 }
759
760 fn all_matches<'tree>(
761 s: &str,
762 tree: &'tree Tree,
763 text: &'tree str,
764 ) -> Vec<HashMap<Metavar, HashSet<&'tree str>>> {
765 match_all(s, tree, text)
766 .into_iter()
767 .map(|m| {
768 m.env
769 .0
770 .into_iter()
771 .map(|(k, v)| {
772 (
773 k,
774 v.iter()
775 .map(|n| n.utf8_text(text.as_bytes()).unwrap())
776 .collect(),
777 )
778 })
779 .collect()
780 })
781 .collect()
782 }
783
784 fn replace(text: &str, find: &str, replace: &str) -> String {
785 let tree = super::parse(language(), text);
786 let candidate = Candidate {
787 node: tree.root_node(),
788 text,
789 };
790 let m = Pattern::parse(language(), &NODE_TYPES, find.to_string())
791 .match_node(Env::default(), candidate)
792 .unwrap();
793 let p = Pattern::parse(language(), &NODE_TYPES, replace.to_string());
794 p.replace(vec![m], text.to_string())
795 }
796
797 #[test]
798 fn test_pattern_parse() {
799 assert_eq!(HashMap::new(), pat("").exprs);
800 assert_eq!(
801 HashMap::from([(
802 Pattern::meta(0),
803 FindExpr::Metavar(Metavar("x".to_string()))
804 )]),
805 pat("$x").exprs
806 );
807 assert_eq!(
808 HashMap::from([(Pattern::meta(0), FindExpr::Anonymous)]),
809 pat("$_").exprs
810 );
811 assert_eq!(
812 HashMap::from([(Pattern::meta(0), FindExpr::Lua(LuaCode("true".to_string())))]),
813 pat("${{true}}").exprs
814 );
815 assert_eq!(
816 HashMap::from([
817 (Pattern::meta(0), FindExpr::Lua(LuaCode("true".to_string()))),
818 (
819 Pattern::meta(1),
820 FindExpr::Lua(LuaCode("false".to_string()))
821 )
822 ]),
823 pat("${{true}} == ${{false}}").exprs
824 );
825 assert_eq!(
826 HashMap::from([(
827 Pattern::meta(0),
828 FindExpr::Lua(LuaCode(r#"match("$x")"#.to_string()))
829 )]),
830 pat(r#"${{match("$x")}}"#).exprs
831 );
832 assert_eq!(
833 HashMap::from([
834 (
835 Pattern::meta(0),
836 FindExpr::Metavar(Metavar("x".to_string()))
837 ),
838 (
839 Pattern::meta(1),
840 FindExpr::Metavar(Metavar("y".to_string()))
841 )
842 ]),
843 pat("let $x = $y;").exprs
844 );
845 assert_eq!(
846 HashMap::from([(
847 Pattern::meta(0),
848 FindExpr::Lua(LuaCode(r#"not match("${{false}}")"#.to_string()))
849 ),]),
850 pat(r#"${{not match("${{false}}")}}"#).exprs
851 );
852 }
853
854 #[test]
855 fn test_matches() {
856 let tree = super::parse(language(), "");
857 assert_eq!(Some(Env::default()), match_one("$_", &tree, ""));
858
859 let text = "a";
860 let tree = super::parse(language(), text);
861 assert_eq!(Some(HashMap::new()), matches("$_", &tree, text));
862
863 let text = "let a = b;";
864 let tree = super::parse(language(), text);
865 assert_eq!(
866 Some(HashMap::from([
867 (Metavar("x".to_string()), HashSet::from(["a"])),
868 (Metavar("y".to_string()), HashSet::from(["b"]))
869 ])),
870 matches("let $x = $y;", &tree, text)
871 );
872
873 let text = "let a = a;";
874 let tree = super::parse(language(), text);
875 assert_eq!(
876 Some(HashMap::from([(
877 Metavar("x".to_string()),
878 HashSet::from(["a"])
879 )])),
880 matches("let $x = $x;", &tree, text)
881 );
882
883 let text = "let a = b;";
884 let tree = super::parse(language(), text);
885 assert_eq!(None, matches("let $x = $x;", &tree, text));
886
887 let text = "0 + 1";
888 let tree = super::parse(language(), text);
889 assert_eq!(
890 Some(HashMap::from([
891 (Metavar("x".to_string()), HashSet::from(["0"])),
892 (Metavar("y".to_string()), HashSet::from(["1"]))
893 ])),
894 matches("$x + $y", &tree, text)
895 );
896
897 let text = "if a == () { }";
910 let tree = super::parse(language(), text);
911 assert_eq!(
912 Some(HashMap::from([
913 (Metavar("x".to_string()), HashSet::from(["a"])),
914 (Metavar("y".to_string()), HashSet::from(["()"]))
915 ])),
916 matches("if $x == $y {}", &tree, text)
917 );
918
919 let text = "{ a; b; c + d; }";
930 let tree = super::parse(language(), text);
931 assert_eq!(
932 Some(HashMap::from([
933 (Metavar("x".to_string()), HashSet::from(["a"])),
934 (Metavar("y".to_string()), HashSet::from(["c"])),
935 (Metavar("z".to_string()), HashSet::from(["d"]))
936 ])),
937 matches("{ $x; $y + $z; }", &tree, text)
938 );
939
940 let text = "if a == () { let b = c; }";
941 let tree = super::parse(language(), text);
942 assert_eq!(
943 Some(HashMap::from([
944 (Metavar("x".to_string()), HashSet::from(["a"])),
945 (Metavar("y".to_string()), HashSet::from(["()"]))
946 ])),
947 matches("if $x == $y { $.. }", &tree, text)
948 );
949 }
950
951 #[test]
952 fn test_ellipses() {
953 let text = "{ a; b; c; }";
958 let tree = super::parse(language(), text);
959 assert_eq!(
960 Some(HashMap::from([(
961 Metavar("x".to_string()),
962 HashSet::from(["a"])
963 ),])),
964 matches("{ $x; $.. }", &tree, text)
965 );
966
967 let text = "{ a; b; c; }";
968 let tree = super::parse(language(), text);
969 assert_eq!(
970 Some(HashMap::from([(
971 Metavar("x".to_string()),
972 HashSet::from(["b"])
973 ),])),
974 matches("{ $..; $x; $.. }", &tree, text)
975 );
976 }
977
978 #[test]
979 fn test_all_matches() {
980 let text = "if a == () { let b = c; }";
981 let tree = super::parse(language(), text);
982 assert_eq!(
983 Vec::from([HashMap::from([
984 (Metavar("x".to_string()), HashSet::from(["b"])),
985 (Metavar("y".to_string()), HashSet::from(["c"]))
986 ])]),
987 all_matches("let $x = $y;", &tree, text)
988 );
989 }
990
991 #[test]
992 fn test_replace() {
993 assert_eq!("a", replace("let a = b;", "let $x = $y;", "$x"));
994 assert_eq!(
995 "let b = a;",
996 replace("let a = b;", "let $x = $y;", "let $y = $x;")
997 );
998 assert_eq!("", replace("let a = b;", "let $x = $y;", r#"${{""}}"#));
999 }
1000}