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);