grit_pattern_matcher/pattern/
variable.rs

1use super::{
2    container::{PatternOrResolved, PatternOrResolvedMut},
3    patterns::{Matcher, PatternName},
4    resolved_pattern::ResolvedPattern,
5    State,
6};
7use crate::{
8    binding::Binding,
9    constants::{
10        ABSOLUTE_PATH_INDEX, DEFAULT_FILE_NAME, FILENAME_INDEX, GLOBAL_VARS_SCOPE_INDEX,
11        PROGRAM_INDEX,
12    },
13    context::{ExecContext, QueryContext},
14};
15use core::fmt::Debug;
16use grit_util::{
17    constants::GRIT_METAVARIABLE_PREFIX,
18    error::{GritPatternError, GritResult},
19    AnalysisLogs, ByteRange, Language,
20};
21use std::{
22    borrow::Cow,
23    collections::BTreeSet,
24    sync::{Arc, RwLock},
25};
26
27#[derive(Debug, Clone, Copy)]
28pub(crate) struct VariableScope {
29    pub(crate) scope: u16,
30    pub(crate) index: u16,
31}
32
33impl VariableScope {
34    pub fn new(scope: usize, index: usize) -> Self {
35        Self {
36            scope: scope as u16,
37            index: index as u16,
38        }
39    }
40}
41
42#[derive(Debug, Clone)]
43struct DynamicVariableInternal {
44    name: String,
45    /// Track the last scope we registed this variable in
46    /// This is mainly just used for cases where we don't have state available
47    last_scope: Arc<RwLock<Option<VariableScope>>>,
48}
49
50#[derive(Debug, Clone)]
51enum VariableInternal {
52    /// Static variable, which is bound at compile time (ex. global variables).
53    /// These are slightly more efficient, and follow the traditional approach in Grit.
54    /// However, they require more direct control over scopes and indexes.
55    Static(VariableScope),
56    /// Dynamic variables are lazy, so we just need to register them by name.
57    /// They will then automatically be bound to the first scope that attempts to use them.
58    /// This should be avoided where possible, since it means names will likely overwrite each other across scopes.
59    Dynamic(DynamicVariableInternal),
60}
61
62#[derive(Clone, Debug)]
63pub struct Variable {
64    internal: VariableInternal,
65}
66
67/// VariableSource is used to track the origin of a variable
68/// It can come from
69#[derive(Debug, Clone)]
70pub enum VariableSource {
71    /// Compiled from a pattern
72    Compiled {
73        name: String,
74        file: String,
75        locations: BTreeSet<ByteRange>,
76    },
77    /// Global variable, which is not defined anywhere
78    Global { name: String },
79}
80
81impl VariableSource {
82    pub fn new(name: String, file: String) -> Self {
83        Self::Compiled {
84            name,
85            file,
86            locations: BTreeSet::new(),
87        }
88    }
89
90    pub fn new_global(name: String) -> Self {
91        Self::Global { name }
92    }
93
94    /// Register a location in a GritQL file where a variable is referenced
95    pub fn register_location(&mut self, location: ByteRange) -> GritResult<()> {
96        match self {
97            VariableSource::Compiled { locations, .. } => {
98                locations.insert(location);
99                Ok(())
100            }
101            VariableSource::Global { .. } => Ok(()),
102        }
103    }
104
105    /// Get locations where the variable is referenced from the main pattern file
106    pub fn get_main_locations(&self) -> Vec<ByteRange> {
107        if let VariableSource::Compiled {
108            locations, file, ..
109        } = self
110        {
111            if file != DEFAULT_FILE_NAME {
112                return vec![];
113            }
114            locations.iter().cloned().collect()
115        } else {
116            vec![]
117        }
118    }
119
120    /// Get the registered variable name
121    pub fn name(&self) -> &str {
122        match self {
123            VariableSource::Compiled { name, .. } => name,
124            VariableSource::Global { name } => name,
125        }
126    }
127}
128
129struct VariableMirror<'a, Q: QueryContext> {
130    scope: u16,
131    index: u16,
132    binding: Q::Binding<'a>,
133}
134
135impl Variable {
136    /// Create a variable, where we already know the scope and index it will be bound to
137    pub fn new(scope: usize, index: usize) -> Self {
138        Self {
139            internal: VariableInternal::Static(VariableScope {
140                scope: scope as u16,
141                index: index as u16,
142            }),
143        }
144    }
145
146    /// Create a dynamic variable, which will be bound to the first scope that uses it
147    ///
148    /// Warning: this is not stable or tested yet. This implementation is still incomplete.
149    pub fn new_dynamic(name: &str) -> Self {
150        Self {
151            internal: VariableInternal::Dynamic(DynamicVariableInternal {
152                name: name.to_string(),
153                last_scope: Arc::new(RwLock::new(None)),
154            }),
155        }
156    }
157
158    fn try_internal(&self) -> GritResult<VariableScope> {
159        match &self.internal {
160            VariableInternal::Static(scope) => Ok(*scope),
161            VariableInternal::Dynamic(lock) => {
162                if let Ok(reader) = lock.last_scope.try_read() {
163                    if let Some(scope) = *reader {
164                        return Ok(scope);
165                    }
166                }
167                Err(GritPatternError::new_matcher(format!(
168                    "variable {} not initialized",
169                    lock.name
170                )))
171            }
172        }
173    }
174
175    fn get_internal<Q: QueryContext>(&self, state: &mut State<'_, Q>) -> GritResult<VariableScope> {
176        match &self.internal {
177            VariableInternal::Static(internal) => Ok(*internal),
178            VariableInternal::Dynamic(lock) => {
179                let scope = state.register_var(&lock.name);
180                if let Ok(mut writer) = lock.last_scope.write() {
181                    *writer = Some(scope);
182                }
183                Ok(scope)
184            }
185        }
186    }
187
188    /// Try to get the scope of the variable, if it has been bound to a scope.
189    /// If the variable has not been bound to a scope, return an error.
190    /// When possible, prefer to use `get_scope()` instead, which will initialize the variable's scope if it is not already bound.
191    pub fn try_scope(&self) -> GritResult<u16> {
192        Ok(self.try_internal()?.scope)
193    }
194
195    /// Try to get the index of the variable, if it has been bound to an index.
196    /// If the variable has not been bound to an index, return an error.
197    /// When possible, prefer to use `get_index()` instead, which will initialize the variable's index if it is not already bound.
198    pub fn try_index(&self) -> GritResult<u16> {
199        Ok(self.try_internal()?.index)
200    }
201
202    /// Get the scope of the variable, initializing it if it is not already bound.
203    pub fn get_scope<Q: QueryContext>(&self, state: &mut State<'_, Q>) -> GritResult<u16> {
204        Ok(self.get_internal(state)?.scope)
205    }
206
207    /// Get the index of the variable, initializing it if it is not already bound.
208    pub fn get_index<Q: QueryContext>(&self, state: &mut State<'_, Q>) -> GritResult<u16> {
209        Ok(self.get_internal(state)?.index)
210    }
211
212    pub fn get_pattern_or_resolved<'a, 'b, Q: QueryContext>(
213        &self,
214        state: &'b State<'a, Q>,
215    ) -> GritResult<Option<PatternOrResolved<'a, 'b, Q>>> {
216        let v = state.trace_var(self);
217        let content = &state.bindings[v.try_scope().unwrap() as usize]
218            .last()
219            .unwrap()[v.try_index().unwrap() as usize];
220        if let Some(pattern) = content.pattern {
221            Ok(Some(PatternOrResolved::Pattern(pattern)))
222        } else if let Some(resolved) = &content.value {
223            Ok(Some(PatternOrResolved::Resolved(resolved)))
224        } else {
225            Ok(None)
226        }
227    }
228    pub fn get_pattern_or_resolved_mut<'a, 'b, Q: QueryContext>(
229        &self,
230        state: &'b mut State<'a, Q>,
231    ) -> GritResult<Option<PatternOrResolvedMut<'a, 'b, Q>>> {
232        let v = state.trace_var_mut(self);
233        let content = &mut state.bindings[v.try_scope().unwrap() as usize]
234            .last_mut()
235            .unwrap()[v.try_index().unwrap() as usize];
236        if let Some(pattern) = content.pattern {
237            Ok(Some(PatternOrResolvedMut::Pattern(pattern)))
238        } else if let Some(resolved) = &mut content.value {
239            Ok(Some(PatternOrResolvedMut::Resolved(resolved)))
240        } else {
241            Ok(None)
242        }
243    }
244
245    pub fn file_name() -> Self {
246        Self::new(GLOBAL_VARS_SCOPE_INDEX.into(), FILENAME_INDEX)
247    }
248
249    pub fn is_file_name(&self) -> bool {
250        let VariableInternal::Static(scope) = &self.internal else {
251            return false;
252        };
253        scope.scope == GLOBAL_VARS_SCOPE_INDEX && scope.index as usize == FILENAME_INDEX
254    }
255
256    pub fn is_program(&self) -> bool {
257        let VariableInternal::Static(scope) = &self.internal else {
258            return false;
259        };
260        scope.scope == GLOBAL_VARS_SCOPE_INDEX && scope.index as usize == PROGRAM_INDEX
261    }
262
263    /// We auto-insert a $match variable during auto-wrap, which we can usually treat as being usable in the program body
264    pub fn is_probably_match(&self) -> bool {
265        let Ok(scope) = self.try_scope() else {
266            return false;
267        };
268        scope == 1
269    }
270
271    pub fn text<'a, Q: QueryContext>(
272        &self,
273        state: &State<'a, Q>,
274        lang: &Q::Language<'a>,
275    ) -> GritResult<Cow<'a, str>> {
276        state.bindings[self.try_scope().unwrap() as usize]
277            .last()
278            .unwrap()[self.try_index().unwrap() as usize]
279            .text(state, lang)
280    }
281
282    fn execute_resolved<'a, Q: QueryContext>(
283        &self,
284        resolved_pattern: &Q::ResolvedPattern<'a>,
285        state: &mut State<'a, Q>,
286        language: &Q::Language<'a>,
287    ) -> GritResult<Option<bool>> {
288        let mut variable_mirrors: Vec<VariableMirror<Q>> = Vec::new();
289        {
290            let scope = self.get_scope(state)?;
291            let index = self.get_index(state)?;
292            let variable_content = state
293                .bindings
294                .get_mut(scope as usize)
295                .unwrap()
296                .last_mut()
297                .unwrap()
298                .get_mut(index as usize);
299            let Some(variable_content) = variable_content else {
300                return Ok(None);
301            };
302            let variable_content = &mut **(variable_content);
303            let value = &mut variable_content.value;
304
305            if let Some(var_side_resolve_pattern) = value {
306                if let (Some(var_binding), Some(binding)) = (
307                    var_side_resolve_pattern.get_last_binding(),
308                    resolved_pattern.get_last_binding(),
309                ) {
310                    if !var_binding.is_equivalent_to(binding, language) {
311                        return Ok(Some(false));
312                    }
313                    let value_history = &mut variable_content.value_history;
314                    var_side_resolve_pattern.push_binding(binding.clone())?;
315
316                    // feels wrong maybe we should push ResolvedPattern::Binding(bindings)?
317                    value_history.push(ResolvedPattern::from_binding(binding.clone()));
318                    variable_mirrors.extend(variable_content.mirrors.iter().map(|mirror| {
319                        VariableMirror {
320                            scope: mirror.try_scope().unwrap(),
321                            index: mirror.try_index().unwrap(),
322                            binding: binding.clone(),
323                        }
324                    }));
325                } else {
326                    return Ok(Some(
327                        resolved_pattern.text(&state.files, language)?
328                            == var_side_resolve_pattern.text(&state.files, language)?,
329                    ));
330                }
331            } else {
332                return Ok(None);
333            };
334        }
335        for mirror in variable_mirrors {
336            let mirror_content = &mut **(state
337                .bindings
338                .get_mut(mirror.scope as usize)
339                .unwrap()
340                .last_mut()
341                .unwrap()
342                .get_mut(mirror.index as usize)
343                .unwrap());
344            if let Some(value) = &mut mirror_content.value {
345                if value.is_binding() {
346                    value.push_binding(mirror.binding.clone())?;
347                    let value_history = &mut mirror_content.value_history;
348                    value_history.push(ResolvedPattern::from_binding(mirror.binding));
349                }
350            }
351        }
352        Ok(Some(true))
353    }
354}
355
356impl PatternName for Variable {
357    fn name(&self) -> &'static str {
358        "VARIABLE"
359    }
360}
361
362impl<Q: QueryContext> Matcher<Q> for Variable {
363    fn execute<'a>(
364        &'a self,
365        resolved_pattern: &Q::ResolvedPattern<'a>,
366        state: &mut State<'a, Q>,
367        context: &'a Q::ExecContext<'a>,
368        logs: &mut AnalysisLogs,
369    ) -> GritResult<bool> {
370        if let Some(res) = self.execute_resolved(resolved_pattern, state, context.language())? {
371            return Ok(res);
372        }
373        // we only check the assignment if the variable is not bound already
374        // otherwise, we assume that the assignment is correct
375
376        // we do this convoluted check to avoid double-borrowing of state
377        // via the variable_content variable
378        let scope = self.get_scope(state)?;
379        let index = self.get_index(state)?;
380
381        let variable_content = state
382            .bindings
383            .get_mut(scope as usize)
384            .unwrap()
385            .last_mut()
386            .unwrap()
387            .get_mut(index as usize);
388        let Some(variable_content) = variable_content else {
389            logs.add_warning(
390                None,
391                format!("Variable unexpectedly not found in scope {:?}", scope),
392            );
393            return Ok(false);
394        };
395
396        let variable_content = &mut **(variable_content);
397        if let Some(pattern) = variable_content.pattern {
398            if !pattern.execute(resolved_pattern, state, context, logs)? {
399                return Ok(false);
400            }
401        }
402        let variable_content = &mut **(state
403            .bindings
404            .get_mut(scope as usize)
405            .unwrap()
406            .last_mut()
407            .unwrap()
408            .get_mut(index as usize)
409            .unwrap());
410        variable_content.value = Some(resolved_pattern.clone());
411        variable_content
412            .value_history
413            .push(resolved_pattern.clone());
414        Ok(true)
415    }
416}
417
418pub fn get_absolute_file_name<'a, Q: QueryContext>(
419    state: &State<'a, Q>,
420    lang: &Q::Language<'a>,
421) -> GritResult<String> {
422    let file = state.bindings[GLOBAL_VARS_SCOPE_INDEX as usize]
423        .last()
424        .unwrap()[ABSOLUTE_PATH_INDEX]
425        .value
426        .as_ref();
427    let file = file
428        .map(|f| f.text(&state.files, lang).map(|s| s.to_string()))
429        .unwrap_or(Ok("No File Found".to_string()))?;
430    Ok(file)
431}
432
433pub fn get_file_name<'a, Q: QueryContext>(
434    state: &State<'a, Q>,
435    lang: &Q::Language<'a>,
436) -> GritResult<String> {
437    let file = state.bindings[GLOBAL_VARS_SCOPE_INDEX as usize]
438        .last()
439        .unwrap()[FILENAME_INDEX]
440        .value
441        .as_ref();
442    let file = file
443        .map(|f| f.text(&state.files, lang).map(|s| s.to_string()))
444        .unwrap_or(Ok("No File Found".to_string()))?;
445    Ok(file)
446}
447
448pub fn is_reserved_metavariable(var: &str, lang: Option<&impl Language>) -> bool {
449    let name = var.trim_start_matches(GRIT_METAVARIABLE_PREFIX);
450    let name = if let Some(lang) = lang {
451        name.trim_start_matches(lang.metavariable_prefix_substitute())
452    } else {
453        name
454    };
455    name == "match"
456        || name == "filename"
457        || name == "absolute_filename"
458        || name == "new_files"
459        || name == "program"
460        || name.starts_with("grit_")
461}