Рубрики
Без рубрики

Используйте Flex и Yacc вместе

Точно так же, как JFlex генерирует лексеры, Yacc генерирует синтаксические анализаторы, но в чем разница? Лексер может отскочить… Помеченный синтаксическим анализом, программированием, интерпретаторами, java.

Точно так же, как 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”