我目前正在使用JavaCC开发一个
JavaScript/ECMAScript 5.1解析器。
RegularExpressionLiteral和
Automatic Semicolon Insertion是ECMAScript语法中让我感到困惑的两个问题。这个问题和答案对于正则表达式问题非常宝贵。在这个答案中,我想把自己的发现整理在一起。
简而言之,在JavaCC中使用
词法状态并
从解析器中切换。
Thom Blake所写的内容非常重要:
除了正则表达式字面量外,除法运算符必须跟在一个表达式后面。因此,在其他情况下,您可以安全地假设您正在查看一个正则表达式字面量。
因此,实际上您需要在之前了解它是否为表达式。在解析器中这很简单,但在词法分析器中却非常困难。
正如Thom所指出的那样,在许多情况下(但不幸的是,并非所有情况),您可以通过“查看”最后一个标记来确定是否为表达式。您必须考虑标点符号以及关键字。
让我们从关键字开始。以下关键字不能在DivPunctuator
之前出现(例如,您不能有case /5
),因此如果在这些关键字后面看到/
,则表示您有一个RegularExpressionLiteral
:
case
delete
do
else
in
instanceof
new
return
throw
typeof
void
接下来是标点符号。以下标点符号不能在DivPunctuator
之前出现(例如在{ /a...
中,符号/
永远不能开始除法):
{ ( [
. ; , < > <=
>= == != === !==
+ - * %
<< >> >>> & | ^
! ~ && || ? :
= += -= *= %= <<=
>>= >>>= &= |= ^=
/=
如果您有其中之一,并在此之后看到
/ ...
,那么这绝不能是
DivPunctuator
,因此必须是
RegularExpressionLiteral
。
接下来,如果您有:
/
接着,紧随其后的必须是一个RegularExpressionLiteral
。如果这些斜杠之间没有空格(例如// ...
),则必须将其视为SingleLineComment
(“最大匹配”)。
接下来,以下标点符号只能结束表达式:
]
所以接下来的
/
必须开始一个
DivPunctuator
。
现在我们还有以下几种情况是不幸的二义性:
}
)
++
对于
}
和
)
,你需要知道它们是否结束一个表达式,对于
++
和
--
,它们会结束一个
后缀表达式
或者开始一个
一元表达式
。
我得出结论,在词法分析器中很难(如果不是不可能)找出。为了让你有所感觉,以下是几个例子。
在这个例子中:
{}/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中的以下问题:
解析器是否可以强制切换到新的词法状态?
是的,但这样做很容易产生错误。
向前看解析器可能已经在令牌流中走得太远了(即已将/
读为DIV
或反之亦然)。
幸运的是,似乎有一种方法可以使切换词法状态变得更加安全:
有没有一种方法可以使SwitchTo更安全?
想法是创建一个“备份”令牌流,并将在向前看期间读取的令牌推回去。
我认为这应该适用于
}
、
)
、
++
和
--
,因为它们通常出现在 LOOKAHEAD(1) 的情况下,但我不能百分之百确定。在最坏的情况下,词法分析器可能已经尝试将以
/
开头的标记解析为
RegularExpressionLiteral
,但由于没有被另一个
/
终止而失败。
无论如何,我看不到更好的方法。下一个好的方法可能是完全放弃这种情况(就像
JSLint
和许多其他人所做的那样),文档化并且不解析这些类型的表达式。
{}/a/g
没有太多意义。
/=
。 - Šime Vidasawait
,这可能会导致歧义 - 如果在async
函数内部,则斜杆将被解析为正则表达式,否则,await
将被解析为变量名,因此斜杆将被解析为除法。 - CertainPerformance