Skip to main content

nu_protocol/engine/
pattern_match.rs

1use crate::{
2    Span, Value, VarId,
3    ast::{Expr, MatchPattern, Pattern, RangeInclusion},
4};
5
6pub trait Matcher {
7    fn match_value(&self, value: &Value, matches: &mut Vec<(VarId, Value)>) -> bool;
8}
9
10impl Matcher for MatchPattern {
11    fn match_value(&self, value: &Value, matches: &mut Vec<(VarId, Value)>) -> bool {
12        self.pattern.match_value(value, matches)
13    }
14}
15
16impl Matcher for Pattern {
17    fn match_value(&self, value: &Value, matches: &mut Vec<(VarId, Value)>) -> bool {
18        match self {
19            Pattern::Garbage => false,
20            Pattern::IgnoreValue => true,
21            Pattern::IgnoreRest => false, // `..` and `..$foo` only match in specific contexts
22            Pattern::Rest(_) => false,    // so we return false here and handle them elsewhere
23            Pattern::Record(field_patterns) => match value {
24                Value::Record { val, .. } => {
25                    'top: for field_pattern in field_patterns {
26                        for (col, val) in &**val {
27                            if col == &field_pattern.0 {
28                                // We have found the field
29                                let result = field_pattern.1.match_value(val, matches);
30                                if !result {
31                                    return false;
32                                } else {
33                                    continue 'top;
34                                }
35                            }
36                        }
37                        return false;
38                    }
39                    true
40                }
41                _ => false,
42            },
43            Pattern::Variable(var_id) => {
44                // TODO: FIXME: This needs the span of this variable
45                matches.push((*var_id, value.clone()));
46                true
47            }
48            Pattern::List(items) => match &value {
49                Value::List { vals, .. } => {
50                    if items.len() > vals.len() {
51                        // We only allow this is to have a rest pattern in the n+1 position
52                        if items.len() == (vals.len() + 1) {
53                            match &items[vals.len()].pattern {
54                                Pattern::IgnoreRest => {}
55                                Pattern::Rest(var_id) => matches.push((
56                                    *var_id,
57                                    Value::list(Vec::new(), items[vals.len()].span),
58                                )),
59                                _ => {
60                                    // There is a pattern which can't skip missing values, so we fail
61                                    return false;
62                                }
63                            }
64                        } else {
65                            // There are patterns that can't be matches, so we fail
66                            return false;
67                        }
68                    }
69                    for (val_idx, val) in vals.iter().enumerate() {
70                        // We require that the pattern and the value have the same number of items, or the pattern does not match
71                        // The only exception is if the pattern includes a `..` pattern
72                        if let Some(pattern) = items.get(val_idx) {
73                            match &pattern.pattern {
74                                Pattern::IgnoreRest => {
75                                    break;
76                                }
77                                Pattern::Rest(var_id) => {
78                                    let rest_vals = vals[val_idx..].to_vec();
79                                    matches.push((*var_id, Value::list(rest_vals, pattern.span)));
80                                    break;
81                                }
82                                _ => {
83                                    if !pattern.match_value(val, matches) {
84                                        return false;
85                                    }
86                                }
87                            }
88                        } else {
89                            return false;
90                        }
91                    }
92
93                    true
94                }
95                _ => false,
96            },
97            Pattern::Expression(pattern_value) => {
98                // TODO: Fill this out with the rest of them
99                match &pattern_value.expr {
100                    Expr::Nothing => {
101                        matches!(value, Value::Nothing { .. })
102                    }
103                    Expr::Int(x) => {
104                        if let Value::Int { val, .. } = &value {
105                            x == val
106                        } else {
107                            false
108                        }
109                    }
110                    Expr::Float(x) => {
111                        if let Value::Float { val, .. } = &value {
112                            x == val
113                        } else {
114                            false
115                        }
116                    }
117                    Expr::Binary(x) => {
118                        if let Value::Binary { val, .. } = &value {
119                            x.as_slice() == val.as_slice()
120                        } else {
121                            false
122                        }
123                    }
124                    Expr::Bool(x) => {
125                        if let Value::Bool { val, .. } = &value {
126                            x == val
127                        } else {
128                            false
129                        }
130                    }
131                    Expr::String(x) | Expr::RawString(x) => {
132                        if let Value::String { val, .. } = &value {
133                            x == val
134                        } else {
135                            false
136                        }
137                    }
138                    Expr::DateTime(x) => {
139                        if let Value::Date { val, .. } = &value {
140                            x == val
141                        } else {
142                            false
143                        }
144                    }
145                    Expr::ValueWithUnit(val) => {
146                        let span = val.unit.span;
147
148                        if let Expr::Int(size) = val.expr.expr {
149                            match &val.unit.item.build_value(size, span) {
150                                Ok(v) => v == value,
151                                _ => false,
152                            }
153                        } else {
154                            false
155                        }
156                    }
157                    Expr::Range(range) => {
158                        // TODO: Add support for floats
159
160                        let start = if let Some(start) = &range.from {
161                            match &start.expr {
162                                Expr::Int(start) => *start,
163                                _ => return false,
164                            }
165                        } else {
166                            0
167                        };
168
169                        let end = if let Some(end) = &range.to {
170                            match &end.expr {
171                                Expr::Int(end) => *end,
172                                _ => return false,
173                            }
174                        } else {
175                            i64::MAX
176                        };
177
178                        let step = if let Some(step) = &range.next {
179                            match &step.expr {
180                                Expr::Int(step) => *step - start,
181                                _ => return false,
182                            }
183                        } else if end < start {
184                            -1
185                        } else {
186                            1
187                        };
188
189                        let (start, end) = if end < start {
190                            (end, start)
191                        } else {
192                            (start, end)
193                        };
194
195                        if let Value::Int { val, .. } = &value {
196                            if matches!(range.operator.inclusion, RangeInclusion::RightExclusive) {
197                                *val >= start && *val < end && ((*val - start) % step) == 0
198                            } else {
199                                *val >= start && *val <= end && ((*val - start) % step) == 0
200                            }
201                        } else {
202                            false
203                        }
204                    }
205                    _ => false,
206                }
207            }
208            Pattern::Value(pattern_value) => match pattern_value {
209                // Ranges match by containment (same as `Pattern::Expression` + `Expr::Range`).
210                Value::Range { val, .. } => val.contains(value),
211                _ => value == pattern_value,
212            },
213            Pattern::Or(patterns) => {
214                let mut result = false;
215
216                for pattern in patterns {
217                    let mut local_matches = vec![];
218                    if !result {
219                        if pattern.match_value(value, &mut local_matches) {
220                            // TODO: do we need to replace previous variables that defaulted to nothing?
221                            matches.append(&mut local_matches);
222                            result = true;
223                        } else {
224                            // Create variables that don't match and assign them to null
225                            let vars = pattern.variables();
226                            for var in &vars {
227                                let mut found = false;
228                                for match_ in matches.iter() {
229                                    if match_.0 == *var {
230                                        found = true;
231                                    }
232                                }
233
234                                if !found {
235                                    // FIXME: don't use Span::unknown()
236                                    matches.push((*var, Value::nothing(Span::unknown())))
237                                }
238                            }
239                        }
240                    } else {
241                        // We already have a match, so ignore the remaining match variables
242                        // And assign them to null
243                        let vars = pattern.variables();
244                        for var in &vars {
245                            let mut found = false;
246                            for match_ in matches.iter() {
247                                if match_.0 == *var {
248                                    found = true;
249                                }
250                            }
251
252                            if !found {
253                                // FIXME: don't use Span::unknown()
254                                matches.push((*var, Value::nothing(Span::unknown())))
255                            }
256                        }
257                    }
258                }
259                result
260            }
261        }
262    }
263}