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