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 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 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 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 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 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}