[ Страница назад | Страница вперед | Содержание | Индекс | Библиотека | Юридическая информация | Поиск ]

Программирование: Разработка и отладка программ


Работа с программами lex и yacc

При автономном использовании генератор программ lex создает лексический анализатор, который способен распознавать простые команды длиной в одно слово или получать статистическую информацию. Программа lex может также применяться вместе с генератором синтаксических анализаторов, например, с командой yacc. Команда yacc создает программу-синтаксический анализатор, которая может обрабатывать конструкции из нескольких слов. Такой синтаксический анализатор может работать с лексическим анализатором, создаваемым программой lex. Синтаксические анализаторы могут распознавать различные типы грамматик, независимо от контекста. Для распознавания лексем синтаксическому анализатору требуется препроцессор - например, анализатор, создаваемый командой lex.

Программа lex распознает только расширенные регулярные выражения и в соответствии с входным файлом формирует из них пакеты символов, называемые лексемами. При использовании программы lex для создания препроцессора синтаксического анализатора, лексический анализатор, созданный командой lex, применяется для разбиения входного потока. Синтаксический анализатор (созданный командой yacc), в свою очередь, структурирует полученные блоки. С программами, созданными с помощью команд lex или yacc, могут также использоваться и другие программы.

Лексема - это минимальная независимая смысловая единица, которая может быть определена синтаксическим или лексическим анализатором. Лексема может содержать данные, ключевое слово языка, идентификатор или другой элемент синтаксиса языка.

Программа yacc использует функцию лексического анализатора yylex, которая присутствует в программах, создаваемых командой lex. Обычно функция yylex вызывается из главной функции программы lex. Однако при загрузке команды yacc применяется главная функция этой команды, в которой yacc также вызывает функцию yylex. В этом случае каждое правило lex должно заканчиваться следующим оператором:

return(token);

Этот оператор возвращает значение соответствующей лексемы (token).

Команда yacc присваивает целое значение каждой лексеме, определенной в файле грамматики yacc с помощью команды препроцессора C #define. У лексического анализатора должен быть доступ к этим макроопределениям, чтобы он мог возвращать лексемы синтаксическому анализатору. Команда yacc -d позволяет создать файл y.tab.h; этот файл y.tab.h затем должен быть подключен к файлу спецификаций lex путем добавления следующей строки в раздел определений файла спецификаций lex:

%{
#include "y.tab.h"
%}

Вы можете также включить файл lex.yy.c в программу, созданную yacc, добавив следующую строку после второго разделителя %% (двойной знак процента) в файле грамматики yacc:

#include "lex.yy.c"

Библиотека yacc должна загружаться до библиотеки lex, поскольку только в этом случае функция main будет вызывать синтаксический анализатор yacc. Вы можете создавать программы lex и yacc в произвольном порядке.

Создание синтаксического анализатора с помощью программы yacc

Программа yacc создает синтаксические анализаторы, которые определяют и контролируют структуру текстового ввода компьютерной программы. Для работы с этой программой необходимо определить следующий элементы:

Файл грамматики Исходный файл, содержащий спецификации распознаваемого языка. В этом файле также находятся определения функций main, yyerror и yylex. Это обязательные функции.
main Функция языка C, которая, как минимум, вызывает функцию yyparse, созданную программой yacc. Ограниченная версия этой функции присутствует в библиотеке yacc.
yyerror Функция языка C, предназначенная для обработки ошибок, возникающих при работе синтаксического анализатора. Ограниченная версия этой функции присутствует в библиотеке yacc.
yylex Функция языка C, выполняющая лексический анализ входного потока и передающая лексемы синтаксическому анализатору. Эта функция может быть создана с помощью команды lex.

На основании файла спецификаций программа yacc создает файл y.tab.c на языке C. После компиляции файла командой cc формируется код функции yyparse, возвращающей целое число. При работе функция yyparse вызывает для чтения лексем функцию yylex. Функция yylex возвращает лексемы до тех пор, пока синтаксический анализатор не обнаружит ошибку или yylex не вернет маркер конца, означающий завершение входного потока. При возникновении неустранимой ошибки функция yyparse вернет в main значение 1. При обнаружении маркера конца yyparse возвращает в main значение 0.

Файл грамматики yacc

Для создания синтаксического анализатора необходимо передать на вход команды yacc файл грамматики, который описывает формат входного потока и определяет действия, которые анализатор будет выполнять над данными. Файл грамматики включает правила, описывающие структуру ввода, код, который будет вызываться при распознавании таких правил, и функцию, отвечающую за ввод.

На основании информации из файла грамматики команда yacc создает синтаксический анализатор, управляющий процессом ввода. Для получения элементов входного потока (лексем) этот анализатор вызывает процедуру ввода (лексический анализатор). Затем он структурирует лексемы в соответствии с правилами, заданными в файле грамматики. Правила структурирования лексем называются правилами грамматики. Когда анализатор распознает одно из заданных правил, он выполняет связанный с этим правилом пользовательский код. Этот пользовательский код называется действием. Действия возвращают значения и используют значения, возвращаемые другими действиями.

Для создания кода действий и других функций применяется язык программирования C. Многие синтаксические конструкции, используемые в файле грамматики yacc, также заимствованы из языка C.

Функции main и yyerror

Для работы синтаксического анализатора должны быть определены функции main и yyerror. Для упрощения работы с yacc в библиотеку yacc включены краткие варианты функций main и yyerror. Для подключения этих определений функций применяется аргумент -ly команды ld (или команды cc). Ниже приведен исходный код библиотечной версии main:

#include <locale.h>
main()
{
     setlocale(LC_ALL, "");
     yyparse();
}

Библиотечная версия функции yyerror определена следующим образом:

#include <stdio.h>
yyerror(s)
        char *s;
{
        fprintf( stderr, "%s\n" ,s);
}

Аргументом yyerror является строка, содержащая сообщение об ошибке - обычно это строка syntax error.

Возможности этих функций невелики. Вы можете расширить их в соответствии с конкретными требованиями. Например, вы можете отслеживать номер строки входного текста и указывать его в сообщениях об ошибках. Вы можете также использовать значение внешней переменной yychar. На момент обнаружения ошибки в этой переменной хранится номер следующей лексемы.

Функция yylex

Функция ввода должна выполнять следующие операции:

Лексема - это символ или имя, сообщающее синтаксическому анализатору о том, какой символьный шаблон передается ему функцией ввода. Нетерминальный символ определяет структуру, распознаваемую анализатором.

Допустим, например, что процедура ввода разделяет входной поток на лексемы WORD, NUMBER и PUNCTUATION (слово, число и знак препинания), а на вход ей передается строка

I have 9 turkeys.

В этом случае функция ввода передаст анализатору следующие строки и лексемы:

Строка Лексема
I WORD
have WORD
9 NUMBER
turkeys WORD
. PUNCTUATION

Синтаксический анализатор должен включать определения лексем, передаваемых ему функцией ввода. Опция -d команды yacc позволяет создать список лексем в файле y.tab.h. Этот список представляет собой набор операторов #define и позволяет использовать одни и те же лексемы в лексическом (yylex) и синтаксическом анализаторах.

Для того чтобы избежать конфликтов с синтаксическим анализатором, не используйте имена, начинающиеся с букв "yy".

Для создания процедуры ввода можно воспользоваться командой lex или написать ее самостоятельно на языке C.

Применение файла грамматики yacc

Файл грамматики yacc содержит три раздела:

Разделы файла грамматики отделяются друг от друга символами %% (двойной знак процента). Обычно символы %% размещают на отдельных строках, что упрощает чтение файла. Общая структура файла грамматики выглядит так:

объявления
%%
правила
%%
программы

Раздел объявлений может быть пустым. Если в файле отсутствует раздел программ, не вводите вторую пару %%. Таким образом, минимальный файл грамматики yacc выглядит следующим образом:

%%
правила

При работе с файлом грамматики команда yacc пропускает пробелы, табуляции и символы новой строки. Таким образом, эти символы могут применяться для форматирования файла грамматики. При этом необходимо помнить, что использование пробелов, символов табуляции и символов новой строки в зарезервированных словах недопустимо.

Комментарии

Для объяснения работы анализатора могут использоваться комментарии. Вы можете помещать комментарии в любом месте файла грамматики. Однако для улучшения читаемости файла рекомендуется помещать комментарии на отдельных строках в начале смысловых блоков или правил. Комментарий в файле грамматики yacc обозначается так же, как и в тексте программы на C. Комментарий заключается в символы /* (косая черта, звездочка) и */ (звездочка, косая черта). Например:

/* Это отдельная строка комментария. */

Литеральные строки

Литеральная строка представляет собой один или несколько символов, заключенных в (одиночные кавычки) ''. Как и в языке C, символ \ (обратная косая черта) определяет escape-символ внутри литерала. Распознаются все escape-коды языка C. Таким образом, команда yacc поддерживает символы из следующей таблицы:

Символ Определение
'\a' Предупреждение
'\n' Новая строка
'\r' Возврат каретки
'\'' Одиночная кавычка (')
'\"' Двойная кавычка (")
'\?' Вопросительный знак (?)
'\\' Обратная косая черта (\)
'\t' Табуляция
'\v' Вертикальная табуляция
'\b' Забой
'\f' Новая страница
'\цифры' Символ, код которого указан одной, двумя или тремя восьмеричными цифрами.
'\xцифры' Символ, код которого указан последовательностью шестнадцатеричных цифр.

Не используйте в правилах грамматики символ \0 или 0 (пустой символ).

Форматирование файла грамматики

Для улучшения читаемости файла грамматики yacc следуйте приведенным ниже рекомендациям:

Ошибки в файле грамматики

Команда yacc не может создать анализатор для всех возможных наборов грамматик. Если правила внутренне противоречивы или используют методы сравнения, недоступные в yacc, то команда yacc не сможет создать анализатор. В большинстве случаев при возникновении ошибки программа yacc выводит сообщение. Для исправления ошибок исправьте правила в файле грамматики или создайте лексический анализатор (программу ввода для синтаксического анализатора), который распознает последовательности, не поддерживаемые yacc.

Объявления yacc

В разделе объявлений файла грамматики yacc находится следующая информация:

Семантическая информация, связанная с лексемами, находящимися в стеке анализа, может храниться в определенном пользователем объединении (union) языка C, если элементы этого объединения связаны с различными именами, указанными в файле грамматики.

В объявлениях переменных и констант используется синтаксис языка C:

СпецификаторТипаИдентификатор ;

СпецификаторТипа - это ключевое слово, определяющее тип данных, Идентификатор - имя переменной или константы. Имена могут иметь произвольную длину и содержать буквы, символы подчеркивания и цифры. Имя не может начинаться с цифры. В именах учитывается регистр символов.

Имена терминальных символов (лексем) описываются с помощью объявления %token, а имена нетерминальных символов - с помощью объявления %type. Объявлять нетерминальные символы с помощью %type необязательно. Они определяются автоматически, когда программа обнаруживает эти символы в левой части какого-либо правила. Если имя не определено в разделе объявлений, оно может применяться только в качестве нетерминального символа. Синтаксис и функции операторов #include аналогичны синтаксису и функциям одноименных директив языка C.

В программе yacc определен набор ключевых слов, которые применяются для описания условий обработки. Каждое ключевое слово начинается с символа % (процент) и заканчивается списком лексем или нетерминальных имен. Существуют следующие ключевые слова:

%left Определяет лексемы с ассоциативностью слева.
%nonassoc Определяет лексемы без ассоциативности.
%right Определяет лексемы с ассоциативностью справа.
%start Обозначает нетерминальное имя символа начала.
%token Определяет имена лексем, распознаваемые yacc. Позволяет описать все имена лексем в разделе объявлений.
%type Задает тип нетерминальных символов. Если это объявление присутствует, выполняется проверка типов.
%union Описывает стек значений yacc в виде объединения с элементами требуемых типов. По умолчанию возвращаются целые числа. Эта конструкция позволяет объявить YYSTYPE непосредственно на основе входных данных.

%{

Код

%} Копирует Код в исходный файл программы. Это ключевое слово позволяет добавлять в раздел объявлений описания и определения на языке C.

Примечание: Символы %{ (процент, левая фигурная скобка) и %} (процент, правая фигурная скобка) должна располагаться на отдельных строках.

Ключевые слова %token, %left, %right и % nonassoc могут поддерживать имя элемента объединения C (определенное с помощью %union), обозначаемое как <Тег > (имя элемента объединения в угловых скобках). В ключевом слове %type <Тег> должен указываться обязательно. Использование конструкции <Тег> позволяет указать, что перечисленные лексемы должны быть того же типа, что и элемент объединения C, на который ссылается <Тег>. Например, объявление

%token [<Тег>] Имя [Число] [Имя [Число]]...

объявляет Имя в качестве лексемы. Если <Тег> указан, то все лексемы, перечисленные в этой строке, должны иметь тот же тип (языка C), что и <Тег>. Если в объявлении присутствует положительное целое Число после параметра Имя, то это число присваивается лексеме.

Все лексемы, перечисленные в строке, имеют одинаковый приоритет и ассоциативность. Строки файла расположены в порядке возрастания приоритета. Например:

%left '+' '-'
%left '*' '/'

Эти строки описывают приоритет и ассоциативность четырех арифметических действий. Символы + (плюс) и - (минус) имеют ассоциативность слева и имеют меньший приоритет, чем символы * (звездочка) и / (косая черта), которые также имеют ассоциативность слева.

Определение глобальных переменных

При объявлении переменных, которые будут использоваться в действиях и в лексическом анализаторе, заключайте их определения в символы %{ (процент, левая фигурная скобка) и %} (процент, правая фигурная скобка). Объявления, заключенные в эти символы, называются глобальными переменными. Например, для того чтобы переменная var была доступна во всех частях программы, добавьте в раздел объявлений файла грамматики следующие строки:

%{
int var = 0;
%}

Начальные условия

Анализатор распознает специальный символа, называемый начальным. Начальный символ - это имя правила, определенного в разделе правил файла грамматики, которое описывает наиболее общую структуру анализируемого языка. Так как это самая общая структура, то анализатор начинает грамматический разбор входного потока именно с нее. Для описания начального символа в разделе объявлений применяется ключевое слово %start. Если начальный символ не определен, анализатор использует первое правило из раздела правил файла грамматики.

Например, при анализе функции языка C наиболее общей является следующая структура:

main()
{
        code_segment
}

Начальный символ должен указывать на правило, описывающее такую структуру. Все остальные правила файла будут описывать структуры более низкого уровня, используемые внутри функции.

Номера лексем

Номера лексем - это неотрицательные целые числа, соответствующие именам лексем. Если лексический анализатор передает синтаксическому анализатору номер лексемы вместо ее имени, то обе программы должны использовать одинаковые обозначения лексем.

Номера лексем могут быть заданы в файле грамматики yacc. Если номера не заданы явно, то файл грамматики yacc назначает номера по следующим правилам:

  1. Литеральному символу соответствует его код в кодировке ASCII.
  2. Остальным символам присваиваются коды, начиная с 257.

    Примечание: Не присваивайте лексемам нулевое значение. Это значение присвоено маркеру конца ввода. Переопределять его нельзя.

Для того чтобы присвоить номера лексемам (включая литералы) в разделе объявлений файла грамматики, укажите номер после имени лексемы в строке %token. Это число будет номером, соответствующим имени лексемы или литерала. Номера лексем должны быть уникальными. По достижении конца ввода лексический анализатор, используемый совместно с yacc, должен возвращать 0 или отрицательное значение.

Правила yacc

Раздел правил содержит одно или несколько грамматических правил. Каждое правило описывает структуру и присваивает ей имя. Грамматические правила задаются в следующей форме:

A : ТЕКСТ;

Здесь A - нетерминальное имя, а ТЕКСТ - последовательность из 0 или более имен, литералов и семантических действий, после которых могут следовать правила приоритета. Для описания грамматики необходимы только имена и литералы. Семантические действия и правила приоритетов необязательны. Двоеточие и точки запятой должны быть указаны в определениях правил yacc обязательно.

Семантические действия позволяют выполнять определенный код при каждом распознавании правила во входном потоке. В качестве действия может применяться любой оператор C, который будет, например, выполнять ввод-вывод, вызывать функции или изменять значения переменных. Действия могут также вызывать операции анализатора, такие как сдвиг или понижение.

Правила приоритетов определяются ключевым словом %prec и изменяют приоритет соответствующего правила грамматики. Зарезервированное слово %prec может располагаться непосредственно после текста грамматического правила, в нем может указываться имя лексемы или литерал. При использовании такой конструкции приоритет правила становится равным приоритету имени лексемы или литерала.

Повторение нетерминальных имен

Если нетерминальное имя применяется в нескольких грамматических правилах, то эти правила можно объединить в одно с помощью символа | (символ конвейера). В этом случае символ ; (точка с запятой) ставится в конце набора объединенных правил. Например, правила

A  :  B  C  D  ;
A  :  E  F  ;
A  :  G  ;
 

могут быть описаны в программе yacc следующим образом:

A  :  B  C  D
   |  E  F
   |  G
   ;

Использование рекурсии в файле грамматики

Рекурсия позволяет определять функцию через себя саму. В определении языков такие правила обычно имеют следующую форму:

правило    :        КонечноеУсловие
        |     правило конечное-условие

Таким образом, в простейшем варианте правило соответствует значению КонечноеУсловие, но при этом правило может содержать произвольное число повторений значения КонечноеУсловие. Вторая строка, использующая правило внутри описания конструкции правило, применяет рекурсию. Анализатор последовательно обрабатывает входной поток, пока он не сократится до единственного значения КонечноеУсловие.

Если в правиле используется рекурсия, то имя правила всегда должно быть самым левым в теле правила (как в предыдущем примере). Если оно не будет первым, как в следующем примере, то может возникнуть переполнение стека и анализатор прервет работу.

правило    :        КонечноеУсловие
        |       конечное-условие правило

Приведенный ниже пример определяет правило line (строка) как произвольную комбинацию элементов string (текст), завершающуюся символом новой строки (\n):

lines   :        line
        |        lines line
        ;
 
line    :        string '\n'
        ;

Пустая строка

Для обозначения нетерминального символа, соответствующего пустой строке, используйте в качестве тела правила одиночный символ ; (точка с запятой). Для определения правила empty, соответствующего пустой строке, используйте правило, аналогичное следующему:

empty   :  ;
        | x;
 

или

empty   :
        | x
        ;

Маркер конца ввода

Когда лексический анализатор достигает конца входного потока, он передает синтаксическому анализатору маркер конца ввода. Этот маркер является специальной лексемой со значением 0, называемой маркером конца. После того, как синтаксический анализатор получил маркер конца, он проверяет, для всех ли входных данных были выбраны правила грамматики и образует ли обработанная информация законченный блок (в соответствии с правилами файла грамматики yacc). Если образован законченный блок, анализатор завершает работу. Если блок не образован, анализатор передает сообщение об ошибке и также завершает работу.

Лексический анализатор должен передавать маркер конца в некоторый обоснованный момент - например, по достижении конца файла или конца записи.

Обработка ошибок в yacc

При чтении анализатором входного потока данные из этого потока могут не соответствовать ни одному правилу файла грамматики. Анализатор должен обнаруживать подобные ошибки как можно раньше. Если в файле грамматики присутствует функция обработки ошибок, то с ее помощью можно запросить повторный ввод данных, пропустить данные с ошибкой или выполнить очистку и восстановление. Например, при обнаружении ошибки может потребоваться освобождение памяти, выделенной для дерева разбора, удаление или изменение записей таблицы символов и установка флагов для предотвращения дальнейшего разбора.

Если функция обработки ошибок не определена, то при обнаружении ошибки анализатор прекращает работу. Для дальнейшей обработки входного потока с целью обнаружения других ошибок необходимо перезапустить анализатор с той точки, в которой он сможет продолжить разбор входного потока. Одним из способов такого перезапуска является пропуск определенного числа лексем, следующих за лексемой, вызывавшей ошибку.

В команде yacc для обработки ошибок применяется специальная лексема error. Эту лексему помещают в раздел правил в тех местах, где может возникнуть ошибка и требуются специальные действия по ее обработке. Если ошибка возникает в таком месте, анализатор выполняет вместо обычного действия действие лексемы error.

При обработке ошибок в действиях yacc могут применяться следующие макроопределения:

YYERROR Вызывает обработку ошибки анализатором.
YYABORT Анализатор возвращает значение 1.
YYACCEPT Анализатор возвращает значение 0.
YYRECOVERING() Возвращает 1, если была обнаружена синтаксическая ошибка и анализатор еще не завершил процесс исправления.

Для того чтобы одна ошибка не приводила к возникновению нескольких сообщений об ошибках, анализатор остается в состоянии ошибки до те пор, пока он не обработает три лексемы, следующие за лексемой, вызвавшей ошибку. Если в течение этого времени возникнет еще одна ошибка, анализатор пропустит соответствующую лексему без выдачи сообщения.

Например, правило

stat  :  error ';'

задает режим обработки, при котором анализатор будет пропускать лексему с ошибкой и все последующие лексемы до символа точки с запятой. Все лексемы, начиная с лексемы, в которой встретилась ошибка, и до точки с запятой, пропускаются. После обнаружения точки с запятой анализатор редуцирует это правило и выполняет установленные действия по очистке.

Исправление ошибок

При возникновении ошибки вы можете предоставить пользователю, работающему с программой в диалоговом режиме, возможность исправить входной текст, запросив повторный ввод строки. Например:

input : error '\n'
        {
          printf(" Введите последнюю строку еще раз: " );
         }
         input
       {
         $$ = $4;
       }
       ;  

Это один из способов исправления ошибок. Однако в этом варианте анализатор остается в состоянии ошибки на протяжении еще 3 лексем. Если в одной из 3 первых лексем исправленной строки присутствует ошибка, анализатор пропустит их, не выдав сообщение об ошибке. Для того чтобы учесть подобную ситуацию, используйте следующий оператор yacc:

yyerrok;

Этот оператор переводит анализатор в обычное состояние. Процедура исправления ошибок с этим оператором выглядит так:

input : error '\n'
        {
          yyerrok;
          printf(" Введите последнюю строку еще раз: " );
         }
         input
       {
         $$ = $4;
       }
       ;  

Очистка следующей лексемы

Под следующей лексемой подразумевается лексема, которая будет проанализирована программой на следующем шаге. При возникновении ошибки следующая лексема - это лексема, в которой анализатор обнаружил ошибку. Если действие по восстановлению после ошибки определяет точку, с которой обработка будет возобновлена, его код должен очищать следующую лексему. Для очистки значения следующей лексемы применяется следующий оператор:

yyclearin ;

Лексический анализ для команды yacc

Для работы синтаксического анализатора, создаваемого yacc, необходимо наличие лексического анализатора, который будет считывать входной поток, разбивать его на лексемы и передавать эти лексемы (а если необходимо, то и их значения) синтаксическому анализатору. Для создания лексического анализатора, который легко интегрируется с синтаксическим анализатором yacc, можно использовать команду lexlex.

Команда lex создает лексический анализатор с именем yylex. Функция yylex должна возвращать целое число, соответствующее типу считанной лексемы. Такое число называется номером лексемы. Кроме этого, если лексеме присвоен номер, то лексический анализатор должен присвоить этот номер внешней переменной yylval.

Работа анализатора, созданного yacc

Команда yacc преобразует файл грамматики в программу на языке C. После компиляции и запуска эта программа может обрабатывать входной поток в соответствии с заданной грамматикой.

Синтаксический анализатор представляет собой конечный стековый автомат. Анализатор способен считывать из потока и запоминать следующую лексему. Текущим состоянием всегда является состояние в вершине стека. Состояния конечного автомата определяются целыми числами. Начальное состояние автомата - 0, стек содержит только 0, а следующая лексема не считана.

Автомат может выполнять следующие четыре действия:

shiftСостояние Сдвиг. Анализатор помещает текущее состояние в стек, делает указанное Состояние текущим и очищает следующую лексему.
reduceПравило Сокращение. Если анализатор обнаруживает во входном потоке строку, определенную указанным Правилом (номером правила), то он заменяет ее в выходном потоке на Правило.
accept Прием. Анализатор просматривает весь входной поток, сравнивает его с определением грамматики и распознает поток, как удовлетворяющий структуре высшего уровня (определяемой начальным символом). Это действие выполняется только в том случае, если следующая лексема - маркер конца, и означает, что анализатор успешно завершил работу.
error Ошибка. Анализатор не может продолжать обработку входного потока и выборку определенных грамматических правил. Считанные лексемы, совместно со следующей лексемой, ни при каких следующих лексемах не могут сформировать набор, удовлетворяющий одному из правил. Анализатор сообщает об ошибке, пытается исправить ее и продолжить работу.

За один шаг анализатор выполняет следующие действия:

  1. В зависимости от текущего состояния анализатор определяет, требуется ли ему для работы следующая лексема. Если следующая лексема нужна для работы, но она отсутствует, анализатор вызывает функцию yylex для считывания очередной лексемы.
  2. Используя текущее состояние и, если требуется, следующую лексему, анализатор принимает решение о следующем действии и выполняет его. В результате этого состояние может быть помещено в стек или считано из стека, а следующая лексема может быть обработана или не обработана.

Shift

Действие shift является наиболее распространенным действием анализатора. Сдвиг выполняется при условии, что существует следующая лексема. Например, рассмотрим следующее грамматическое правило:

IF shift 34

Если анализатор находится в состоянии, содержащем это правило и следующая лексема - IF, то он выполняет следующие действия:

  1. Оставляет текущее состояние в стеке.
  2. Делает состояние 34 текущим (помещает его в вершину стека).
  3. Очищает следующую лексему.

Сокращение

Действие reduce сокращает стек, что позволяет избежать переполнения. Анализатор применяет это действие после успешного сравнения элемента входного потока с правой частью правила. После этого символы входного потока заменяются на значение из левой части правила. Для определения законченности конструкции анализатор может применять следующую лексему.

Сокращение относится к отдельным грамматическим правилам. В связи с тем, что правилам также присваиваются целые числа, можно легко спутать значения аргументов в действиях shift и reduce. Например, следующее действие относится к правилу грамматики 18:

. reduce 18

А это действие относится к состоянию 34:

IF shift 34

Например, для сокращения следующего правила анализатор считывает из стека три состояния:

A  :  x y z ;

Число считываемых из стека состояний совпадает с числом символов в правой части правила. Эти состояния были помещены в стек при распознавании x, y и z. После считывания этих состояний в вершине стека оказывается состояние, в котором анализатор находится перед обработкой этого правила, то есть текущим становится состояние, которое требовало распознавания правила A для удовлетворения своего правила. Используя это состояние и символ в левой части правила, анализатор выполняет действие goto, которое похоже на сдвиг A. Вычисляется новое состояние, оно помещается в стек и обработка продолжается.

Действие goto отличается от обычного сдвига лексемы. Следующая лексема очищается при сдвиге, но не изменяется действием goto. После считывания этих трех состояний новое состояние может соответствует, например, такой записи:

A  goto 20

Эта запись помещает запись состояния 20 в стек (оно становится текущим состоянием).

Действие сокращения также важно при обработке пользовательских действий и значений. При сокращении правила анализатор перед изменением стека выполняет связанное с правилом действие. Еще один стек, существующий одновременно со стеком состояний, хранит значения, которые были возвращены лексическим анализатором и действиями. При сдвиге значение внешней переменной yylval копируется в этот стек. После выполнения указанного кода анализатор выполняет сокращение. При выполнении действия goto значение внешней переменной yylval также копируется в стек значений. Ключевые слова yacc, начинающиеся с символа $, работают со стеком значений.

Использование неоднозначных правил в программе yacc

Набор грамматических правил является неоднозначным, если какая-либо строка ввода может быть интерпретирована разными способами. Например,правило

expr : expr '-' expr

определяет выражение, состоящее из двух выражений и символа минус между ними. Однако такое правило не дает информации о разборе сложных выражений. Например, текст

expr - expr - expr

может быть разобран с ассоциативностью слева

( expr - expr ) - expr

или с ассоциативностью справа

expr - ( expr - expr )

с получением разных результатов.

Конфликты анализатора

При попытке обработать неоднозначное правило в одном из действий анализатора возникает конфликт. Существует два основных типа конфликтов:

конфликт shift/reduce Правило может быть правильно обработано действиями shift и reduce, но результаты, полученные при выполнении этих действий, будут различаться.
конфликт reduce/reduce Правило может быть правильно обработано двумя способами сокращения, с получением двух разных действий.

Возникновение конфликта shift/shift невозможно. Конфликты shift/reduce и reduce/reduce возникают из-за неполного описания правила. Например, если определено приведенное выше неоднозначное правило, то при получении строки

expr - expr - expr

анализатор читает первые три части:

expr - expr

- они соответствуют правой части правила. Поэтому анализатор может сократить их, используя это правило. После сокращения входной поток преобразуется к виду

expr

- то есть к левой части правила. После этого анализатор считывает оставшуюся часть

- expr

и сокращает ее. При такой интерпретации реализуется ассоциативность слева.

Однако у анализатора есть возможность упреждающего чтения входного потока. Если после считывания выражения

expr - expr

анализатор продолжит чтение, он может получить следующее выражение:

expr - expr - expr

Применение правила к трем правым элементам сократит их до expr. После этого будет обработано выражение

expr - expr

После очередного сокращение будет реализована обработка с ассоциативностью справа.

Таким образом, после считывания первых трех элементов анализатор может выбрать одно из двух допустимых действий - shift или reduce. Если правило выбора не определено, возникает конфликт shift/reduce.

Аналогичная ситуация возникает, когда анализатор может выбрать одно из двух допустимых действий сокращения. Такая ситуация называется конфликтом reduce/reduce.

Реакция анализатора на конфликт

При возникновении конфликта shift/reduce или reduce/reduce команда yacc создает анализатор, выбирая один из допустимых вариантов. Если правило выбора не определено, yacc использует следующие два правила:

  1. При конфликтах shift/reduce выбирается shift.
  2. При конфликтах reduce/reduce выбирается правило, по которому входной поток может быть сокращен раньше.

Использование действий внутри правил может привести к конфликтам в тех случаях, когда действие должно быть выполнено до того, как анализатор определит правило. В таких ситуациях использование приведенных выше правил создает ошибочный анализатор. По этой причине программа yacc сообщает о числе конфликтов shift/reduce и reduce/reduce, разрешенных с использованием предыдущих правил.

Применение отладочного режима в анализаторе, созданном yacc

Вы можете получить доступ к отладочному коду, вызывав команду yacc с опцией -t или откомпилировав файл y.tab.c с ключом -DYYDEBUG.

В обычном режиме работы внешняя переменная yydebug имеет значение 0. Однако, если ей присвоено ненулевое значение, анализатор будет создавать следующие описания:

Для установки переменной можно использовать несколько способов:

Связанная информация

Глава 1, Инструменты и утилиты

Примеры программ с использованием lex и yacc

Создание языка ввода с помощью команд lex и yacc

команды lex, yacc, ed и sed

функция printf


[ Страница назад | Страница вперед | Содержание | Индекс | Библиотека | Юридическая информация | Поиск ]