grit_pattern_matcher/pattern/
list_index.rs

1use super::{
2    accessor::execute_resolved_with_binding,
3    container::{Container, PatternOrResolved, PatternOrResolvedMut},
4    list::List,
5    patterns::{Matcher, Pattern, PatternName},
6    resolved_pattern::ResolvedPattern,
7    state::State,
8};
9use crate::{
10    binding::Binding,
11    context::{ExecContext, QueryContext},
12};
13use grit_util::{
14    error::{GritPatternError, GritResult},
15    AnalysisLogs,
16};
17
18#[derive(Debug, Clone)]
19pub enum ListOrContainer<Q: QueryContext> {
20    Container(Container<Q>),
21    List(List<Q>),
22}
23
24#[derive(Debug, Clone)]
25pub enum ContainerOrIndex<Q: QueryContext> {
26    Container(Container<Q>),
27    Index(isize),
28}
29
30#[derive(Debug, Clone)]
31pub struct ListIndex<Q: QueryContext> {
32    pub list: ListOrContainer<Q>,
33    pub index: ContainerOrIndex<Q>,
34}
35
36impl<Q: QueryContext> ListIndex<Q> {
37    fn get_index<'a>(&'a self, state: &State<'a, Q>, lang: &Q::Language<'a>) -> GritResult<isize> {
38        match &self.index {
39            ContainerOrIndex::Container(c) => {
40                let raw_index = c.get_pattern_or_resolved(state, lang)?.ok_or_else(|| {
41                    GritPatternError::new(format!("list index must be resolvable: {:?}", self))
42                })?;
43                let index = match raw_index {
44                    PatternOrResolved::Resolved(r) => r.text(&state.files, lang)?,
45                    PatternOrResolved::ResolvedBinding(r) => r.text(&state.files, lang)?,
46                    PatternOrResolved::Pattern(_) => {
47                        return Err(GritPatternError::new("list index must be resolved"))
48                    }
49                };
50                let int_index = index.parse::<isize>().map_err(|_| {
51                    GritPatternError::new(format!(
52                        "list index must be an integer but got {:?}",
53                        index
54                    ))
55                })?;
56                Ok(int_index)
57            }
58            ContainerOrIndex::Index(i) => Ok(*i),
59        }
60    }
61
62    pub fn get<'a, 'b>(
63        &'a self,
64        state: &'b State<'a, Q>,
65        lang: &Q::Language<'a>,
66    ) -> GritResult<Option<PatternOrResolved<'a, 'b, Q>>> {
67        let index = self.get_index(state, lang)?;
68        match &self.list {
69            ListOrContainer::Container(c) => match c.get_pattern_or_resolved(state, lang)? {
70                None => Ok(None),
71                Some(PatternOrResolved::Pattern(Pattern::List(l))) => {
72                    Ok(l.get(index).map(PatternOrResolved::Pattern))
73                }
74                Some(PatternOrResolved::Resolved(resolved)) => {
75                    if resolved.is_list() {
76                        Ok(resolved
77                            .get_list_item_at(index)
78                            .map(PatternOrResolved::Resolved))
79                    } else if let Some(mut items) =
80                        resolved.get_last_binding().and_then(Binding::list_items)
81                    {
82                        let len = items.clone().count();
83                        return Ok(to_unsigned(index, len)
84                            .and_then(|index| items.nth(index))
85                            .map(|n| {
86                                PatternOrResolved::ResolvedBinding(
87                                    ResolvedPattern::from_node_binding(n),
88                                )
89                            }));
90                    } else {
91                        return Err(GritPatternError::new(
92                            "left side of a listIndex must be a list",
93                        ));
94                    }
95                }
96                Some(s) => Err(GritPatternError::new(format!(
97                    "left side of a listIndex must be a list but got {:?}",
98                    s
99                ))),
100            },
101            ListOrContainer::List(l) => Ok(l.get(index).map(PatternOrResolved::Pattern)),
102        }
103    }
104
105    pub fn get_mut<'a, 'b>(
106        &'a self,
107        state: &'b mut State<'a, Q>,
108        lang: &Q::Language<'a>,
109    ) -> GritResult<Option<PatternOrResolvedMut<'a, 'b, Q>>> {
110        let index = self.get_index(state, lang)?;
111        match &self.list {
112            ListOrContainer::Container(c) => match c.get_pattern_or_resolved_mut(state, lang)? {
113                None => Ok(None),
114                Some(PatternOrResolvedMut::Pattern(Pattern::List(l))) => {
115                    Ok(l.get(index).map(PatternOrResolvedMut::Pattern))
116                }
117                Some(PatternOrResolvedMut::Resolved(resolved)) => {
118                    if let Some(mut items) = resolved.get_list_binding_items() {
119                        let len = items.clone().count();
120                        return Ok(to_unsigned(index, len)
121                            .and_then(|index| items.nth(index))
122                            .map(|_| PatternOrResolvedMut::_ResolvedBinding));
123                    }
124
125                    if resolved.is_list() {
126                        Ok(resolved
127                            .get_list_item_at_mut(index)
128                            .map(PatternOrResolvedMut::Resolved))
129                    } else {
130                        Err(GritPatternError::new(
131                            "left side of a listIndex must be a list",
132                        ))
133                    }
134                }
135                Some(s) => Err(GritPatternError::new(format!(
136                    "left side of a listIndex must be a list but got {:?}",
137                    s
138                ))),
139            },
140            ListOrContainer::List(l) => Ok(l.get(index).map(PatternOrResolvedMut::Pattern)),
141        }
142    }
143
144    pub fn set_resolved<'a>(
145        &'a self,
146        state: &mut State<'a, Q>,
147        lang: &Q::Language<'a>,
148        value: Q::ResolvedPattern<'a>,
149    ) -> GritResult<bool> {
150        let index = self.get_index(state, lang)?;
151        match &self.list {
152            ListOrContainer::Container(c) => match c.get_pattern_or_resolved_mut(state, lang)? {
153                None => Ok(false),
154                Some(PatternOrResolvedMut::Resolved(resolved)) => {
155                    resolved.set_list_item_at_mut(index, value)
156                }
157                Some(_) => Err(GritPatternError::new(
158                    "accessor can only mutate a resolved list",
159                )),
160            },
161            ListOrContainer::List(_) => Err(GritPatternError::new("cannot mutate a list literal")),
162        }
163    }
164}
165
166pub fn to_unsigned(index: isize, len: usize) -> Option<usize> {
167    if index >= 0 {
168        Some(index as usize)
169    } else if len as isize + index < 0 {
170        None
171    } else {
172        Some((len as isize + index) as usize)
173    }
174}
175
176impl<Q: QueryContext> PatternName for ListIndex<Q> {
177    fn name(&self) -> &'static str {
178        "LIST_INDEX"
179    }
180}
181
182impl<Q: QueryContext> Matcher<Q> for ListIndex<Q> {
183    fn execute<'a>(
184        &'a self,
185        binding: &Q::ResolvedPattern<'a>,
186        state: &mut State<'a, Q>,
187        context: &'a Q::ExecContext<'a>,
188        logs: &mut AnalysisLogs,
189    ) -> GritResult<bool> {
190        match self.get(state, context.language())? {
191            Some(PatternOrResolved::Resolved(r)) => {
192                execute_resolved_with_binding(r, binding, state, context.language())
193            }
194            Some(PatternOrResolved::ResolvedBinding(r)) => {
195                execute_resolved_with_binding(&r, binding, state, context.language())
196            }
197            Some(PatternOrResolved::Pattern(p)) => p.execute(binding, state, context, logs),
198            None => Ok(binding.matches_false_or_undefined()),
199        }
200    }
201}