grit_pattern_matcher/pattern/
regex.rs1use 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 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 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 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}