JsLex 错误解析了哪些 JavaScript 结构?
JsLex 是我用 Python 写的一个 JavaScript 词法分析器。它的表现还不错,花了一天左右的时间完成,但我相信它在某些情况下会出错。特别是,它对分号的插入规则完全不理解,而这可能对词法分析很重要。我只是还不知道具体有哪些情况。
那么,JsLex 在处理哪些 JavaScript 代码时会出错呢?我特别想知道哪些有效的 JavaScript 代码中,JsLex 错误地识别了正则表达式字面量。
为了更清楚,我所说的“词法分析”是指在源文件中识别出不同的标记。JsLex 并不尝试解析 JavaScript,更不用说执行它了。我写 JsLex 是为了进行完整的词法分析,但老实说,如果它能成功找到所有的正则表达式字面量,我也会很满意。
5 个回答
你处理这个复杂问题的解决方案真是太简单了,太酷了。不过我注意到,它并没有完全处理ES5中的something.property
语法变化。在ES5中,保留字可以跟在.
后面。比如,a.if = 'foo'; (function () {a.if /= 3;});
,在一些最近的实现中这是有效的语句。
如果我没记错的话,属性的使用中其实只有一个.
,所以解决这个问题的方法可以是在.
后面增加一个状态,只接受identifierName这个标记(这就是identifier使用的,但它不排除保留字)。这样应该就能解决问题了。(显然,div状态在这之后是照常的。)
举个例子,下面第一次出现的 / 2 /i
(赋值给 a
)应该被分解成 除法、数字字面量、除法、标识符,因为它是在 输入元素除法 的上下文中。而第二次出现的(赋值给 b
)应该被分解成 正则表达式字面量,因为它是在 输入元素正则表达式 的上下文中。
i = 1;
var a = 1 / 2 /i;
console.info(a); // ⇒ 0.5
console.info(typeof a); // number
var b = 1 + / 2 /i;
console.info(b); // ⇒ 1/2/i
console.info(typeof b); // ⇒ string
来源:
在词法语法中,有两个目标符号。输入元素除法符号用于那些允许使用除法(
/
)或除法赋值(/=
)运算符的语法上下文中。而 输入元素正则表达式符号则用于其他语法上下文。需要注意的是,在某些语法上下文中,既允许使用除法,也允许使用 正则表达式字面量;但是,由于词法语法在这种情况下使用的是 输入元素除法 目标符号,因此开头的斜杠不会被识别为正则表达式字面量的开始。作为一种解决方法,可以将正则表达式字面量放在括号中。
— 标准 ECMA-262 第三版 - 1999年12月,第11页
有趣的是,我试着用你的词法分析器去分析我用JS写的词法分析器/求值器的代码;)你说得对,它在处理正则表达式时并不总是表现得很好。这里有一些例子:
rexl.re = {
NAME: /^(?!\d)(?:\w)+|^"(?:[^"]|"")+"/,
UNQUOTED_LITERAL: /^@(?:(?!\d)(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/,
QUOTED_LITERAL: /^'(?:[^']|'')*'/,
NUMERIC_LITERAL: /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/,
SYMBOL: /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/
};
这个例子大体上是好的,只有UNQUITED_LITERAL
没有被识别,其他的都没问题。不过现在我们来稍微改动一下:
rexl.re = {
NAME: /^(?!\d)(?:\w)+|^"(?:[^"]|"")+"/,
UNQUOTED_LITERAL: /^@(?:(?!\d)(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/,
QUOTED_LITERAL: /^'(?:[^']|'')*'/,
NUMERIC_LITERAL: /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/,
SYMBOL: /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/
};
str = '"';
现在在NAME's
的正则表达式之后,所有的内容都搞混了。它把所有的东西都当成了一个大字符串。我觉得后面的问题是字符串的标记太贪心了。前面的问题可能是regex
标记的正则表达式太聪明了。
编辑:我想我修复了regex
标记的正则表达式。在你的代码中,把146到153行(整个“后续字符”部分)替换成以下表达式:
([^/]|(?<!\\)(?<=\\)/)*
这个想法是允许所有字符,除了/
,同时允许\/
,但不允许\\/
。
编辑:另一个有趣的案例,修复后通过了,但可能作为内置测试用例添加会很有意思:
case 'UNQUOTED_LITERAL':
case 'QUOTED_LITERAL': {
this._js = "e.str(\"" + this.value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\")";
break;
}
编辑:又一个案例。它似乎对关键字也太贪心了。看看这个案例:
var clazz = function() {
if (clazz.__) return delete(clazz.__);
this.constructor = clazz;
if(constructor)
constructor.apply(this, arguments);
};
它把它分析成:(keyword, const), (id, ructor)
。对于标识符inherits
也是一样:in
和herits
。