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