feat: 添加对单引号字符串的支持,更新词法分析器和相关测试用例
This commit is contained in:
@@ -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
@@ -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]
|
||||||
|
|||||||
Reference in New Issue
Block a user