17

ANTLR で、すべての選択肢を任意の順序で 1 回だけ一致させるルールを作成するにはどうすればよいですか?

すなわち

rule: ('example\r\n' | 'example2\r\n') nextRule

次のルールに移る前に、'example' と 'example2' が 1 回だけ一致するようにします。

次の入力と一致する必要があります。

example
example2

また

example2
example

ただし、次の入力ではありません:

example
example
example2
4

2 に答える 2

20

ANTLRで、すべての選択肢を任意の順序で1回だけ一致させるルールを作成するにはどうすればよいですか?

「そのすべての選択肢は一度だけ」は単純rule: altA altB altC ...です。それは簡単な部分です。任意の取り決めでruleすべての選択肢、、、などを受け入れるように求めることは、すべての取り決めに対応することを意味します。これは、少数の場合は手動で処理するのに十分簡単です()。しかし、すべての順列を自動的に処理するために私が知っている自動ショートカットはありません。altAaltBaltCrule: (altA altB | altB altA);

組み込みの方法がなく、要件を緩和できないと仮定した場合のいくつかのアプローチを次に示します。警告:あなたの問題の全容はわかりません。私はあなたの文法を知りません。なぜあなたが求めているものが欲しいのか分かりません。これらのオプションのどれよりも簡単にしたいと思う以外は、どのクラスのソリューションを好むかわかりません。


まず、手で、または順列ジェネレーターを実行して、弾丸を噛み、マッチのすべての順列を自分で生成することができます。そうすれば、ANTLRはあなたが理解できる方法であなたが望むものを手に入れるでしょう。それは大雑把ですが効率的です:それはまっすぐなANTLR構文なので、以下のオプションにあるような外部コードは含まれていません。

たとえば、のfieldような入力を処理するルールがあり"public static final x"、3つの修飾子すべてが予期されていますが、特定の順序ではないとします。順列は次のようになります。

field : modifiers ID EOF;

modifiers
    : PUBLIC STATIC FINAL //ABC
    | PUBLIC FINAL STATIC //ACB
    | STATIC PUBLIC FINAL //BAC
    | STATIC FINAL PUBLIC //BCA
    | FINAL PUBLIC STATIC //CAB
    | FINAL STATIC PUBLIC //CBA
    ;

これで終わりです。外部コードも述語も何もありません。


次に、文法でセマンティック述語を使用して、すべての一致が提供され、重複することなく一致するようにすることができます。述語自体を記述する方法はさまざまですが、要約すると、(重複を防ぐために)一致したものを追跡し、ルールが期待するすべての部分に一致したかどうかをテストします。これは、前の要件と同じ要件に従った基本的な例です。

field 
    locals [java.util.HashSet<String> names = new java.util.HashSet<String>();]
    : modifiers ID EOF;

modifiers
    //Ensure that the full number of modifiers have been provided
    : {$field::names.size() < 3}? modifier modifiers
    | {$field::names.size() == 3}? //match nothing once we have (any) three modifiers
    ;

modifier
    //Ensure that no duplicates have been provided
    : {!$field::names.contains("public")}? PUBLIC {$field::names.add("public");}
    | {!$field::names.contains("static")}? STATIC {$field::names.add("static");}
    | {!$field::names.contains("final")}? FINAL {$field::names.add("final");}
    ;

ここで、ルールfieldはローカル変数の修飾子名を追跡しますnames。ルールは、3つの値が含まれるまでルールmodifiersを呼び出します。ルールは、に対応するキーがない名前と一致します。値は手動でに追加されることに注意してください。代替が一致するトークンの両側に同じ値を追加する限り、それらは任意の値にすることができます。modifiernamesmodifiernamesnamesmodifier

修飾子が生成された解析ツリーにネストされるため(1つと1つを含むmodifiersため)、私の実装は少し粗雑ですが、あなたがアイデアを理解してくれることを願っています。modifiermodifiersmodifiermodifiers


第三に、貧弱なパーサーをそのままにして、呼び出し元のコードの完全性をテストすることができます。これは、パーサーリスナーを使用して解析中に実行することもParserRuleContext、パーサーによって生成されたオブジェクトを使用して解析後に実行することもできます。これにより、問題が2つの部分に分割されます。パーサーに「任意のX、Y、Zを任意の順序で」解決させ、呼び出し元のコードに「X、Y、Zのみ」を解決させます。

リスナーアプローチを使用した例を次に示します。

//partial grammar

field : modifier* ID EOF; //accept any modifiers in any order

modifier  
    : PUBLIC
    | STATIC
    | FINAL
    ;

 

//snippet of calling code
//initialize lexer and parser

parser.addParseListener(new MyGrammarBaseListener() {
    @Override
    public void exitField(FieldContext ctx) {
        // The field rule has finished. Let's verify that no modifiers
        // were duplicated.

        //TODO check for duplicates, ensure all modifiers are specified.
        //TODO log errors accordingly.

    }
});

//Call the parser.
parser.field();

文法はきれいに保たれています。修飾子は、入力の前IDに任意の数で、任意の順序で表示できます。呼び出し元のコードは、選択した手段でテストを実行し、必要なエラーをログに記録します。


これは、私が話していることをより明確に理解するために、私が言及した3つのオプションをまとめたuberexampleです。

Modifiers.g

grammar Modifiers;

//Hard-coded version : all the permutations are specified //
permutationField : permutationModifiers ID EOF;

permutationModifiers
    : PUBLIC STATIC FINAL //ABC
    | PUBLIC FINAL STATIC //ACB
    | STATIC PUBLIC FINAL //BAC
    | STATIC FINAL PUBLIC //BCA
    | FINAL PUBLIC STATIC //CAB
    | FINAL STATIC PUBLIC //CBA
    ;

// Predicate version : use semantic predicates to prevent duplicates and ensure all the modifiers are provided //

predicateField 
    locals [java.util.HashSet<String> names = new java.util.HashSet<String>();]
    : predicateModifiers ID EOF;

predicateModifiers
    //Ensure that the full number of modifiers have been provided
    : {$predicateField::names.size() < 3}? predicateModifier predicateModifiers
    | {$predicateField::names.size() == 3}? //match nothing once we have (any) three modifiers
    ;

predicateModifier
    //Ensure that no duplicates have been provided
    : {!$predicateField::names.contains("public")}? PUBLIC {$predicateField::names.add("public");}
    | {!$predicateField::names.contains("static")}? STATIC {$predicateField::names.add("static");}
    | {!$predicateField::names.contains("final")}? FINAL {$predicateField::names.add("final");}
    ;

//Listener version : test everything when the parser calls the listener //

listenerField : listenerModifier* ID EOF;

listenerModifier  
    : PUBLIC
    | STATIC
    | FINAL
    ;


PUBLIC : 'public';
STATIC : 'static';
FINAL  : 'final';
FOO    : 'foo';
ID     : [a-zA-Z]+;
WS     : [ \r\n\t]+ -> skip; 

ModifiersTest.java

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import org.antlr.v4.runtime.misc.Nullable;

public class ModifiersTest {

    public static void main(String[] args) {

        run("public static final x", "ok");
        run("final static public x", "ok");
        run("static public static final x", "too many modifiers");
        run("static x", "missing modifiers");
        run("final final x", "missing & duplicated modifiers");
    }

    private static void run(String code, String title) {
        System.out.printf("%n---%n**Input : %s**%n%n\t%s%n%n", title, code);

        System.out.println("**Permutation Output**\n");
        runPermutationTest(code);
        System.out.println();

        System.out.println("**Predicate Output**\n");
        runPredicateTest(code);
        System.out.println();

        System.out.println("**Listener Output**\n");
        runListenerTest(code);
        System.out.println();

    }
    private static void runPermutationTest(String code) {
        ModifiersParser parser = createParser(code);

        parser.permutationField();
        System.out.println("\t(done)");
    }

    private static void runPredicateTest(String code) {
        ModifiersParser parser = createParser(code);

        parser.predicateField();
        System.out.println("\t(done)");
    }

    private static void runListenerTest(String code) {
        ModifiersParser parser = createParser(code);

        parser.addParseListener(new ModifiersBaseListener() {
            @Override
            public void exitListenerField(ModifiersParser.ListenerFieldContext ctx) {
                // The field rule has finished. Let's verify that no modifiers
                // were duplicated.

                HashSet<String> uniqueNames = new HashSet<String>();
                ArrayList<String> allNames = new ArrayList<String>();
                HashSet<String> expectedNames = new HashSet<String>();
                expectedNames.add("public");
                expectedNames.add("static");
                expectedNames.add("final");

                if (ctx.listenerModifier() != null && !ctx.listenerModifier().isEmpty()) {
                    List<ModifiersParser.ListenerModifierContext> modifiers = ctx.listenerModifier();

                    // Collect all the modifier names in a set.
                    for (ModifiersParser.ListenerModifierContext modifier : modifiers) {
                        uniqueNames.add(modifier.getText());
                        allNames.add(modifier.getText());
                    }
                }

                // Is the number of unique modifiers less than the number of
                // all given modifiers? If so, then there must be duplicates.
                if (uniqueNames.size() < allNames.size()) {
                    ArrayList<String> names = new ArrayList<String>(allNames);
                    for (String name : uniqueNames){
                        names.remove(name);
                    }
                    System.out.println("\tDetected duplicate modifiers : " + names);
                } else {
                    System.out.println("\t(No duplicate modifiers detected)");
                }

                //Are we missing any expected modifiers?
                if (!uniqueNames.containsAll(expectedNames)) {
                    ArrayList<String> names = new ArrayList<String>(expectedNames);
                    names.removeAll(uniqueNames);
                    System.out.println("\tDetected missing modifiers : " + names);
                } else {
                    System.out.println("\t(No missing modifiers detected)");
                }
            }
        });

        parser.listenerField();

        System.out.println("\t(done)");

    }

    private static ModifiersParser createParser(String code) {
        ANTLRInputStream input = new ANTLRInputStream(code);

        ModifiersLexer lexer = new ModifiersLexer(input);

        ModifiersParser parser = new ModifiersParser(new CommonTokenStream(lexer));

        BaseErrorListener errorListener = createErrorListener();

        lexer.addErrorListener(errorListener);
        parser.addErrorListener(errorListener);
        return parser;
    }

    private static BaseErrorListener createErrorListener() {
        BaseErrorListener errorListener = new BaseErrorListener() {

            @Override
            public void syntaxError(Recognizer<?, ?> recognizer, @Nullable Object offendingSymbol, int line,
                    int charPositionInLine, String msg, @Nullable RecognitionException e) {
                //Print the syntax error 
                System.out.printf("\t%s at (%d, %d)%n", msg, line, charPositionInLine);
            }
        };
        return errorListener;
    }
}

テストシナリオ(上記のコードからの出力)


入力:わかりました

public static final x

順列出力

(done)

述語出力

(done)

リスナー出力

(No duplicate modifiers detected)
(No missing modifiers detected)
(done)

入力:わかりました

final static public x

順列出力

(done)

述語出力

(done)

リスナー出力

(No duplicate modifiers detected)
(No missing modifiers detected)
(done)

入力:修飾子が多すぎます

static public static final x

順列出力

extraneous input 'static' expecting 'final' at (1, 14)
(done)

述語出力

no viable alternative at input 'static' at (1, 14)
(done)

リスナー出力

Detected duplicate modifiers : [static]
(No missing modifiers detected)
(done)

入力:修飾子がありません

static x

順列出力

no viable alternative at input 'staticx' at (1, 7)
(done)

述語出力

no viable alternative at input 'x' at (1, 7)
(done)

リスナー出力

(No duplicate modifiers detected)
Detected missing modifiers : [final, public]
(done)

入力:修飾子が欠落していて重複しています

final final x

順列出力

no viable alternative at input 'finalfinal' at (1, 6)
(done)

述語出力

no viable alternative at input 'final' at (1, 6)
(done)

リスナー出力

Detected duplicate modifiers : [final]
Detected missing modifiers : [static, public]
(done)
于 2013-02-18T22:02:39.507 に答える
13

ANTLR 4 では、実際には次のようなものを使用することを好みます。ここで、入力にはab、およびがそれぞれ 1 つだけ含まれていると予想されますc

items : (a | b | c)*;

次に、リスナーでは、次のようなコードを使用します。

@Override
public void enterItems(ItemsContext ctx) {
    if (ctx.a().size() != 1) {
        // report error
    } else if (ctx.b().size() != 1) {
        // report error
    } else ...
}
于 2013-02-18T22:36:16.963 に答える