ANTLR4: Согласование всех входных альтернатив exaclty один раз

Как я могу сделать правило для соответствия всем его альтернативам только один раз в любом порядке, в ANTLR?

то есть.

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

Я хотел бы, чтобы "example" и "example2" совпадали только один раз, прежде чем перейти к следующему правилу.

Должны соответствовать входы:

example
example2

или

example2
example

но не входы:

example
example
example2

Ответ 1

Как я могу сделать правило для соответствия всем его альтернативам только один раз в любом порядке, в ANTLR?

"Все его альтернативы только один раз" просто rule: altA altB altC .... Это легкая часть. Попросить rule принять все альтернативы altA, altB, altC и т.д. В любом устройстве означает размещение каждой компоновки. Это достаточно легко обрабатывать вручную для небольших номеров (rule: (altA altB | altB altA);). Но нет автоматического ярлыка, который я знаю для обработки всех перестановок для вас автоматически.

Вот некоторые подходы в случае, если нет встроенного способа и предполагается, что вы не можете расслабить свои требования. Предостережения: я не знаю полного объема вашей проблемы; Я не знаю вашей грамматики; Я не знаю, зачем вам то, о чем вы просите; Я не знаю, какой класс решения вы предпочитаете, кроме того, что вам, вероятно, будет проще, чем любой из этих вариантов.


Во-первых, вы можете укусить пулю и произвести все перестановки совпадений самостоятельно, либо вручную, либо путем запуска генератора перестановок. Тогда ANTLR будет иметь то, что вы хотите, таким образом, чтобы оно понимало. Он сырой, но эффективный: это прямой синтаксис ANTLR, поэтому внешний код не задействован, как есть в приведенных ниже вариантах.

Например, предположим, что у вас есть правило field, которое обрабатывает ввод типа "public static final x", при этом ожидаются все три модификатора, но не в определенном порядке. Перестановки будут выглядеть так:

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. Правило modifiers вызывает правило modifier, пока names не содержит три значения. Правило modifier соответствует любому имени, которое не имеет соответствующего ключа в names. Обратите внимание, что значения добавляются вручную в names. Они могут быть любым произвольным значением, если альтернативы modifier добавляют одно и то же значение к обеим сторонам соответствующего ему токена.

Моя реализация немного грубо, потому что модификаторы заканчиваются вложенным в созданное дерево синтаксического анализа (поскольку modifiers содержит один modifier и один modifiers, который содержит один modifier и один modifiers, который...), но я надеюсь, что вы получите эту идею.


В-третьих, вы можете оставить только слабый парсер и проверить полноту кода вызова. Это можно сделать во время разбора с использованием прослушивателя парсера или после разбора с использованием объекта ParserRuleContext, созданного синтаксическим анализатором. Это разбивает проблему на две части: пусть синтаксический анализатор решает "любые 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 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, в любом количестве и в любом порядке. Вызывающий код выполняет тесты любыми способами, которые он выбирает, регистрируя любые ошибки, которые он хочет.


Вот пример, который объединяет три варианта, о которых я говорил, чтобы дать более ясное представление о том, о чем я говорю.

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 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;
    }
}

Сценарии тестирования (вывод из приведенного выше кода)


Вход: ok

public static final x

Вывод перестановки

(done)

Выход предиката

(done)

Выход для прослушивателя

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

Вход: ok

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)

Ответ 2

С ANTLR 4 я бы предпочел использовать что-то вроде следующего, где ожидается, что вход будет содержать ровно 1 каждый из a, b и 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 ...
}