ANTLR4:恰好匹配所有输入选项

时间:2013-02-18 10:26:49

标签: antlr4

如何在ANTLR中制定规则以任何顺序匹配其所有替代品?

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

我想'示例'和' example2'在进入下一个规则之前只匹配一次。

应匹配以下输入:

example
example2

example2
example

但不是输入:

example
example
example2

2 个答案:

答案 0 :(得分:15)

  

如何在ANTLR中制定规则以任何顺序匹配其所有替代品?

“所有替代品只有一次”只是rule: altA altB altC ...。这很容易。要求rule接受任何安排中的所有替代altAaltBaltC等,这意味着适应每个安排。这很容易手动处理小数字(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'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之前任意数量和任意顺序任意出现在输入中。调用代码通过它选择的任何方式执行测试,记录它想要的任何错误。


这是一个将我提到的三个选项结合在一起的示例,以便更清楚地了解我在说什么。

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)

输入:缺失&amp;重复的修饰符

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)

答案 1 :(得分:12)

使用ANTLR 4,我实际上更喜欢使用类似下面的内容,其中输入应该包含abc中的每一个。

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