Чиним grun в Antlr
Наткнулся на интересное поведение инструмента grun
в Antlr. Для того, чтобы визуализация правильно заработала, потребовалось наложить дополнительные ограничения на грамматку.
Исходная грамматика
Для примера возьмем грамматику из примера самой библиотеки Antlr:
Hello.g4: // Define a grammar called Hello grammar Hello; r : 'hello' ID ; // match keyword hello followed by an identifier ID : [a-z]+ ; // match lower-case identifiers WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
hello.txt hello tale
java -cp ".:antlr-X.Y.Z-complete.jar" org.antlr.v4.Tool Hello.g4
javac -cp antlr-X.Y.Z-complete.jar *.java
java -cp ".;antlr-X.Y.Z-complete.jar" org.antlr.v4.gui.TestRig Hello r -gui hello.txt
Очень удобно, мы видим структуру кода. Очень помогает при работе с грамматикой.
Делим грамматику на лексер и парсер
Использовать грамматику в таком виде можно, но только до тех пор, пока она небольшая. Крупную грамматику (эмпирически — больше экрана текста) нужно делить на две части.
HelloLexer.g4 lexer grammar HelloLexer; ID : [a-z]+ ; // match lower-case identifiers WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
HelloParser.g4 grammar HelloParser; options { tokenVocab = HelloLexer; } r : 'hello' ID ; // match keyword hello followed by an identifier
java -cp ".;antlr-X.Y.Z-complete.jar" org.antlr.v4.Tool HelloParser.g4 HelloLexer.g4
javac -cp antlr-X.Y.Z-complete.jar *.java
java -cp ".;antlr-X.Y.Z-complete.jar" org.antlr.v4.gui.TestRig HelloParser r -gui hello.txt
Обращаю внимание на то, что мы теперь вызываем grun
для грамматики HelloParser
, что отражено в команде вызова.
и очень интересное сообщение об ошибке:
line 1:5 token recognition error at: ' ' line 1:6 token recognition error at: 't' line 1:7 token recognition error at: 'a' line 1:8 token recognition error at: 'l' line 1:9 token recognition error at: 'e' line 1:10 token recognition error at: '\r' line 1:11 token recognition error at: '\n' line 2:0 missing 'hello' at '<EOF>'
Ничего не понятно, но очень интересно ©.
Разбирательство
Переименуем файл HelloParser.g4 в Hello.g4, меняя, соответственно, название грамматики:
Hello.g4 grammar Hello; options { tokenVocab = HelloLexer; } r : 'hello' ID ; // match keyword hello followed by an identifier
java -cp ".;antlr-X.Y.Z-complete.jar" org.antlr.v4.Tool Hello.g4 HelloLexer.g4
Получаем довольно странное сообщение об ошибке:
warning(125): Hello.g4:7:13: implicit definition of token ID in parser
ID
у нас определен в лексере, по идее, не должно быть здесь ошибки.
Поиск по интернету дал наводку, что такое предупреждение выдается для лексем, определенных в парсере, в нашем случае это 'hello'
, т.е. похоже, что в Antlr здесь ошибка, можно поразбираться.
HelloLexer.g4 lexer grammar HelloLexer; HELLO : 'hello'; ID : [a-z]+ ; // match lower-case identifiers WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
HelloParser.g4 grammar HelloParser; options { tokenVocab = HelloLexer; } r : hello ID ; // match keyword hello followed by an identifier hello : HELLO;
java -cp ".;antlr-X.Y.Z-complete.jar" org.antlr.v4.Tool HelloLexer.g4 HelloParser.g4 javac -cp antlr-X.Y.Z-complete.jar *.java
java -cp ".;antlr-X.Y.Z-complete.jar" org.antlr.v4.gui.TestRig HelloParser r -gui hello.txt
Can't load HelloParser as lexer or parser
Пробуем подменить название грамматики:
java -cp ".;antlr-X.Y.Z-complete.jar" org.antlr.v4.gui.TestRig Hello r -gui hello.txt
Exception in thread "main" java.lang.ClassNotFoundException: HelloParser at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:602) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522) at org.antlr.v4.gui.TestRig.process(TestRig.java:150) at org.antlr.v4.gui.TestRig.main(TestRig.java:119)
Решение
Решение оказалось в том, что при разделении на лексер и парсер необходимо, чтобы прасерная грамматика была помечена как parser
:
HelloLexer.g4 lexer grammar HelloLexer; HELLO : 'hello'; ID : [a-z]+ ; // match lower-case identifiers WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
HelloParser.g4 // см ключевое слово parser parser grammar HelloParser; options { tokenVocab = HelloLexer; } r : hello ID ; // match keyword hello followed by an identifier hello : HELLO;
Вот при таком раскладе у нас работает все, что нужно.
java -cp ".;antlr-X.Y.Z-complete.jar" org.antlr.v4.gui.TestRig Hello r -gui hello.txt
Обращаю внимание, что, несмотря на названия HelloLexer
и HelloParser
, grun
мы вызываем для грамматики Hello
. Lexer
и Parser
будут подставлены самостоятельно.
Выводы
- При разделении грамматики нужно лексемы переносить в лексер.
- Грамматики нужно помечать как
lexer
/parser
. - Экспериментируйте :)
[1] Используем полный путь до antlr-X.Y.Z-complete.jar
, на Windows используем разделитель ;
.