Skip to main content

lisette_syntax/parse/
patterns.rs

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(); // consume `|`
211                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(); // consume first `:`
437            self.next(); // consume second `:`
438            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                    // &mut self
657                    self.stream.peek_ahead(2).byte_offset + self.stream.peek_ahead(2).byte_length
658                        - amp_token.byte_offset
659                } else {
660                    // &self
661                    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}