65 lines
3.3 KiB
Rust
65 lines
3.3 KiB
Rust
//! Rust 测试框架直接验证 lex 的原文偏移与 Token 边界,不进入语法分析、编译或 VM。
|
|
use frontend::{DiagnosticKind, Span, TokenKind, lex};
|
|
|
|
#[test]
|
|
/// 验证多 Token 和 Unicode 前缀之后的错误仍定位原文绝对字节范围,并区分数字/转义/换行/EOF/未知字符失败。
|
|
fn lexical_errors_keep_original_byte_ranges_after_multiple_tokens() {
|
|
// 固定有效前缀含多字节名称,防止只在零偏移或 ASCII 输入上通过的错误位置实现。
|
|
let prefix = "let 商品 = ";
|
|
// 各尾部给出违规片段及准确错误说明,包含 Unicode 转义、原始 CR/LF/NUL 与两种未闭合结尾。
|
|
for (tail, offending, message) in [
|
|
(
|
|
"12.50名前 ;",
|
|
"12.50名前",
|
|
"unsupported numeric suffix or missing token separator",
|
|
),
|
|
("\"日\\q\" ;", "\\q", "unsupported string escape"),
|
|
("\"日\\🌸\" ;", "\\🌸", "unsupported string escape"),
|
|
("\"日\\\n\" ;", "\\\n", "unsupported string escape"),
|
|
("\"日\nrest\" ;", "\n", "unescaped newline or NUL in string"),
|
|
("\"日\rrest\" ;", "\r", "unescaped newline or NUL in string"),
|
|
("\"日\0rest\" ;", "\0", "unescaped newline or NUL in string"),
|
|
("\"未完", "\"未完", "unterminated string"),
|
|
("\"未完\\", "\"未完\\", "unterminated string"),
|
|
("🌸 ;", "🌸", "unexpected source character"),
|
|
] {
|
|
// 把合法前缀与错误尾部拼成真实输入,后续位置必须指向完整原文。
|
|
let source = format!("{prefix}{tail}");
|
|
// 分词必须失败,不允许回溯把非法数字/字符串拆成若干合法 Token。
|
|
let error = lex(&source).unwrap_err();
|
|
// 从完整原文查找预期片段,用字节长度构造期望半开范围。
|
|
let start = source.find(offending).unwrap();
|
|
assert_eq!(error.kind, DiagnosticKind::Lexical, "{source:?}");
|
|
assert_eq!(error.span, Span::new(start, start + offending.len()));
|
|
assert_eq!(error.message, message, "{source:?}");
|
|
assert!(error.expected.is_empty());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
/// 验证注释与字符串中的双斜杠、成员点、除号和小数边界互不混淆,空白/注释输入不产生 Token。
|
|
fn token_boundaries_distinguish_members_division_comments_and_string_contents() {
|
|
// 原文混合 CRLF、Unicode、行注释中的 NUL,以及字符串内必须保留的双斜杠。
|
|
let source = " \t// 前置\r\n商品.価格 / 12.50// 行末\0\n\"//実文\" \r\n// 終端";
|
|
// lex 跳过 trivia 后只保留语法 Token,所有跨度仍指向未经修改的 source。
|
|
let tokens = lex(source).unwrap();
|
|
// 把种类与原文切片配对,既检查 Token 分类,也检查是否误吞相邻内容。
|
|
let spellings: Vec<_> = tokens
|
|
.iter()
|
|
.map(|token| (token.kind, &source[token.span.start..token.span.end]))
|
|
.collect();
|
|
assert_eq!(
|
|
spellings,
|
|
vec![
|
|
(TokenKind::Name, "商品"),
|
|
(TokenKind::Dot, "."),
|
|
(TokenKind::Name, "価格"),
|
|
(TokenKind::Slash, "/"),
|
|
(TokenKind::Number, "12.50"),
|
|
(TokenKind::String, "\"//実文\""),
|
|
]
|
|
);
|
|
assert!(lex("").unwrap().is_empty());
|
|
assert!(lex(" \t\r\n// 注釈\0").unwrap().is_empty());
|
|
}
|