1use ecow::EcoString;
2
3use super::strings::cook_string_contents;
4use super::{MAX_TUPLE_ARITY, ParseError, Parser};
5use crate::ast::{Annotation, Binding, Literal, Pattern, RestPattern, Span, StructFieldPattern};
6use crate::lex::Token;
7use crate::lex::TokenKind::*;
8use crate::types::Type;
9
10impl<'source> Parser<'source> {
11 pub fn parse_pattern_allowing_or(&mut self) -> Pattern {
12 let start = self.current_token();
13 let first = self.parse_pattern();
14
15 if self.is_not(Pipe) {
16 return first;
17 }
18
19 let mut patterns = vec![first];
20 while self.advance_if(Pipe) {
21 patterns.push(self.parse_pattern());
22 }
23
24 Pattern::Or {
25 patterns,
26 span: self.span_from_tokens(start),
27 }
28 }
29
30 pub fn parse_pattern(&mut self) -> Pattern {
31 if !self.enter_recursion() {
32 let span = self.span_from_token(self.current_token());
33 self.resync_on_error();
34 return Pattern::WildCard { span };
35 }
36 let start = self.current_token();
37 let mut result = self.parse_pattern_inner();
38 if self.advance_if(As) {
39 if !self.is(Identifier) {
40 self.track_error("expected identifier after `as`", "Use `as <name>`");
41 } else if self.current_token().text == "_" {
42 self.track_error(
43 "`_` is not a valid `as` alias",
44 "Use a named binding, or omit `as _`",
45 );
46 self.next();
47 } else {
48 let name: EcoString = self.current_token().text.into();
49 self.next();
50 result = Pattern::AsBinding {
51 pattern: Box::new(result),
52 name,
53 span: self.span_from_tokens(start),
54 };
55 }
56 }
57 self.leave_recursion();
58 result
59 }
60
61 fn parse_pattern_inner(&mut self) -> Pattern {
62 let start = self.current_token();
63
64 if self.current_token().kind.is_keyword() {
65 let keyword = self.current_token().text.to_string();
66 let span = self.span_from_token(start);
67 let help = match keyword.as_str() {
68 "task" => {
69 "Rename this binding. `task` is a keyword reserved for spawning concurrent work"
70 .to_string()
71 }
72 "type" => {
73 "Rename this binding. `type` is a keyword reserved for declaring named types"
74 .to_string()
75 }
76 "select" => {
77 "Rename this binding. `select` is a keyword reserved for waiting on multiple channel operations"
78 .to_string()
79 }
80 "match" => {
81 "Rename this binding. `match` is a keyword reserved for pattern matching"
82 .to_string()
83 }
84 "recover" => {
85 "Rename this binding. `recover` is a keyword reserved for catching panics"
86 .to_string()
87 }
88 "defer" => {
89 "Rename this binding. `defer` is a keyword reserved for scheduling cleanup"
90 .to_string()
91 }
92 _ => format!("Rename binding `{}`", keyword),
93 };
94 let error = ParseError::new("Reserved keyword", span, "reserved keyword")
95 .with_parse_code("keyword_as_binding")
96 .with_help(help);
97 self.errors.push(error);
98 self.next();
99 return Pattern::Identifier {
100 identifier: keyword.into(),
101 span,
102 };
103 }
104
105 match self.current_token().kind {
106 Integer => self.parse_integer_pattern(),
107 Float => self.parse_float_pattern(),
108 Boolean => self.parse_boolean_pattern(),
109 String => self.parse_string_pattern(),
110 RawString => self.parse_string_pattern(),
111 Char => self.parse_char_pattern(),
112
113 Imaginary => {
114 self.track_error(
115 "not allowed",
116 "Imaginary literals are not supported in patterns",
117 );
118 self.next();
119 Pattern::WildCard {
120 span: self.span_from_tokens(start),
121 }
122 }
123
124 LeftParen => self.parse_tuple_or_unit_pattern(),
125
126 LeftSquareBracket => self.parse_slice_pattern(),
127
128 Identifier => self.parse_identifier_based_pattern(),
129
130 Minus => self.parse_negative_pattern(),
131
132 _ => {
133 self.unexpected_token("pattern");
134 Pattern::WildCard {
135 span: self.span_from_tokens(start),
136 }
137 }
138 }
139 }
140
141 fn parse_negative_pattern(&mut self) -> Pattern {
142 let start = self.current_token();
143 self.next();
144
145 match self.current_token().kind {
146 Integer => {
147 let int_pattern = self.parse_integer_pattern();
148 let Pattern::Literal {
149 literal: Literal::Integer { value, text },
150 ..
151 } = int_pattern
152 else {
153 return int_pattern;
154 };
155 let span = self.span_from_tokens(start);
156 if value > i64::MIN.unsigned_abs() {
157 self.track_error_at(
158 span,
159 "negative integer out of range",
160 "Negative integer must be ≥ -9223372036854775808 (i64 minimum).",
161 );
162 return Pattern::WildCard { span };
163 }
164 let neg_text = match text {
165 Some(t) => format!("-{t}"),
166 None => format!("-{value}"),
167 };
168 Pattern::Literal {
169 literal: Literal::Integer {
170 value: value.wrapping_neg(),
171 text: Some(neg_text),
172 },
173 ty: Type::uninferred(),
174 span,
175 }
176 }
177 Float => {
178 let span = self.span_from_tokens(start);
179 self.track_error_at(
180 span,
181 "not allowed",
182 "Float literals are not supported in patterns",
183 );
184 self.next();
185 Pattern::WildCard {
186 span: self.span_from_tokens(start),
187 }
188 }
189 _ => {
190 self.track_error(
191 "expected number after `-`",
192 "Negative patterns require a number, e.g., `-5`",
193 );
194 Pattern::WildCard {
195 span: self.span_from_tokens(start),
196 }
197 }
198 }
199 }
200
201 fn parse_nested_pattern(&mut self) -> Pattern {
202 let pattern = self.parse_pattern();
203
204 if self.is(Pipe) {
205 let token = self.current_token();
206 let span = Span::new(self.file_id, token.byte_offset, token.byte_length);
207 self.emit_nested_or_error(span);
208
209 while self.is(Pipe) {
210 self.next(); self.parse_pattern();
212 }
213 }
214
215 pattern
216 }
217
218 fn emit_nested_or_error(&mut self, span: Span) {
219 let error = ParseError::new("Invalid or-pattern", span, "or-pattern not allowed here")
220 .with_parse_code("nested_or_pattern")
221 .with_help("Use `Ok(x) | Ok(y)` instead of `Ok(x | y)`");
222 self.errors.push(error);
223 }
224
225 fn check_nested_or_pattern(&mut self, pattern: &Pattern) {
226 if let Pattern::Or { span, .. } = pattern {
227 self.emit_nested_or_error(*span);
228 }
229 }
230
231 fn parse_integer_pattern(&mut self) -> Pattern {
232 let start = self.current_token();
233 let text = start.text;
234 let literal = self.parse_integer_text(text);
235 self.next();
236
237 Pattern::Literal {
238 literal,
239 ty: Type::uninferred(),
240 span: self.span_from_tokens(start),
241 }
242 }
243
244 fn parse_float_pattern(&mut self) -> Pattern {
245 let start = self.current_token();
246 let float_text = start.text.to_string();
247 self.next();
248
249 let span = self.span_from_tokens(start);
250 self.error_float_pattern_not_allowed(span, &float_text);
251
252 Pattern::WildCard { span }
253 }
254
255 fn parse_boolean_pattern(&mut self) -> Pattern {
256 let start = self.current_token();
257 let b = start.text == "true";
258 self.next();
259
260 Pattern::Literal {
261 literal: Literal::Boolean(b),
262 ty: Type::uninferred(),
263 span: self.span_from_tokens(start),
264 }
265 }
266
267 fn parse_string_pattern(&mut self) -> Pattern {
268 let start = self.current_token();
269 let s = start.text;
270 let kind = start.kind;
271 self.next();
272 let (value, raw) = if kind == crate::lex::TokenKind::RawString {
273 let stripped = if s.len() >= 3 && s.starts_with("r\"") && s.ends_with('"') {
274 &s[2..s.len() - 1]
275 } else if s.len() >= 2 && s.starts_with("r\"") {
276 &s[2..]
277 } else {
278 s
279 };
280 (cook_string_contents(stripped), true)
281 } else {
282 let stripped = if s.len() >= 2 && s.starts_with('"') && s.ends_with('"') {
283 &s[1..s.len() - 1]
284 } else {
285 s
286 };
287 (cook_string_contents(stripped), false)
288 };
289
290 Pattern::Literal {
291 literal: Literal::String { value, raw },
292 ty: Type::uninferred(),
293 span: self.span_from_tokens(start),
294 }
295 }
296
297 fn parse_char_pattern(&mut self) -> Pattern {
298 let start = self.current_token();
299 let s = start.text;
300 self.next();
301 let char_str = if s.len() >= 2 && s.starts_with('\'') && s.ends_with('\'') {
302 s[1..s.len() - 1].to_string()
303 } else {
304 s.to_string()
305 };
306
307 Pattern::Literal {
308 literal: Literal::Char(char_str),
309 ty: Type::uninferred(),
310 span: self.span_from_tokens(start),
311 }
312 }
313
314 fn parse_tuple_or_unit_pattern(&mut self) -> Pattern {
315 let start = self.current_token();
316 self.ensure(LeftParen);
317
318 if self.advance_if(RightParen) {
319 return Pattern::Unit {
320 ty: Type::uninferred(),
321 span: self.span_from_tokens(start),
322 };
323 }
324
325 let first = self.parse_pattern_allowing_or();
326
327 if self.advance_if(RightParen) {
328 if matches!(first, Pattern::Or { .. }) {
329 self.check_nested_or_pattern(&first);
330 }
331 return first;
332 }
333
334 if matches!(first, Pattern::Or { .. }) {
335 self.check_nested_or_pattern(&first);
336 }
337
338 let mut elements = vec![first];
339 self.expect_comma_or(RightParen);
340
341 while self.is_not(RightParen) {
342 elements.push(self.parse_nested_pattern());
343 self.expect_comma_or(RightParen);
344 }
345
346 self.ensure(RightParen);
347
348 let span = self.span_from_tokens(start);
349
350 if elements.len() > MAX_TUPLE_ARITY {
351 self.error_tuple_arity(elements.len(), span);
352 }
353
354 Pattern::Tuple { elements, span }
355 }
356
357 fn parse_slice_pattern(&mut self) -> Pattern {
358 let start = self.current_token();
359 self.ensure(LeftSquareBracket);
360
361 let mut elements = Vec::new();
362 let mut rest = RestPattern::Absent;
363
364 while self.is_not(RightSquareBracket) {
365 if let Some((binding, rest_start)) = self.try_parse_rest() {
366 if rest.is_present() {
367 self.track_error(
368 "multiple rest patterns in slice pattern",
369 "Only one `..` or `..rest` is allowed.",
370 );
371 } else {
372 rest = match binding {
373 Some(name) => RestPattern::Bind {
374 name,
375 span: self.span_from_tokens(rest_start),
376 },
377 None => RestPattern::Discard(self.span_from_tokens(rest_start)),
378 };
379 }
380 self.expect_comma_or(RightSquareBracket);
381 continue;
382 }
383
384 if rest.is_present() {
385 let suffix_start = self.current_token();
386 self.parse_pattern();
387 let suffix_span = self.span_from_tokens(suffix_start);
388 let error = ParseError::new("Invalid pattern", suffix_span, "not supported")
389 .with_parse_code("suffix_slice_pattern")
390 .with_help("Use `[first, ..rest]` instead of `[..rest, last]`.")
391 .with_note("Elements after rest pattern are not supported.");
392 self.errors.push(error);
393 self.expect_comma_or(RightSquareBracket);
394 continue;
395 }
396
397 elements.push(self.parse_nested_pattern());
398 self.expect_comma_or(RightSquareBracket);
399 }
400
401 let span = self.span_from_tokens(start);
402 self.ensure(RightSquareBracket);
403
404 Pattern::Slice {
405 prefix: elements,
406 rest,
407 element_ty: Type::uninferred(),
408 span,
409 }
410 }
411
412 fn parse_identifier_based_pattern(&mut self) -> Pattern {
413 let start = self.current_token();
414 let name = self.current_token().text.to_string();
415 self.next();
416
417 let full_name = if self.is(Dot) {
418 self.parse_qualified_pattern_name(name)
419 } else if self.is(Colon) && self.stream.peek_ahead(1).kind == Colon {
420 let colon_token = self.current_token();
421 let span = Span::new(self.file_id, colon_token.byte_offset, 2);
422 let after = self.stream.peek_ahead(2);
423 let example = if after.kind == Identifier {
424 format!("{}.{}", name, after.text)
425 } else {
426 format!("{}.<variant>", name)
427 };
428 self.track_error_at(
429 span,
430 "invalid syntax",
431 format!(
432 "Use `.` instead of `::` for enum variant access, e.g. `{}`",
433 example
434 ),
435 );
436 self.next(); self.next(); let mut full_name = name;
439 if self.is(Identifier) {
440 full_name.push('.');
441 full_name.push_str(self.current_token().text);
442 self.next();
443 }
444 self.parse_qualified_pattern_name(full_name)
445 } else {
446 name.clone()
447 };
448
449 match self.current_token().kind {
450 LeftCurlyBrace => self.parse_struct_pattern(full_name, start),
451 LeftParen => self.parse_enum_variant_pattern(full_name, start),
452 _ => {
453 let span = self.span_from_tokens(start);
454 if full_name == "_" {
455 Pattern::WildCard { span }
456 } else if full_name.contains('.') || self.is_uppercase(&full_name) {
457 Pattern::EnumVariant {
458 identifier: full_name.into(),
459 fields: vec![],
460 rest: false,
461 ty: Type::uninferred(),
462 span,
463 }
464 } else {
465 Pattern::Identifier {
466 identifier: full_name.into(),
467 span,
468 }
469 }
470 }
471 }
472 }
473
474 fn parse_qualified_pattern_name(
475 &mut self,
476 initial: std::string::String,
477 ) -> std::string::String {
478 let mut name = initial;
479
480 while self.advance_if(Dot) {
481 if self.is_not(Identifier) {
482 break;
483 }
484 name.push('.');
485 name.push_str(self.current_token().text);
486 self.next();
487 }
488
489 name
490 }
491
492 fn parse_struct_pattern(
493 &mut self,
494 name: std::string::String,
495 start: Token<'source>,
496 ) -> Pattern {
497 self.ensure(LeftCurlyBrace);
498
499 let mut fields = Vec::new();
500 let mut seen_fields: Vec<(EcoString, Span)> = Vec::new();
501 let mut rest = false;
502
503 while self.is_not(RightCurlyBrace) {
504 if self.advance_if(DotDot) {
505 rest = true;
506 if self.is(Identifier) {
507 self.next();
508 }
509 if self.advance_if(Comma) && self.is_not(RightCurlyBrace) {
510 self.track_error(
511 "cannot be last",
512 "Move the spread expression `..rest` to the last position in the struct",
513 );
514 }
515 break;
516 }
517
518 let field_start = self.current_token();
519 let field_name = self.read_identifier();
520 let field_name_span = self.span_from_tokens(field_start);
521
522 if let Some((_, first_span)) = seen_fields.iter().find(|(n, _)| n == &field_name) {
523 self.error_duplicate_field_in_pattern(&field_name, *first_span, field_name_span);
524 }
525
526 let field_pattern = if self.advance_if(Colon) {
527 self.parse_nested_pattern()
528 } else {
529 let span = field_name_span;
530 if field_name == "_" {
531 Pattern::WildCard { span }
532 } else {
533 Pattern::Identifier {
534 identifier: field_name.clone(),
535 span,
536 }
537 }
538 };
539
540 seen_fields.push((field_name.clone(), field_name_span));
541 fields.push(StructFieldPattern {
542 name: field_name,
543 value: field_pattern,
544 });
545
546 self.expect_comma_or(RightCurlyBrace);
547 }
548
549 self.ensure(RightCurlyBrace);
550
551 Pattern::Struct {
552 identifier: name.into(),
553 fields,
554 rest,
555 ty: Type::uninferred(),
556 span: self.span_from_tokens(start),
557 }
558 }
559
560 fn parse_enum_variant_pattern(
561 &mut self,
562 name: std::string::String,
563 start: Token<'source>,
564 ) -> Pattern {
565 self.ensure(LeftParen);
566
567 let mut fields = Vec::new();
568 let mut rest = false;
569
570 while self.is_not(RightParen) {
571 if self.advance_if(DotDot) {
572 rest = true;
573 self.advance_if(Comma);
574 break;
575 }
576 fields.push(self.parse_nested_pattern());
577 self.expect_comma_or(RightParen);
578 }
579
580 self.ensure(RightParen);
581
582 Pattern::EnumVariant {
583 identifier: name.into(),
584 fields,
585 rest,
586 ty: Type::uninferred(),
587 span: self.span_from_tokens(start),
588 }
589 }
590
591 pub fn parse_binding(&mut self) -> Binding {
592 Binding {
593 pattern: self.parse_pattern(),
594 annotation: self.parse_optional_type_annotation(),
595 typed_pattern: None,
596 ty: Type::uninferred(),
597 mutable: false,
598 }
599 }
600
601 pub fn parse_binding_allowing_or(&mut self) -> Binding {
602 Binding {
603 pattern: self.parse_pattern_allowing_or(),
604 annotation: self.parse_optional_type_annotation(),
605 typed_pattern: None,
606 ty: Type::uninferred(),
607 mutable: false,
608 }
609 }
610
611 fn parse_optional_type_annotation(&mut self) -> Option<Annotation> {
612 if self.advance_if(Colon) {
613 if self.can_start_annotation() {
614 Some(self.parse_annotation())
615 } else {
616 self.track_error(
617 "expected type after `:`",
618 "Annotate the type, e.g. `x: int`.",
619 );
620 None
621 }
622 } else {
623 None
624 }
625 }
626
627 pub fn parse_binding_with_type(&mut self) -> Binding {
628 if self.is_current_uppercase() && self.stream.peek_ahead(1).kind == Colon {
629 let start = self.current_token();
630 let name = start.text.to_string();
631 self.next();
632
633 let span = self.span_from_tokens(start);
634 self.error_uppercase_binding(span);
635
636 return Binding {
637 pattern: Pattern::Identifier {
638 identifier: name.into(),
639 span,
640 },
641 annotation: self.parse_optional_type_annotation(),
642 typed_pattern: None,
643 ty: Type::uninferred(),
644 mutable: false,
645 };
646 }
647
648 if self.is(Ampersand) {
649 let amp_token = self.current_token();
650 let next = self.stream.peek_ahead(1);
651 let is_mut_self = next.kind == Mut && self.stream.peek_ahead(2).text == "self";
652 let is_ref_self = next.kind == Identifier && next.text == "self";
653
654 if is_ref_self || is_mut_self {
655 let span_len = if is_mut_self {
656 self.stream.peek_ahead(2).byte_offset + self.stream.peek_ahead(2).byte_length
658 - amp_token.byte_offset
659 } else {
660 next.byte_offset + next.byte_length - amp_token.byte_offset
662 };
663 let span = Span::new(self.file_id, amp_token.byte_offset, span_len);
664 self.track_error_at(
665 span,
666 "invalid syntax",
667 "Lisette methods receive `self` by reference. Use `self` instead",
668 );
669 self.next();
670 if is_mut_self {
671 self.next();
672 }
673 }
674 }
675
676 let is_mut = self.advance_if(Mut);
677
678 let pattern = self.parse_pattern();
679
680 if let Pattern::Identifier { identifier, .. } = &pattern
681 && identifier == "self"
682 && self.is_not(Colon)
683 {
684 return Binding {
685 pattern,
686 annotation: None,
687 typed_pattern: None,
688 ty: Type::uninferred(),
689 mutable: false,
690 };
691 }
692
693 self.ensure(Colon);
694 let annotation = self.parse_annotation();
695
696 Binding {
697 pattern,
698 annotation: Some(annotation),
699 typed_pattern: None,
700 ty: Type::uninferred(),
701 mutable: is_mut,
702 }
703 }
704
705 fn try_parse_rest(&mut self) -> Option<(Option<EcoString>, Token<'source>)> {
706 if self.is(DotDot) {
707 let rest_start = self.current_token();
708 self.ensure(DotDot);
709 if self.is(Identifier) {
710 let name: EcoString = self.current_token().text.into();
711 self.next();
712 return Some((Some(name), rest_start));
713 }
714 return Some((None, rest_start));
715 }
716
717 if self.is(Identifier) {
718 let text = self.current_token().text;
719 if let Some(binding) = text.strip_prefix("..") {
720 let rest_start = self.current_token();
721 self.next();
722 let name = if binding.is_empty() {
723 None
724 } else {
725 Some(EcoString::from(binding))
726 };
727 return Some((name, rest_start));
728 }
729 }
730
731 None
732 }
733
734 fn is_uppercase(&self, identifier: &str) -> bool {
735 identifier.chars().next().unwrap_or('a').is_uppercase()
736 }
737
738 fn is_current_uppercase(&self) -> bool {
739 self.is(Identifier) && self.is_uppercase(self.current_token().text)
740 }
741
742 pub fn can_start_pattern(&self) -> bool {
743 matches!(
744 self.current_token().kind,
745 Integer
746 | Float
747 | Boolean
748 | String
749 | RawString
750 | Char
751 | LeftParen
752 | LeftSquareBracket
753 | Identifier
754 | Minus
755 )
756 }
757}