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