JsLex 错误解析了哪些 JavaScript 结构?

10 投票
5 回答
925 浏览
提问于 2025-04-16 15:01

JsLex 是我用 Python 写的一个 JavaScript 词法分析器。它的表现还不错,花了一天左右的时间完成,但我相信它在某些情况下会出错。特别是,它对分号的插入规则完全不理解,而这可能对词法分析很重要。我只是还不知道具体有哪些情况。

那么,JsLex 在处理哪些 JavaScript 代码时会出错呢?我特别想知道哪些有效的 JavaScript 代码中,JsLex 错误地识别了正则表达式字面量。

为了更清楚,我所说的“词法分析”是指在源文件中识别出不同的标记。JsLex 并不尝试解析 JavaScript,更不用说执行它了。我写 JsLex 是为了进行完整的词法分析,但老实说,如果它能成功找到所有的正则表达式字面量,我也会很满意。

5 个回答

1

你处理这个复杂问题的解决方案真是太简单了,太酷了。不过我注意到,它并没有完全处理ES5中的something.property语法变化。在ES5中,保留字可以跟在.后面。比如,a.if = 'foo'; (function () {a.if /= 3;});,在一些最近的实现中这是有效的语句。

如果我没记错的话,属性的使用中其实只有一个.,所以解决这个问题的方法可以是在.后面增加一个状态,只接受identifierName这个标记(这就是identifier使用的,但它不排除保留字)。这样应该就能解决问题了。(显然,div状态在这之后是照常的。)

1

举个例子,下面第一次出现的 / 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页
7

有趣的是,我试着用你的词法分析器去分析我用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也是一样:inherits

撰写回答