2

式ノードにアクセスして実行する操作を決定するときに演算子をオンにする必要のないバイナリ非終端記号のみを含む文法と評価器 (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では違法です

4

1 に答える 1

2

この質問を書いた後、もう少し機能的に考えるようになりました

簡単に言えば、私は文法を使用しています

expr : 
    | expr (plus|minus) expr
    | expr (multi|div) expr
    | '(' expr ')'
    | literal
    ;

plus   : '+' ;
minus  : '-' ;
multi  : '*' ;
div    : '/' ;

これにより、リスニング ビジターが得られます。

onVisitExit(ExprContext ctx){
    left = values.get(ctx.child(0));
    right = values.get(ctx.child(2));
    operator = binaryOperators.get(ctx.child(1));

    result = operator.doUsing(left, right);
    values.put(ctx, result);
}

onVisitExit(PlusContext ctx){
    binaryOperators.put(ctx, (left, right) -> left + right);
}

onVisitExit(MinusContext ctx){
    binaryOperators.put(ctx, (left, right) -> left - right);
}

//...

これにより、私の問題はすべて解決されます。実際、最初のビジターの実装には、単一のiforforステートメントが含まれていない可能性が非常に高く、これは本当に気の利いたものです。

しかし、長い話を長くするために:

標準的なポリモーフィズム手法では、スイッチの機能をスイッチをオンにした変数にプッシュしようとするでしょう。だからコード

switch(operator.getToken()){
    case "+": do(left + right);
    //...

になる

operator.doOperationUsing(left, right);

そして、オペレーターのさまざまな実装はさまざまなことを行います。問題は、オペレーターのさまざまな実装を生成することです。典型的なポリモーフィズムでは、列挙型を使用するだけの場合、列挙型インスタンスごとにカスタム実装を使用する列挙型は、単に切り替えるよりも優れているとは言えません。しかし、ここでは、訪問済みの非終端ルールを使用して、ラムダでその実装を生成できます:)

于 2015-01-09T21:18:15.530 に答える