- •Предисловие
- •Глава 1 введение в проблематику конструирования компиляторов
- •1.1. Понятие компилятора и его структура
- •1.2. Применение компиляторов и задачи их разработки
- •Глава 2 способы задания формальных языков
- •2.1. Математический аппарат теории
- •Формальных языков, перевода и компиляции
- •1) AR*a для всех aÎa;
- •2.2 Цепочки и языки
- •2.3 Грамматики
- •2.4. Распознаватели
- •2.5 Регулярные выражения и синтаксические диаграммы
- •2.6. Автоматы с магазинной памятью (мп - автоматы )
- •2.7. Соответствия между способами описания языков
- •Глава 3 основы теории перевода
- •3.1. Определение перевода
- •3.2. Модели простейших трансляторов
- •3.2.1. Конечные преобразователи
- •3.2.2. Преобразователи с магазинной памятью
- •Глава 4 конструирование сканеров
- •4.1. Общая характеристика процесса сканирования
- •4.2. Описание лексем в языке расширенных регулярных выражений
- •4.3. Построение недетерминированного конечного автомата по расширенному регулярному выражению
- •4.4. Преобразование недетерминированного конечного автомата в детерминированный
- •Замечание. Среди состояний могут оказаться недостижимые состояния . Состояние р называется достижимым , если существует такая цепочка w , что (q0, w) *(p , e ). ¨
- •4.5. Преобразование синтаксической диаграммы в конечный автомат
- •4.6. Представление результатов сканирования
- •4.7. Методики конструирования сканеров
- •Глава 5 конструирование однопроходных нисходящих анализаторов
- •5.1. Определение синтаксического разбора
- •5.2. Нисходящий и восходящий разборы
- •5.3. Ll(k) - грамматики
- •5.4. Предсказывающие алгоритмы разбора
- •5.5. Алгоритмы построения управляющих таблиц для левых анализаторов
- •5.6. Приведение грамматик к ll - форме
- •Глава 6 основы генерации кода
- •6.1. Перевод и генерация кода
- •6.2. Представления промежуточной программы
- •6.3. Преобразование промежуточной программы в ассемблерный код
6.2. Представления промежуточной программы
Промежуточная программа должна, с одной стороны, отражать синтаксическую структуру исходной программы, а с другой стороны, каждый оператор промежуточной программы должен относительно просто транслироваться в машинный код.
Наиболее распространенными способами представления промежуточной программы являются :
1) постфиксная польская запись ;
2) префиксная польская запись ;
3) списочные структуры, представляющие деревья ;
4) многоадресный код с явно именуемыми результатами ;
5) многоадресный код с неявно именуемыми результатами.
Рассмотрим эти способы представления на примере оператора присваивания :
A:=B+C* (- D ) ,
постфиксная и префиксная формы польской записи этого оператора имеют вид :
A B C D - * + := ; - постфиксная форма ;
:= A + B * C - D ; - префиксная форма .
Эти формы записи являются линейными представлениями синтаксического дерева , показанного на рис. 6.1.
Правило построения синтаксического дерева следующее :
внутренние вершины дерева представляют операции , операндами которых служат их (вершины) прямые потомки .
Можно использовать три способа обхода дерева : слева - направо, сверху - вниз, снизу - вверх . Легко видеть, что между этими способами обхода и формами записи существует соответствие :
обход слева - направо ® инфиксная форма (обычная)
A :=B + C *- D ;
обход сверху - вниз ® префиксная форма := A + B * C - D ;
обход снизу - вверх ® постфиксная форма A B C D - * + := .
Синтаксическое дерево можно закодировать списочной структурой. Тогда эта структура будет являться промежуточной программой.
Другой метод кодирования синтаксического дерева - применение многоадресного кода с явно именуемыми результатами. Используя этот код, синтаксическое дерево (рис.6.1) можно представить в виде последовательности следующих элементарных кодов :
T1 ¬ - D,
T2 ¬ * C T1,
T3 ¬ + B T2,
A ¬ T3 .
Код вида А¬ Q B1¼Bn означает, что n - местную операцию Q необходимо применить к текущим значениям переменных (операндов) B1¼Bn, и полученное значение присвоить переменной А. В таком коде используется ряд промежуточных переменных для запоминания результатов. Можно избежать их использования, если применить многоадресный код с неявно именуемыми результатами. Для этого каждый элементарный код помечается числом, затем левая часть кода убирается и обращение к промежуточному результату происходит при помощи этого приписанного числа. Для рассматриваемого примера последовательность кодов с неявно именуемыми результатами выглядит следующим образом :
-
1 : - D,
2 : * C (1),
3 : + B (2),
4 : = A (3).
Число в скобках здесь означает адрес выражения, помеченного этим числом. Проиллюстрируем еще раз технику представления промежуточной программы на примере оператора “ если ” , следующего вида :
if i = j then S1 else S2 ,
где S1 и S2 - некоторые произвольные операторы.
Возможное постфиксное польское представление этого оператора имеет вид i j EQUAL L2 JFALSE L JAMP ,
где и - постфиксное представление операторов S1 и S2 соответственно ;
EQUAL - бинарная операция проверки на равенство операндов (принимает значение истина или ложь ) ;
L2 - метка начала ;
JFALSE - бинарная операция , вызывающая переход по второму аргументу , если значение первого - ложь , и не вызывающая никаких действий , если значение первого - истина ;
L - метка первой команды следующей за ;
JUMP - унарная операция, вызывающая переход на точку, задаваемую аргументом .
Левый анализатор построил бы для данного оператора “если” его левый разбор p, которому соответствует дерево вывода, показанное на рис. 6.2.
Обработав это дерево и оставив в нем только существенную информацию об операциях и операндах, анализатор выдаст синтаксическое дерево (промежуточную программу), показанное на рис. 6.3.
В заключение приведем два примера без комментариев.
Пример 6.1. Задан оператор A := ( B - C ) / ( B + C ). Синтаксическое дерево, представляющее этот оператор, показано на рис. 6.4.
Постфиксное польское представление имеет вид
ABC - BC + / := .
Рис. 6.4
Многоадресный код с явно именуемыми результатами представлен следующей последовательностью кодов :
T1 ¬ - BC,
T2 ¬ +BC,
T3 ¬ / T1 T2 ,
A ¬ T3 .
Пример 6.2. Задан оператор :
if B > C then
if D > E then A := B + С
else A := B - C
else A := B * C
Синтаксическое дерево для этого оператора показано на рис. 6.5.
Постфиксное польское представление имеет вид
B C GT L2 JFALSE D E GT L3 JFALSE A B C + := L JUMP
A B C-:= L JUMP A B C * := .
Многоадресный код с явно именуемыми результатами - это последовательность кодов :
T1 ¬ > BC
JFALSE T1 L2
T2 ¬ > D E
JFALSE T2 L3
T3 ¬ + B C
A ¬ T3
JUMP L
L3 : T4 ¬ - B C
A ¬ T4
JUMP L
L2 : T5 ¬ * B C
A ¬ T5
Рис. 6.5