mogglo/
pattern.rs

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    // TODO: Disallow anything after ellipses
138    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                // ${{code}}
153                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                // $_
165                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                // $..
173                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                // $x
181                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        // NOTE[expression-hack]: tree-sitter will try to parse the pattern
214        // as a whole program, which can fail if e.g., the language is Rust
215        // and the pattern is `$x + $y` (which is not valid at the top level
216        // of a program). When parsing the pattern fails, we try wrapping the
217        // pattern in braces or ending it with a semicolon, and then unwrapping
218        // it into an expression when transforming it into a goal.
219        //
220        // Weggli appears to work similarly by default.
221        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        // Get rid of top-level "program" node
235        if root.child_count() == 1 {
236            root = root.child(0).unwrap();
237        }
238        // See NOTE[expression-hack]
239        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            // ex:
287            // candidate: { x; }
288            // goal: { }
289            if candidate_count != 0 {
290                return None;
291            }
292            // ex:
293            // candidate: x
294            // goal: x
295            if Self::match_leaf_node(goal, candidate) {
296                return Some(Match {
297                    env,
298                    root: candidate.node,
299                });
300            }
301            // ex:
302            // candidate: x
303            // goal: y
304            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                            // Might be an ellipsis
336                            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            // Match goal with any child
354            for i in 0..candidate.node.child_count() {
355                // TODO: rm clone
356                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                        // TODO: debug assert all goals are matched
391                        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                // TODO: Handle errors
412                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                        // TODO: Option to export metavariables
432                        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                        // TODO: Option to export metavariables
480                        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                // TODO: Maybe check for collisions
505                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        // TODO: Avoid allocation
529        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    // TODO: Only named children
568    // TODO: Minimum match size
569    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()); // guess
583            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        // See NOTE[expression-hack]
622        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        // See NOTE[expression-hack] for why this isn't just self.text
638        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        /// This is an example for using doc comment attributes
714        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        // TODO:
898        // let text = "let a = a;";
899        // let tree = super::parse(language(), text);
900        // assert_eq!(Some(HashMap::new()), matches("$/a/", &tree, text));
901        // assert_eq!(Some(HashMap::new()), matches("$/./", &tree, text));
902
903        // TODO:
904        // let text = "let foo = 0 == 1;";
905        // let text = "0 == 1;";
906        // let tree = super::parse(language(), text);
907        // assert_eq!(Some(HashMap::new()), matches("$_ == $_", &tree, text));
908
909        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; }";
920        // let tree = super::parse(language(), text);
921        // assert_eq!(
922        //     Some(HashMap::from([(
923        //         Metavar("x".to_string()),
924        //         HashSet::from(["a"])
925        //     )])),
926        //     matches("{ $x; }", &tree, text)
927        // );
928
929        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; }";
954        // let tree = super::parse(language(), text);
955        // assert_eq!(Some(HashMap::new()), matches("{ $.. }", &tree, text));
956
957        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}