Skip to main content

lisette_syntax/parse/
patterns.rs

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