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