- •Содержание
- •1.Рабочая программа
- •2.Модуль Вводный
- •3.Модуль Формальные грамматики и языки
- •3.1.Языки и цепочки символов. Способы задания языков
- •3.1.1.Цепочки символов. Операции над цепочками символов
- •3.1.2.Понятие языка. Формальное определение языка
- •3.1.3.Способы задания языков
- •3.1.4.Синтаксис и семантика языка
- •3.2.Определение грамматики
- •3.2.1.Особенности языков программирования
- •3.2.2.Определение грамматики. Форма Бэкуса—Наура
- •3.2.3.Принцип рекурсии в правилах грамматики
- •3.2.4.Другие способы задания грамматик
- •3.3.Классификация языков и грамматик
- •3.3.1.Классификация грамматик
- •3.3.2.Классификация языков
- •3.4.Контроль
- •4.Модуль Распознаватели, механизм вывода цепочек символов
- •4.1.Цепочки вывода. Сентенциальная форма.
- •4.1.1.Сентенциальная форма грамматики. Язык, заданный грамматикой
- •4.1.2.Левосторонний и правосторонний выводы
- •4.1.3.Однозначные и неоднозначные грамматики
- •4.1.4.Эквивалентность и преобразование грамматик
- •4.2.Распознаватели. Задача разбора
- •4.2.1.Общая схема распознавателя
- •4.2.2.Виды распознавателей
- •4.2.3.Классификация распознавателей по типам языков
- •4.3.Контроль
- •5.Модуль Регулярные грамматики и языки
- •5.1.Регулярные языки и грамматики
- •5.2.Леволинейные и праволинейные грамматики. Автоматные грамматики
- •5.3.Алгоритм преобразования регулярной грамматики к автоматному виду
- •5.4.Конечные автоматы
- •5.4.1.Определение конечного автомата
- •5.4.2.Детерминированные и недетерминированные конечные автоматы
- •5.4.3.Преобразование конечного автомата к детерминированному виду
- •5.5.Контроль
- •6.Модуль Контекстно-свободные грамматики и языки
- •6.1.Контекстно-свободные языки
- •6.1.1.Распознаватели кс-языков. Автоматы с магазинной памятью. Определение мп-автомата
- •6.2.Классы кс-языков и грамматик. Класс ll(k) грамматик.
- •6.3.Принципы построения распознавателей для ll(k)-грамматик
- •6.4.Левая факторизация
- •6.5.Удаление левой рекурсии
- •6.6.Алгоритм разбора для ll(1)-грамматик
- •6.7.Алгоритм построения множества first(1,a)
- •6.8.Алгоритм построения множества follow(1,a)
- •6.9.Восходящие распознаватели кс-языков без возвратов
- •6.9.1.Определение lr(k)-грамматики
- •6.10.Принципы построения распознавателей для lr(k)-грамматик
- •6.10.1.Грамматики простого предшествования
- •6.11.Распознаватели для lr(0) и lr(1) грамматик
- •6.11.1.Распознаватель для lr(0)-грамматики
- •6.11.2.Распознаватель для lr(1) грамматики
- •6.12.Контроль
- •7.Модуль Инструментальные средства для построения трансляторов
- •7.1.Инструментальные средства для построения компиляторов
- •7.1.1.Построитель лексических анализаторов Lex
- •7.2.Контроль
- •8.Модуль Особенности программирование трансляторов
- •8.1.Использование значений произвольных типов, алгоритм разбора
- •8.1.1.Алгоритм синтаксического разбора
- •8.1.2.Семантический стек
- •8.2.Неоднозначности и конфликты
- •8.3.Старшинство операций
- •8.4.Дополнительные возможности программ yacc и lex
- •8.4.1.Обработка ошибок
- •8.5.Совместное использование lex и yacc
- •8.5.1.Кодировка лексем и интерфейс
- •8.5.2.Сборка yacc-программ
- •8.6.Советы по подготовке спецификаций
- •8.6.1.Стиль
- •8.6.2.Использование левой рекурсии
- •8.6.3.Уловки анализа лексики
- •8.6.4.Входной синтаксис yacc'а
- •8.7.Контроль
- •9.Модуль Заключение
- •10.Обеспечение лабораторного практикума
- •11.Дополнительная информация. Примеры
- •11.4.Пример простейшего интерпретатора формул
- •11.5.Простой пример
- •11.6.Более сложный пример
- •11.7.Генераторы лексических и синтаксических анализаторов
- •11.8.Генераторы лексических и синтаксических анализаторов на java
- •11.9.Пакеты для разработки компиляторов
- •Список сокращений
- •Литература
- •Приложения Приложение 1. Учебно–методическая карта дисциплины “Системное программное обеспечение. Синтаксические анализаторы”
- •Приложение 2. Вопросы для зачета по дисциплине “Системное программное обеспечение. Синтаксические анализаторы”
- •Приложение 3. Методические указания к лабораторным работам по дисциплине «Системное программное обеспечение. Синтаксические анализаторы»
- •Порядок выполнения работы:
- •Контрольные вопросы
- •Лексический анализатор lex. Анализ структуры программ
- •Краткая теория:
- •Рассмотрим примеры:
- •Краткая теория:
- •Порядок выполнения работы:
- •Контрольные вопросы
- •Лексический анализатор lex, синтаксический анализатор yacc. Алгебраические вычисления
- •Порядок выполнения работы:
- •Контрольные вопросы
- •Лексический анализатор lex и синтаксический анализатор yacc. Изображение геометрических фигур
- •Краткая теория:
- •Создание метафайла и работа сним
- •Порядок выполнения работы:
- •Контрольные вопросы
- •Приложение 4. Организация рейтингового контроля по дисциплине «Системное программное обеспечение. Синтаксические анализаторы»
8.1.2.Семантический стек
Для естественного обмена данными между действиями, каждый терминал или нетерминал может иметь значение. Для доступа к нему в действиях используются псевдопеременные $$ - значение левого символа правила, $ - значение i-ого символа правой части правила. Другими словами, кроме обычного стека состояний построенный YACC'ом анализатор содержит "семантический" стек, содержащий значения символов. Значения имеют тип YYSTYPE, который по умолчанию определяется как int. Действие
expr : expr '+' expr { $$ = $1 + $3; } ;
может быть использовано в интерпретаторе формул, в котором значение нетерминала "выражение" есть его вычисленное значение.
Если для правила не указано никакого действия, или действие не содержит упоминания псевдопеременной $$, то значение левой части правила становится равным значению первого символа правой части, т.е. неявно выполняется действие { $$ = $1; }. Значение очередной лексемы копируется из переменной int yylval, в которую его заносит сканер.
Естественно, что различные символы грамматики могут иметь значения разных типов. Для этого следует определить тип YYSTYPE как union и специфицировать тип терминалов и нетерминалов в разделе описаний. Например:
%{
#define YYSTYPE yys
typedef union {
int intval;
long longval;
nodeptr *ptrval;
} yys;
%
%token ICONST LCONST
%type ICONST
%type LCONST
%type expr
В этом примере символ грамматики может иметь значение одного из трех типов: intval, longval и ptrval, имена которых совпадают с именами полей типа YYSTYPE. При обращении к значению символа, тип которого объявлен в операторе %type, в порожденной YACC'ом C-програме будет обращение к соответствующему полю объединения yys.
Если в качестве внутреннего представления программы используется дерево, естественно иметь в качестве значения нетерминала указатель на соответствующий ему узел дерева.
8.2.Неоднозначности и конфликты
Множество грамматических правил неоднозначно, если существует входная цепочка, которую можно структурировать несколькими различными способами. Например, правило
expr : expr '-' expr
;
- естественный способ выразить тот факт, что арифметическое выражение можно сформировать, взяв два других выражения и разделив их знаком минус. К сожалению, это правило не полностью определяет способ структуризации всей совокупности исходных данных. Так, если входная цепочка - это
expr - expr - expr
правило позволяет представить ее как в виде
( expr - expr ) - expr
так и в виде
expr - ( expr - expr )
(В первом случае говорят о левой ассоциативности, во втором - оправой).
yacc выявляет подобные неоднозначности при построении алгоритма разбора. На примере входной цепочки
expr - expr - expr
рассмотрим проблему, с которой сталкивается алгоритм. После того, как алгоритм считывает второе вхождение expr, исходная цепочка вида:
expr - expr
сопоставляется с правой частью правила. Алгоритм может выполнить свертку с применением данного правила, после чего цепочка преобразуется в expr (левая часть правила). Затем алгоритм считывает окончание входной цепочки
- expr
и снова выполняет свертку. Такая последовательность действий соответствует левой ассоциативности.
Если алгоритм прочитал
expr - expr
он, напротив, может отложить немедленное применение правила и продолжить чтение до тех пор, пока не будет считана цепочка
expr - expr - expr
Алгоритм может теперь применить правило к трем правым символами свернуть их в expr, что даст в результате
expr - expr
Теперь можно еще раз выполнить свертку. Такая последовательность действий соответствует правой ассоциативности. Итак, прочитав
expr - expr
алгоритм может сделать один из двух шагов, каждый из которых законен: перенос или свертку. Нет оснований предпочесть один из них. Такая ситуация называется конфликтом перенос-свертка. Может произойти и так, что алгоритм будет иметь выбор между двумя допустимыми свертками. Такой конфликт называется конфликтом свертка-свертка. Отметим, что конфликтов перенос-перенос возникнуть не может.
Когда возникают конфликты перенос-свертка или свертка-свертка, yacc все-таки порождает алгоритм разбора. Для этого он самостоятельно выбирает один из допустимых шагов в случае, когда выбор есть. Правило, описывающее, как сделать выбор в определенной ситуации, называется правилом разрешения неоднозначностей.
Yacc по умолчанию использует два метода разрешения неоднозначностей:
При конфликте перенос-свертка по умолчанию выполняется перенос.
При конфликте свертка-свертка по умолчанию выбирается правило, встретившееся в yacc-спецификации первым.
Первый метод устанавливает, что свертки менее предпочтительны, чем переносы, если есть выбор. Второй метод дает пользователю довольно грубый механизм управления разбором, поэтому, когда возможно, конфликтов свертка-свертка следует избегать.
Конфликты могут возникать либо из-за ошибок в исходных данных или в логике спецификаций, либо из-за того, что грамматические правила (вполне корректные) требуют более сложного алгоритма разбора, чем может построить yacc. Использование действий внутри правил также может вызывать конфликты, если действие должно выполняться до того, как алгоритм сможет определить достоверно, какое правило следует применить. В таких случаях указанные методы разрешения неоднозначностей не подходят, поскольку приводят к некорректному алгоритму разбора. По этой причине yacc всегда сообщает число конфликтов перенос-свертка и свертка-свертка, разрешенных при помощи метода 1 и метода 2.
Вообще говоря, если можно применить методы разрешения неоднозначностей и породить корректный алгоритм разбора, то можно и переписать грамматические правила таким образом, чтобы они обрабатывали те же исходные данные, но не содержали конфликтов. По этой причине большинство более ранних генераторов компиляторов считали конфликты фатальными ошибками. Опыт показал, однако, что подобное переписывание противоестественно и порождает медленные алгоритмы. Поэтому yacc порождает алгоритмы разбора даже при наличии конфликтов.
Рассмотрим методы разрешения неоднозначностей на следующем примере:
stat : IF '(' cond ')' stat
| IF '(' cond ')' stat ELSE stat
;
Это - фрагмент спецификации языка программирования, включающего оператор if-then-else. В приведенном правиле IF и ELSE являются лексемами, cond - нетерминальный символ, описывающий условные (логические) выражения, а stat - нетерминальный символ, описывающий операторы. Первую альтернативу будем называть простым if-правилом, вторую - if-else-правилом.
Указанные альтернативы в совокупности образуют неоднозначную конструкцию, поскольку входная цепочка
IF ( C1 ) IF ( C2 ) S1 ELSE S2
может в соответствии с ними структурироваться двумя способами:
IF ( C1 ) {
IF ( C2 )
S1
}
ELSE
S2
или
IF ( C1 ) {
IF ( C2 )
S1
ELSE
S2
}
Большинство языков программирования, имеющих такую конструкцию, придерживаются второй интерпретации: ELSE-часть ассоциируется с последним предшествующим IF, которому ELSE-часть еще не сопоставлена. В нашем примере рассмотрим состояние, когда алгоритм разбора уже прочитал цепочку
IF ( C1 ) IF ( C2 ) S1
и встречает слово ELSE. Он может немедленно выполнить свертку по простому if-правилу и получить
IF ( C1 ) stat
а затем прочитать остаток входной цепочки
ELSE S2
и выполнить свертку
IF ( C1 ) stat ELSE S2
по if-else-правилу. Это соответствует первой интерпретации входной цепочки.
С другой стороны, алгоритм может сначала сделать перенос ELSE, прочитать S2, а затем выполнить свертку правой части цепочки IF ( C1 ) IF ( C2 ) S1 ELSE S2
по if-else-правилу и получить
IF ( C1 ) stat
что в свою очередь свертывается по простому if-правилу. Это соответствует второй интерпретации, которая обычно считается более предпочтительной. Такой конфликт перенос-свертка возникает только тогда, когда текущим является определенный входной символ, ELSE, и прочитана определенная входная цепочка.
Вообще говоря, может быть много конфликтов, и каждому соответствует входной символ и прочитанная перед ним входная цепочка. Прочитанная входная цепочка задает состояние алгоритма разбора.
Сообщения yacc о конфликтах лучше всего понять, анализируя описание алгоритма, формируемое по опции -v. Например, фрагмент, описывающий рассмотренное выше конфликтное состояние, выглядит так:
23: shift-reduce conflict (shift 45, reduce 18) on ELSE
state 23
stat : IF ( cond ) stat_ (18)
stat : IF ( cond ) stat_ELSE stat
ELSE shift 45
. reduce 18
Первая строка описывает конфликт - сообщается состояние и входной символ. Обычное для всех состояний описание показывает грамматическое правило, активное в этом состоянии, и действия алгоритма разбора. Повторим, что подчеркивание указывает часть грамматического правила, которая прочитана. В нашем примере в состоянии 23 прочитана входная цепочка, соответствующая
IF ( cond ) stat
В описании показаны два грамматических правила, активных в этот момент. Алгоритм разбора может поступить двояко. Если входной символ - ELSE, можно выполнить перенос, переводящий разбор в состояние 45. Частью описания состояния 45 будет строка
stat : IF ( cond ) stat ELSE_stat
так как в этом состоянии перенос ELSE будет уже выполнен. Вторая строка, описывающая состояние 23, содержит альтернативное действие. Оно описывается при помощи точки, которая означает, что входной символ явно в действии не участвует. В этом случае, если входной символ не ELSE, выполняется свертка
stat : IF ( cond ) stat
по грамматическому правилу 18.
Вновь напомним, что номера, следующие за командами переноса, указывают состояния, а номера, следующие за командами свертки, обозначают грамматические правила. В файле y.output номера правил печатаются в скобках после самих правил, для которых может быть выполнена свертка. В большинстве состояний возможно заданное по умолчанию действие свертка. Пользователь, обнаруживший непредвиденные конфликты перенос-свертка, захочет, быть может, просмотреть файл описаний, чтобы решить, допустимы ли действия по умолчанию.
