#antlr4
#antlr4
Вопрос:
Используя Antlr 4, у меня возникла ситуация, которую я не уверен, как разрешить. Первоначально я задал вопрос в https://groups.google.com/forum /#!тема/antlr-обсуждение/1yxxxAvU678 на дискуссионном форуме Antlr. Но этот форум, похоже, не получает большого трафика, поэтому я снова спрашиваю здесь.
У меня есть следующая грамматика:
expression
: ...
| path
;
path
: ...
| dotIdentifierSequence
;
dotIdentifierSequence
: identifier (DOT identifier)*
;
Проблема здесь в том, что семантически это dotIdentifierSequence
может означать несколько вещей, и не все из них являются «путями». Но на данный момент все они распознаются как пути в дереве синтаксического анализа, а затем мне нужно обработать их специально в моем посетителе.
Но что мне действительно хотелось бы, так это способ выражения способов использования dotIdentifierSequence, которые не являются путями в expression
правиле, а не в path
правиле, и все еще имеют dotIdentifierSequence в path для обработки способов использования пути.
Чтобы было ясно, последовательность dotIdentifierSequence может быть любой из следующих:
- Путь — это грамматика, подобная SQL, и выражение пути будет похоже на ссылку на таблицу или столбец в SQL, например
a.b.c
- Имя класса Java — например
com.acme.SomeJavaType
- Ссылка на статическое поле Java — например
com.acme.SomeJavaType.SOME_FIELD
- Ссылка на значение перечисления Java — например
com.acme.Gender.MALE
Идея заключается в том, что во время посещения «dotIdentifierSequence как путь» разрешается как тип, сильно отличающийся от других способов использования.
Есть идеи, как я могу это сделать?
Ответ №1:
Проблема здесь в том, что вы пытаетесь провести различие между «путями» при создании в анализаторе. Построение путей внутри лексера было бы проще (следует псевдокод):
grammar T;
tokens {
JAVA_TYPE_PATH,
JAVA_FIELD_PATH
}
// parser rules
PATH
: IDENTIFIER ('.' IDENTIFIER)*
{
String s = getText();
if (s is a Java class) {
setType(JAVA_TYPE_PATH);
} else if (s is a Java field) {
setType(JAVA_FIELD_PATH);
}
}
;
fragment IDENTIFIER : [a-zA-Z_] [a-zA-Z_0-9]*;
и затем в синтаксическом анализаторе вы бы сделали:
expression
: JAVA_TYPE_PATH #javaTypeExpression
| JAVA_FIELD_PATH #javaFieldExpression
| PATH #pathExpression
;
Но тогда, конечно, ввод, подобный этому java./*comment*/lang.String
, будет ошибочно помечен.
Обработка всего этого в анализаторе означала бы вручную просматривать поток токенов и проверять, существует ли тип Java или поле.
Быстрая демонстрация:
grammar T;
@parser::members {
String getPathAhead() {
Token token = _input.LT(1);
if (token.getType() != IDENTIFIER) {
return null;
}
StringBuilder builder = new StringBuilder(token.getText());
// Try to collect ('.' IDENTIFIER)*
for (int stepsAhead = 2; ; stepsAhead = 2) {
Token expectedDot = _input.LT(stepsAhead);
Token expectedIdentifier = _input.LT(stepsAhead 1);
if (expectedDot.getType() != DOT || expectedIdentifier.getType() != IDENTIFIER) {
break;
}
builder.append('.').append(expectedIdentifier.getText());
}
return builder.toString();
}
boolean javaTypeAhead() {
String path = getPathAhead();
if (path == null) {
return false;
}
try {
return Class.forName(path) != null;
} catch (Exception e) {
return false;
}
}
boolean javaFieldAhead() {
String path = getPathAhead();
if (path == null || !path.contains(".")) {
return false;
}
int lastDot = path.lastIndexOf('.');
String typeName = path.substring(0, lastDot);
String fieldName = path.substring(lastDot 1);
try {
Class<?> clazz = Class.forName(typeName);
return clazz.getField(fieldName) != null;
} catch (Exception e) {
return false;
}
}
}
expression
: {javaTypeAhead()}? path #javaTypeExpression
| {javaFieldAhead()}? path #javaFieldExpression
| path #pathExpression
;
path
: dotIdentifierSequence
;
dotIdentifierSequence
: IDENTIFIER (DOT IDENTIFIER)*
;
IDENTIFIER
: [a-zA-Z_] [a-zA-Z_0-9]*
;
DOT
: '.'
;
который можно протестировать со следующим классом:
package tl.antlr4;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.misc.NotNull;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
public class Main {
public static void main(String[] args) {
String[] tests = {
"mu",
"tl.antlr4.The",
"java.lang.String",
"foo.bar.Baz",
"tl.antlr4.The.answer",
"tl.antlr4.The.ANSWER"
};
for (String test : tests) {
TLexer lexer = new TLexer(new ANTLRInputStream(test));
TParser parser = new TParser(new CommonTokenStream(lexer));
ParseTreeWalker.DEFAULT.walk(new TestListener(), parser.expression());
}
}
}
class TestListener extends TBaseListener {
@Override
public void enterJavaTypeExpression(@NotNull TParser.JavaTypeExpressionContext ctx) {
System.out.println("JavaTypeExpression -> " ctx.getText());
}
@Override
public void enterJavaFieldExpression(@NotNull TParser.JavaFieldExpressionContext ctx) {
System.out.println("JavaFieldExpression -> " ctx.getText());
}
@Override
public void enterPathExpression(@NotNull TParser.PathExpressionContext ctx) {
System.out.println("PathExpression -> " ctx.getText());
}
}
class The {
public static final int ANSWER = 42;
}
который выведет на консоль следующее:
PathExpression -> mu
JavaTypeExpression -> tl.antlr4.The
JavaTypeExpression -> java.lang.String
PathExpression -> foo.bar.Baz
PathExpression -> tl.antlr4.The.answer
JavaFieldExpression -> tl.antlr4.The.ANSWER
Комментарии:
1. Ах, я никогда не рассматривал возможность переноса этого в лексер. Хотя это имеет смысл, поскольку на самом деле все они представляют разные типы токенов. Спасибо за оригинальное решение!
2. Из вопроса я предположил, что OP уже выполняет такую проверку, чтобы выяснить, является ли такой идентификатор путем или ссылкой на класс / тип, и хотел другого решения. Кстати. с функциональной точки зрения не имеет значения, есть ли у вас эта проверка в синтаксическом анализаторе или в лексере, но наличие ее в лексере означает: ручная обработка пробелов.