grit_pattern_matcher/pattern/
resolved_pattern.rs

1use super::{
2    accessor::Accessor,
3    dynamic_snippet::{DynamicPattern, DynamicSnippet},
4    list_index::ListIndex,
5    patterns::Pattern,
6    state::{FilePtr, FileRegistry, State},
7};
8use crate::{binding::Binding, constant::Constant, context::QueryContext, effects::Effect};
9use grit_util::{error::GritResult, AnalysisLogs, ByteRange, CodeRange, Range};
10use itertools::Itertools;
11use std::{
12    borrow::Cow,
13    collections::{BTreeMap, HashMap},
14    fmt::Debug,
15    path::Path,
16};
17
18pub trait ResolvedPattern<'a, Q: QueryContext>: Clone + Debug + PartialEq {
19    fn from_binding(binding: Q::Binding<'a>) -> Self;
20
21    fn from_constant(constant: Constant) -> Self;
22
23    fn from_constant_binding(constant: &'a Constant) -> Self {
24        Self::from_binding(Binding::from_constant(constant))
25    }
26
27    fn from_file_pointer(file: FilePtr) -> Self;
28
29    fn from_files(files: Self) -> Self;
30
31    fn from_list_parts(parts: impl Iterator<Item = Self>) -> Self;
32
33    fn from_node_binding(node: Q::Node<'a>) -> Self {
34        Self::from_binding(Binding::from_node(node))
35    }
36
37    fn from_path_binding(path: &'a Path) -> Self {
38        Self::from_binding(Binding::from_path(path))
39    }
40
41    fn from_range_binding(range: ByteRange, src: &'a str) -> Self {
42        Self::from_binding(Binding::from_range(range, src))
43    }
44
45    fn from_string(string: String) -> Self;
46
47    fn from_resolved_snippet(snippet: ResolvedSnippet<'a, Q>) -> Self;
48
49    fn from_dynamic_snippet(
50        snippet: &'a DynamicSnippet,
51        state: &mut State<'a, Q>,
52        context: &'a Q::ExecContext<'a>,
53        logs: &mut AnalysisLogs,
54    ) -> GritResult<Self>;
55
56    fn from_dynamic_pattern(
57        pattern: &'a DynamicPattern<Q>,
58        state: &mut State<'a, Q>,
59        context: &'a Q::ExecContext<'a>,
60        logs: &mut AnalysisLogs,
61    ) -> GritResult<Self>;
62
63    fn from_accessor(
64        accessor: &'a Accessor<Q>,
65        state: &mut State<'a, Q>,
66        context: &'a Q::ExecContext<'a>,
67        logs: &mut AnalysisLogs,
68    ) -> GritResult<Self>;
69
70    fn from_list_index(
71        index: &'a ListIndex<Q>,
72        state: &mut State<'a, Q>,
73        context: &'a Q::ExecContext<'a>,
74        logs: &mut AnalysisLogs,
75    ) -> GritResult<Self>;
76
77    fn from_pattern(
78        pattern: &'a Pattern<Q>,
79        state: &mut State<'a, Q>,
80        context: &'a Q::ExecContext<'a>,
81        logs: &mut AnalysisLogs,
82    ) -> GritResult<Self>;
83
84    fn from_patterns(
85        patterns: &'a [Option<Pattern<Q>>],
86        state: &mut State<'a, Q>,
87        context: &'a Q::ExecContext<'a>,
88        logs: &mut AnalysisLogs,
89    ) -> GritResult<Vec<Option<Self>>> {
90        patterns
91            .iter()
92            .map(|p| match p {
93                Some(pattern) => Ok(Some(Self::from_pattern(pattern, state, context, logs)?)),
94                None => Ok(None),
95            })
96            .collect()
97    }
98
99    fn undefined() -> Self {
100        Self::from_constant(Constant::Undefined)
101    }
102
103    fn extend(
104        &mut self,
105        with: Q::ResolvedPattern<'a>,
106        effects: &mut Vec<Effect<'a, Q>>,
107        language: &Q::Language<'a>,
108    ) -> GritResult<()>;
109
110    fn float(&self, state: &FileRegistry<'a, Q>, language: &Q::Language<'a>) -> GritResult<f64>;
111
112    fn get_bindings(&self) -> Option<impl Iterator<Item = Q::Binding<'a>>>;
113
114    fn get_file(&self) -> Option<&Q::File<'a>>;
115
116    fn get_file_pointers(&self) -> Option<Vec<FilePtr>>;
117
118    fn get_files(&self) -> Option<&Self>;
119
120    fn get_last_binding(&self) -> Option<&Q::Binding<'a>>;
121
122    fn get_list_item_at(&self, index: isize) -> Option<&Self>;
123
124    fn get_list_item_at_mut(&mut self, index: isize) -> Option<&mut Self>;
125
126    fn get_list_items(&self) -> Option<impl Iterator<Item = &Self>>;
127
128    fn get_list_binding_items(&self) -> Option<impl Iterator<Item = Self> + Clone>;
129
130    fn get_map(&self) -> Option<&BTreeMap<String, Self>>;
131
132    fn get_map_mut(&mut self) -> Option<&mut BTreeMap<String, Self>>;
133
134    fn get_snippets(&self) -> Option<impl Iterator<Item = ResolvedSnippet<'a, Q>>>;
135
136    fn is_binding(&self) -> bool;
137
138    fn is_list(&self) -> bool;
139
140    fn is_truthy(&self, state: &mut State<'a, Q>, language: &Q::Language<'a>) -> GritResult<bool>;
141
142    fn linearized_text(
143        &self,
144        language: &Q::Language<'a>,
145        effects: &[Effect<'a, Q>],
146        files: &FileRegistry<'a, Q>,
147        memo: &mut HashMap<CodeRange, Option<String>>,
148        should_pad_snippet: bool,
149        logs: &mut AnalysisLogs,
150    ) -> GritResult<Cow<'a, str>>;
151
152    fn matches_undefined(&self) -> bool;
153
154    fn matches_false_or_undefined(&self) -> bool;
155
156    fn normalize_insert(
157        &mut self,
158        binding: &Q::Binding<'a>,
159        is_first: bool,
160        language: &Q::Language<'a>,
161    ) -> GritResult<()>;
162
163    fn position(&self, language: &Q::Language<'a>) -> Option<Range>;
164
165    fn push_binding(&mut self, binding: Q::Binding<'a>) -> GritResult<()>;
166
167    fn set_list_item_at_mut(&mut self, index: isize, value: Self) -> GritResult<bool>;
168
169    // should we instead return an Option?
170    fn text(
171        &self,
172        state: &FileRegistry<'a, Q>,
173        language: &Q::Language<'a>,
174    ) -> GritResult<Cow<'a, str>>;
175}
176
177#[derive(Debug, Clone, PartialEq)]
178pub enum ResolvedSnippet<'a, Q: QueryContext> {
179    // if refering to a dynamic_snippet, we can use the &str variant,
180    // but if referring to the result of a BuiltIn, we need the
181    // String variant
182    Text(Cow<'a, str>),
183    Binding(Q::Binding<'a>),
184    LazyFn(Box<LazyBuiltIn<'a, Q>>),
185}
186
187impl<'a, Q: QueryContext> ResolvedSnippet<'a, Q> {
188    pub fn from_binding(binding: Q::Binding<'a>) -> ResolvedSnippet<Q> {
189        Self::Binding(binding)
190    }
191
192    // if the snippet is text consisting of newlines followed by spaces, returns the number of spaces.
193    // might not be general enough, but should be good for a first pass
194    pub fn padding(
195        &self,
196        state: &FileRegistry<'a, Q>,
197        language: &Q::Language<'a>,
198    ) -> GritResult<usize> {
199        let text = self.text(state, language)?;
200        let len = text.len();
201        let trim_len = text.trim_end_matches(' ').len();
202        Ok(len - trim_len)
203    }
204
205    pub fn text(
206        &self,
207        state: &FileRegistry<'a, Q>,
208        language: &Q::Language<'a>,
209    ) -> GritResult<Cow<'a, str>> {
210        match self {
211            ResolvedSnippet::Text(text) => Ok(text.clone()),
212            ResolvedSnippet::Binding(binding) => {
213                // we are now taking the unmodified source code, and replacing the binding with the snippet
214                // we will want to apply effects next
215                binding.text(language).map(|c| c.into_owned().into())
216            }
217            ResolvedSnippet::LazyFn(lazy) => lazy.text(state, language),
218        }
219    }
220
221    pub fn linearized_text(
222        &self,
223        language: &Q::Language<'a>,
224        effects: &[Effect<'a, Q>],
225        files: &FileRegistry<'a, Q>,
226        memo: &mut HashMap<CodeRange, Option<String>>,
227        distributed_indent: Option<usize>,
228        logs: &mut AnalysisLogs,
229    ) -> GritResult<Cow<str>> {
230        let res = match self {
231            Self::Text(text) => {
232                if let Some(indent) = distributed_indent {
233                    Ok(pad_text(text, indent).into())
234                } else {
235                    Ok(text.clone())
236                }
237            }
238            Self::Binding(binding) => {
239                // we are now taking the unmodified source code, and replacing the binding with the snippet
240                // we will want to apply effects next
241                binding.linearized_text(language, effects, files, memo, distributed_indent, logs)
242            }
243            Self::LazyFn(lazy) => {
244                lazy.linearized_text(language, effects, files, memo, distributed_indent, logs)
245            }
246        };
247        res
248    }
249
250    pub fn is_truthy(
251        &self,
252        state: &mut State<'a, Q>,
253        language: &Q::Language<'a>,
254    ) -> GritResult<bool> {
255        let truthiness = match self {
256            Self::Binding(b) => b.is_truthy(),
257            Self::Text(t) => !t.is_empty(),
258            Self::LazyFn(t) => !t.text(&state.files, language)?.is_empty(),
259        };
260        Ok(truthiness)
261    }
262}
263
264#[derive(Debug, Clone, PartialEq)]
265pub enum LazyBuiltIn<'a, Q: QueryContext> {
266    Join(JoinFn<'a, Q>),
267}
268
269impl<'a, Q: QueryContext> LazyBuiltIn<'a, Q> {
270    fn linearized_text(
271        &self,
272        language: &Q::Language<'a>,
273        effects: &[Effect<'a, Q>],
274        files: &FileRegistry<'a, Q>,
275        memo: &mut HashMap<CodeRange, Option<String>>,
276        distributed_indent: Option<usize>,
277        logs: &mut AnalysisLogs,
278    ) -> GritResult<Cow<str>> {
279        match self {
280            LazyBuiltIn::Join(join) => {
281                join.linearized_text(language, effects, files, memo, distributed_indent, logs)
282            }
283        }
284    }
285
286    pub fn text(
287        &self,
288        state: &FileRegistry<'a, Q>,
289        language: &Q::Language<'a>,
290    ) -> GritResult<Cow<'a, str>> {
291        match self {
292            LazyBuiltIn::Join(join) => join.text(state, language),
293        }
294    }
295}
296
297#[derive(Debug, Clone, PartialEq)]
298pub struct JoinFn<'a, Q: QueryContext> {
299    pub list: Vec<Q::ResolvedPattern<'a>>,
300    separator: String,
301}
302
303impl<'a, Q: QueryContext> JoinFn<'a, Q> {
304    pub fn from_patterns(
305        patterns: impl Iterator<Item = Q::ResolvedPattern<'a>>,
306        separator: String,
307    ) -> Self {
308        Self {
309            list: patterns.collect(),
310            separator,
311        }
312    }
313
314    fn linearized_text(
315        &self,
316        language: &Q::Language<'a>,
317        effects: &[Effect<'a, Q>],
318        files: &FileRegistry<'a, Q>,
319        memo: &mut HashMap<CodeRange, Option<String>>,
320        distributed_indent: Option<usize>,
321        logs: &mut AnalysisLogs,
322    ) -> GritResult<Cow<str>> {
323        let res = self
324            .list
325            .iter()
326            .map(|pattern| {
327                pattern.linearized_text(
328                    language,
329                    effects,
330                    files,
331                    memo,
332                    distributed_indent.is_some(),
333                    logs,
334                )
335            })
336            .collect::<GritResult<Vec<_>>>()?
337            .join(&self.separator);
338        if let Some(padding) = distributed_indent {
339            Ok(pad_text(&res, padding).into())
340        } else {
341            Ok(res.into())
342        }
343    }
344
345    fn text(
346        &self,
347        state: &FileRegistry<'a, Q>,
348        language: &Q::Language<'a>,
349    ) -> GritResult<Cow<'a, str>> {
350        Ok(self
351            .list
352            .iter()
353            .map(|pattern| pattern.text(state, language))
354            .collect::<GritResult<Vec<_>>>()?
355            .join(&self.separator)
356            .into())
357    }
358}
359
360fn pad_text(text: &str, padding: usize) -> String {
361    if text.trim().is_empty() {
362        text.to_owned()
363    } else {
364        let mut res = if text.starts_with('\n') {
365            "\n".to_owned()
366        } else {
367            String::new()
368        };
369        res.push_str(&text.lines().join(&format!("\n{}", " ".repeat(padding))));
370        if text.ends_with('\n') {
371            res.push('\n')
372        };
373        res
374    }
375}
376
377pub trait File<'a, Q: QueryContext> {
378    fn name(&self, files: &FileRegistry<'a, Q>) -> Q::ResolvedPattern<'a>;
379
380    fn absolute_path(
381        &self,
382        files: &FileRegistry<'a, Q>,
383        language: &Q::Language<'a>,
384    ) -> GritResult<Q::ResolvedPattern<'a>>;
385
386    fn body(&self, files: &FileRegistry<'a, Q>) -> Q::ResolvedPattern<'a>;
387
388    fn binding(&self, files: &FileRegistry<'a, Q>) -> Q::ResolvedPattern<'a>;
389}
390
391#[derive(Debug, Clone, PartialEq)]
392pub struct ResolvedFile<'a, Q: QueryContext> {
393    pub name: Q::ResolvedPattern<'a>,
394    pub body: Q::ResolvedPattern<'a>,
395}