nickel_lang_core/term/pattern/
mod.rs

1//! Pattern matching and destructuring of Nickel values.
2use std::collections::{hash_map::Entry, HashMap};
3
4use super::{
5    record::{Field, RecordData},
6    NickelString, Number, RichTerm, TypeAnnotation,
7};
8
9use crate::{
10    error::EvalError, identifier::LocIdent, impl_display_from_pretty, parser::error::ParseError,
11    position::TermPos,
12};
13
14pub mod bindings;
15pub mod compile;
16
17#[derive(Debug, PartialEq, Clone)]
18pub enum PatternData {
19    /// A wildcard pattern, matching any value. As opposed to any, this pattern doesn't bind any
20    /// variable.
21    Wildcard,
22    /// A simple pattern consisting of an identifier. Match anything and bind the result to the
23    /// corresponding identifier.
24    Any(LocIdent),
25    /// A record pattern as in `{ a = { b, c } }`
26    Record(RecordPattern),
27    /// An array pattern as in `[a, b, c]`
28    Array(ArrayPattern),
29    /// An enum pattern as in `'Foo x` or `'Foo`
30    Enum(EnumPattern),
31    /// A constant pattern as in `42` or `true`.
32    Constant(ConstantPattern),
33    /// A sequence of alternative patterns as in `'Foo _ or 'Bar _ or 'Baz _`.
34    Or(OrPattern),
35}
36
37/// A generic pattern, that can appear in a match expression (not yet implemented) or in a
38/// destructuring let-binding.
39#[derive(Debug, PartialEq, Clone)]
40pub struct Pattern {
41    /// The content of this pattern
42    pub data: PatternData,
43    /// A potential alias for this pattern, capturing the whole matched value. In the source
44    /// language, an alias is introduced by `x @ <pattern>`, where `x` is an arbitrary identifier.
45    pub alias: Option<LocIdent>,
46    /// The position of the pattern in the source.
47    pub pos: TermPos,
48}
49
50/// An enum pattern, including both an enum tag and an enum variant.
51#[derive(Debug, PartialEq, Clone)]
52pub struct EnumPattern {
53    pub tag: LocIdent,
54    pub pattern: Option<Box<Pattern>>,
55    pub pos: TermPos,
56}
57
58/// A field pattern inside a record pattern. Every field can be annotated with a type, contracts or
59/// with a default value.
60#[derive(Debug, PartialEq, Clone)]
61pub struct FieldPattern {
62    /// The name of the matched field. For example, in `{..., foo = {bar, baz}, ...}`, the matched
63    /// identifier is `foo`.
64    pub matched_id: LocIdent,
65    /// Type and contract annotations of this field.
66    pub annotation: TypeAnnotation,
67    /// Potentital default value, set with the `? value` syntax.
68    pub default: Option<RichTerm>,
69    /// The pattern on the right-hand side of the `=`. A pattern like `{foo, bar}`, without the `=`
70    /// sign, is parsed as `{foo=foo, bar=bar}`. In this case, `pattern.data` will be
71    /// [PatternData::Any].
72    pub pattern: Pattern,
73    pub pos: TermPos,
74}
75
76/// The last match in a data structure pattern. This can either be a normal match, or an ellipsis
77/// which can capture the rest of the data structure. The type parameter `P` is the type of the
78/// pattern of the data structure: currently, ellipsis matches are only supported for record, but
79/// we'll probably support them for arrays as well.
80///
81/// This enum is mostly used during parsing.
82///
83/// # Example
84///
85/// - In `{foo={}, bar}`, the last match is an normal match.
86/// - In `{foo={}, bar, ..}`, the last match is a non-capturing ellipsis.
87/// - In `{foo={}, bar, ..rest}`, the last match is a capturing ellipsis.
88#[derive(Debug, PartialEq, Clone)]
89pub enum LastPattern<P> {
90    /// The last field is a normal match. In this case the pattern is "closed" so every record
91    /// fields should be matched.
92    Normal(Box<P>),
93    /// The pattern is "open" `, ..}`. Optionally you can bind a record containing the remaining
94    /// fields to an `Identifier` using the syntax `, ..y}`.
95    Ellipsis(Option<LocIdent>),
96}
97
98/// A record pattern.
99#[derive(Debug, PartialEq, Clone)]
100pub struct RecordPattern {
101    /// The patterns for each field in the record.
102    pub patterns: Vec<FieldPattern>,
103    /// The tail of the pattern, indicating if the pattern is open, i.e. if it ended with an
104    /// ellipsis, capturing the rest or not.
105    pub tail: TailPattern,
106    pub pos: TermPos,
107}
108
109/// An array pattern.
110#[derive(Debug, PartialEq, Clone)]
111pub struct ArrayPattern {
112    /// The patterns of the elements of the array.
113    pub patterns: Vec<Pattern>,
114    /// The tail of the pattern, indicating if the pattern is open, i.e. if it ended with an
115    /// ellipsis, capturing the rest or not.
116    pub tail: TailPattern,
117    pub pos: TermPos,
118}
119
120impl ArrayPattern {
121    /// Check if this record contract is open, meaning that it accepts additional fields to be
122    /// present, whether the rest is captured or not.
123    pub fn is_open(&self) -> bool {
124        self.tail.is_open()
125    }
126}
127
128/// A constant pattern, matching a constant value.
129#[derive(Debug, PartialEq, Clone)]
130pub struct ConstantPattern {
131    pub data: ConstantPatternData,
132    pub pos: TermPos,
133}
134
135#[derive(Debug, PartialEq, Clone)]
136pub enum ConstantPatternData {
137    Bool(bool),
138    Number(Number),
139    String(NickelString),
140    Null,
141}
142
143#[derive(Debug, PartialEq, Clone)]
144pub struct OrPattern {
145    pub patterns: Vec<Pattern>,
146    pub pos: TermPos,
147}
148
149/// The tail of a data structure pattern (record or array) which might capture the rest of said
150/// data structure.
151#[derive(Debug, PartialEq, Clone)]
152pub enum TailPattern {
153    /// The pattern is closed, i.e. it doesn't allow more fields. For example, `{foo, bar}`.
154    Empty,
155    /// The pattern ends with an ellipsis, making it open. For example, `{foo, bar, ..}`.
156    Open,
157    /// The pattern ends with an ellispis and a variable capturing the rest of the record. For
158    /// example, `{foo, bar, ..rest}`.
159    Capture(LocIdent),
160}
161
162impl TailPattern {
163    /// Check if this tail pattern makes the enclosing data structure pattern open, meaning that it
164    /// accepts additional fields or elements to be present, whether the rest is captured or not.
165    pub fn is_open(&self) -> bool {
166        matches!(self, TailPattern::Open | TailPattern::Capture(_))
167    }
168}
169
170impl RecordPattern {
171    /// Check the matches for duplication, and raise an error if any occurs.
172    ///
173    /// Note that for backwards-compatibility reasons this function _only_
174    /// checks top-level matches. In Nickel 1.0, this code panicked:
175    ///
176    /// ```text
177    /// let f = fun { x, x, .. } => x in f { x = 1 }
178    /// ```
179    ///
180    /// However this "works", even though the binding to `y` is duplicated.
181    ///
182    /// ```text
183    /// let f =
184    ///   fun { x = { y }, z = { y }, .. } => y
185    /// in f { x = { y = 1 }, z = { y = 2 } }
186    /// # evaluates to 1
187    /// ```
188    ///
189    /// This function aims to raise errors in the first case, but maintain the
190    /// behaviour in the second case.
191    pub fn check_dup(&self) -> Result<(), ParseError> {
192        let mut bindings = HashMap::new();
193
194        for pat in self.patterns.iter() {
195            let binding = pat.matched_id;
196            let label = binding.label().to_owned();
197            match bindings.entry(label) {
198                Entry::Occupied(occupied_entry) => {
199                    return Err(ParseError::DuplicateIdentInRecordPattern {
200                        ident: binding,
201                        prev_ident: occupied_entry.remove_entry().1,
202                    })
203                }
204                Entry::Vacant(vacant_entry) => {
205                    vacant_entry.insert(binding);
206                }
207            }
208        }
209
210        Ok(())
211    }
212
213    /// Check if this record contract is open, meaning that it accepts additional fields to be
214    /// present, whether the rest is captured or not.
215    pub fn is_open(&self) -> bool {
216        self.tail.is_open()
217    }
218}
219
220impl_display_from_pretty!(PatternData);
221impl_display_from_pretty!(Pattern);
222impl_display_from_pretty!(ConstantPatternData);
223impl_display_from_pretty!(ConstantPattern);
224impl_display_from_pretty!(RecordPattern);
225impl_display_from_pretty!(EnumPattern);
226impl_display_from_pretty!(ArrayPattern);