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 | Type)
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, attributes),
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, vec![])
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_var_initializer(&mut self, span: ast::Span) {
660        if self.too_many_errors() {
661            return;
662        }
663        let error = ParseError::new("Syntax error", span, "not allowed")
664            .with_parse_code("var_not_allowed")
665            .with_help(
666                "Use `const` for a primitive, or a function that returns the value e.g. `fn origin() -> Point { ... }` for a composite",
667            );
668
669        self.errors.push(error);
670    }
671
672    fn error_import_alias_after_path(&mut self, span: ast::Span, alias: &str, path: &str) {
673        if self.too_many_errors() {
674            return;
675        }
676        let error = ParseError::new("Syntax error", span, "import alias goes before the path")
677            .with_parse_code("import_alias_position")
678            .with_help(format!(
679                "Use Go-style alias syntax: `import {alias} \"{path}\"`"
680            ));
681
682        self.errors.push(error);
683    }
684
685    fn error_match_arm_missing_comma(&mut self, span: ast::Span) {
686        if self.too_many_errors() {
687            return;
688        }
689        let error = ParseError::new("Syntax error", span, "missing comma after match arm")
690            .with_parse_code("match_arm_missing_comma")
691            .with_help("Match arms must be separated by commas, even when the body is a block.");
692
693        self.errors.push(error);
694    }
695
696    pub(super) fn error_map_literal_not_supported(&mut self, span: ast::Span) {
697        if self.too_many_errors() {
698            return;
699        }
700        let error = ParseError::new("Invalid `Map` initialization", span, "invalid syntax")
701            .with_parse_code("invalid_map_initialization")
702            .with_help("To initialize a `Map`, use `Map.new<K, V>()` then `m[key] = value`");
703
704        self.errors.push(error);
705    }
706
707    pub(super) fn error_missing_initializer(&mut self, span: ast::Span) {
708        if self.too_many_errors() {
709            return;
710        }
711        let error = ParseError::new(
712            "Missing initializer",
713            span,
714            "annotated binding needs a value",
715        )
716        .with_parse_code("missing_initializer")
717        .with_help("Bindings must be initialized");
718
719        self.errors.push(error);
720    }
721
722    fn track_ensure_error(&mut self, expected_token: TokenKind) {
723        if self.too_many_errors() {
724            return;
725        }
726        let current = self.current_token();
727
728        let error_code = match expected_token {
729            Semicolon => "missing_semicolon",
730            RightCurlyBrace => "unclosed_block",
731            _ => "unexpected_token",
732        };
733
734        let span = ast::Span::new(self.file_id, current.byte_offset, current.byte_length);
735        let error = ParseError::new("Syntax error", span, format!("expected {}", expected_token))
736            .with_parse_code(error_code);
737
738        self.errors.push(error);
739    }
740
741    fn close_brace_span(&mut self, start: Token<'source>, error_anchor: Token<'source>) -> Span {
742        if self.is(RightCurlyBrace) {
743            let close = self.current_token();
744            self.next();
745            let end = close.byte_offset + close.byte_length;
746            Span::new(
747                self.file_id,
748                start.byte_offset,
749                end.saturating_sub(start.byte_offset),
750            )
751        } else {
752            self.error_unclosed_block(&error_anchor);
753            self.span_from_tokens(start)
754        }
755    }
756
757    fn error_unclosed_block(&mut self, open_brace: &Token) {
758        let span = ast::Span::new(self.file_id, open_brace.byte_offset, open_brace.byte_length);
759        let error = ParseError::new("Unclosed block", span, "opening brace here")
760            .with_parse_code("unclosed_block")
761            .with_help("Add a closing `}`");
762
763        self.errors.push(error);
764    }
765
766    fn error_tuple_arity(&mut self, arity: usize, span: Span) {
767        let help = if arity == 0 {
768            "Use `()` for unit type".to_string()
769        } else if arity == 1 {
770            "Use the type directly without wrapping in a tuple".to_string()
771        } else {
772            "For >5 elements, use a struct with named fields".to_string()
773        };
774
775        let error = ParseError::new(
776            "Invalid tuple",
777            span,
778            format!("{}-element tuple not allowed", arity),
779        )
780        .with_parse_code("tuple_element_count")
781        .with_help(help);
782
783        self.errors.push(error);
784    }
785
786    fn error_duplicate_field_in_pattern(
787        &mut self,
788        field_name: &str,
789        first_span: Span,
790        second_span: Span,
791    ) {
792        let error = ParseError::new(
793            "Duplicate field",
794            first_span,
795            format!("first use of `{}`", field_name),
796        )
797        .with_span_label(second_span, "used again")
798        .with_parse_code("duplicate_field_in_pattern")
799        .with_help("Remove the duplicate binding");
800
801        self.errors.push(error);
802    }
803
804    fn error_duplicate_embed_parent(&mut self, first_span: Span, second_span: Span) {
805        let error = ParseError::new("Duplicate embed", first_span, "first use")
806            .with_span_label(second_span, "used again")
807            .with_parse_code("duplicate_embed_parent")
808            .with_help("Remove the duplicate parent");
809
810        self.errors.push(error);
811    }
812
813    fn error_impl_interface_embed(&mut self, keyword_span: Span) {
814        let error = ParseError::new(
815            "Interface embedding uses `embed`",
816            keyword_span,
817            "write `embed` here",
818        )
819        .with_parse_code("impl_interface_embed")
820        .with_help("Interfaces embed other interfaces with `embed`. Replace `impl` with `embed`");
821
822        self.errors.push(error);
823    }
824
825    fn error_duplicate_struct_field(&mut self, name: &str, first_span: Span, second_span: Span) {
826        let error = ParseError::new("Duplicate field", first_span, "first defined")
827            .with_span_label(second_span, "defined again")
828            .with_parse_code("duplicate_struct_field")
829            .with_help(format!("Remove the duplicate field `{}`", name));
830
831        self.errors.push(error);
832    }
833
834    fn error_duplicate_enum_variant(&mut self, name: &str, first_span: Span, second_span: Span) {
835        let error = ParseError::new("Duplicate variant", first_span, "first defined")
836            .with_span_label(second_span, "defined again")
837            .with_parse_code("duplicate_enum_variant")
838            .with_help(format!("Remove the duplicate variant `{}`", name));
839
840        self.errors.push(error);
841    }
842
843    fn error_duplicate_interface_method(
844        &mut self,
845        name: &str,
846        first_span: Span,
847        second_span: Span,
848    ) {
849        let error = ParseError::new("Duplicate method", first_span, "first defined")
850            .with_span_label(second_span, "defined again")
851            .with_parse_code("duplicate_interface_method")
852            .with_help(format!("Remove the duplicate method `{}`", name));
853
854        self.errors.push(error);
855    }
856
857    fn error_float_pattern_not_allowed(&mut self, span: Span, float_text: &str) {
858        let error = ParseError::new("Invalid pattern", span, "float literal not allowed here")
859            .with_parse_code("float_pattern")
860            .with_help(format!(
861                "Use a guard instead: `x if x == {} =>`",
862                float_text
863            ));
864
865        self.errors.push(error);
866    }
867
868    fn error_uppercase_binding(&mut self, span: Span) {
869        let error = ParseError::new("Invalid binding name", span, "uppercase not allowed here")
870            .with_parse_code("uppercase_binding")
871            .with_help("Lowercase the binding");
872
873        self.errors.push(error);
874    }
875
876    fn error_detached_doc_comment(&mut self, span: Span) {
877        let error = ParseError::new("Unattached doc comment", span, "is detached")
878            .with_parse_code("detached_doc_comment")
879            .with_help("Place the doc comment on the line immediately above a symbol definition");
880
881        self.errors.push(error);
882    }
883
884    fn error_misplaced_attribute(&mut self, span: Span) {
885        let error = ParseError::new(
886            "Attribute not supported on target",
887            span,
888            "not supported on target",
889        )
890        .with_parse_code("misplaced_attribute")
891        .with_help("Remove the attribute, or move it onto an enum, struct, or function");
892
893        self.errors.push(error);
894    }
895
896    fn error_interface_method_with_type_parameters(&mut self, span: Span, count: usize) {
897        let label = if count == 1 {
898            "type parameter not allowed"
899        } else {
900            "type parameters not allowed"
901        };
902        let error = ParseError::new("Invalid interface method", span, label)
903            .with_parse_code("interface_method_with_type_parameters")
904            .with_help(
905                "Interface methods cannot have type parameters, because Go interfaces do not support generic methods",
906            );
907
908        self.errors.push(error);
909    }
910
911    fn error_leading_zero(&mut self) {
912        let span = self.span_from_token(self.current_token());
913        let error = ParseError::new(
914            "Invalid number literal",
915            span,
916            "leading zero in integer literal",
917        )
918        .with_parse_code("number_leading_zero")
919        .with_help("Prefix with `0o` for octal (e.g. `0o644`) or remove the leading zero");
920        self.errors.push(error);
921    }
922
923    pub(crate) fn parse_integer_text(&mut self, text: &str) -> ast::Literal {
924        self.parse_integer_text_with(text, false)
925    }
926
927    pub(crate) fn parse_integer_text_with(
928        &mut self,
929        text: &str,
930        preserve_decimal_text: bool,
931    ) -> ast::Literal {
932        let clean = if text.contains('_') {
933            std::borrow::Cow::Owned(text.replace('_', ""))
934        } else {
935            std::borrow::Cow::Borrowed(text)
936        };
937
938        let (n, is_decimal) = if clean.starts_with("0x") || clean.starts_with("0X") {
939            let value = u64::from_str_radix(&clean[2..], 16).unwrap_or_else(|_| {
940                self.track_error(
941                    format!("hex literal '{text}' is too large"),
942                    "Maximum value is `0xFFFFFFFFFFFFFFFF`.",
943                );
944                0
945            });
946            (value, false)
947        } else if clean.starts_with("0o") || clean.starts_with("0O") {
948            let value = u64::from_str_radix(&clean[2..], 8).unwrap_or_else(|_| {
949                self.track_error(
950                    format!("octal literal '{text}' is too large"),
951                    "Maximum value is `0o1777777777777777777777`.",
952                );
953                0
954            });
955            (value, false)
956        } else if clean.starts_with("0b") || clean.starts_with("0B") {
957            let value = u64::from_str_radix(&clean[2..], 2).unwrap_or_else(|_| {
958                self.track_error(
959                    format!("binary literal '{text}' is too large"),
960                    "Value must fit in 64 bits.",
961                );
962                0
963            });
964            (value, false)
965        } else if clean.len() > 1 && clean.starts_with('0') {
966            self.error_leading_zero();
967            (clean.parse().unwrap_or(0), false)
968        } else {
969            let value = clean.parse().unwrap_or_else(|_| {
970                self.track_error(
971                    format!("integer literal '{text}' is too large"),
972                    "Maximum value is `18446744073709551615`.",
973                );
974                0
975            });
976            (value, true)
977        };
978
979        let original_text = if is_decimal && !preserve_decimal_text {
980            None
981        } else {
982            Some(text.to_string())
983        };
984
985        ast::Literal::Integer {
986            value: n,
987            text: original_text,
988        }
989    }
990
991    fn unexpected_token(&mut self, ctx: &str) -> ast::Expression {
992        let token = self.current_token();
993        let token_descriptor = if token.text.is_empty() {
994            format!("{:?}", token.kind)
995        } else {
996            format!("`{}`", token.text)
997        };
998
999        let span = ast::Span::new(self.file_id, token.byte_offset, token.byte_length);
1000
1001        let (label, error_code, help) = match ctx {
1002            "expr" => (
1003                format!("expected expression, found {}", token_descriptor),
1004                "expected_expression",
1005                "Check your syntax.",
1006            ),
1007            "pattern" => (
1008                format!("unexpected {} in pattern", token_descriptor),
1009                "invalid_pattern",
1010                "Patterns include literals, variables, and destructuring.",
1011            ),
1012            "literal" => (
1013                format!("expected literal, found {}", token_descriptor),
1014                "expected_literal",
1015                "Literals include numbers, strings, characters, and booleans.",
1016            ),
1017            "top_item" if token.text == "trait" => (
1018                format!("unexpected {}", token_descriptor),
1019                "trait_unsupported",
1020                "Lisette uses `interface` with Go-style structural typing. Types automatically satisfy interfaces if they have the required methods.",
1021            ),
1022            "top_item" if token.text == "use" => (
1023                "unexpected syntax for import".to_string(),
1024                "use_unsupported",
1025                "Use `import` instead of `use` for imports: `import \"module/path\"`",
1026            ),
1027            "top_item" if token.kind == Let => (
1028                "`let` is not allowed at the top level".to_string(),
1029                "top_level_let",
1030                "Use `const` for a primitive, or a function that returns the value e.g. `fn origin() -> Point { ... }` for a composite",
1031            ),
1032            "top_item" => (
1033                "expected declaration".to_string(),
1034                "expected_declaration",
1035                "At the top level of a file, Lisette expects `fn`, `struct`, `enum`, `interface`, `impl`, `const`, `import`, or `type`.",
1036            ),
1037            _ => (
1038                format!("unexpected {}", token_descriptor),
1039                "unexpected_token",
1040                "Check your syntax.",
1041            ),
1042        };
1043
1044        let error = ParseError::new("Syntax error", span, label)
1045            .with_parse_code(error_code)
1046            .with_help(help);
1047
1048        if !self.too_many_errors() {
1049            self.errors.push(error);
1050        }
1051
1052        self.resync_on_error();
1053
1054        ast::Expression::Unit {
1055            ty: Type::uninferred(),
1056            span,
1057        }
1058    }
1059}
1060
1061struct TokenStream<'source> {
1062    tokens: Vec<Token<'source>>,
1063    position: usize,
1064    last_index: usize,
1065}
1066
1067impl<'source> TokenStream<'source> {
1068    fn new(tokens: Vec<Token<'source>>) -> Self {
1069        debug_assert!(
1070            !tokens.is_empty(),
1071            "lexer must always produce at least an EOF token",
1072        );
1073        let last_index = tokens.len() - 1;
1074        Self {
1075            tokens,
1076            position: 0,
1077            last_index,
1078        }
1079    }
1080
1081    fn peek(&self) -> Token<'source> {
1082        self.tokens[self.position]
1083    }
1084
1085    fn peek_ahead(&self, n: usize) -> Token<'source> {
1086        let idx = self.position.saturating_add(n);
1087        let idx = if idx > self.last_index {
1088            self.last_index
1089        } else {
1090            idx
1091        };
1092        self.tokens[idx]
1093    }
1094
1095    fn consume(&mut self) -> Token<'source> {
1096        let token = self.tokens[self.position];
1097        if self.position < self.last_index {
1098            self.position += 1;
1099        }
1100        token
1101    }
1102}