ANTLR4:匹配所有输入备选方案exaclty一次

Har*_*Har 14 antlr4

如何在ANTLR中以任何顺序制定规则以匹配其所有备选方案一次?

rule: ('example\r\n' | 'example2\r\n') nextRule
Run Code Online (Sandbox Code Playgroud)

我希望'example'和'example2'在进入下一个规则之前只匹配一次.

应匹配以下输入:

example
example2
Run Code Online (Sandbox Code Playgroud)

要么

example2
example
Run Code Online (Sandbox Code Playgroud)

但没有输入:

example
example
example2
Run Code Online (Sandbox Code Playgroud)

use*_*210 17

如何在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
    ;
Run Code Online (Sandbox Code Playgroud)

这就结束了.没有外部代码,没有谓词,没有任何东西.


其次,您可以在语法中使用语义谓词,以确保提供和匹配所有匹配而无需重复.编写谓词本身有多种方法,但它归结为跟踪已经进行的匹配(以防止重复),然后测试规则是否匹配了它所期望的所有部分.这是一个基本示例,遵循与前一个相同的要求:

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");}
    ;
Run Code Online (Sandbox Code Playgroud)

此处规则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
    ;
Run Code Online (Sandbox Code Playgroud)

 

//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();
Run Code Online (Sandbox Code Playgroud)

语法保持清洁.修饰符可以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; 
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

测试场景(从上面的代码输出)


输入:好的

public static final x
Run Code Online (Sandbox Code Playgroud)

置换输出

(done)
Run Code Online (Sandbox Code Playgroud)

谓词输出

(done)
Run Code Online (Sandbox Code Playgroud)

听众输出

(No duplicate modifiers detected)
(No missing modifiers detected)
(done)
Run Code Online (Sandbox Code Playgroud)

输入:好的

final static public x
Run Code Online (Sandbox Code Playgroud)

置换输出

(done)
Run Code Online (Sandbox Code Playgroud)

谓词输出

(done)
Run Code Online (Sandbox Code Playgroud)

听众输出

(No duplicate modifiers detected)
(No missing modifiers detected)
(done)
Run Code Online (Sandbox Code Playgroud)

输入:修改器太多

static public static final x
Run Code Online (Sandbox Code Playgroud)

置换输出

extraneous input 'static' expecting 'final' at (1, 14)
(done)
Run Code Online (Sandbox Code Playgroud)

谓词输出

no viable alternative at input 'static' at (1, 14)
(done)
Run Code Online (Sandbox Code Playgroud)

听众输出

Detected duplicate modifiers : [static]
(No missing modifiers detected)
(done)
Run Code Online (Sandbox Code Playgroud)

输入:缺少修饰符

static x
Run Code Online (Sandbox Code Playgroud)

置换输出

no viable alternative at input 'staticx' at (1, 7)
(done)
Run Code Online (Sandbox Code Playgroud)

谓词输出

no viable alternative at input 'x' at (1, 7)
(done)
Run Code Online (Sandbox Code Playgroud)

听众输出

(No duplicate modifiers detected)
Detected missing modifiers : [final, public]
(done)
Run Code Online (Sandbox Code Playgroud)

输入:缺少和重复的修饰符

final final x
Run Code Online (Sandbox Code Playgroud)

置换输出

no viable alternative at input 'finalfinal' at (1, 6)
(done)
Run Code Online (Sandbox Code Playgroud)

谓词输出

no viable alternative at input 'final' at (1, 6)
(done)
Run Code Online (Sandbox Code Playgroud)

听众输出

Detected duplicate modifiers : [final]
Detected missing modifiers : [static, public]
(done)
Run Code Online (Sandbox Code Playgroud)

  • 感谢您非常完整和详尽的回答!我正在尝试编写自定义配置文件解析器。我对上述方法的结论是: 1. 排列方法——如果排列数量很少,当排列数量很多时似乎可以进行调试,这是很好的。2.解析器内验证,这种方法很好,很紧凑,对于小文法非常好。3.解析器与代码的分离——我最喜欢这种方法,因为很清楚谁做了什么以及错误在哪里。它也很容易扩展。很好的答案,非常感谢! (2认同)

Sam*_*ell 12

随着ANTLR 4,我真的喜欢使用类似于以下,其中输入预期正好包含各1个a,b以及c.

items : (a | b | c)*;
Run Code Online (Sandbox Code Playgroud)

然后在监听器中,我将使用如下代码:

@Override
public void enterItems(ItemsContext ctx) {
    if (ctx.a().size() != 1) {
        // report error
    } else if (ctx.b().size() != 1) {
        // report error
    } else ...
}
Run Code Online (Sandbox Code Playgroud)