通常教えられる通常のパーサーは、パーサーが入力に触れる前に、字句解析器ステージを持っています。レクサー(「スキャナー」または「トークン化ツール」も)は、タイプで注釈が付けられた小さなトークンに入力を切り分けます。これにより、メインパーサーは各文字を端末として扱う必要がなく、トークンを端末要素として使用できます。これにより、効率が著しく向上します。特に、レクサーはすべてのコメントと空白を削除することもできます。ただし、個別のトークナイザーフェーズは、キーワードが識別子としても使用できないことを意味します(言語が好意から外れたストロッピングをサポートしないか、すべての識別子の前にのようなシギルを付ける場合を除く$foo
)。
どうして?次のトークンを理解する単純なトークナイザーがあるとしましょう:
FOR = 'for'
LPAREN = '('
RPAREN = ')'
IN = 'in'
IDENT = /\w+/
COLON = ':'
SEMICOLON = ';'
トークナイザーは常に最長のトークンと一致し、識別子よりもキーワードを優先します。だから、interesting
としてレクサー処理されますIDENT:interesting
が、in
としてレクサー処理されませんIN
ように、決してIDENT:interesting
。次のようなコードスニペット
for(var in expression)
トークンストリームに変換されます
FOR LPAREN IDENT:var IN IDENT:expression RPAREN
これまでのところ、うまくいきます。しかし、どの変数も、コードではなく変数ではなくin
キーワードとして字句解析されIN
ます。レクサーはトークン間の状態を保持せずin
、forループにいる場合を除いて、通常は変数である必要があることを認識できません。また、次のコードは合法である必要があります。
for(in in expression)
1 in
つ目は識別子、2つ目はキーワードです。
この問題には2つの反応があります。
コンテキストキーワードはわかりにくいので、代わりにキーワードを再利用しましょう。
Javaには多くの予約語がありますが、C ++からJavaに切り替えるプログラマに役立つエラーメッセージを提供する以外に、その一部は使用できません。新しいキーワードを追加すると、コードが壊れます。コンテキストキーワードを追加すると、構文の強調表示が適切でない限り、コードの読者は混乱し、より高度な解析手法を使用する必要があるため、ツールの実装が難しくなります(以下を参照)。
言語を拡張したい場合、唯一の健全なアプローチは、以前はその言語では合法でなかった記号を使用することです。特に、これらを識別子にすることはできません。foreachループ構文により、Javaは既存の:
キーワードを新しい意味で再利用しました。ラムダでは、Javaが追加->
(以前に法的なプログラムで発生することができなかったキーワードを-->
まだとしてレクサー処理されるだろう'--' '>'
合法であり、これは->
以前としてレクサー処理されている場合があります'-', '>'
が、そのシーケンスは、パーサーによって拒否されるだろう)。
コンテキストキーワードは言語を簡素化し、実装しましょう
レクサーは間違いなく便利です。ただし、パーサーの前にレクサーを実行する代わりに、パーサーと連携して実行できます。ボトムアップパーサーは常に、任意の場所で受け入れられるトークンタイプのセットを認識しています。次に、パーサーは、現在の位置でこれらのタイプのいずれかに一致するようにレクサーに要求できます。for-eachループでは·
、変数が見つかった後、パーサーは(簡略化された)文法で示される位置にあります。
for_loop = for_loop_cstyle | for_each_loop
for_loop_cstyle = 'for' '(' declaration · ';' expression ';' expression ')'
for_each_loop = 'for' '(' declaration · 'in' expression ')'
その位置では、正当なトークンはSEMICOLON
or IN
ですが、ではありませんIDENT
。キーワードin
は完全に明確です。
この特定の例では、上記の文法を次のように書き換えることができるため、トップダウンパーサーにも問題はありません。
for_loop = 'for' '(' declaration · for_loop_rest ')'
for_loop_rest = · ';' expression ';' expression
for_loop_rest = · 'in' expression
そして、決定に必要なすべてのトークンは、バックトラックすることなく見ることができます。
使いやすさを考慮する
Javaは常に、意味的および構文的な単純化に向かっています。たとえば、言語はコードをはるかに複雑にするため、演算子のオーバーロードをサポートしていません。そのため、for-eachループ構文間in
を決定するとき:
は、混乱が少なく、ユーザーにとってわかりやすい構文を検討する必要があります。極端なケースはおそらく
for (in in in in())
for (in in : in())
(注:Javaには、型名、変数、およびメソッド用の個別の名前空間があります。これは間違いであると思います。これは、ほとんどの場合、これは、後の言語設計でさらに間違いを追加する必要があることを意味しません。)
反復変数と反復コレクションの間の視覚的な分離を明確にするのはどの選択肢ですか?コードを見たときに、どの代替案がより迅速に認識されますか?これらの基準に関しては、記号を分離することは一連の単語よりも優れていることがわかりました。他の言語は異なる値を持っています。たとえば、Pythonは多くの演算子を英語で表記しているため、自然に読みやすく、理解しやすくなっていますが、同じ特性のため、Pythonを一目で理解するのは非常に困難です。