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        self.recover_to_comma_or(closing);
296    }
297
298    pub(super) fn recover_to_comma_or(&mut self, closing: TokenKind) {
299        while !self.at_eof() && !self.is(Comma) && !self.is(closing) && !self.at_item_boundary() {
300            self.next();
301        }
302
303        self.advance_if(Comma);
304    }
305
306    pub fn at_eof(&self) -> bool {
307        self.is(EOF)
308    }
309
310    fn at_range(&self) -> bool {
311        matches!(self.current_token().kind, DotDot | DotDotEqual)
312    }
313
314    fn advance_if(&mut self, token_kind: TokenKind) -> bool {
315        if self.is(token_kind) {
316            self.next();
317            return true;
318        }
319
320        false
321    }
322
323    fn is(&self, token_kind: TokenKind) -> bool {
324        self.current_token().kind == token_kind
325    }
326
327    fn is_not(&self, token_kind: TokenKind) -> bool {
328        if self.at_eof() {
329            return false;
330        }
331
332        self.current_token().kind != token_kind
333    }
334
335    fn ensure(&mut self, token_kind: TokenKind) {
336        if self.current_token().kind != token_kind {
337            self.track_ensure_error(token_kind);
338        }
339
340        if self.at_eof() {
341            return;
342        }
343
344        self.next();
345    }
346
347    fn ensure_progress(&mut self, start_position: usize, closing: TokenKind) {
348        if self.stream.position == start_position && self.is_not(closing) && !self.at_eof() {
349            self.next();
350        }
351    }
352
353    fn span_from_token(&self, token: Token<'source>) -> ast::Span {
354        ast::Span::new(self.file_id, token.byte_offset, token.byte_length)
355    }
356
357    fn span_from_tokens(&self, start_token: Token<'source>) -> ast::Span {
358        let end_byte_offset = self.previous_token.byte_offset + self.previous_token.byte_length;
359        let byte_length = end_byte_offset.saturating_sub(start_token.byte_offset);
360
361        ast::Span::new(self.file_id, start_token.byte_offset, byte_length)
362    }
363
364    fn span_from_offset(&self, start_byte_offset: u32) -> ast::Span {
365        let end_byte_offset = self.previous_token.byte_offset + self.previous_token.byte_length;
366        let byte_length = end_byte_offset.saturating_sub(start_byte_offset);
367
368        ast::Span::new(self.file_id, start_byte_offset, byte_length)
369    }
370
371    fn is_type_args_call(&self) -> bool {
372        let mut position = 1; // 0 is <
373        let mut depth = 1;
374
375        loop {
376            if position > MAX_LOOKAHEAD {
377                return false;
378            }
379            match self.stream.peek_ahead(position).kind {
380                LeftAngleBracket => depth += 1,
381                RightAngleBracket if depth == 1 => {
382                    let next = self.stream.peek_ahead(position + 1).kind;
383                    return next == LeftParen
384                        || (next == Dot
385                            && self.stream.peek_ahead(position + 2).kind == Identifier
386                            && self.stream.peek_ahead(position + 3).kind == LeftParen);
387                }
388                RightAngleBracket => depth -= 1,
389                LeftParen => {
390                    let mut paren_depth = 1;
391                    position += 1;
392                    while paren_depth > 0 {
393                        if position > MAX_LOOKAHEAD {
394                            return false;
395                        }
396                        match self.stream.peek_ahead(position).kind {
397                            LeftParen => paren_depth += 1,
398                            RightParen => paren_depth -= 1,
399                            EOF => return false,
400                            _ => {}
401                        }
402                        position += 1;
403                    }
404                    continue;
405                }
406                EOF | Plus | Minus | Star | Slash | Percent | EqualDouble | NotEqual
407                | AmpersandDouble | PipeDouble | Semicolon | LeftCurlyBrace | RightCurlyBrace
408                | LeftSquareBracket | RightSquareBracket => return false,
409                _ => {}
410            }
411            position += 1;
412        }
413    }
414
415    fn has_block_after_struct(&self) -> bool {
416        let mut depth = 1;
417        let mut i = 0;
418        while depth > 0 {
419            i += 1;
420            if i > MAX_LOOKAHEAD {
421                return false;
422            }
423            let token = self.stream.peek_ahead(i);
424            match token.kind {
425                LeftCurlyBrace => depth += 1,
426                RightCurlyBrace => depth -= 1,
427                EOF => return false,
428                _ => {}
429            }
430        }
431        let after = self.stream.peek_ahead(i + 1);
432        matches!(
433            after.kind,
434            LeftCurlyBrace
435                | RightParen
436                | EqualDouble
437                | NotEqual
438                | LeftAngleBracket
439                | RightAngleBracket
440                | LessThanOrEqual
441                | GreaterThanOrEqual
442                | AmpersandDouble
443                | PipeDouble
444                | Plus
445                | Minus
446                | Star
447                | Slash
448                | Percent
449        )
450    }
451
452    fn is_struct_instantiation(&self) -> bool {
453        if self.previous_token.kind != Identifier {
454            return false;
455        }
456
457        let is_uppercase = self
458            .previous_token
459            .text
460            .starts_with(|c: char| c.is_uppercase());
461        let first_ahead = self.stream.peek_ahead(1);
462
463        if first_ahead.kind == DotDot {
464            return true;
465        }
466
467        if first_ahead.kind == RightCurlyBrace {
468            if self.in_control_flow_header {
469                return is_uppercase && self.has_block_after_struct();
470            }
471            return is_uppercase;
472        }
473
474        if first_ahead.kind == Identifier {
475            let second_ahead = self.stream.peek_ahead(2);
476            return match second_ahead.kind {
477                Colon => self.stream.peek_ahead(3).kind != Colon,
478                Comma | RightCurlyBrace => {
479                    if self.in_control_flow_header {
480                        is_uppercase && self.has_block_after_struct()
481                    } else {
482                        is_uppercase
483                    }
484                }
485                _ => false,
486            };
487        }
488
489        false
490    }
491
492    fn enter_recursion(&mut self) -> bool {
493        if self.depth >= MAX_DEPTH {
494            let span = self.span_from_token(self.current_token());
495            self.track_error_at(span, "too deeply nested", "Reduce nesting depth");
496            return false;
497        }
498        self.depth += 1;
499        true
500    }
501
502    fn leave_recursion(&mut self) {
503        self.depth -= 1;
504    }
505
506    fn too_many_errors(&self) -> bool {
507        self.errors.len() >= MAX_ERRORS
508    }
509
510    fn position(&self) -> u32 {
511        self.current_token().byte_offset
512    }
513
514    fn at_sync_point(&self) -> bool {
515        matches!(
516            self.current_token().kind,
517            Semicolon
518                | RightCurlyBrace
519                | RightParen
520                | RightSquareBracket
521                | Comma
522                | Function
523                | Struct
524                | Enum
525                | Const
526                | Impl
527                | Interface
528                | Type
529                | Import
530        )
531    }
532
533    fn can_start_annotation(&self) -> bool {
534        matches!(self.current_token().kind, Identifier | Function | LeftParen)
535    }
536
537    fn at_item_boundary(&self) -> bool {
538        matches!(
539            self.current_token().kind,
540            Let | Function | Struct | Enum | Impl | Interface | Type | Const | Import
541        )
542    }
543
544    fn at_match_arm_terminator(&self) -> bool {
545        self.at_eof() || self.is(Comma) || self.is(RightCurlyBrace) || self.at_item_boundary()
546    }
547
548    fn resync_on_error(&mut self) {
549        if !self.at_eof() {
550            self.next();
551        }
552
553        while !self.at_sync_point() && !self.at_eof() {
554            self.next();
555        }
556    }
557
558    fn track_error(
559        &mut self,
560        label: impl Into<std::string::String>,
561        help: impl Into<std::string::String>,
562    ) {
563        let current = self.current_token();
564        let span = ast::Span::new(self.file_id, current.byte_offset, current.byte_length);
565        self.track_error_at(span, label, help);
566    }
567
568    fn track_error_at(
569        &mut self,
570        span: ast::Span,
571        label: impl Into<std::string::String>,
572        help: impl Into<std::string::String>,
573    ) {
574        if self.too_many_errors() {
575            return;
576        }
577        let error = ParseError::new("Syntax error", span, label.into())
578            .with_parse_code("syntax_error")
579            .with_help(help.into());
580
581        self.errors.push(error);
582    }
583
584    fn error_import_alias_after_path(&mut self, span: ast::Span, alias: &str, path: &str) {
585        if self.too_many_errors() {
586            return;
587        }
588        let error = ParseError::new("Syntax error", span, "import alias goes before the path")
589            .with_parse_code("import_alias_position")
590            .with_help(format!(
591                "Use Go-style alias syntax: `import {alias} \"{path}\"`"
592            ));
593
594        self.errors.push(error);
595    }
596
597    fn error_match_arm_missing_comma(&mut self, span: ast::Span) {
598        if self.too_many_errors() {
599            return;
600        }
601        let error = ParseError::new("Syntax error", span, "missing comma after match arm")
602            .with_parse_code("match_arm_missing_comma")
603            .with_help("Match arms must be separated by commas, even when the body is a block.");
604
605        self.errors.push(error);
606    }
607
608    fn track_ensure_error(&mut self, expected_token: TokenKind) {
609        if self.too_many_errors() {
610            return;
611        }
612        let current = self.current_token();
613
614        let error_code = match expected_token {
615            Semicolon => "missing_semicolon",
616            RightCurlyBrace => "unclosed_block",
617            _ => "unexpected_token",
618        };
619
620        let span = ast::Span::new(self.file_id, current.byte_offset, current.byte_length);
621        let error = ParseError::new("Syntax error", span, format!("expected {}", expected_token))
622            .with_parse_code(error_code);
623
624        self.errors.push(error);
625    }
626
627    fn close_brace_span(&mut self, start: Token<'source>, error_anchor: Token<'source>) -> Span {
628        if self.is(RightCurlyBrace) {
629            let close = self.current_token();
630            self.next();
631            let end = close.byte_offset + close.byte_length;
632            Span::new(
633                self.file_id,
634                start.byte_offset,
635                end.saturating_sub(start.byte_offset),
636            )
637        } else {
638            self.error_unclosed_block(&error_anchor);
639            self.span_from_tokens(start)
640        }
641    }
642
643    fn error_unclosed_block(&mut self, open_brace: &Token) {
644        let span = ast::Span::new(self.file_id, open_brace.byte_offset, open_brace.byte_length);
645        let error = ParseError::new("Unclosed block", span, "opening brace here")
646            .with_parse_code("unclosed_block")
647            .with_help("Add a closing `}`");
648
649        self.errors.push(error);
650    }
651
652    fn error_tuple_arity(&mut self, arity: usize, span: Span) {
653        let help = if arity == 0 {
654            "Use `()` for unit type".to_string()
655        } else if arity == 1 {
656            "Use the type directly without wrapping in a tuple".to_string()
657        } else {
658            "For >5 elements, use a struct with named fields".to_string()
659        };
660
661        let error = ParseError::new(
662            "Invalid tuple",
663            span,
664            format!("{}-element tuple not allowed", arity),
665        )
666        .with_parse_code("tuple_element_count")
667        .with_help(help);
668
669        self.errors.push(error);
670    }
671
672    fn error_duplicate_field_in_pattern(
673        &mut self,
674        field_name: &str,
675        first_span: Span,
676        second_span: Span,
677    ) {
678        let error = ParseError::new(
679            "Duplicate field",
680            first_span,
681            format!("first use of `{}`", field_name),
682        )
683        .with_span_label(second_span, "used again")
684        .with_parse_code("duplicate_field_in_pattern")
685        .with_help("Remove the duplicate binding");
686
687        self.errors.push(error);
688    }
689
690    fn error_duplicate_impl_parent(&mut self, first_span: Span, second_span: Span) {
691        let error = ParseError::new("Duplicate impl", first_span, "first use")
692            .with_span_label(second_span, "used again")
693            .with_parse_code("duplicate_impl_parent")
694            .with_help("Remove the duplicate parent");
695
696        self.errors.push(error);
697    }
698
699    fn error_duplicate_struct_field(&mut self, name: &str, first_span: Span, second_span: Span) {
700        let error = ParseError::new("Duplicate field", first_span, "first defined")
701            .with_span_label(second_span, "defined again")
702            .with_parse_code("duplicate_struct_field")
703            .with_help(format!("Remove the duplicate field `{}`", name));
704
705        self.errors.push(error);
706    }
707
708    fn error_duplicate_enum_variant(&mut self, name: &str, first_span: Span, second_span: Span) {
709        let error = ParseError::new("Duplicate variant", first_span, "first defined")
710            .with_span_label(second_span, "defined again")
711            .with_parse_code("duplicate_enum_variant")
712            .with_help(format!("Remove the duplicate variant `{}`", name));
713
714        self.errors.push(error);
715    }
716
717    fn error_duplicate_interface_method(
718        &mut self,
719        name: &str,
720        first_span: Span,
721        second_span: Span,
722    ) {
723        let error = ParseError::new("Duplicate method", first_span, "first defined")
724            .with_span_label(second_span, "defined again")
725            .with_parse_code("duplicate_interface_method")
726            .with_help(format!("Remove the duplicate method `{}`", name));
727
728        self.errors.push(error);
729    }
730
731    fn error_float_pattern_not_allowed(&mut self, span: Span, float_text: &str) {
732        let error = ParseError::new("Invalid pattern", span, "float literal not allowed here")
733            .with_parse_code("float_pattern")
734            .with_help(format!(
735                "Use a guard instead: `x if x == {} =>`",
736                float_text
737            ));
738
739        self.errors.push(error);
740    }
741
742    fn error_uppercase_binding(&mut self, span: Span) {
743        let error = ParseError::new("Invalid binding name", span, "uppercase not allowed here")
744            .with_parse_code("uppercase_binding")
745            .with_help("Lowercase the binding");
746
747        self.errors.push(error);
748    }
749
750    fn error_detached_doc_comment(&mut self, span: Span) {
751        let error = ParseError::new("Unattached doc comment", span, "is detached")
752            .with_parse_code("detached_doc_comment")
753            .with_help("Place the doc comment on the line immediately above a symbol definition");
754
755        self.errors.push(error);
756    }
757
758    fn error_interface_method_with_type_parameters(&mut self, span: Span, count: usize) {
759        let label = if count == 1 {
760            "type parameter not allowed"
761        } else {
762            "type parameters not allowed"
763        };
764        let error = ParseError::new("Invalid interface method", span, label)
765            .with_parse_code("interface_method_with_type_parameters")
766            .with_help(
767                "Interface methods cannot have type parameters, because Go interfaces do not support generic methods",
768            );
769
770        self.errors.push(error);
771    }
772
773    pub(crate) fn parse_integer_text(&mut self, text: &str) -> ast::Literal {
774        self.parse_integer_text_with(text, false)
775    }
776
777    pub(crate) fn parse_integer_text_with(
778        &mut self,
779        text: &str,
780        preserve_decimal_text: bool,
781    ) -> ast::Literal {
782        let clean = if text.contains('_') {
783            std::borrow::Cow::Owned(text.replace('_', ""))
784        } else {
785            std::borrow::Cow::Borrowed(text)
786        };
787
788        let (n, is_decimal) = if clean.starts_with("0x") || clean.starts_with("0X") {
789            let value = u64::from_str_radix(&clean[2..], 16).unwrap_or_else(|_| {
790                self.track_error(
791                    format!("hex literal '{text}' is too large"),
792                    "Maximum value is `0xFFFFFFFFFFFFFFFF`.",
793                );
794                0
795            });
796            (value, false)
797        } else if clean.starts_with("0o") || clean.starts_with("0O") {
798            let value = u64::from_str_radix(&clean[2..], 8).unwrap_or_else(|_| {
799                self.track_error(
800                    format!("octal literal '{text}' is too large"),
801                    "Maximum value is `0o1777777777777777777777`.",
802                );
803                0
804            });
805            (value, false)
806        } else if clean.starts_with("0b") || clean.starts_with("0B") {
807            let value = u64::from_str_radix(&clean[2..], 2).unwrap_or_else(|_| {
808                self.track_error(
809                    format!("binary literal '{text}' is too large"),
810                    "Value must fit in 64 bits.",
811                );
812                0
813            });
814            (value, false)
815        } else if clean.len() > 1
816            && clean.starts_with('0')
817            && clean.chars().skip(1).all(|c| c.is_ascii_digit())
818        {
819            let value = u64::from_str_radix(&clean[1..], 8).unwrap_or_else(|_| {
820                self.track_error(
821                    format!("octal literal '{text}' is too large"),
822                    "Maximum value is `01777777777777777777777`.",
823                );
824                0
825            });
826            (value, false)
827        } else {
828            let value = clean.parse().unwrap_or_else(|_| {
829                self.track_error(
830                    format!("integer literal '{text}' is too large"),
831                    "Maximum value is `18446744073709551615`.",
832                );
833                0
834            });
835            (value, true)
836        };
837
838        let original_text = if is_decimal && !preserve_decimal_text {
839            None
840        } else {
841            Some(text.to_string())
842        };
843
844        ast::Literal::Integer {
845            value: n,
846            text: original_text,
847        }
848    }
849
850    fn unexpected_token(&mut self, ctx: &str) -> ast::Expression {
851        let token = self.current_token();
852        let token_descriptor = if token.text.is_empty() {
853            format!("{:?}", token.kind)
854        } else {
855            format!("`{}`", token.text)
856        };
857
858        let span = ast::Span::new(self.file_id, token.byte_offset, token.byte_length);
859
860        let (label, error_code, help) = match ctx {
861            "expr" => (
862                format!("expected expression, found {}", token_descriptor),
863                "expected_expression",
864                "Check your syntax.",
865            ),
866            "pattern" => (
867                format!("unexpected {} in pattern", token_descriptor),
868                "invalid_pattern",
869                "Patterns include literals, variables, and destructuring.",
870            ),
871            "literal" => (
872                format!("expected literal, found {}", token_descriptor),
873                "expected_literal",
874                "Literals include numbers, strings, characters, and booleans.",
875            ),
876            "top_item" if token.text == "trait" => (
877                format!("unexpected {}", token_descriptor),
878                "trait_unsupported",
879                "Lisette uses `interface` with Go-style structural typing. Types automatically satisfy interfaces if they have the required methods.",
880            ),
881            "top_item" if token.text == "use" => (
882                "unexpected syntax for import".to_string(),
883                "use_unsupported",
884                "Use `import` instead of `use` for imports: `import \"module/path\"`",
885            ),
886            "top_item" => (
887                "expected declaration".to_string(),
888                "expected_declaration",
889                "At the top level of a file, Lisette expects `fn`, `struct`, `enum`, `interface`, `import`, or `type`.",
890            ),
891            _ => (
892                format!("unexpected {}", token_descriptor),
893                "unexpected_token",
894                "Check your syntax.",
895            ),
896        };
897
898        let error = ParseError::new("Syntax error", span, label)
899            .with_parse_code(error_code)
900            .with_help(help);
901
902        if !self.too_many_errors() {
903            self.errors.push(error);
904        }
905
906        self.resync_on_error();
907
908        ast::Expression::Unit {
909            ty: Type::uninferred(),
910            span,
911        }
912    }
913}
914
915struct TokenStream<'source> {
916    tokens: Vec<Token<'source>>,
917    position: usize,
918}
919
920impl<'source> TokenStream<'source> {
921    fn new(tokens: Vec<Token<'source>>) -> Self {
922        Self {
923            tokens,
924            position: 0,
925        }
926    }
927
928    fn peek(&self) -> Token<'source> {
929        self.tokens
930            .get(self.position)
931            .copied()
932            .unwrap_or_else(|| Token {
933                kind: TokenKind::EOF,
934                text: "",
935                byte_offset: self
936                    .tokens
937                    .last()
938                    .map(|t| t.byte_offset + t.byte_length)
939                    .unwrap_or(0),
940                byte_length: 0,
941            })
942    }
943
944    fn peek_ahead(&self, n: usize) -> Token<'source> {
945        self.tokens
946            .get(self.position + n)
947            .copied()
948            .unwrap_or_else(|| Token {
949                kind: TokenKind::EOF,
950                text: "",
951                byte_offset: self
952                    .tokens
953                    .last()
954                    .map(|t| t.byte_offset + t.byte_length)
955                    .unwrap_or(0),
956                byte_length: 0,
957            })
958    }
959
960    fn consume(&mut self) -> Token<'source> {
961        let token = self.peek();
962        if self.position < self.tokens.len() {
963            self.position += 1;
964        }
965        token
966    }
967}