grit_pattern_matcher/pattern/
variable.rs1use 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 last_scope: Arc<RwLock<Option<VariableScope>>>,
48}
49
50#[derive(Debug, Clone)]
51enum VariableInternal {
52 Static(VariableScope),
56 Dynamic(DynamicVariableInternal),
60}
61
62#[derive(Clone, Debug)]
63pub struct Variable {
64 internal: VariableInternal,
65}
66
67#[derive(Debug, Clone)]
70pub enum VariableSource {
71 Compiled {
73 name: String,
74 file: String,
75 locations: BTreeSet<ByteRange>,
76 },
77 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 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 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 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 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 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 pub fn try_scope(&self) -> GritResult<u16> {
192 Ok(self.try_internal()?.scope)
193 }
194
195 pub fn try_index(&self) -> GritResult<u16> {
199 Ok(self.try_internal()?.index)
200 }
201
202 pub fn get_scope<Q: QueryContext>(&self, state: &mut State<'_, Q>) -> GritResult<u16> {
204 Ok(self.get_internal(state)?.scope)
205 }
206
207 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 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 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 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}