Skip to main content

lisette_syntax/parse/
mod.rs

1use crate::ast::{self, Span};
2use crate::lex;
3use crate::lex::TokenKind::*;
4use crate::lex::{Token, TokenKind};
5use crate::types::Type;
6
7pub const MAX_TUPLE_ARITY: usize = 5;
8pub const TUPLE_FIELDS: &[&str] = &["First", "Second", "Third", "Fourth", "Fifth"];
9const MAX_DEPTH: u32 = 64;
10const MAX_ERRORS: usize = 50;
11const MAX_LOOKAHEAD: usize = 256;
12
13mod annotations;
14mod control_flow;
15mod definitions;
16mod directives;
17mod error;
18mod expressions;
19mod identifiers;
20mod patterns;
21mod pratt;
22mod strings;
23
24pub use error::ParseError;
25
26pub struct ParseResult {
27    pub ast: Vec<ast::Expression>,
28    pub errors: Vec<ParseError>,
29}
30
31impl ParseResult {
32    pub fn failed(&self) -> bool {
33        !self.errors.is_empty()
34    }
35}
36
37pub struct Parser<'source> {
38    stream: TokenStream<'source>,
39    previous_token: Token<'source>,
40    pub errors: Vec<ParseError>,
41    file_id: u32,
42    in_control_flow_header: bool,
43    source: &'source str,
44    depth: u32,
45}
46
47impl<'source> Parser<'source> {
48    pub fn new(tokens: Vec<Token<'source>>, source: &'source str) -> Parser<'source> {
49        Self::with_file_id(tokens, source, 0)
50    }
51
52    pub fn lex_and_parse_file(source: &str, file_id: u32) -> ParseResult {
53        let lex_result = lex::Lexer::new(source, file_id).lex();
54
55        if lex_result.failed() {
56            return ParseResult {
57                ast: vec![],
58                errors: lex_result.errors,
59            };
60        }
61
62        Parser::with_file_id(lex_result.tokens, source, file_id).parse()
63    }
64
65    fn with_file_id(
66        tokens: Vec<Token<'source>>,
67        source: &'source str,
68        file_id: u32,
69    ) -> Parser<'source> {
70        let stream = TokenStream::new(tokens);
71        let first_token = stream.peek();
72
73        let mut parser = Parser {
74            stream,
75            previous_token: first_token,
76            errors: Default::default(),
77            file_id,
78            in_control_flow_header: false,
79            source,
80            depth: 0,
81        };
82
83        parser.skip_comments();
84
85        parser
86    }
87
88    pub fn parse(mut self) -> ParseResult {
89        let mut top_items = vec![];
90
91        self.skip_comments();
92
93        while !self.at_eof() && !self.too_many_errors() {
94            let position = self.position();
95            let item = self.parse_top_item();
96            if !matches!(item, ast::Expression::Unit { .. }) {
97                top_items.push(item);
98            }
99            self.advance_if(Semicolon);
100            if self.position() == position {
101                self.next();
102            }
103        }
104
105        ParseResult {
106            ast: top_items,
107            errors: self.errors,
108        }
109    }
110
111    pub fn parse_top_item(&mut self) -> ast::Expression {
112        let doc_with_span = self.collect_doc_comments();
113
114        let attributes = self.parse_attributes();
115
116        let pub_token = if self.is(Pub) {
117            Some(self.current_token())
118        } else {
119            None
120        };
121        let is_public = pub_token.is_some();
122        if is_public {
123            self.next();
124        }
125
126        if is_public && self.is(Impl) {
127            let token = pub_token.unwrap();
128            let span = ast::Span::new(self.file_id, token.byte_offset, token.byte_length);
129            let error = ParseError::new("Misplaced `pub`", span, "not allowed here")
130                .with_parse_code("syntax_error")
131                .with_help("Place `pub` on individual methods inside the `impl` block instead");
132            self.errors.push(error);
133        }
134
135        let is_documentable = matches!(
136            self.current_token().kind,
137            Enum | Struct | Interface | Function | Const | Var | Type
138        );
139
140        if let Some((_, ref span)) = doc_with_span
141            && !is_documentable
142        {
143            self.error_detached_doc_comment(*span);
144        }
145
146        let doc = doc_with_span.map(|(text, _)| text);
147
148        let expression = match self.current_token().kind {
149            Enum => self.parse_enum_definition(doc, attributes),
150            Struct => self.parse_struct_definition(doc, attributes),
151            Interface => self.parse_interface_definition(doc),
152            Function => self.parse_function(doc, attributes),
153            Impl => self.parse_impl_block(),
154            Const => self.parse_const_definition(doc),
155            Var => self.parse_var_declaration(doc),
156            Import => self.parse_import(),
157            Type => self.parse_type_alias_with_doc(doc),
158            Comment => {
159                let start = self.current_token();
160                self.skip_comments();
161                ast::Expression::Unit {
162                    ty: Type::uninferred(),
163                    span: self.span_from_tokens(start),
164                }
165            }
166            _ => self.unexpected_token("top_item"),
167        };
168
169        if is_public {
170            return expression.set_public();
171        }
172
173        expression
174    }
175
176    pub fn parse_block_item(&mut self) -> ast::Expression {
177        match self.current_token().kind {
178            Enum => {
179                self.track_error(
180                    "misplaced",
181                    "Move this enum definition to the top level of the file.",
182                );
183                self.parse_enum_definition(None, vec![])
184            }
185            Struct => {
186                self.track_error(
187                    "misplaced",
188                    "Move this struct definition to the top level of the file.",
189                );
190                self.parse_struct_definition(None, vec![])
191            }
192            Type => {
193                self.track_error(
194                    "misplaced",
195                    "Move this type alias to the top level of the file.",
196                );
197                self.parse_type_alias_with_doc(None)
198            }
199            Import => {
200                self.track_error(
201                    "misplaced",
202                    "Move this import to the top level of the file.",
203                );
204                self.parse_import()
205            }
206            Impl => {
207                self.track_error(
208                    "misplaced",
209                    "Move this `impl` block to the top level of the file.",
210                );
211                self.parse_impl_block()
212            }
213            Interface => {
214                self.track_error(
215                    "misplaced",
216                    "Move this interface definition to the top level of the file.",
217                );
218                self.parse_interface_definition(None)
219            }
220            Function => self.parse_function(None, vec![]),
221            Const => self.parse_const_definition(None),
222
223            Let => self.parse_let(),
224            Return => self.parse_return(),
225            For => self.parse_for(),
226            While => self.parse_while(),
227            Loop => self.parse_loop(),
228            Break => self.parse_break(),
229            Continue => self.parse_continue(),
230            Defer => self.parse_defer(),
231            Directive => self.parse_directive(),
232            _ => self.parse_assignment(),
233        }
234    }
235
236    fn current_token(&self) -> Token<'source> {
237        self.stream.peek()
238    }
239
240    fn newline_before_current(&self) -> bool {
241        let prev_end = (self.previous_token.byte_offset + self.previous_token.byte_length) as usize;
242        let curr_start = self.current_token().byte_offset as usize;
243        if prev_end <= curr_start && curr_start <= self.source.len() {
244            return self.source[prev_end..curr_start].contains('\n');
245        }
246        false
247    }
248
249    fn next(&mut self) {
250        self.previous_token = self.current_token();
251        self.stream.consume();
252        self.skip_comments();
253    }
254
255    fn skip_comments(&mut self) {
256        while self.is(Comment) {
257            self.previous_token = self.current_token();
258            self.stream.consume();
259        }
260    }
261
262    fn collect_doc_comments(&mut self) -> Option<(std::string::String, ast::Span)> {
263        let mut docs = Vec::new();
264        let mut first_span: Option<ast::Span> = None;
265
266        while self.is(DocComment) {
267            let token = self.current_token();
268            if first_span.is_none() {
269                first_span = Some(self.span_from_token(token));
270            }
271            docs.push(token.text.to_string());
272            self.previous_token = token;
273            self.stream.consume();
274            self.skip_comments();
275        }
276
277        if docs.is_empty() {
278            None
279        } else {
280            Some((docs.join("\n"), first_span.unwrap()))
281        }
282    }
283
284    fn expect_comma_or(&mut self, closing: TokenKind) {
285        if self.is(Comma) || self.is(closing) || self.at_item_boundary() {
286            self.advance_if(Comma);
287            return;
288        }
289
290        self.track_error(
291            format!("expected `,` or {}", closing),
292            "Add a comma between elements.",
293        );
294
295        loop {
296            if self.at_eof() || self.is(Comma) || self.is(closing) || self.at_item_boundary() {
297                break;
298            }
299            self.next();
300        }
301
302        self.advance_if(Comma);
303    }
304
305    pub fn at_eof(&self) -> bool {
306        self.is(EOF)
307    }
308
309    fn at_range(&self) -> bool {
310        matches!(self.current_token().kind, DotDot | DotDotEqual)
311    }
312
313    fn advance_if(&mut self, token_kind: TokenKind) -> bool {
314        if self.is(token_kind) {
315            self.next();
316            return true;
317        }
318
319        false
320    }
321
322    fn is(&self, token_kind: TokenKind) -> bool {
323        self.current_token().kind == token_kind
324    }
325
326    fn is_not(&self, token_kind: TokenKind) -> bool {
327        if self.at_eof() {
328            return false;
329        }
330
331        self.current_token().kind != token_kind
332    }
333
334    fn ensure(&mut self, token_kind: TokenKind) {
335        if self.current_token().kind != token_kind {
336            self.track_ensure_error(token_kind);
337        }
338
339        if self.at_eof() {
340            return;
341        }
342
343        self.next();
344    }
345
346    fn ensure_progress(&mut self, start_position: usize, closing: TokenKind) {
347        if self.stream.position == start_position && self.is_not(closing) && !self.at_eof() {
348            self.next();
349        }
350    }
351
352    fn span_from_token(&self, token: Token<'source>) -> ast::Span {
353        ast::Span::new(self.file_id, token.byte_offset, token.byte_length)
354    }
355
356    fn span_from_tokens(&self, start_token: Token<'source>) -> ast::Span {
357        let end_byte_offset = self.previous_token.byte_offset + self.previous_token.byte_length;
358        let byte_length = end_byte_offset.saturating_sub(start_token.byte_offset);
359
360        ast::Span::new(self.file_id, start_token.byte_offset, byte_length)
361    }
362
363    fn span_from_offset(&self, start_byte_offset: u32) -> ast::Span {
364        let end_byte_offset = self.previous_token.byte_offset + self.previous_token.byte_length;
365        let byte_length = end_byte_offset.saturating_sub(start_byte_offset);
366
367        ast::Span::new(self.file_id, start_byte_offset, byte_length)
368    }
369
370    fn is_type_args_call(&self) -> bool {
371        let mut position = 1; // 0 is <
372        let mut depth = 1;
373
374        loop {
375            if position > MAX_LOOKAHEAD {
376                return false;
377            }
378            match self.stream.peek_ahead(position).kind {
379                LeftAngleBracket => depth += 1,
380                RightAngleBracket if depth == 1 => {
381                    let next = self.stream.peek_ahead(position + 1).kind;
382                    return next == LeftParen
383                        || (next == Dot
384                            && self.stream.peek_ahead(position + 2).kind == Identifier
385                            && self.stream.peek_ahead(position + 3).kind == LeftParen);
386                }
387                RightAngleBracket => depth -= 1,
388                LeftParen => {
389                    let mut paren_depth = 1;
390                    position += 1;
391                    while paren_depth > 0 {
392                        if position > MAX_LOOKAHEAD {
393                            return false;
394                        }
395                        match self.stream.peek_ahead(position).kind {
396                            LeftParen => paren_depth += 1,
397                            RightParen => paren_depth -= 1,
398                            EOF => return false,
399                            _ => {}
400                        }
401                        position += 1;
402                    }
403                    continue;
404                }
405                EOF | Plus | Minus | Star | Slash | Percent | EqualDouble | NotEqual
406                | AmpersandDouble | PipeDouble | Semicolon | LeftCurlyBrace | RightCurlyBrace
407                | LeftSquareBracket | RightSquareBracket => return false,
408                _ => {}
409            }
410            position += 1;
411        }
412    }
413
414    fn has_block_after_struct(&self) -> bool {
415        let mut depth = 1;
416        let mut i = 0;
417        while depth > 0 {
418            i += 1;
419            if i > MAX_LOOKAHEAD {
420                return false;
421            }
422            let token = self.stream.peek_ahead(i);
423            match token.kind {
424                LeftCurlyBrace => depth += 1,
425                RightCurlyBrace => depth -= 1,
426                EOF => return false,
427                _ => {}
428            }
429        }
430        let after = self.stream.peek_ahead(i + 1);
431        matches!(
432            after.kind,
433            LeftCurlyBrace
434                | RightParen
435                | EqualDouble
436                | NotEqual
437                | LeftAngleBracket
438                | RightAngleBracket
439                | LessThanOrEqual
440                | GreaterThanOrEqual
441                | AmpersandDouble
442                | PipeDouble
443                | Plus
444                | Minus
445                | Star
446                | Slash
447                | Percent
448        )
449    }
450
451    fn is_struct_instantiation(&self) -> bool {
452        if self.previous_token.kind != Identifier {
453            return false;
454        }
455
456        let is_uppercase = self
457            .previous_token
458            .text
459            .starts_with(|c: char| c.is_uppercase());
460        let first_ahead = self.stream.peek_ahead(1);
461
462        if first_ahead.kind == DotDot {
463            return true;
464        }
465
466        if first_ahead.kind == RightCurlyBrace {
467            if self.in_control_flow_header {
468                return is_uppercase && self.has_block_after_struct();
469            }
470            return is_uppercase;
471        }
472
473        if first_ahead.kind == Identifier {
474            let second_ahead = self.stream.peek_ahead(2);
475            return match second_ahead.kind {
476                Colon => self.stream.peek_ahead(3).kind != Colon,
477                Comma | RightCurlyBrace => {
478                    if self.in_control_flow_header {
479                        is_uppercase && self.has_block_after_struct()
480                    } else {
481                        is_uppercase
482                    }
483                }
484                _ => false,
485            };
486        }
487
488        false
489    }
490
491    fn enter_recursion(&mut self) -> bool {
492        if self.depth >= MAX_DEPTH {
493            let span = self.span_from_token(self.current_token());
494            self.track_error_at(span, "too deeply nested", "Reduce nesting depth");
495            return false;
496        }
497        self.depth += 1;
498        true
499    }
500
501    fn leave_recursion(&mut self) {
502        self.depth -= 1;
503    }
504
505    fn too_many_errors(&self) -> bool {
506        self.errors.len() >= MAX_ERRORS
507    }
508
509    fn position(&self) -> u32 {
510        self.current_token().byte_offset
511    }
512
513    fn at_sync_point(&self) -> bool {
514        matches!(
515            self.current_token().kind,
516            Semicolon
517                | RightCurlyBrace
518                | RightParen
519                | RightSquareBracket
520                | Comma
521                | Function
522                | Struct
523                | Enum
524                | Const
525                | Impl
526                | Interface
527                | Type
528                | Import
529        )
530    }
531
532    fn can_start_annotation(&self) -> bool {
533        matches!(self.current_token().kind, Identifier | Function | LeftParen)
534    }
535
536    fn at_item_boundary(&self) -> bool {
537        matches!(
538            self.current_token().kind,
539            Let | Function | Struct | Enum | Impl | Interface | Type | Const | Import
540        )
541    }
542
543    fn resync_on_error(&mut self) {
544        if !self.at_eof() {
545            self.next();
546        }
547
548        while !self.at_sync_point() && !self.at_eof() {
549            self.next();
550        }
551    }
552
553    fn track_error(
554        &mut self,
555        label: impl Into<std::string::String>,
556        help: impl Into<std::string::String>,
557    ) {
558        let current = self.current_token();
559        let span = ast::Span::new(self.file_id, current.byte_offset, current.byte_length);
560        self.track_error_at(span, label, help);
561    }
562
563    fn track_error_at(
564        &mut self,
565        span: ast::Span,
566        label: impl Into<std::string::String>,
567        help: impl Into<std::string::String>,
568    ) {
569        if self.too_many_errors() {
570            return;
571        }
572        let error = ParseError::new("Syntax error", span, label.into())
573            .with_parse_code("syntax_error")
574            .with_help(help.into());
575
576        self.errors.push(error);
577    }
578
579    fn track_ensure_error(&mut self, expected_token: TokenKind) {
580        if self.too_many_errors() {
581            return;
582        }
583        let current = self.current_token();
584
585        let error_code = match expected_token {
586            Semicolon => "missing_semicolon",
587            RightCurlyBrace => "unclosed_block",
588            _ => "unexpected_token",
589        };
590
591        let span = ast::Span::new(self.file_id, current.byte_offset, current.byte_length);
592        let error = ParseError::new("Syntax error", span, format!("expected {}", expected_token))
593            .with_parse_code(error_code);
594
595        self.errors.push(error);
596    }
597
598    fn close_brace_span(&mut self, start: Token<'source>, error_anchor: Token<'source>) -> Span {
599        if self.is(RightCurlyBrace) {
600            let close = self.current_token();
601            self.next();
602            let end = close.byte_offset + close.byte_length;
603            Span::new(
604                self.file_id,
605                start.byte_offset,
606                end.saturating_sub(start.byte_offset),
607            )
608        } else {
609            self.error_unclosed_block(&error_anchor);
610            self.span_from_tokens(start)
611        }
612    }
613
614    fn error_unclosed_block(&mut self, open_brace: &Token) {
615        let span = ast::Span::new(self.file_id, open_brace.byte_offset, open_brace.byte_length);
616        let error = ParseError::new("Unclosed block", span, "opening brace here")
617            .with_parse_code("unclosed_block")
618            .with_help("Add a closing `}`");
619
620        self.errors.push(error);
621    }
622
623    fn error_tuple_arity(&mut self, arity: usize, span: Span) {
624        let help = if arity == 0 {
625            "Use `()` for unit type".to_string()
626        } else if arity == 1 {
627            "Use the type directly without wrapping in a tuple".to_string()
628        } else {
629            "For >5 elements, use a struct with named fields".to_string()
630        };
631
632        let error = ParseError::new(
633            "Invalid tuple",
634            span,
635            format!("{}-element tuple not allowed", arity),
636        )
637        .with_parse_code("tuple_element_count")
638        .with_help(help);
639
640        self.errors.push(error);
641    }
642
643    fn error_duplicate_field_in_pattern(
644        &mut self,
645        field_name: &str,
646        first_span: Span,
647        second_span: Span,
648    ) {
649        let error = ParseError::new(
650            "Duplicate field",
651            first_span,
652            format!("first use of `{}`", field_name),
653        )
654        .with_span_label(second_span, "used again")
655        .with_parse_code("duplicate_field_in_pattern")
656        .with_help("Remove the duplicate binding");
657
658        self.errors.push(error);
659    }
660
661    fn error_duplicate_impl_parent(&mut self, first_span: Span, second_span: Span) {
662        let error = ParseError::new("Duplicate impl", first_span, "first use")
663            .with_span_label(second_span, "used again")
664            .with_parse_code("duplicate_impl_parent")
665            .with_help("Remove the duplicate parent");
666
667        self.errors.push(error);
668    }
669
670    fn error_duplicate_struct_field(&mut self, name: &str, first_span: Span, second_span: Span) {
671        let error = ParseError::new("Duplicate field", first_span, "first defined")
672            .with_span_label(second_span, "defined again")
673            .with_parse_code("duplicate_struct_field")
674            .with_help(format!("Remove the duplicate field `{}`", name));
675
676        self.errors.push(error);
677    }
678
679    fn error_duplicate_enum_variant(&mut self, name: &str, first_span: Span, second_span: Span) {
680        let error = ParseError::new("Duplicate variant", first_span, "first defined")
681            .with_span_label(second_span, "defined again")
682            .with_parse_code("duplicate_enum_variant")
683            .with_help(format!("Remove the duplicate variant `{}`", name));
684
685        self.errors.push(error);
686    }
687
688    fn error_duplicate_interface_method(
689        &mut self,
690        name: &str,
691        first_span: Span,
692        second_span: Span,
693    ) {
694        let error = ParseError::new("Duplicate method", first_span, "first defined")
695            .with_span_label(second_span, "defined again")
696            .with_parse_code("duplicate_interface_method")
697            .with_help(format!("Remove the duplicate method `{}`", name));
698
699        self.errors.push(error);
700    }
701
702    fn error_float_pattern_not_allowed(&mut self, span: Span, float_text: &str) {
703        let error = ParseError::new("Invalid pattern", span, "float literal not allowed here")
704            .with_parse_code("float_pattern")
705            .with_help(format!(
706                "Use a guard instead: `x if x == {} =>`",
707                float_text
708            ));
709
710        self.errors.push(error);
711    }
712
713    fn error_uppercase_binding(&mut self, span: Span) {
714        let error = ParseError::new("Invalid binding name", span, "uppercase not allowed here")
715            .with_parse_code("uppercase_binding")
716            .with_help("Lowercase the binding");
717
718        self.errors.push(error);
719    }
720
721    fn error_detached_doc_comment(&mut self, span: Span) {
722        let error = ParseError::new("Unattached doc comment", span, "is detached")
723            .with_parse_code("detached_doc_comment")
724            .with_help("Place the doc comment on the line immediately above a symbol definition");
725
726        self.errors.push(error);
727    }
728
729    fn error_interface_method_with_type_parameters(&mut self, span: Span, count: usize) {
730        let label = if count == 1 {
731            "type parameter not allowed"
732        } else {
733            "type parameters not allowed"
734        };
735        let error = ParseError::new("Invalid interface method", span, label)
736            .with_parse_code("interface_method_with_type_parameters")
737            .with_help(
738                "Interface methods cannot have type parameters, because Go interfaces do not support generic methods",
739            );
740
741        self.errors.push(error);
742    }
743
744    pub(crate) fn parse_integer_text(&mut self, text: &str) -> ast::Literal {
745        self.parse_integer_text_with(text, false)
746    }
747
748    pub(crate) fn parse_integer_text_with(
749        &mut self,
750        text: &str,
751        preserve_decimal_text: bool,
752    ) -> ast::Literal {
753        let clean = if text.contains('_') {
754            std::borrow::Cow::Owned(text.replace('_', ""))
755        } else {
756            std::borrow::Cow::Borrowed(text)
757        };
758
759        let (n, is_decimal) = if clean.starts_with("0x") || clean.starts_with("0X") {
760            let value = u64::from_str_radix(&clean[2..], 16).unwrap_or_else(|_| {
761                self.track_error(
762                    format!("hex literal '{text}' is too large"),
763                    "Maximum value is `0xFFFFFFFFFFFFFFFF`.",
764                );
765                0
766            });
767            (value, false)
768        } else if clean.starts_with("0o") || clean.starts_with("0O") {
769            let value = u64::from_str_radix(&clean[2..], 8).unwrap_or_else(|_| {
770                self.track_error(
771                    format!("octal literal '{text}' is too large"),
772                    "Maximum value is `0o1777777777777777777777`.",
773                );
774                0
775            });
776            (value, false)
777        } else if clean.starts_with("0b") || clean.starts_with("0B") {
778            let value = u64::from_str_radix(&clean[2..], 2).unwrap_or_else(|_| {
779                self.track_error(
780                    format!("binary literal '{text}' is too large"),
781                    "Value must fit in 64 bits.",
782                );
783                0
784            });
785            (value, false)
786        } else if clean.len() > 1
787            && clean.starts_with('0')
788            && clean.chars().skip(1).all(|c| c.is_ascii_digit())
789        {
790            let value = u64::from_str_radix(&clean[1..], 8).unwrap_or_else(|_| {
791                self.track_error(
792                    format!("octal literal '{text}' is too large"),
793                    "Maximum value is `01777777777777777777777`.",
794                );
795                0
796            });
797            (value, false)
798        } else {
799            let value = clean.parse().unwrap_or_else(|_| {
800                self.track_error(
801                    format!("integer literal '{text}' is too large"),
802                    "Maximum value is `18446744073709551615`.",
803                );
804                0
805            });
806            (value, true)
807        };
808
809        let original_text = if is_decimal && !preserve_decimal_text {
810            None
811        } else {
812            Some(text.to_string())
813        };
814
815        ast::Literal::Integer {
816            value: n,
817            text: original_text,
818        }
819    }
820
821    fn unexpected_token(&mut self, ctx: &str) -> ast::Expression {
822        let token = self.current_token();
823        let token_descriptor = if token.text.is_empty() {
824            format!("{:?}", token.kind)
825        } else {
826            format!("`{}`", token.text)
827        };
828
829        let span = ast::Span::new(self.file_id, token.byte_offset, token.byte_length);
830
831        let (label, error_code, help) = match ctx {
832            "expr" => (
833                format!("expected expression, found {}", token_descriptor),
834                "expected_expression",
835                "Check your syntax.",
836            ),
837            "pattern" => (
838                format!("unexpected {} in pattern", token_descriptor),
839                "invalid_pattern",
840                "Patterns include literals, variables, and destructuring.",
841            ),
842            "literal" => (
843                format!("expected literal, found {}", token_descriptor),
844                "expected_literal",
845                "Literals include numbers, strings, characters, and booleans.",
846            ),
847            "top_item" if token.text == "trait" => (
848                format!("unexpected {}", token_descriptor),
849                "trait_unsupported",
850                "Lisette uses `interface` with Go-style structural typing. Types automatically satisfy interfaces if they have the required methods.",
851            ),
852            "top_item" if token.text == "use" => (
853                "unexpected syntax for import".to_string(),
854                "use_unsupported",
855                "Use `import` instead of `use` for imports: `import \"module/path\"`",
856            ),
857            "top_item" => (
858                "expected declaration".to_string(),
859                "expected_declaration",
860                "At the top level of a file, Lisette expects `fn`, `struct`, `enum`, `interface`, `import`, or `type`.",
861            ),
862            _ => (
863                format!("unexpected {}", token_descriptor),
864                "unexpected_token",
865                "Check your syntax.",
866            ),
867        };
868
869        let error = ParseError::new("Syntax error", span, label)
870            .with_parse_code(error_code)
871            .with_help(help);
872
873        if !self.too_many_errors() {
874            self.errors.push(error);
875        }
876
877        self.resync_on_error();
878
879        ast::Expression::Unit {
880            ty: Type::uninferred(),
881            span,
882        }
883    }
884}
885
886struct TokenStream<'source> {
887    tokens: Vec<Token<'source>>,
888    position: usize,
889}
890
891impl<'source> TokenStream<'source> {
892    fn new(tokens: Vec<Token<'source>>) -> Self {
893        Self {
894            tokens,
895            position: 0,
896        }
897    }
898
899    fn peek(&self) -> Token<'source> {
900        self.tokens
901            .get(self.position)
902            .copied()
903            .unwrap_or_else(|| Token {
904                kind: TokenKind::EOF,
905                text: "",
906                byte_offset: self
907                    .tokens
908                    .last()
909                    .map(|t| t.byte_offset + t.byte_length)
910                    .unwrap_or(0),
911                byte_length: 0,
912            })
913    }
914
915    fn peek_ahead(&self, n: usize) -> Token<'source> {
916        self.tokens
917            .get(self.position + n)
918            .copied()
919            .unwrap_or_else(|| Token {
920                kind: TokenKind::EOF,
921                text: "",
922                byte_offset: self
923                    .tokens
924                    .last()
925                    .map(|t| t.byte_offset + t.byte_length)
926                    .unwrap_or(0),
927                byte_length: 0,
928            })
929    }
930
931    fn consume(&mut self) -> Token<'source> {
932        let token = self.peek();
933        if self.position < self.tokens.len() {
934            self.position += 1;
935        }
936        token
937    }
938}