grit_pattern_matcher/pattern/
regex.rs

1use super::{
2    patterns::{Matcher, Pattern, PatternName},
3    resolved_pattern::ResolvedPattern,
4    variable::Variable,
5    State,
6};
7use crate::{
8    binding::Binding,
9    context::{ExecContext, QueryContext},
10};
11use core::fmt::Debug;
12use grit_util::{
13    error::{GritPatternError, GritResult},
14    AnalysisLogs,
15};
16use regex::Regex;
17
18#[derive(Debug, Clone)]
19pub struct RegexPattern<Q: QueryContext> {
20    pub regex: RegexLike<Q>,
21    pub variables: Vec<Variable>,
22}
23
24#[derive(Debug, Clone)]
25pub enum RegexLike<Q: QueryContext> {
26    Regex(String),
27    Pattern(Box<Pattern<Q>>),
28}
29
30impl<Q: QueryContext> RegexPattern<Q> {
31    pub fn new(regex: RegexLike<Q>, variables: Vec<Variable>) -> Self {
32        Self { regex, variables }
33    }
34
35    pub(crate) fn execute_matching<'a>(
36        &'a self,
37        binding: &Q::ResolvedPattern<'a>,
38        state: &mut State<'a, Q>,
39        context: &'a Q::ExecContext<'a>,
40        logs: &mut AnalysisLogs,
41        must_match_entire_string: bool,
42    ) -> GritResult<bool> {
43        let text = binding.text(&state.files, context.language())?;
44        let resolved_regex_text = match &self.regex {
45            RegexLike::Regex(regex) => match must_match_entire_string {
46                true => format!("^{}$", regex),
47                false => regex.to_string(),
48            },
49            RegexLike::Pattern(ref pattern) => {
50                let resolved = Q::ResolvedPattern::from_pattern(pattern, state, context, logs)?;
51                let text = resolved.text(&state.files, context.language())?;
52                match must_match_entire_string {
53                    true => format!("^{}$", text),
54                    false => text.to_string(),
55                }
56            }
57        };
58        let final_regex = Regex::new(&resolved_regex_text)?;
59        let captures = match final_regex.captures(&text) {
60            Some(captures) => captures,
61            None => return Ok(false),
62        };
63
64        // todo: make sure the entire string is matched
65
66        if captures.len() != self.variables.len() + 1 {
67            return Err(GritPatternError::new(format!(
68                "regex pattern matched {} variables, but expected {}",
69                captures.len() - 1,
70                self.variables.len()
71            )));
72        }
73        // why not zip?
74        for (i, variable) in self.variables.iter().enumerate() {
75            let value = captures
76                .get(i + 1)
77                .ok_or_else(|| GritPatternError::new("missing capture group"))?;
78
79            let range = value.range();
80            let value = value.as_str();
81
82            // we should really be making the resolved pattern first, and using
83            // variable execute, instead of reimplementing here.
84            let variable_content = &mut state.bindings[variable.try_scope().unwrap() as usize]
85                .last_mut()
86                .unwrap()[variable.try_index().unwrap() as usize];
87
88            if let Some(previous_value) = &variable_content.value {
89                if previous_value
90                    .text(&state.files, context.language())
91                    .unwrap()
92                    != value
93                {
94                    return Ok(false);
95                } else {
96                    continue;
97                }
98            } else {
99                let res = if let Some((Some(mut byte_range), Some(source))) = binding
100                    .get_last_binding()
101                    .map(|binding| (binding.range(context.language()), binding.source()))
102                {
103                    byte_range.end = byte_range.start + range.end;
104                    byte_range.start += range.start;
105                    Q::ResolvedPattern::from_range_binding(byte_range, source)
106                } else {
107                    Q::ResolvedPattern::from_string(value.to_string())
108                };
109                if let Some(pattern) = variable_content.pattern {
110                    if !pattern.execute(&res, state, context, logs)? {
111                        return Ok(false);
112                    }
113                }
114                let variable_content = &mut state.bindings[variable.try_scope().unwrap() as usize]
115                    .last_mut()
116                    .unwrap()[variable.try_index().unwrap() as usize];
117                variable_content.set_value(res);
118            }
119        }
120
121        Ok(true)
122    }
123}
124
125impl<Q: QueryContext> PatternName for RegexPattern<Q> {
126    fn name(&self) -> &'static str {
127        "REGEX"
128    }
129}
130
131impl<Q: QueryContext> Matcher<Q> for RegexPattern<Q> {
132    fn execute<'a>(
133        &'a self,
134        binding: &Q::ResolvedPattern<'a>,
135        state: &mut State<'a, Q>,
136        context: &'a Q::ExecContext<'a>,
137        logs: &mut AnalysisLogs,
138    ) -> GritResult<bool> {
139        self.execute_matching(binding, state, context, logs, true)
140    }
141}