
- •Введение
- •1 Предварительные математические сведения
- •1.1 Множества
- •1.2 Операции и отношения
- •1.2.1 Операции над множествами
- •1.2.2 Отношения на множествах
- •1.3.1 Цепочки
- •1.3.2 Операции над цепочками
- •1.4.2 Операции над языком
- •1.5 Алгоритмы
- •1.5.1 Частичные алгоритмы
- •1.5.2 Всюду определенные алгоритмы
- •1.5.3 Рекурсивные алгоритмы
- •1.5.4 Задание алгоритмов
- •1.5.5 Проблемы
- •1.6 Некоторые понятия теории графов
- •1.6.1 Ориентированные графы
- •1.6.2 Ориентированные ациклические графы
- •1.6.3 Деревья
- •1.6.4 Упорядоченные графы
- •2 Введение в компиляцию
- •2.1 Задание языков программирования
- •2.2 Синтаксис и семантика
- •2.3 Процесс компиляции
- •2.4 Лексический анализ
- •2.5 Работа с таблицами
- •2.6 Синтаксический анализ
- •2.7 Генератор кода
- •2.8 Оптимизация кода
- •2.9 Исправление ошибок
- •2.10 Резюме
- •3 Теория языков
- •3.1 Способы определения языков
- •3.2 Грамматики
- •3.3 Грамматики с ограничениями на правила
- •3.4 Распознаватели
- •3.5.1 Определения
- •3.8 Конечные автоматы и регулярные множества
- •3.9.1 Постановка задачи
- •3.10 Контекстно-свободные языки
- •3.10.2 Преобразование КС-грамматик
- •3.10.2.1. Алгоритм проверки пустоты языка
- •3.10.2.2. Алгоритм устранения недостижимых символов
- •3.10.2.3. Алгоритм устранения бесполезных символов
- •3.10.2.5. Алгоритм устранения цепных правил
- •3.10.3 Грамматика без циклов
- •3.10.4 Нормальная форма Хомского
- •3.10.5 Нормальная форма Грейбах
- •3.11 Автоматы с магазинной памятью
- •3.11.1 Основные определения
- •4.1 LL(k)-грамматики
- •4.2.2 Алгоритм поиска направляющих символов
- •4.2.2.1 Множество предшествующих символов
- •4.2.2.2 Множество последующих символов
- •4.2.2.3 Множество направляющих символов
- •4.3 LL(1)-таблица разбора
- •4.3.1 Построение таблицы
- •5 Синтаксический анализ снизу вверх
- •5.1 LR(k)-грамматики
- •5.2 LR(1)-грамматики
- •5.3 LR(1)-таблица разбора
- •5.3.1 Состояния анализатора
- •5.3.2 Построение таблицы
- •5.3.3 LR-конфликты
- •5.3.4 Разбор цепочки по таблице
- •5.4 Сравнение LL- и LR-методов разбора
- •6 Включение действий в синтаксис
- •6.2 Работа с таблицей символов
- •7 Проектирование компиляторов
- •7.1 Число проходов
- •7.2 Таблицы символов
- •7.2.2 Бинарное дерево
- •7.4.1 Стек времени прогона
- •7.4.2 Методы вызова параметров
- •7.4.3 Обстановка выполнения процедур
- •8 Генерация кода
- •8.1 Генерация промежуточного кода
- •8.2 Структура данных для генерации кода
- •8.3.1 Присвоение
- •8.3.2 Условные зависимости
- •8.3.3 Описание идентификаторов
- •8.3.4 Циклы
- •8.3.5 Вход и выход из блока
- •8.3.6 Прикладные реализации
- •8.4 Проблемы, связанные с типами
- •8.5 Время компиляции и время прогона
- •9 Исправление и диагностика ошибок
- •9.1 Типы ошибок
- •9.2 Лексические ошибки
- •9.3 Ошибки в употреблении скобок
- •9.4 Синтаксические ошибки
- •9.4.1 Методы исправления синтаксических ошибок
- •9.4.2 Предупреждения
- •9.4.3 Сообщения о синтаксических ошибках
- •9.5 Контекстно-зависимые ошибки
- •9.6 Ошибки, связанные с употреблением типов
- •9.7 Ошибки, допускаемые во время прогона
- •9.8 Ошибки, связанные с нарушением ограничений
- •Заключение
- •Список литературы
- •Глоссарий
236
8.3.6 ПРИКЛАДНЫЕ РЕАЛИЗАЦИИ
Во время компиляции в соответствии с прикладной реализацией иден-
тификатора, например x в x + 4, необходимо:
1)найти в таблице символов запись, соответствующую определяющей реализации (int x) идентификатора;
2)поместить в нижний стек статические характеристики, соответству-
ющие идентификатору.
Подразумевается, что в нижний стек также помещаются статические характеристики констант и т.д.
8.4 ПРОБЛЕМЫ, СВЯЗАННЫЕ С ТИПАМИ
Основной проблемой для трансляторов с языков высокого уровня явля-
ется приведение (автоматическое изменение) типов. Здесь можно выделить,
как минимум, шесть задач [2]:
1)Распроцедуривание, например переход от procedure real к real.
2)Разыменование, например переход от pointer real к real.
3)Объединение, например переход от real к struct(real, char).
4)Векторизация, например переход от real к real [ ].
5)Обобщение, например переход от int к real.
6)Чистка, например переход от real к void.
Возможность осуществления приведения зависит от синтаксической позиции. Например, в левой части присвоения может иметь место только распроцедуривание (вызов процедур без параметров), а в правой части – лю-
бое из шести приведений. Иногда возникает необходимость нескольких при-
ведений. Например, если x имеет вид pointer real и a – pointer int, то прежде чем производить присвоение x := a, необходимо сначала разыменовать, а за-
тем обобщить.
237
В зависимости от того, какие приведения могут выполняться в синтак-
сических позициях, последние называются мягкими, слабыми, раскрытыми,
крепкими и сильными. Например, левая часть присвоения называется мягкой
(допускает только распроцедуривание), а правая часть – сильной (допускает любое приведение). Кроме ограничений типов приведений, разрешаемых в заданной синтаксической позиции, существуют правила, определяющие по-
рядок осуществления различных приведений. Например, объединение может произойти только один раз и не должно следовать за векторизацией. Можно определить грамматику, которая генерирует все допустимые последователь-
ности приведений в заданной синтаксической позиции, например:
SOFT deprocedure | deprocedure SOFT
Любое предложение, генерированное посредством SOFT, представляет собой допустимую последовательность приведений в мягкой синтаксической позиции (т.е. в левой части присвоения).
Для раскрытия позиции (например, индекса в a[i]) справедливы следу-
ющие правила:
MEEK deprocedure | deprocedure MEEK | dereference | dereference MEEK
Другими словами, в раскрытой позиции можно выполнять распроцедуривание и разыменование любое число раз и в любом порядке,
например pointer procedure poiter int в вид int.
Для сильной позиции (например, правая часть присвоения или пара-
метр в вызове процедуры) правила таковы:
STRONG dereference STRONG | deprocedure STRONG |

238
unit |
unit ROW | widen | widen widen | widen ROW |
widen widen ROW | ROW
ROW row | row ROW
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Вид данных до выполнения приведений называется априор-
ным, а после выполнения – апостериорным.
·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Вслучае сильных и раскрытых синтаксических позиций известны и априорный, и апостериорный виды. Для других позиций известен лишь априорный вид и некоторая информация об апостериорном виде, например о том, что он должен начинаться со struct или pointer struct, или о том, что он не должен начинаться с proc, как в левой части присвоения.
Компилятор, применяя соответствующую грамматику, генерирует по-
следовательность приведений из априорного вида к известному либо подхо-
дящему апостериорному виду. Если нельзя найти никакой последовательно-
сти приведений, программа синтаксически неправильная.
С другой стороны, если подходящая последовательность существует,
компилятор, применяя приведения по порядку, генерирует код времени про-
гона.
Еще один вид приведения – чистка. Чистка представляет собой особую форму приведения и происходит в тех местах, где стоит точка с запятой:
x := y;
239
Еще одна сложность связана с выбирающим предложением. В предло-
жении
x + if b then 1 else 2.3
во время компиляции необходимо знать тип (вид) правого операнда знака
«+». Все варианты выбирающего предложения должны приводить к общему виду, называемому объектным. Этот процесс называется уравнением, и его правила подразумевают, что последовательность сильных приведений можно применять во всех вариантах, кроме одного, в котором используется лишь последовательность приведений, уместных лишь для синтаксической пози-
ции выбирающего предложения. В вышеприведенном примере выбирающее предложение находится в крепкой синтаксической позиции, которая не до-
пускает расширения. Однако внутри выбирающего предложения один вари-
ант допускает сильное приведение, что может повлечь за собой расширение.
В этом случае объектным видом окажется real, и первый вариант следует расширить, а второй нежелательно подвергать приведению.
Действия компилятора при обращении с выбирающими предложения-
ми заключаются в том, что статические характеристики всех вариантов вы-
бирающего предложения помещаются в нижний стек, а затем выводятся объ-
ектный вид и различные последовательности приведения для каждого вари-
анта. Если какая-либо последовательность вызывает необходимость генера-
ции кода во время прогона, ее можно выделить в отдельный поток, и между двумя этими потоками ввести указатели, чтобы во время следующего прохо-
да код можно было соединить в нужном порядке.
8.5 ВРЕМЯ КОМПИЛЯЦИИ И ВРЕМЯ ПРОГОНА
Как отмечалось ранее, генератор кода во время компиляции обращает-
ся к нижнему стеку и генерирует код операций, которые будут выполняться во время прогона. Что именно должно быть сделано во время компиляции, а
что во время прогона, существенно зависит от языка программирования [2].

240
Тем не менее, разработчик компилятора имеет возможность разделять функ-
ции компиляции и прогона. Например, не вполне ясно, повлечет ли разыме-
нование за собой какие-либо действия при прогоне или нет. На первый взгляд может показаться, что нет, так как это просто замена в памяти одного значения другим и изменение адреса времени прогона. Новым адресом будет тот, на который указывает первоначальный адрес. Однако значение указателя может быть неизвестным при компиляции, новый адрес можно обозначить,
изменив первоначальный адрес так, чтобы в нем указывался дополнительный косвенный уровень. То есть в некоторых случаях требуется выполнить дей-
ствие (переслать значение по новому адресу).
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Для повышения эффективности выдаваемого кода при компиляции можно проделать дополнительную работу, которую называют оптимизацией.
·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Воптимизацию входит удаление кодов из циклов там, где это не влияет на значение программы, исключение возможности вычисления идентичных выражений более одного раза и т.д. Особое внимание уделяется возможности избежать перезаписи сложных структур данных во время прогона.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Контрольные вопросы по главе 8
·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
1.Технология создания промежуточного кода. Виды промежуточного кода.
2.Алгоритм преобразования арифметического выражения в префикс-
ную и постфиксную формы.
3.Формализация записи промежуточного кода.
4.Структуры данных для генерации промежуточного кода.
241
5.Алгоритм генерации промежуточного кода для арифметических вы-
ражений.
6.Таблица блоков, ее назначение.
7.Генерация кода для присвоения.
8.Генерация кода для условных зависимостей.
9.Генерация кода для описания идентификаторов.
10.Генерация кода для циклов.
11.Генерация кода для входа и выхода из блока.
12.Генерация кода для прикладной реализации.
13.Проблемы генерации кода, связанные с типами.
14.Работа генератора кода во время компиляции и во время прогона.