式ノードにアクセスして実行する操作を決定するときに演算子をオンにする必要のないバイナリ非終端記号のみを含む文法と評価器 (ANTLR パース ツリー ウォーカー) が必要です (左前の因数分解された文法の場合のように) 、訪問者は「additionNode」にアクセスするため、訪問者は追加を行う必要があると静的に想定できます)。
かなり直接的な質問。ANTLR は左再帰をサポートしているため、これは有効な文法です。
expr :
| expr ('+'|'-') expr
| expr ('*'|'/') expr
| '(' expr ')'
| literal
;
気の利いた、しかし、このためのウォーカー/ビジター/コンパイラーバックエンドは、型で独自のディスパッチを行う必要があり、それは悪臭を放ちます:
onVisitExit(ExprContext ctx){
left = compiledMap.get(ctx.getChild(0));
right = compiledMap.get(ctx.getChild(2));
operator = ctx.getChild(1);
switch(operator.getToken()){
case "+": compiledMap.put(ctx, left + right);
case "-": compiledMap.put(ctx, left - right);
case "*": compiledMap.put(ctx, left * right);
case "/": compiledMap.put(ctx, left / right);
}
}
この戦略の長所と短所:
- antlr はバイナリ ツリーを作成します。ここで、(バイナリ) 各ルールには左と右の引数があります。つまり、kleene クロージャの while ループについて心配する必要はありません。私は本当にこれが好きです
- ノードのタイプではなく、トークンを手動でディスパッチ (切り替え) する必要があります。
より伝統的で、すでに左に分解された文法を使用する
expr : addOrSub ;
addOrSub : multOrDiv (('+'/'-') multOrDiv)* ;
multOrDiv : bracks (('*'/'/') backs)* ;
bracks : '(' expr ')' | literal ;
literal : TOKEN ;
これに対応するビジターには、上記の 2 つの文法とは反対の長所と短所があります: ANTLR は、私のために型に対してこのディスパッチを行います -- まあ、ほとんどの場合、まだ '+' と '-' を区別する必要があります -- しかし、今はこれらの kleene クロージャーに while ループを含めることは、厳密なバイナリ ツリーがもうないためです。これは面倒です。
私の理想の文法はこのようになると思います
expression : expr ;
fragment expr :
(addition | subtraction)
| (multiplication | division)
| brackets
| literal
;
addition : expr '+' expr ;
subtraction : expr '-' expr ;
multiplication : expr '*' expr ;
division : expr '/' expr ;
brackets : '(' expr ')' ;
literal : TOKEN ;
そして、これは私の問題をすべて解決しますが、もちろんそれはANTLRでは違法です