Точно так же, как JFlex генерирует лексеры, Yacc генерирует синтаксические анализаторы, но в чем разница? Лексер может распознавать слова, а синтаксический анализатор может распознавать целые предложения или, более формально, использовать лексеры для работы с обычными грамматиками и синтаксическими анализаторами для работы с контекстно-свободными грамматиками .
Именно по этой причине они часто используются вместе — сначала вы используете лексер для распознавания слов и передаете эти слова анализатору, который способен определить, образуют ли слова правильное предложение.
Теперь, когда Java является Java, существует множество генераторов синтаксических анализаторов: Antlr распространен повсеместно, но есть также COCO/R , JavaCC, СаблеЦК , Чашка , Byacc/J и, вероятно, многие другие. Даже почтенный Зубр способен генерировать Java-парсеры. Некоторым анализаторам, таким как Antlr, COCO/R и JavaCC, даже не нужен отдельный лексер для подачи слов — они могут генерировать свои собственные!
Так почему же Джек? Почему нет?
Итак, как JFlex и Джек работают вместе:
JFlex считывает входные данные в виде потока символов и выдает токен для Джека, когда Джек запрашивает его. Токен – это строка со значением . Например, +, true и 3.14 — это все токены – некоторым из них на самом деле не нужно значение, кроме их типа: true – логический литерал, но некоторые из них нужны: 3.14 – целочисленный литерал со значением 3.14.
Как и в случае с JFlex, файл Jacc состоит из трех отдельных разделов:
directives section %% rules section %% additional code section
Jacc создает список всех ожидаемых токенов в отдельном файле. Вы указываете файл и список маркеров следующим образом:
%interface ParserTokens %token X NL
Давайте посмотрим на сгенерированный файл:
// Output created by jacc on Mon Mar 11 09:54:05 CET 2019 interface ParserTokens { int ENDINPUT = 0; int NL = 1; int X = 2; int error = 3; }
Это интерфейс, который одновременно выполняет функцию перечисления. Помимо двух запрошенных нами типов токенов, NL и X, создаются еще два: один для завершения ввода и один для ошибок. Вернувшись в файл JFlex, вы “реализуете” этот “интерфейс”.:
%class Lexer %implements ParserTokens
просто для того, чтобы наш лексер мог видеть константы ENDINPUT, NL, X И error. Есть еще несколько вещей, которых ожидает Жак:
- Функция, которая возвращает целочисленные значения, представляющие типы токенов (0, 1 или 3 в нашем примере). Называть эту функцию yylex – традиция, так что давайте сделаем это:
%function yylex %int
- Еще три функции: getToken для получения текущего кода токена, nextToken для чтения следующего кода токена и getSemantic для получения текущего значения токена:
%{ private int token; private String semantic; public int getToken() { return token; } public String getSemantic() { return semantic; } public int nextToken() { try { token = yylex(); } catch (java.io.IOException e) { System.out.println( "IO exception occured:\n" + e); } return token; } %}
Вы можете заметить, что мы решили сделать семантическое значение токена строкой, поэтому нам также нужно указать это в анализаторе:
%semantic String
Для нашего примера мы просто попросим лексер распознать следующие слова: слово, Слово, слово, слово, … , слово и СЛОВО и новые строки. Мы будем игнорировать пробелы.
x = [wW][oO][rR][dD] nl = \n | \r | \r\n space = [\t] %%
Когда лексер найдет слово, он вернет токен X (т.Е. 2 из интерфейса ParserTokens) со значением word, Word,… — это то, что делает();. Когда он встретит новую строку, он вернет КОНЕЧНЫЙ ВВОД (т.Е. 0):
{x} { semantic = yytext(); return X; } {space} { /\* Ignore space \*/ } {nl} { return ENDINPUT; } [^] { System.out.println("Error?"); }
Это оно. Сейчас синтаксический анализатор “грамматика” во всей своей красе:
sentence : X { System.out.println("X found: " + $1); } | sentence X { System.out.println("X found: " + $2); } ;
Грамматика леворекурсивная , что позволяет нам иметь одно или несколько X слов в предложении. $1 и $ 2 будут содержать семантическое значение X, переданное туда лексером, и мы просто распечатаем его.
В основном методе мы создаем экземпляры лексера и синтаксического анализатора и начинаем синтаксический анализ. Здесь следует отметить одну вещь: мы должны “загрунтовать” лексер с помощью
parser.lexer.nextToken();
до того, как анализатор сможет его использовать. Для справки, вот полный источник лексера
import java.io.\*; %% %class Lexer %implements ParserTokens %function yylex %int %{ private int token; private String semantic; public int getToken() { return token; } public String getSemantic() { return semantic; } public int nextToken() { try { token = yylex(); } catch (java.io.IOException e) { System.out.println( "IO exception occured:\n" + e); } return token; } %} x = [wW][oO][rR][dD] nl = \n | \r | \r\n space = [\t] %% {x} { semantic = yytext(); return X; } {space} { /\* Ignore space \*/ } {nl} { return ENDINPUT; } [^] { System.out.println("Error?"); }
и что из синтаксического анализатора:
%{ import java.io.\*; %} %class Parser %interface ParserTokens %semantic String %token X NL %% sentence : X { System.out.println("X found: " + $1); } | sentence X { System.out.println("X found: " + $2); } ; %% private Lexer lexer; public Parser(Reader reader) { lexer = new Lexer(reader); } public void yyerror(String error) { System.err.println("Error: " + error); } public static void main(String args[]) throws IOException { System.out.println("Interactive evaluation:"); Parser parser = new Parser( new InputStreamReader(System.in)); parser.lexer.nextToken(); parser.parse(); }
Вам нужно скомпилировать лексер.flex
jflex lexer.flex
и и
jacc parser.jacc
и три сгенерированных Java-файла ( Lexer.java , Parser.java и ПарсерТокены.java ):
javac \*.java
чтобы, наконец, иметь возможность запустить анализатор:
java Parser
Вот пример терминального сеанса:
Interactive evaluation: word Word wOrD WORD X found: word X found: Word X found: wOrD X found: WORD
Вы можете найти полный исходный код на Github .
На самом деле это было довольно скучно, но теперь мы можем свободно играть с контекстно-свободными грамматиками!
Оригинал: “https://dev.to/vicentemaldonado/use-jflex-and-jacc-together-3nnd”