Skip to main content

lisette_syntax/parse/
mod.rs

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