我目前正在使用 JavaCC开发JavaScript/ECMAScript 5.1 解析器。RegularExpressionLiteral和自动分号插入是让我在 ECMAScript 语法中疯狂的两件事。这个问题和答案对于正则表达式问题非常宝贵。在这个答案中,我想把我自己的发现放在一起。
TL;DR在 JavaCC 中,使用词法状态并从解析器切换它们。
非常重要的是汤姆布莱克写道:
除法运算符必须跟在表达式后面,而正则表达式文字不能跟在表达式后面,因此在所有其他情况下,您可以放心地假设您正在查看正则表达式文字。
所以你实际上需要了解它是否是一个表达式before。这在解析器中是微不足道的,但在词法分析器中却非常困难。
正如 Thom指出的那样,在许多(但不幸的是,并非全部)情况下,您可以通过“查看”最后一个标记来理解它是否是一个表达式。您必须考虑标点符号和关键字。
让我们从关键字开始。以下关键字不能在 a 之前DivPunctuator
(例如,您不能有case /5
),因此如果您/
在这些关键字之后看到 a,则您有一个RegularExpressionLiteral
:
case
delete
do
else
in
instanceof
new
return
throw
typeof
void
接下来是标点符号。以下标点符号不能在 a 之前DivPunctuator
(例如, { /a...
在符号/
中永远不能开始除法):
{ ( [
. ; , < > <=
>= == != === !==
+ - * %
<< >> >>> & | ^
! ~ && || ? :
= += -= *= %= <<=
>>= >>>= &= |= ^=
/=
因此,如果您拥有其中一个并/...
在此之后查看,则 this 永远不可能是 a DivPunctuator
,因此必须是 a RegularExpressionLiteral
。
接下来,如果您有:
/
/...
之后它也必须是一个RegularExpressionLiteral
. 如果这些斜线(即 )之间没有空格 // ...
,则必须将其作为SingleLineComment
(“最大咀嚼”)处理。
接下来,以下标点符号只能结束一个表达式:
]
所以下面/
必须开始一个DivPunctuator
.
现在我们有以下剩余的情况,不幸的是,这些情况模棱两可:
}
)
++
--
对于}
and)
你必须知道他们是否结束一个表达式,因为++
and --
- 他们结束一个PostfixExpression
或开始一个UnaryExpression
.
我得出的结论是,很难(如果不是不可能的话)在词法分析器中找到。为了让您了解这一点,举几个例子。
在这个例子中:
{}/a/g
/a/g
是一个RegularExpressionLiteral
,但在这个:
+{}/a/g
/a/g
是一个部门。
如果)
你可以有一个部门:
('a')/a/g
以及一个RegularExpressionLiteral
:
if ('a')/a/g
因此,不幸的是,您似乎无法单独使用词法分析器来解决它。或者你必须在词法分析器中引入如此多的语法,这样它就不再是词法分析器了。
这是个问题。
现在,一个可能的解决方案,在我的例子中是基于 JavaCC 的。
我不确定您在其他解析器生成器中是否有类似的功能,但 JavaCC 有一个词法状态功能,可用于在“我们期望一个DivPunctuator
”和“我们期望一个RegularExpressionLiteral
”状态之间切换。例如,在这个语法中,NOREGEXP
状态的意思是“我们不期望在RegularExpressionLiteral
这里”。
这解决了部分问题,但不能解决模棱两可)
的}
,++
和--
.
为此,您需要能够从解析器切换词法状态。这是可能的,请参阅JavaCC FAQ中的以下问题:
解析器可以强制切换到新的词法状态吗?
是的,但是这样做很容易产生错误。
前瞻解析器可能已经在令牌流中走得太远(即已经读/
作 aDIV
或反之亦然)。
幸运的是,似乎有一种方法可以让切换词法状态更安全:
有没有办法让 SwitchTo 更安全?
这个想法是制作一个“备份”令牌流并再次推送在前瞻期间读取的令牌。
我认为这应该适用于}
, )
, ++
,--
因为它们通常出现在 LOOKAHEAD(1) 情况下,但我不能 100% 确定这一点。在最坏的情况下,词法分析器可能已经尝试将/
-starting 标记解析为 aRegularExpressionLiteral
并失败,因为它没有被另一个 终止/
。
无论如何,我认为没有更好的方法可以做到这一点。下一件好事可能是完全放弃案例(就像JSLint
许多其他人所做的那样),记录并且不解析这些类型的表达式。{}/a/g
反正也没多大意义。