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