nickel_lang_core/bytecode/ast/pattern/
mod.rs

1//! Pattern matching and destructuring of Nickel values.
2use std::collections::{hash_map::Entry, HashMap};
3
4use super::{Annotation, Ast, Number};
5
6use crate::{
7    identifier::LocIdent, impl_display_from_bytecode_pretty, parser::error::ParseError,
8    position::TermPos, traverse::*,
9};
10
11pub mod bindings;
12
13#[derive(Debug, PartialEq, Eq, Clone)]
14pub enum PatternData<'ast> {
15    /// A wildcard pattern, matching any value. As opposed to any, this pattern doesn't bind any
16    /// variable.
17    Wildcard,
18    /// A simple pattern consisting of an identifier. Match anything and bind the result to the
19    /// corresponding identifier.
20    Any(LocIdent),
21    /// A record pattern as in `{ a = { b, c } }`
22    Record(&'ast RecordPattern<'ast>),
23    /// An array pattern as in `[a, b, c]`
24    Array(&'ast ArrayPattern<'ast>),
25    /// An enum pattern as in `'Foo x` or `'Foo`
26    Enum(&'ast EnumPattern<'ast>),
27    /// A constant pattern as in `42` or `true`.
28    Constant(&'ast ConstantPattern<'ast>),
29    /// A sequence of alternative patterns as in `'Foo _ or 'Bar _ or 'Baz _`.
30    Or(&'ast OrPattern<'ast>),
31}
32
33/// A generic pattern, that can appear in a match expression (not yet implemented) or in a
34/// destructuring let-binding.
35#[derive(Debug, PartialEq, Eq, Clone)]
36pub struct Pattern<'ast> {
37    /// The content of this pattern
38    pub data: PatternData<'ast>,
39    /// A potential alias for this pattern, capturing the whole matched value. In the source
40    /// language, an alias is introduced by `x @ <pattern>`, where `x` is an arbitrary identifier.
41    pub alias: Option<LocIdent>,
42    /// The position of the pattern in the source.
43    pub pos: TermPos,
44}
45
46/// An enum pattern, including both an enum tag and an enum variant.
47#[derive(Debug, PartialEq, Eq, Clone)]
48pub struct EnumPattern<'ast> {
49    pub tag: LocIdent,
50    pub pattern: Option<Pattern<'ast>>,
51    pub pos: TermPos,
52}
53
54/// A field pattern inside a record pattern. Every field can be annotated with a type, contracts or
55/// with a default value.
56#[derive(Debug, PartialEq, Eq, Clone)]
57pub struct FieldPattern<'ast> {
58    /// The name of the matched field. For example, in `{..., foo = {bar, baz}, ...}`, the matched
59    /// identifier is `foo`.
60    pub matched_id: LocIdent,
61    /// Type and contract annotations of this field.
62    pub annotation: Annotation<'ast>,
63    /// Potential default value, set with the `? value` syntax.
64    pub default: Option<Ast<'ast>>,
65    /// The pattern on the right-hand side of the `=`. A pattern like `{foo, bar}`, without the `=`
66    /// sign, is parsed as `{foo=foo, bar=bar}`. In this case, `pattern.data` will be
67    /// [PatternData::Any].
68    pub pattern: Pattern<'ast>,
69    pub pos: TermPos,
70}
71
72/// A record pattern.
73#[derive(Debug, PartialEq, Eq, Clone)]
74pub struct RecordPattern<'ast> {
75    /// The patterns for each field in the record.
76    pub patterns: &'ast [FieldPattern<'ast>],
77    /// The tail of the pattern, indicating if the pattern is open, i.e. if it ended with an
78    /// ellipsis, capturing the rest or not.
79    pub tail: TailPattern,
80    pub pos: TermPos,
81}
82
83/// An array pattern.
84#[derive(Debug, PartialEq, Eq, Clone)]
85pub struct ArrayPattern<'ast> {
86    /// The patterns of the elements of the array.
87    pub patterns: &'ast [Pattern<'ast>],
88    /// The tail of the pattern, indicating if the pattern is open, i.e. if it ended with an
89    /// ellipsis, capturing the rest or not.
90    pub tail: TailPattern,
91    pub pos: TermPos,
92}
93
94impl ArrayPattern<'_> {
95    /// Check if this record contract is open, meaning that it accepts additional fields to be
96    /// present, whether the rest is captured or not.
97    pub fn is_open(&self) -> bool {
98        self.tail.is_open()
99    }
100}
101
102/// A constant pattern, matching a constant value.
103#[derive(Debug, PartialEq, Eq, Clone)]
104pub struct ConstantPattern<'ast> {
105    pub data: ConstantPatternData<'ast>,
106    pub pos: TermPos,
107}
108
109#[derive(Debug, PartialEq, Eq, Clone)]
110pub enum ConstantPatternData<'ast> {
111    Bool(bool),
112    Number(&'ast Number),
113    String(&'ast str),
114    Null,
115}
116
117#[derive(Debug, PartialEq, Eq, Clone)]
118pub struct OrPattern<'ast> {
119    pub patterns: &'ast [Pattern<'ast>],
120    pub pos: TermPos,
121}
122
123/// The tail of a data structure pattern (record or array) which might capture the rest of said
124/// data structure.
125#[derive(Debug, PartialEq, Eq, Clone)]
126pub enum TailPattern {
127    /// The pattern is closed, i.e. it doesn't allow more fields. For example, `{foo, bar}`.
128    Empty,
129    /// The pattern ends with an ellipsis, making it open. For example, `{foo, bar, ..}`.
130    Open,
131    /// The pattern ends with an ellispis and a variable capturing the rest of the record. For
132    /// example, `{foo, bar, ..rest}`.
133    Capture(LocIdent),
134}
135
136impl Pattern<'_> {
137    /// Creates an `Any` pattern with the corresponding capture name.
138    pub fn any(id: LocIdent) -> Self {
139        let pos = id.pos;
140
141        Pattern {
142            data: PatternData::Any(id),
143            alias: None,
144            pos,
145        }
146    }
147
148    /// Returns `Some(id)` if this pattern is an [PatternData::Any] pattern, `None` otherwise.
149    pub fn try_as_any(&self) -> Option<LocIdent> {
150        if let PatternData::Any(id) = &self.data {
151            Some(*id)
152        } else {
153            None
154        }
155    }
156}
157
158impl TailPattern {
159    /// Check if this tail pattern makes the enclosing data structure pattern open, meaning that it
160    /// accepts additional fields or elements to be present, whether the rest is captured or not.
161    pub fn is_open(&self) -> bool {
162        matches!(self, TailPattern::Open | TailPattern::Capture(_))
163    }
164}
165
166impl RecordPattern<'_> {
167    /// Check the matches for duplication, and raise an error if any occurs.
168    ///
169    /// Note that for backwards-compatibility reasons this function _only_
170    /// checks top-level matches. In Nickel 1.0, this code panicked:
171    ///
172    /// ```text
173    /// let f = fun { x, x, .. } => x in f { x = 1 }
174    /// ```
175    ///
176    /// However this "works", even though the binding to `y` is duplicated.
177    ///
178    /// ```text
179    /// let f =
180    ///   fun { x = { y }, z = { y }, .. } => y
181    /// in f { x = { y = 1 }, z = { y = 2 } }
182    /// # evaluates to 1
183    /// ```
184    ///
185    /// This function aims to raise errors in the first case, but maintain the
186    /// behaviour in the second case.
187    pub fn check_dup(&self) -> Result<(), ParseError> {
188        let mut bindings = HashMap::new();
189
190        for pat in self.patterns.iter() {
191            let binding = pat.matched_id;
192            let label = binding.label().to_owned();
193            match bindings.entry(label) {
194                Entry::Occupied(occupied_entry) => {
195                    return Err(ParseError::DuplicateIdentInRecordPattern {
196                        ident: binding,
197                        prev_ident: occupied_entry.remove_entry().1,
198                    })
199                }
200                Entry::Vacant(vacant_entry) => {
201                    vacant_entry.insert(binding);
202                }
203            }
204        }
205
206        Ok(())
207    }
208
209    /// Check if this record contract is open, meaning that it accepts additional fields to be
210    /// present, whether the rest is captured or not.
211    pub fn is_open(&self) -> bool {
212        self.tail.is_open()
213    }
214}
215
216impl<'ast> TraverseAlloc<'ast, Ast<'ast>> for Pattern<'ast> {
217    fn traverse<F, E>(
218        self,
219        alloc: &'ast super::AstAlloc,
220        f: &mut F,
221        order: TraverseOrder,
222    ) -> Result<Self, E>
223    where
224        F: FnMut(Ast<'ast>) -> Result<Ast<'ast>, E>,
225    {
226        match self.data {
227            data @ (PatternData::Wildcard | PatternData::Any(_) | PatternData::Constant(_)) => {
228                Ok(Pattern { data, ..self })
229            }
230            PatternData::Record(record) => {
231                let record = record.clone();
232                let patterns =
233                    traverse_alloc_many(alloc, record.patterns.iter().cloned(), f, order)?;
234
235                Ok(Pattern {
236                    data: PatternData::Record(alloc.alloc(RecordPattern { patterns, ..record })),
237                    ..self
238                })
239            }
240            PatternData::Array(array) => {
241                let array = array.clone();
242                let patterns =
243                    traverse_alloc_many(alloc, array.patterns.iter().cloned(), f, order)?;
244
245                Ok(Pattern {
246                    data: PatternData::Array(alloc.alloc(ArrayPattern { patterns, ..array })),
247                    ..self
248                })
249            }
250            PatternData::Enum(enum_pat) => {
251                let enum_pat = enum_pat.clone();
252                let pattern = enum_pat
253                    .pattern
254                    .map(|p| p.traverse(alloc, f, order))
255                    .transpose()?;
256
257                Ok(Pattern {
258                    data: PatternData::Enum(alloc.alloc(EnumPattern {
259                        pattern,
260                        ..enum_pat
261                    })),
262                    ..self
263                })
264            }
265            PatternData::Or(or) => {
266                let or = or.clone();
267                let patterns = traverse_alloc_many(alloc, or.patterns.iter().cloned(), f, order)?;
268
269                Ok(Pattern {
270                    data: PatternData::Or(alloc.alloc(OrPattern { patterns, ..or })),
271                    ..self
272                })
273            }
274        }
275    }
276
277    fn traverse_ref<S, U>(
278        &'ast self,
279        f: &mut dyn FnMut(&'ast Ast<'ast>, &S) -> TraverseControl<S, U>,
280        scope: &S,
281    ) -> Option<U> {
282        match &self.data {
283            PatternData::Wildcard | PatternData::Any(_) | PatternData::Constant(_) => None,
284            PatternData::Record(record) => record
285                .patterns
286                .iter()
287                .find_map(|field_pat| field_pat.traverse_ref(f, scope)),
288            PatternData::Array(array) => array
289                .patterns
290                .iter()
291                .find_map(|pat| pat.traverse_ref(f, scope)),
292            PatternData::Enum(enum_pat) => enum_pat
293                .pattern
294                .as_ref()
295                .and_then(|pat| pat.traverse_ref(f, scope)),
296            PatternData::Or(or) => or
297                .patterns
298                .iter()
299                .find_map(|pat| pat.traverse_ref(f, scope)),
300        }
301    }
302}
303
304impl<'ast> TraverseAlloc<'ast, Ast<'ast>> for FieldPattern<'ast> {
305    fn traverse<F, E>(
306        self,
307        alloc: &'ast super::AstAlloc,
308        f: &mut F,
309        order: TraverseOrder,
310    ) -> Result<Self, E>
311    where
312        F: FnMut(Ast<'ast>) -> Result<Ast<'ast>, E>,
313    {
314        let annotation = self.annotation.traverse(alloc, f, order)?;
315        let default = self
316            .default
317            .map(|d| d.traverse(alloc, f, order))
318            .transpose()?;
319        let pattern = self.pattern.traverse(alloc, f, order)?;
320
321        Ok(FieldPattern {
322            annotation,
323            default,
324            pattern,
325            ..self
326        })
327    }
328
329    fn traverse_ref<S, U>(
330        &'ast self,
331        f: &mut dyn FnMut(&'ast Ast<'ast>, &S) -> TraverseControl<S, U>,
332        scope: &S,
333    ) -> Option<U> {
334        self.annotation
335            .traverse_ref(f, scope)
336            .or_else(|| self.default.as_ref().and_then(|d| d.traverse_ref(f, scope)))
337            .or_else(|| self.pattern.traverse_ref(f, scope))
338    }
339}
340
341impl_display_from_bytecode_pretty!(PatternData<'_>);
342impl_display_from_bytecode_pretty!(Pattern<'_>);
343impl_display_from_bytecode_pretty!(ConstantPatternData<'_>);
344impl_display_from_bytecode_pretty!(ConstantPattern<'_>);
345impl_display_from_bytecode_pretty!(RecordPattern<'_>);
346impl_display_from_bytecode_pretty!(EnumPattern<'_>);
347impl_display_from_bytecode_pretty!(ArrayPattern<'_>);