![标题:PEG.js 中正则字符类 [A-z] 的陷阱与变量名解析修复指南](https://img.php.cn/upload/article/001/246/273/176734675117237.jpg)
本文揭示 peg.js 语法中 `[a-z]` 字符类的常见误解——它并非仅匹配字母,而是包含 ascii 表中 `'z'`(ascii 90)到 `'a'`(ascii 97)之间的所有字符(如 `[`, `\`, `]`, `^`, `_`, `` ` ``),导致变量名意外吞并左方括号,引发 `test[` 被误识别为非法变量名的错误;并提供安全、可维护的修复方案。
在 PEG.js 中,字符类(character class)如 [A-z] 并非等价于“大小写字母”,而是一个基于 ASCII 码值的连续区间匹配。'A' 的 ASCII 值为 65,'z' 为 122,但 'Z' 是 90,'a' 是 97 —— 因此 [A-z] 实际覆盖了 A–Z(65–90)、[–(91)、\–(92)、](93)、^(94)、_(95)、`(96)以及 a–z(97–122)。这正是问题根源:当输入为 test["foobar"] 时,Varname 规则中的 [A-z0-9]+ 会贪婪匹配 test[(因为 [ 属于该范围),导致后续的 '[' 无法被 Getvar 中的属性访问语法 '[', _, exp, _, ']' 捕获,最终抛出 Variable 'test[' does not exist. 错误。
✅ 正确做法是显式限定字母范围,使用 [A-Za-z] 或更推荐的 不区分大小写的内建修饰符 i 配合 [A-Z0-9]:
Varname "variable name"
= [A-Za-z][A-Za-z0-9]* { return text(); }
// 或更简洁、语义清晰的写法:
// = [A-Z][A-Z0-9]*i { return text(); }⚠️ 注意:i 修饰符必须作用于整个字符类(如 [A-Z0-9]i),而非单个字符;且首字符应确保为字母(避免数字开头的非法标识符),因此建议拆分为「首字母」+「字母数字续字符」结构:
Varname "variable name"
= first:[A-Za-z] rest:[A-Za-z0-9]* {
const name = first + rest.join('');
if (!/[A-Za-z]/.test(first)) {
error(`Variable name must start with a letter. (got '${name}')`);
}
return name;
}此外,原 Getvar 规则末尾缺少跳过空白的 _,易导致 test["foobar"] 中引号前空格解析失败。应修正为:
Getvar
= name:Varname _ path:('[' _ exp:(String / Integer) _ ']' { return exp; })* {
let rt = glob[name];
if (rt === undefined && name !== 'undefined' && name !== 'null') {
error(`Variable '${name}' does not exist.`);
}
for (const p of path) {
rt = rt?.[p]; // 使用可选链增强健壮性
if (rt === undefined) break;
}
return rt;
}? 总结关键修复点:
- ❌ 删除 [A-z] —— 它是危险的 ASCII 区间陷阱;
- ✅ 使用 [A-Za-z] 或 [A-Z]i 明确表达“字母”意图;
- ✅ 为 Varname 添加首字符校验,禁止数字开头;
- ✅ 在所有语法连接处(如 name 与 '[' 之间)插入 _ 消除空白干扰;
- ✅ 在属性访问中加入 ?. 可选链,避免 undefined["prop"] 报错;
- ✅ 优先用规则拆分(如 Vstart/Vtail)替代复杂字符类量化,提升可读性与回溯可控性。
遵循以上原则,你的 PEG.js 解析器将准确识别 test 和 test["foobar"],并稳定支持嵌套属性访问语法。










