web-dev-qa-db-ja.com

ANTLR4ビジターパターンの簡単な演算例

私は完全なANTLR4初心者なので、無知を許してください。非常に単純な算術式の文法が定義されている このプレゼンテーション に遭遇しました。それは次のようになります:

_grammar Expressions;

start : expr ;

expr  : left=expr op=('*'|'/') right=expr #opExpr
      | left=expr op=('+'|'-') right=expr #opExpr
      | atom=INT #atomExpr
      ;

INT   : ('0'..'9')+ ;

WS    : [ \t\r\n]+ -> skip ;
_

スライドで説明されているように、ビジターパターンを使用してトラバースできる非常に単純なバイナリツリーが生成されるため、これはすばらしいことです。たとえば、exprにアクセスする関数は次のとおりです。

_public Integer visitOpExpr(OpExprContext ctx) {
  int left = visit(ctx.left);
  int right = visit(ctx.right);
  String op = ctx.op.getText();
  switch (op.charAt(0)) {
    case '*': return left * right;
    case '/': return left / right;
    case '+': return left + right;
    case '-': return left - right;
    default: throw new IllegalArgumentException("Unkown opeator " + op);
  }
}
_

次に付け加えたいのは、括弧のサポートです。そこで、exprを次のように変更しました。

_expr  : '(' expr ')'                      #opExpr
      | left=expr op=('*'|'/') right=expr #opExpr
      | left=expr op=('+'|'-') right=expr #opExpr
      | atom=INT #atomExpr
      ;
_

残念ながら、かっこが見つかると、3つの属性opleftおよびrightがnullになるため、上記のコードは失敗します(NPEで失敗します)。

私は、たとえばparenthesized='(' expr ')'などの新しい属性を定義して、ビジターコードでそれを処理することで、これを回避できると思います。ただし、括弧内に式を表すためにノードタイプ全体を追加することは、私にはやり過ぎに思えます。より単純ですが醜い解決策は、visitOpExprメソッドの先頭に次のコード行を追加することです。

_if (ctx.op == null) return visit(ctx.getChild(1)); // 0 and 2 are the parentheses!
_

上記は非常に壊れやすく、文法構造に大きく依存しているので、私はまったく好きではありません。

括弧を「食べる」だけで表現を子のように扱うようにANTLRに指示する方法があるかどうか疑問に思っています。ある?これを行うより良い方法はありますか?

:私の最終目標は、例を拡張して、_(2+4*3)/10 >= 11_などの算術式を含めることができるブール式を含めることです。つまり、関係(<、> 、==、〜=など)算術式の間では、アトミックなブール式を定義できます。これは簡単で、すでに文法をスケッチしていますが、かっこで同じ問題があります。つまり、次のようなものを書く必要があります(変数のサポートも追加します):

_((2+4*x)/10 >= 11) | ( x>1 & x<3 )
_

[〜#〜] edit [〜#〜]:かっこで囲まれた式の優先順位が修正されました。かっこが常に優先されます。

21
Giovanni Botta

もちろん、別のラベルを付けてください。結局のところ、代替の'(' expr ')'#opExprではありません。

expr  : left=expr op=('*'|'/') right=expr #opExpr
      | left=expr op=('+'|'-') right=expr #opExpr
      | '(' expr ')'                      #parenExpr
      | atom=INT                          #atomExpr
      ;

そしてあなたのビジターでは、次のようなことをします:

public class EvalVisitor extends ExpressionsBaseVisitor<Integer> {

    @Override
    public Integer visitOpExpr(@NotNull ExpressionsParser.OpExprContext ctx) {
        int left = visit(ctx.left);
        int right = visit(ctx.right);
        String op = ctx.op.getText();
        switch (op.charAt(0)) {
            case '*': return left * right;
            case '/': return left / right;
            case '+': return left + right;
            case '-': return left - right;
            default: throw new IllegalArgumentException("Unknown operator " + op);
        }
    }

    @Override
    public Integer visitStart(@NotNull ExpressionsParser.StartContext ctx) {
        return this.visit(ctx.expr());
    }

    @Override
    public Integer visitAtomExpr(@NotNull ExpressionsParser.AtomExprContext ctx) {
        return Integer.valueOf(ctx.getText());
    }

    @Override
    public Integer visitParenExpr(@NotNull ExpressionsParser.ParenExprContext ctx) {
        return this.visit(ctx.expr());
    }

    public static void main(String[] args) {
        String expression = "2 * (3 + 4)";
        ExpressionsLexer lexer = new ExpressionsLexer(new ANTLRInputStream(expression));
        ExpressionsParser parser = new ExpressionsParser(new CommonTokenStream(lexer));
        ParseTree tree = parser.start();
        Integer answer = new EvalVisitor().visit(tree);
        System.out.printf("%s = %s\n", expression, answer);
    }
}

上記のクラスを実行すると、次の出力が表示されます。

2 *(3 + 4)= 14
21
Bart Kiers

上記にPython訪問者そしてPython Listenerに移植しました==

Pythonリスナー

from antlr4 import *
from arithmeticLexer import arithmeticLexer
from arithmeticListener import arithmeticListener
from arithmeticParser import arithmeticParser
import sys

##  grammar arithmetic;
##  
##  start : expr ;
##  
##  expr  : left=expr op=('*'|'/') right=expr #opExpr
##        | left=expr op=('+'|'-') right=expr #opExpr
##        | '(' expr ')'                      #parenExpr
##        | atom=INT                          #atomExpr
##        ;
##  
##  INT   : ('0'..'9')+ ;
##  
##  WS    : [ \t\r\n]+ -> skip ;

import codecs
import sys

def dump(obj):
  for attr in dir(obj):
    print("obj.%s = %r" % (attr, getattr(obj, attr)))

def is_number(s):
    try:
        float(s)
        return True
    except ValueError:
        return False


class arithmeticPrintListener(arithmeticListener):

    def __init__(self):
        self.stack = []

    # Exit a parse tree produced by arithmeticParser#opExpr.
    def exitOpExpr(self, ctx:arithmeticParser.OpExprContext):

        print('exitOpExpr INP',ctx.op.text,ctx.left.getText(),ctx.right.getText())

        op = ctx.op.text

        opchar1=op[0]
        right= self.stack.pop()
        left= self.stack.pop()

        if opchar1 == '*':
           val = left * right 
        Elif opchar1 == '/':
           val = left / right 
        Elif opchar1 == '+':
           val = left + right 
        Elif opchar1 == '-':
           val = left - right
        else:
           raise ValueError("Unknown operator " + op) 

        print("exitOpExpr OUT",opchar1,left,right,val)

        self.stack.append(val)


    # Exit a parse tree produced by arithmeticParser#atomExpr.
    def exitAtomExpr(self, ctx:arithmeticParser.AtomExprContext):
         val=int(ctx.getText())
         print('exitAtomExpr',val)
         self.stack.append(val)

def main():
    #lexer = arithmeticLexer(StdinStream())
    expression = "(( 4 - 10 ) * ( 3 + 4 )) / (( 2 - 5 ) * ( 3 + 4 ))"
    lexer = arithmeticLexer(InputStream(expression))
    stream = CommonTokenStream(lexer)
    parser = arithmeticParser(stream)
    tree = parser.start()
    printer = arithmeticPrintListener()
    walker = ParseTreeWalker()
    walker.walk(printer, tree)

if __name__ == '__main__':
    main()

Pythonビジター

from antlr4 import *
from arithmeticLexer import arithmeticLexer
from arithmeticVisitor import arithmeticVisitor
from arithmeticParser import arithmeticParser
import sys
from pprint import pprint


##  grammar arithmetic;
##  
##  start : expr ;
##  
##  expr  : left=expr op=('*'|'/') right=expr #opExpr
##        | left=expr op=('+'|'-') right=expr #opExpr
##        | '(' expr ')'                      #parenExpr
##        | atom=INT                          #atomExpr
##        ;
##  
##  INT   : ('0'..'9')+ ;
##  
##  WS    : [ \t\r\n]+ -> skip ;

import codecs
import sys

class EvalVisitor(arithmeticVisitor):
    def visitOpExpr(self, ctx):

        #print("visitOpExpr",ctx.getText())

        left = self.visit(ctx.left)
        right = self.visit(ctx.right)
        op = ctx.op.text;

        # for attr in dir(ctx.op): ########### BEST 
        #   print("ctx.op.%s = %r" % (attr, getattr(ctx.op, attr)))
        #print("visitOpExpr",dir(ctx.op),left,right)

        opchar1=op[0]
        if opchar1 == '*':
           val = left * right 
        Elif opchar1 == '/':
           val = left / right 
        Elif opchar1 == '+':
           val = left + right 
        Elif opchar1 == '-':
           val = left - right
        else:
           raise ValueError("Unknown operator " + op) 
        print("visitOpExpr",opchar1,left,right,val)
        return val 

    def visitStart(self, ctx):
        print("visitStart",ctx.getText())
        return self.visit(ctx.expr())

    def visitAtomExpr(self, ctx):
        print("visitAtomExpr",int(ctx.getText()))
        return int(ctx.getText())

    def visitParenExpr(self, ctx):
        print("visitParenExpr",ctx.getText())
        return self.visit(ctx.expr())

def main():
    #lexer = arithmeticLexer(StdinStream())
    expression = "(( 4 - 10 ) * ( 3 + 4 )) / (( 2 - 5 ) * ( 3 + 4 ))"
    lexer = arithmeticLexer(InputStream(expression))
    stream = CommonTokenStream(lexer)
    parser = arithmeticParser(stream)
    tree = parser.start()
    answer = EvalVisitor().visit(tree) 
    print(answer)

if __name__ == '__main__':
    main()
0
Jayanta