Тип токена зависит от следующего токена
Я застрял с довольно простой грамматикой. Поиск в Google и чтение книг не помогли. Я начал использовать ANTLR совсем недавно, так что, вероятно, это очень простой вопрос.
Я пытаюсь написать очень простой Lexer, используя ANTLR v3.
grammar TestLexer;
options {
language = Java;
}
TEST_COMMENT
: '/*' WS? TEST WS? '*/'
;
ML_COMMENT
: '/*' ( options {greedy=false;} : .)* '*/' {$channel=HIDDEN;}
;
TEST : 'TEST'
;
WS : (' ' | '\t' | '\n' | '\r' | '\f')+ {$channel=HIDDEN;}
;
Тестовый класс:
public class TestParserInvoker {
private static void extractCommandsTokens(final String script) throws RecognitionException {
final ANTLRStringStream input = new ANTLRStringStream(script);
final Lexer lexer = new TestLexer(input);
final TokenStream tokenStream = new CommonTokenStream(lexer);
Token t;
do {
t = lexer.nextToken();
if (t != null) {
System.out.println(t);
}
} while (t == null || t.getType() != Token.EOF);
}
public static void main(final String[] args) throws RecognitionException {
final String script = "/* TEST */";
extractCommandsTokens(script);
}
}
Поэтому, когда тестовая строка имеет вид "/* TEST */", лексер выдает два ожидаемых токена. Один с типом TEST_COMMENT и один с EOF. Все отлично.
Но если тестовая строка содержит один дополнительный пробел в конце: "/* TEST */ " лексер создает три токена: ML_COMMENT, WS и EOF.
Почему первый токен получает тип ML_COMMENT? Я думал, что способ обнаружения токена зависит только от приоритета правил лексера в грамматике. И конечно это не должно зависеть от следующих токенов.
Спасибо за помощь!
PS Я могу использовать опцию lexer filter=true - токен получит правильный тип, но этот подход требует дополнительной работы в определениях токенов. Если честно, я не хочу использовать этот тип лексера.
1 ответ
ANTLR токенизирует поток символов, начиная с верхнего правила и ниже, и старается максимально соответствовать. Так что да, я также ожидал TEST_COMMENT
быть создан для обоих "/* TEST */"
а также "/* TEST */ "
, Вы всегда можете взглянуть на сгенерированный исходный код лексера, чтобы понять, почему он решил создать ML_COMMENT
для второго входа.
Будь то ошибка или ожидаемое поведение, я бы не стал использовать отдельные правила лексера, которые выглядят очень похожими. Не могли бы вы объяснить, что вы действительно пытаетесь решить здесь?
user776872 написал (а):
Я могу использовать опцию lexer filter=true - токен получит правильный тип, но этот подход требует дополнительной работы в определениях токенов. Если честно, я не хочу использовать этот тип лексера.
Я не совсем понимаю это замечание. Вас интересует только часть входного источника? В таком случае, filter=true
это, безусловно, хороший вариант. Если вы хотите токенизировать весь входной источник, вам не следует использовать filter=true
,
РЕДАКТИРОВАТЬ
В случае проведения различия между многострочными комментариями и комментариями Javadoc, лучше оставить их в одном правиле и изменить тип токена, если он начинается с /**
как это:
grammar T;
// options
tokens {
DOC_COMMENT;
}
// rules
COMMENT
: '/*' (~'*' .*)? '*/'
| '/**' ~'/' .* '*/' {$type=DOC_COMMENT;}
;
Обратите внимание, что оба .*
а также .+
по умолчанию не жадные в ANTLR (вопреки распространенному мнению).
демонстрация
grammar T;
tokens {
DOC_COMMENT;
}
@parser::members {
public static void main(String[] args) throws Exception {
TLexer lexer = new TLexer(new ANTLRStringStream("/**/ /*foo*/ /**bar*/"));
TParser parser = new TParser(new CommonTokenStream(lexer));
parser.parse();
}
}
parse
: (t=. {System.out.println(tokenNames[$t.type] + " :: " + $t.text);})* EOF
;
COMMENT
: '/*' (~'*' .*)? '*/'
| '/**' ~'/' .* '*/' {$type=DOC_COMMENT;}
;
SPACE
: ' ' {$channel=HIDDEN;}
;
который производит:
bart @ hades: ~ / Программирование /ANTLR/ Демоверсии /T$ java -cp antlr-3.3.jar org.antlr.Tool Tg bart@hades:~/ Программирование /ANTLR/ Демоверсии /T$ javac -cp antlr-3.3.jar *.java bart@hades:~/ Программирование /ANTLR/ Демоверсии /T$ java -cp .:antlr-3.3.jar TParser КОММЕНТАРИЙ:: /**/ КОММЕНТАРИЙ:: /*foo*/ DOC_COMMENT:: /** бар * /