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