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