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; 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_impl_parent(&mut self, first_span: Span, second_span: Span) {
805 let error = ParseError::new("Duplicate impl", first_span, "first use")
806 .with_span_label(second_span, "used again")
807 .with_parse_code("duplicate_impl_parent")
808 .with_help("Remove the duplicate parent");
809
810 self.errors.push(error);
811 }
812
813 fn error_duplicate_struct_field(&mut self, name: &str, first_span: Span, second_span: Span) {
814 let error = ParseError::new("Duplicate field", first_span, "first defined")
815 .with_span_label(second_span, "defined again")
816 .with_parse_code("duplicate_struct_field")
817 .with_help(format!("Remove the duplicate field `{}`", name));
818
819 self.errors.push(error);
820 }
821
822 fn error_duplicate_enum_variant(&mut self, name: &str, first_span: Span, second_span: Span) {
823 let error = ParseError::new("Duplicate variant", first_span, "first defined")
824 .with_span_label(second_span, "defined again")
825 .with_parse_code("duplicate_enum_variant")
826 .with_help(format!("Remove the duplicate variant `{}`", name));
827
828 self.errors.push(error);
829 }
830
831 fn error_duplicate_interface_method(
832 &mut self,
833 name: &str,
834 first_span: Span,
835 second_span: Span,
836 ) {
837 let error = ParseError::new("Duplicate method", first_span, "first defined")
838 .with_span_label(second_span, "defined again")
839 .with_parse_code("duplicate_interface_method")
840 .with_help(format!("Remove the duplicate method `{}`", name));
841
842 self.errors.push(error);
843 }
844
845 fn error_float_pattern_not_allowed(&mut self, span: Span, float_text: &str) {
846 let error = ParseError::new("Invalid pattern", span, "float literal not allowed here")
847 .with_parse_code("float_pattern")
848 .with_help(format!(
849 "Use a guard instead: `x if x == {} =>`",
850 float_text
851 ));
852
853 self.errors.push(error);
854 }
855
856 fn error_uppercase_binding(&mut self, span: Span) {
857 let error = ParseError::new("Invalid binding name", span, "uppercase not allowed here")
858 .with_parse_code("uppercase_binding")
859 .with_help("Lowercase the binding");
860
861 self.errors.push(error);
862 }
863
864 fn error_detached_doc_comment(&mut self, span: Span) {
865 let error = ParseError::new("Unattached doc comment", span, "is detached")
866 .with_parse_code("detached_doc_comment")
867 .with_help("Place the doc comment on the line immediately above a symbol definition");
868
869 self.errors.push(error);
870 }
871
872 fn error_misplaced_attribute(&mut self, span: Span) {
873 let error = ParseError::new(
874 "Attribute not supported on target",
875 span,
876 "not supported on target",
877 )
878 .with_parse_code("misplaced_attribute")
879 .with_help("Remove the attribute, or move it onto an enum, struct, or function");
880
881 self.errors.push(error);
882 }
883
884 fn error_interface_method_with_type_parameters(&mut self, span: Span, count: usize) {
885 let label = if count == 1 {
886 "type parameter not allowed"
887 } else {
888 "type parameters not allowed"
889 };
890 let error = ParseError::new("Invalid interface method", span, label)
891 .with_parse_code("interface_method_with_type_parameters")
892 .with_help(
893 "Interface methods cannot have type parameters, because Go interfaces do not support generic methods",
894 );
895
896 self.errors.push(error);
897 }
898
899 fn error_leading_zero(&mut self) {
900 let span = self.span_from_token(self.current_token());
901 let error = ParseError::new(
902 "Invalid number literal",
903 span,
904 "leading zero in integer literal",
905 )
906 .with_parse_code("number_leading_zero")
907 .with_help("Prefix with `0o` for octal (e.g. `0o644`) or remove the leading zero");
908 self.errors.push(error);
909 }
910
911 pub(crate) fn parse_integer_text(&mut self, text: &str) -> ast::Literal {
912 self.parse_integer_text_with(text, false)
913 }
914
915 pub(crate) fn parse_integer_text_with(
916 &mut self,
917 text: &str,
918 preserve_decimal_text: bool,
919 ) -> ast::Literal {
920 let clean = if text.contains('_') {
921 std::borrow::Cow::Owned(text.replace('_', ""))
922 } else {
923 std::borrow::Cow::Borrowed(text)
924 };
925
926 let (n, is_decimal) = if clean.starts_with("0x") || clean.starts_with("0X") {
927 let value = u64::from_str_radix(&clean[2..], 16).unwrap_or_else(|_| {
928 self.track_error(
929 format!("hex literal '{text}' is too large"),
930 "Maximum value is `0xFFFFFFFFFFFFFFFF`.",
931 );
932 0
933 });
934 (value, false)
935 } else if clean.starts_with("0o") || clean.starts_with("0O") {
936 let value = u64::from_str_radix(&clean[2..], 8).unwrap_or_else(|_| {
937 self.track_error(
938 format!("octal literal '{text}' is too large"),
939 "Maximum value is `0o1777777777777777777777`.",
940 );
941 0
942 });
943 (value, false)
944 } else if clean.starts_with("0b") || clean.starts_with("0B") {
945 let value = u64::from_str_radix(&clean[2..], 2).unwrap_or_else(|_| {
946 self.track_error(
947 format!("binary literal '{text}' is too large"),
948 "Value must fit in 64 bits.",
949 );
950 0
951 });
952 (value, false)
953 } else if clean.len() > 1 && clean.starts_with('0') {
954 self.error_leading_zero();
955 (clean.parse().unwrap_or(0), false)
956 } else {
957 let value = clean.parse().unwrap_or_else(|_| {
958 self.track_error(
959 format!("integer literal '{text}' is too large"),
960 "Maximum value is `18446744073709551615`.",
961 );
962 0
963 });
964 (value, true)
965 };
966
967 let original_text = if is_decimal && !preserve_decimal_text {
968 None
969 } else {
970 Some(text.to_string())
971 };
972
973 ast::Literal::Integer {
974 value: n,
975 text: original_text,
976 }
977 }
978
979 fn unexpected_token(&mut self, ctx: &str) -> ast::Expression {
980 let token = self.current_token();
981 let token_descriptor = if token.text.is_empty() {
982 format!("{:?}", token.kind)
983 } else {
984 format!("`{}`", token.text)
985 };
986
987 let span = ast::Span::new(self.file_id, token.byte_offset, token.byte_length);
988
989 let (label, error_code, help) = match ctx {
990 "expr" => (
991 format!("expected expression, found {}", token_descriptor),
992 "expected_expression",
993 "Check your syntax.",
994 ),
995 "pattern" => (
996 format!("unexpected {} in pattern", token_descriptor),
997 "invalid_pattern",
998 "Patterns include literals, variables, and destructuring.",
999 ),
1000 "literal" => (
1001 format!("expected literal, found {}", token_descriptor),
1002 "expected_literal",
1003 "Literals include numbers, strings, characters, and booleans.",
1004 ),
1005 "top_item" if token.text == "trait" => (
1006 format!("unexpected {}", token_descriptor),
1007 "trait_unsupported",
1008 "Lisette uses `interface` with Go-style structural typing. Types automatically satisfy interfaces if they have the required methods.",
1009 ),
1010 "top_item" if token.text == "use" => (
1011 "unexpected syntax for import".to_string(),
1012 "use_unsupported",
1013 "Use `import` instead of `use` for imports: `import \"module/path\"`",
1014 ),
1015 "top_item" if token.kind == Let => (
1016 "`let` is not allowed at the top level".to_string(),
1017 "top_level_let",
1018 "Use `const` for a primitive, or a function that returns the value e.g. `fn origin() -> Point { ... }` for a composite",
1019 ),
1020 "top_item" => (
1021 "expected declaration".to_string(),
1022 "expected_declaration",
1023 "At the top level of a file, Lisette expects `fn`, `struct`, `enum`, `interface`, `impl`, `const`, `import`, or `type`.",
1024 ),
1025 _ => (
1026 format!("unexpected {}", token_descriptor),
1027 "unexpected_token",
1028 "Check your syntax.",
1029 ),
1030 };
1031
1032 let error = ParseError::new("Syntax error", span, label)
1033 .with_parse_code(error_code)
1034 .with_help(help);
1035
1036 if !self.too_many_errors() {
1037 self.errors.push(error);
1038 }
1039
1040 self.resync_on_error();
1041
1042 ast::Expression::Unit {
1043 ty: Type::uninferred(),
1044 span,
1045 }
1046 }
1047}
1048
1049struct TokenStream<'source> {
1050 tokens: Vec<Token<'source>>,
1051 position: usize,
1052 last_index: usize,
1053}
1054
1055impl<'source> TokenStream<'source> {
1056 fn new(tokens: Vec<Token<'source>>) -> Self {
1057 debug_assert!(
1058 !tokens.is_empty(),
1059 "lexer must always produce at least an EOF token",
1060 );
1061 let last_index = tokens.len() - 1;
1062 Self {
1063 tokens,
1064 position: 0,
1065 last_index,
1066 }
1067 }
1068
1069 fn peek(&self) -> Token<'source> {
1070 self.tokens[self.position]
1071 }
1072
1073 fn peek_ahead(&self, n: usize) -> Token<'source> {
1074 let idx = self.position.saturating_add(n);
1075 let idx = if idx > self.last_index {
1076 self.last_index
1077 } else {
1078 idx
1079 };
1080 self.tokens[idx]
1081 }
1082
1083 fn consume(&mut self) -> Token<'source> {
1084 let token = self.tokens[self.position];
1085 if self.position < self.last_index {
1086 self.position += 1;
1087 }
1088 token
1089 }
1090}