在 ANTLR 中,如何制定规则以任何顺序仅匹配其所有备选方案一次?
IE
rule: ('example\r\n' | 'example2\r\n') nextRule
我希望 'example' 和 'example2' 在进入下一条规则之前只匹配一次。
应匹配以下输入:
example
example2
或者
example2
example
但不是以下输入:
example
example
example2
在 ANTLR 中,如何制定规则以任何顺序仅匹配其所有备选方案一次?
IE
rule: ('example\r\n' | 'example2\r\n') nextRule
我希望 'example' 和 'example2' 在进入下一条规则之前只匹配一次。
应匹配以下输入:
example
example2
或者
example2
example
但不是以下输入:
example
example
example2
在 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
's 的替代项将相同的值添加到它匹配的令牌的两侧。
我的实现有点粗糙,因为修饰符最终嵌套在生成的解析树中(因为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
,任意数量和任意顺序。调用代码通过它选择的任何方式执行测试,记录它想要的任何错误。
这是一个将我提到的三个选项结合在一起的超级示例,以便更清楚地了解我在说什么。
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;
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)
使用 ANTLR 4,我实际上更喜欢使用类似以下内容的内容,其中输入应包含 、 和 中的每个正好a
1b
个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 ...
}