SMIEで採用されている構文解析手法では、文脈によってトークンの挙動が異なることは許容されない。ほとんどのプログラミング言語において、これはBNF文法を変換する際の優先順位の競合として現れる。
こうした競合は、文法をわずかに異なる形で表現することで回避できる場合があります。例えば、Modula-2の場合、次のようなBNF文法を用いるのが自然かもしれません。
...
(inst ("IF" exp "THEN" insts "ELSE" insts "END")
("CASE" exp "OF" cases "END")
...)
(cases (cases "|" cases)
(caselabel ":" insts)
("ELSE" insts))
...
しかし、これにより "ELSE" に競合が生じます。一方で、IF
ルールは(他にも多くのことがありますが)"ELSE" =
"END" であることを意味します。しかし他方で、"END" の左側にある cases
の中に "ELSE" が含まれているため、"ELSE" > "END"
も存在することになります。この競合は、以下を用いることで解決できます:
...
(inst ("IF" exp "THEN" insts "ELSE" insts "END")
("CASE" exp "OF" cases "END")
("CASE" exp "OF" cases "ELSE" insts "END")
...)
(cases (cases "|" cases) (caselabel ":" insts))
...
または
...
(inst ("IF" exp "THEN" else "END")
("CASE" exp "OF" cases "END")
...)
(else (insts "ELSE" insts))
(cases (cases "|" cases) (caselabel ":" insts) (else))
...
競合を解消しようと文法を練り直すことにはデメリットもあります。というのも、SMIEは文法がコードの論理構造を反映していると想定しているため、BNFを意図した抽象構文木に近い状態に保つ方が望ましいからです。
また、慎重に検討した結果、それらの競合は深刻ではないと判断し、単に smie-bnf->prec2 の resolvers
引数を用いて解決するという場合もあります。通常、これは文法が単に曖昧であるためです。つまり、その競合は文法で記述されるプログラムの集合には影響せず、それらのプログラムの構文解析方法にのみ影響します。これは、'((assoc "|"))
のようなリゾルバーを追加したい区切り文字や結合的な中置演算子の場合に典型的です。同様のケースとして、'((assoc "else" "then")) を使用する古典的な
dangling else
問題が挙げられます。また、競合が実質的なもので実際には解決できない場合にも起こり得ますが、実用上問題となる可能性は低いです。
最後に、文法の再構成にあらゆる努力を尽くしたとしても、多くの場合、何らかの競合が残ってしまうものです。しかし、絶望する必要はありません。パーサーをより巧妙にすることはできませんが、レクサーは望む限り賢くすることができます。したがって、解決策は、競合に関与しているトークンを確認し、それらのうちの1つを2つ(あるいはそれ以上)の異なるトークンに分割することです。例えば、文法においてトークン
"begin"
の互換性のない2つの用法を区別する必要がある場合、レクサーがどちらの種類の "begin" を見つけたかに応じて、異なるトークン(例えば "begin-fun" と "begin-plain")を返すようにします。これにより、異なるケースを区別する処理がレクサー側に委ねられ、レクサーは周辺のテキストを確認してその場限りの手がかりを探すことになります。