現在、JavaCC を使用してJavaScript/ECMAScript 5.1 パーサーを開発しています。RegularExpressionLiteralと自動セミコロン挿入は、ECMAScript 文法で私を夢中にさせる 2 つのことです。この質問と回答は、正規表現の質問にとって非常に貴重でした。この回答では、私自身の調査結果をまとめたいと思います。
TL;DR JavaCC では、字句状態を使用し、パーサーから切り替えます。
非常に重要なのは、トム・ブレイクが書いたことです。
除算演算子は式の後に続く必要があり、正規表現リテラルは式の後に続くことはできません。したがって、他のすべての場合では、正規表現リテラルを見ていると安全に想定できます。
したがって、それが式であったかどうかを実際に理解する必要があります。これはパーサーでは些細なことですが、レクサーでは非常に困難です。
Thomが指摘したように、多くの場合 (残念ながら、すべてではありません)、最後のトークンを「見る」ことによって、それが式であったかどうかを理解できます。句読点とキーワードを考慮する必要があります。
キーワードから始めましょう。次のキーワードを a の前にDivPunctuator
置くことはできません (たとえば、を使用することはできませんcase /5
)。したがって、/
これらの後に a が表示される場合はRegularExpressionLiteral
、
case
delete
do
else
in
instanceof
new
return
throw
typeof
void
次に句読点。次の句読点は a の前に置くことはできませんDivPunctuator
(例 :{ /a...
記号内では/
除算を開始することはできません):
{ ( [
. ; , < > <=
>= == != === !==
+ - * %
<< >> >>> & | ^
! ~ && || ? :
= += -= *= %= <<=
>>= >>>= &= |= ^=
/=
したがって、これらのいずれかを持っていて、/...
この後を見ると、これは になることはDivPunctuator
ありません。したがって、 でなければなりませんRegularExpressionLiteral
。
次に、次の場合:
/
/...
その後、それも . である必要がありますRegularExpressionLiteral
。これらのスラッシュの間にスペースがなかった場合 (つまり // ...
)、これはSingleLineComment
(「最大マンチ」) として処理されたに違いありません。
次に、次の句読点は式を終了するだけです。
]
したがって、次/
はDivPunctuator
.
残念ながら、あいまいな次のケースが残っています。
}
)
++
--
}
とが式を終了するか)
どうかを知る必要があります。++
--
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の次の質問を参照してください。
パーサーは強制的に新しい字句状態に切り替えることができますか?
はい。ただし、そうすると非常に簡単にバグが作成されます。
先読みパーサーは、トークン ストリーム内で既に行き過ぎている可能性があります (つまり、既に a として読み取ら/
れているか、DIV
またはその逆)。
幸いなことに、字句状態の切り替えを少し安全にする方法があるようです:
SwitchTo をより安全にする方法はありますか?
アイデアは、「バックアップ」トークン ストリームを作成し、ルックアヘッド中に読み取られたトークンを再度プッシュすることです。
通常、LOOKAHEAD(1) の状況で見られるため}
、これは , )
,++
で機能すると思いますが、100% 確実ではありません。--
最悪の場合、レクサーはすでに/
-starting トークンを として解析しようとしていRegularExpressionLiteral
て、別の によって終了されなかったため失敗した可能性があります/
。
いずれにせよ、それを行うより良い方法はありません。次の良いことは、おそらくケースを完全に削除し (JSLint
他の多くのケースと同様に)、文書化して、これらのタイプの式を解析しないことです。{}/a/g
とにかくあまり意味がありません。