feat: 添加对单引号字符串的支持,更新词法分析器和相关测试用例

This commit is contained in:
0264408
2026-06-16 19:16:48 +08:00
parent 5e431dbba4
commit fdd42fce19
2 changed files with 85 additions and 5 deletions
+24
View File
@@ -750,6 +750,30 @@ mod tests {
} }
} }
#[test]
fn eval_single_quoted_string() {
match eval_expr("'hello'") {
Value::String(s) => assert_eq!(s, "hello"),
v => panic!("Expected String, got {:?}", v),
}
}
#[test]
fn eval_mixed_quotes_concat() {
match eval_expr("\"a\" + 'b'") {
Value::String(s) => assert_eq!(s, "ab"),
v => panic!("Expected String, got {:?}", v),
}
}
#[test]
fn eval_single_quote_containing_double() {
match eval_expr("'he\"llo'") {
Value::String(s) => assert_eq!(s, "he\"llo"),
v => panic!("Expected String, got {:?}", v),
}
}
#[test] #[test]
fn eval_string_plus_number() { fn eval_string_plus_number() {
match eval_expr("\"a\" + 42") { match eval_expr("\"a\" + 42") {
+61 -5
View File
@@ -157,7 +157,7 @@ impl Lexer {
} }
} }
'"' => return self.string_literal(line, column, errors), '"' | '\'' => return self.string_literal(line, column, errors, c),
c if c.is_ascii_digit() => { c if c.is_ascii_digit() => {
return Some(self.number_literal(c, line, column)); return Some(self.number_literal(c, line, column));
@@ -230,10 +230,11 @@ impl Lexer {
line: usize, line: usize,
column: usize, column: usize,
errors: &mut Vec<RuntimeError>, errors: &mut Vec<RuntimeError>,
quote: char,
) -> Option<Token> { ) -> Option<Token> {
let mut value = String::new(); let mut value = String::new();
while !self.is_at_end() && self.peek() != '"' { while !self.is_at_end() && self.peek() != quote {
let c = self.advance(); let c = self.advance();
if c == '\\' { if c == '\\' {
// Handle escape sequences // Handle escape sequences
@@ -246,6 +247,7 @@ impl Lexer {
't' => value.push('\t'), 't' => value.push('\t'),
'r' => value.push('\r'), 'r' => value.push('\r'),
'"' => value.push('"'), '"' => value.push('"'),
'\'' => value.push('\''),
'\\' => value.push('\\'), '\\' => value.push('\\'),
other => { other => {
errors.push(RuntimeError::lex( errors.push(RuntimeError::lex(
@@ -254,11 +256,11 @@ impl Lexer {
column, column,
)); ));
// Consume the rest of the string to avoid cascading errors // Consume the rest of the string to avoid cascading errors
while !self.is_at_end() && self.peek() != '"' { while !self.is_at_end() && self.peek() != quote {
self.advance(); self.advance();
} }
if !self.is_at_end() { if !self.is_at_end() {
self.advance(); // consume closing " self.advance(); // consume closing quote
} }
return None; return None;
} }
@@ -273,7 +275,7 @@ impl Lexer {
return None; return None;
} }
self.advance(); // consume closing " self.advance(); // consume closing quote
Some(Token { Some(Token {
kind: TokenKind::String(value), kind: TokenKind::String(value),
@@ -522,6 +524,60 @@ mod tests {
assert!(errors[0].to_string().contains("Unknown escape")); assert!(errors[0].to_string().contains("Unknown escape"));
} }
// ----- single-quoted strings -----
#[test]
fn single_quoted_string() {
let kinds = tokenize("'hello'");
assert_eq!(kinds, vec![TokenKind::String("hello".into())]);
}
#[test]
fn single_quoted_empty_string() {
let kinds = tokenize("''");
assert_eq!(kinds, vec![TokenKind::String("".into())]);
}
#[test]
fn single_quoted_with_escape() {
let kinds = tokenize("'a\\'b'");
assert_eq!(kinds, vec![TokenKind::String("a'b".into())]);
}
#[test]
fn single_quoted_with_newline_escape() {
let kinds = tokenize("'a\\nb'");
assert_eq!(kinds, vec![TokenKind::String("a\nb".into())]);
}
#[test]
fn mixed_quote_strings() {
let kinds = tokenize("\"double\" 'single'");
assert_eq!(kinds, vec![
TokenKind::String("double".into()),
TokenKind::String("single".into()),
]);
}
#[test]
fn single_quote_can_contain_double_quote() {
let kinds = tokenize("'he\"llo'");
assert_eq!(kinds, vec![TokenKind::String("he\"llo".into())]);
}
#[test]
fn double_quote_can_contain_single_quote() {
let kinds = tokenize("\"it's\"");
assert_eq!(kinds, vec![TokenKind::String("it's".into())]);
}
#[test]
fn unterminated_single_quoted_string() {
let (_, errors) = tokenize_full("'unterminated");
assert_eq!(errors.len(), 1);
assert!(errors[0].to_string().contains("Unterminated"));
}
// ----- identifiers and keywords ----- // ----- identifiers and keywords -----
#[test] #[test]