Ответ 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)