任意の優先順位を記述する一般的な文法は、テーブルベースで yacc を使用して生成できる LALR パーサーを使用して解析できます。しかし、これは、再帰降下パーサーを使用したい場合にすべてが失われるという意味ではありません。
再帰降下パーサーは、構文が正しいかどうかのみを検証できます。構文ツリーの構築は別の問題であり、ツリー構築レベルで優先順位を処理する必要があります。
したがって、中置式を解析できる左再帰を使用しない次の文法を考えてみましょう。優先順位の兆候はありません:
Expr := Term (InfixOp Term)*
InfixOp := '+' | '-' | '*' | '/'
Term := '(' Expr ')'
Term := identifier
(右側で使用される表記は正規表現のようなもので、大きなキャメル ケースを使用して記述された置換を持つルール、トークンは小さなキャメル ケースを使用して引用または記述されます)。
構文ツリーを構築するとき、新しいノードを追加する現在のノードがあります。
ほとんどの場合、ルールを解析するときに、現在のノードに新しい子ノードを作成し、その子を現在のノードにします。解析が終了したら、親ノードにステップアップします。
これは、InfixOp
ルールを解析するときに別の方法で行う必要があることです。関連するノードに優先順位の強さを割り当てる必要があります。ノードのExpr
優先順位は最も低く、他のすべての演算子は優先順位が高くなります。
中置優先順位の処理
ルールを解析するときInfixOp
は、次のようにします。
現在のノードの優先順位が着信ノードの優先順位よりも強い間、1 レベル上げ続けます (親ノードを現在のノードにします)。
次に、着信ノードのノードを現在のノードの最後の子の親として挿入し、現在のノードにします。
Expr
ノードは優先順位が最も低いと宣言されているため、最終的に上昇を停止します。
例を見てみましょう:A+B*C
現在のノードは!
、現在のトークンを消費した後、常に でマークされます。
Parsed: none
Expr!
Parsed: A
Expr!
|
A
Parsed: A+
Expr
|
+!
|
A
Parsed: A+B
Expr
|
+!
/ \
A B
Parsed: A+B*
Expr
|
+
/ \
A *!
/
B
Parsed: A+B*C
Expr
|
+
/ \
A *!
/ \
B C
これをポストオーダー方式でトラバースすると、評価に使用できる式の逆ポーランド記法が得られます。
またはその逆の例:A*B+C
Parsed: none
Expr!
Parsed: A
Expr!
|
A
Parsed: A*
Expr
|
*!
|
A
Parsed: A*B
Expr
|
*!
/ \
A B
Parsed: A*B+
Expr
|
+!
|
*
/ \
A B
Parsed: A*B+C
Expr
|
+!
/ \
* C
/ \
A B
結合性の処理
左結合の演算子と右結合の演算子があります。たとえば、C 言語ファミリでは、+
は左結合ですが、=
は右結合です。
実際には、結合性全体は、同じ優先順位レベルでの演算子の処理に要約されます。同じ優先レベルの演算子に遭遇すると、上昇するときの左連想演算子の場合は上昇し続けます。右結合演算子の場合、同じ演算子に遭遇したら停止します。
(すべてのテクニックを説明するにはスペースがかかりすぎます。紙の上で試してみることをお勧めします。)
前置演算子と後置演算子の処理
この場合、文法を少し変更する必要があります。
Expr := PrefixOp* Term PostfixOp* (InfixOp PrefixOp* Term PostfixOp*)*
InfixOp := '+' | '-' | '*' | '/'
Term := '(' Expr ')'
Term := identifier
前置演算子に遭遇したら、それを現在のノードに新しい子として追加し、新しい子を現在のノードにします。優先順位に関係なく、それが強い演算子であっても弱い演算子であっても正しいものになります。中置演算子は正確性を保証します。
後置演算子の場合、中置演算子で説明したのと同じ優先順位の上昇を使用できます。唯一の違いは、後置演算子の右辺がないため、子が 1 つだけになることです。
三項演算子の扱い
C 言語ファミリには、?:
三項演算子があります。構文ツリーの構築に関しては、?
and:
を別の中置演算子として扱うことができます。しかし、トリックがあります。用に作成するノードは?
、不完全な 3 項ノードである必要があります。つまり、通常の優先順位の上昇を行って配置しますが、この不完全なノードの優先順位は最も低くなります。これにより、コンマ オペレーターのような弱いオペレーターがそれを乗り越えることができなくなります。に到達し:
たら、最初の不完全な 3 項ノードまで登り (見つからない場合は、構文エラーを報告します)、通常の優先順位を持つ完全なノードに変更し、現在のノードにする必要があります。現在の分岐に不完全な 3 項ノードがあるときに式の最後に予期せず到達した場合は、再び構文エラーを報告します。
したがって、a , b ? c : d
は と解釈されa , (b ? c : d)
ます。
しかし、コンマが ? を超えるのを防いだので、a ? c , d : e
は と解釈されます。a ? (c , d) : e
配列インデックスと関数呼び出しの処理
後置のように見えますが、これらは右側に構文的に強制された括弧で囲まれた項を持つ中置演算子です。これは、配列インデックスと関数呼び出しにも当てはまります。