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