From 99c9e9c9bde8fc4f2e47583cb8a0fe86512239ea Mon Sep 17 00:00:00 2001 From: 0264408 Date: Wed, 17 Jun 2026 17:26:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=9D=97=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=E5=8F=8A=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lexer/lexer.rs | 29 ++++++++++++++++++++++++++ src/lexer/tests.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/src/lexer/lexer.rs b/src/lexer/lexer.rs index a79f0de..a3a4042 100644 --- a/src/lexer/lexer.rs +++ b/src/lexer/lexer.rs @@ -92,6 +92,9 @@ impl Lexer { self.advance(); } return None; + } else if self.match_char('*') { + // 多行注释 /* ... */ + return self.block_comment(line, column, errors); } else if self.match_char('=') { TokenKind::SlashEqual } else { @@ -225,6 +228,32 @@ impl Lexer { self.current >= self.src.len() } + /// consume multi-line comment, tracking line numbers for error reporting + fn block_comment( + &mut self, + start_line: usize, + start_column: usize, + errors: &mut Vec, + ) -> Option { + while !self.is_at_end() { + let c = self.advance(); + if c == '\n' { + self.line += 1; + self.column = 1; + } else if c == '*' && !self.is_at_end() && self.peek() == '/' { + self.advance(); // consume closing '/' + return None; + } + } + // EOF in block comment + errors.push(RuntimeError::lex( + "Unterminated block comment (missing */)", + start_line, + start_column, + )); + None + } + fn string_literal( &mut self, line: usize, diff --git a/src/lexer/tests.rs b/src/lexer/tests.rs index 7720971..9be9976 100644 --- a/src/lexer/tests.rs +++ b/src/lexer/tests.rs @@ -282,6 +282,58 @@ fn comment_between_tokens() { assert_eq!(kinds, vec![TokenKind::Number(1.0), TokenKind::Number(2.0)]); } +// ----- block comments /* ... */ ----- + +#[test] +fn block_comment_ignored() { + let kinds = tokenize("42 /* comment */ 99"); + assert_eq!(kinds, vec![TokenKind::Number(42.0), TokenKind::Number(99.0)]); +} + +#[test] +fn empty_block_comment() { + let kinds = tokenize("42/**/99"); + assert_eq!(kinds, vec![TokenKind::Number(42.0), TokenKind::Number(99.0)]); +} + +#[test] +fn block_comment_multiline() { + let kinds = tokenize("1 /* line 1\nline 2\nline 3 */ 2"); + assert_eq!(kinds, vec![TokenKind::Number(1.0), TokenKind::Number(2.0)]); +} + +#[test] +fn block_comment_only() { + let kinds = tokenize("/* just a block comment */"); + assert!(kinds.is_empty()); +} + +#[test] +fn block_comment_with_asterisks_inside() { + let kinds = tokenize("1 /*** weird ***/ 2"); + assert_eq!(kinds, vec![TokenKind::Number(1.0), TokenKind::Number(2.0)]); +} + +#[test] +fn block_comment_line_numbers_tracked() { + let (tokens, _errors) = tokenize_full("/* line1\nline2 */\n42"); + let num = tokens.iter().find(|t| matches!(t.kind, TokenKind::Number(_))).unwrap(); + assert_eq!(num.line, 3); +} + +#[test] +fn unterminated_block_comment() { + let (_, errors) = tokenize_full("/* no closing"); + assert_eq!(errors.len(), 1); + assert!(errors[0].to_string().contains("Unterminated block comment")); +} + +#[test] +fn block_then_line_comment() { + let kinds = tokenize("/* block */ // line\n42"); + assert_eq!(kinds, vec![TokenKind::Number(42.0)]); +} + // ----- whitespace ----- #[test]