1mod list;
10mod record;
11mod term;
12mod tuple;
13
14use bumpalo::collections::Vec as BumpVec;
15use nash_region::{Located, Position, Region};
16use nash_source::Pattern;
17
18use crate::Parser;
19use crate::error;
20
21impl<'a> Parser<'a> {
22 pub fn pattern_term(&mut self) -> Result<&'a Located<Pattern<'a>>, error::Pattern<'a>> {
36 let start = self.get_position();
37
38 self.one_of(
39 error::Pattern::Start,
40 vec![
41 Box::new(|p: &mut Parser<'a>| p.pattern_record(start)),
42 Box::new(|p| p.pattern_tuple(start)),
43 Box::new(|p| p.pattern_list(start)),
44 Box::new(|p| p.pattern_term_help(start)),
45 ],
46 )
47 }
48
49 pub fn pattern_expr(
63 &mut self,
64 ) -> Result<(&'a Located<Pattern<'a>>, Position), error::Pattern<'a>> {
65 let start = self.get_position();
66 let (first_pattern, first_end) = self.pattern_expr_part()?;
67 self.pattern_expr_help(start, first_pattern, first_end)
68 }
69
70 fn pattern_expr_part(
74 &mut self,
75 ) -> Result<(&'a Located<Pattern<'a>>, Position), error::Pattern<'a>> {
76 let start = self.get_position();
77
78 if matches!(self.peek(), Some(b) if b.is_ascii_uppercase()) {
80 let ctor_start = self.pos;
81 self.advance();
82 self.chomp_inner_chars();
83
84 let (region, module, name) = if self.is_dot_upper() || self.is_dot_lower() {
86 self.pattern_ctor_qualified_parts(start, ctor_start)?
87 } else {
88 let name = self.slice_from(ctor_start);
89 let end = self.get_position();
90 (Region::new(start, end), None, name)
91 };
92
93 self.pattern_ctor_with_args(start, region, module, name)
95 } else {
96 let pattern = self.pattern_term()?;
99 let end = pattern.region.end;
100 self.chomp(error::Pattern::Space)?;
101 Ok((pattern, end))
102 }
103 }
104
105 fn pattern_ctor_qualified_parts(
107 &mut self,
108 start: Position,
109 ctor_start: usize,
110 ) -> Result<(Region, Option<&'a str>, &'a str), error::Pattern<'a>> {
111 let (row, col) = self.position();
112
113 loop {
115 if self.is_dot_upper() {
116 self.advance(); self.advance(); self.chomp_inner_chars();
119 } else if self.is_dot_lower() {
120 return Err(error::Pattern::Start(row, col));
122 } else {
123 break;
124 }
125 }
126
127 let full = self.slice_from(ctor_start);
128 let end = self.get_position();
129 let region = Region::new(start, end);
130
131 if let Some(last_dot) = full.rfind('.') {
132 let module = &full[..last_dot];
133 let name = &full[last_dot + 1..];
134 Ok((region, Some(module), name))
135 } else {
136 Ok((region, None, full))
137 }
138 }
139
140 fn pattern_ctor_with_args(
142 &mut self,
143 start: Position,
144 region: Region,
145 module: Option<&'a str>,
146 name: &'a str,
147 ) -> Result<(&'a Located<Pattern<'a>>, Position), error::Pattern<'a>> {
148 let mut end = self.get_position();
149 self.chomp(error::Pattern::Space)?;
150
151 let mut args: BumpVec<'a, &'a Located<Pattern<'a>>> = BumpVec::new_in(self.bump);
152
153 loop {
155 let arg_result = self.one_of_with_fallback(
156 vec![Box::new(|p: &mut Parser<'a>| {
157 let (check_row, check_col) = p.position();
159 p.check_indent(check_row, check_col, error::Pattern::IndentStart)?;
160
161 let arg = p.pattern_term()?;
162 Ok(Some(arg))
163 })],
164 None,
165 )?;
166
167 match arg_result {
168 Some(arg) => {
169 args.push(arg);
170 end = self.get_position();
171 self.chomp(error::Pattern::Space)?;
172 }
173 None => break,
174 }
175 }
176
177 let args_slice = args.into_bump_slice();
178 let pattern = match module {
179 Some(module) => Pattern::CtorQual {
180 region,
181 module,
182 name,
183 args: args_slice,
184 },
185 None => Pattern::Ctor {
186 region,
187 name,
188 args: args_slice,
189 },
190 };
191
192 Ok((self.add_end(start, pattern), end))
193 }
194
195 fn pattern_expr_help(
201 &mut self,
202 start: Position,
203 pattern: &'a Located<Pattern<'a>>,
204 end: Position,
205 ) -> Result<(&'a Located<Pattern<'a>>, Position), error::Pattern<'a>> {
206 let mut patterns: BumpVec<'a, &'a Located<Pattern<'a>>> = BumpVec::new_in(self.bump);
207 let mut current = pattern;
208 let mut current_end = end;
209
210 loop {
211 if self
214 .check_indent(
215 current_end.line,
216 current_end.column,
217 error::Pattern::IndentStart,
218 )
219 .is_err()
220 {
221 let result = self.build_cons_chain(&mut patterns, current);
222 return Ok((result, current_end));
223 }
224
225 let result = self.one_of_with_fallback(
227 vec![
228 Box::new(|p: &mut Parser<'a>| {
230 p.word2(0x3A, 0x3A, error::Pattern::Start)?; p.chomp_and_check_indent(error::Pattern::Space, error::Pattern::IndentStart)?;
232
233 let (next_pattern, next_end) = p.pattern_expr_part()?;
234 Ok(ConsOrAs::Cons(next_pattern, next_end))
235 }),
236 Box::new(|p: &mut Parser<'a>| {
238 let (row, col) = p.position();
240 if !p.remaining().starts_with(b"as")
241 || matches!(p.peek_at(2), Some(b) if b.is_ascii_alphanumeric() || b == b'_')
242 {
243 return Err(error::Pattern::Start(row, col));
244 }
245 p.advance_by(2);
246
247 p.chomp_and_check_indent(error::Pattern::Space, error::Pattern::IndentAlias)?;
248
249 let name_start = p.get_position();
250 let name = p.lower_name(error::Pattern::Alias)?;
251 let name_end = p.get_position();
252 p.chomp(error::Pattern::Space)?;
253
254 let alias = p.add_end(name_start, name);
255 Ok(ConsOrAs::As(alias, name_end))
256 }),
257 ],
258 ConsOrAs::Done,
259 )?;
260
261 match result {
262 ConsOrAs::Cons(next_pattern, next_end) => {
263 patterns.push(current);
264 current = next_pattern;
265 current_end = next_end;
266 }
267 ConsOrAs::As(alias, alias_end) => {
268 let base = self.build_cons_chain(&mut patterns, current);
270 return Ok((
272 self.add_end(
273 start,
274 Pattern::Alias {
275 pattern: base,
276 name: alias,
277 },
278 ),
279 alias_end,
280 ));
281 }
282 ConsOrAs::Done => {
283 let result = self.build_cons_chain(&mut patterns, current);
285 return Ok((result, current_end));
286 }
287 }
288 }
289 }
290
291 fn build_cons_chain(
293 &self,
294 patterns: &mut BumpVec<'a, &'a Located<Pattern<'a>>>,
295 tail: &'a Located<Pattern<'a>>,
296 ) -> &'a Located<Pattern<'a>> {
297 if patterns.is_empty() {
298 tail
299 } else {
300 let mut result = tail;
302 while let Some(head) = patterns.pop() {
303 let region = Region::new(
304 Position::new(head.region.start.line, head.region.start.column),
305 Position::new(result.region.end.line, result.region.end.column),
306 );
307 result = self.alloc(Located::at(region, Pattern::Cons { head, tail: result }));
308 }
309 result
310 }
311 }
312}
313
314enum ConsOrAs<'a> {
316 Cons(&'a Located<Pattern<'a>>, Position),
317 As(&'a Located<&'a str>, Position),
318 Done,
319}
320
321#[cfg(test)]
323macro_rules! assert_pattern_snapshot {
324 ($code:expr) => {{
325 let bump = bumpalo::Bump::new();
326 let src = bump.alloc_str(indoc::indoc!($code));
327 let mut parser = $crate::Parser::new(&bump, src.as_bytes());
328 let (result, _end) = parser.pattern_expr().expect("expected successful parse");
329
330 insta::with_settings!({
331 description => format!("Code:\n\n{}", indoc::indoc!($code)),
332 omit_expression => true,
333 }, {
334 insta::assert_debug_snapshot!(result);
335 });
336 }};
337}
338
339#[cfg(test)]
341macro_rules! assert_pattern_error_snapshot {
342 ($code:expr) => {{
343 let bump = bumpalo::Bump::new();
344 let src = bump.alloc_str(indoc::indoc!($code));
345 let mut parser = $crate::Parser::new(&bump, src.as_bytes());
346 let result = parser.pattern_expr().expect_err("expected parse error");
347
348 insta::with_settings!({
349 description => format!("Code:\n\n{}", indoc::indoc!($code)),
350 omit_expression => true,
351 }, {
352 insta::assert_debug_snapshot!(result);
353 });
354 }};
355}
356
357#[cfg(test)]
358pub(crate) use assert_pattern_error_snapshot;
359#[cfg(test)]
360pub(crate) use assert_pattern_snapshot;
361
362#[cfg(test)]
363mod tests {
364 #[test]
366 fn cons_simple() {
367 assert_pattern_snapshot!("head :: tail");
368 }
369
370 #[test]
371 fn cons_multiple() {
372 assert_pattern_snapshot!("a :: b :: c");
373 }
374
375 #[test]
376 fn cons_with_empty_list() {
377 assert_pattern_snapshot!("head :: []");
378 }
379
380 #[test]
382 fn as_simple() {
383 assert_pattern_snapshot!("x as foo");
384 }
385
386 #[test]
387 fn as_with_tuple() {
388 assert_pattern_snapshot!("(a, b) as pair");
389 }
390
391 #[test]
392 fn as_with_cons() {
393 assert_pattern_snapshot!("head :: tail as list");
394 }
395
396 #[test]
398 fn ctor_with_one_arg() {
399 assert_pattern_snapshot!("Just x");
400 }
401
402 #[test]
403 fn ctor_with_multiple_args() {
404 assert_pattern_snapshot!("Node left value right");
405 }
406
407 #[test]
408 fn ctor_qualified_with_args() {
409 assert_pattern_snapshot!("Maybe.Just x");
410 }
411
412 #[test]
413 fn ctor_nested() {
414 assert_pattern_snapshot!("Just (Just x)");
415 }
416
417 #[test]
419 fn complex_pattern() {
420 assert_pattern_snapshot!("Just x :: rest as list");
421 }
422}