
- •Глава 1. Введение 2
- •Глава 2. Лексический анализ 6
- •2.4. Построение детерминированного конечного
- •Глава 3. Синтаксический анализ 20
- •Глава 8. Генерация кода 76
- •Глава 9. Системы автоматизации построения трансляторов 120
- •Глава 1. Введение
- •1.1. Место компилятора в программном обеспечении
- •1.2. Структура компилятора
- •Глава 2. Лексический анализ
- •2.1. Регулярные множества и регулярные выражения
- •2.2. Конечные автоматы
- •2.5. Программирование лексических анализаторов
- •2.6. Конструктор лексических анализаторов lex
- •Глава 3. Синтаксический анализ
- •3.1. Основные понятия и определения
- •3.2. Таблично-управляемый предсказывающий разбор
- •3.2.1. Алгоритм разбора сверху-вниз
- •3.2.2. Множества first и follow.
- •3.2.4. Ll(1)-грамматики
- •3.2.5. Удаление левой рекурсии
- •3.2.6. Левая факторизация
- •3.2.7. Рекурсивный спуск
- •3.2.8. Диаграммы переходов для рекурсивного спуска
- •3.2.9. Восстановление после синтаксических ошибок
- •3.3. Разбор снизу-вверх типа сдвиг-свертка
- •3.3.1. Основа
- •3.3.2. Lr(k)-анализаторы
- •3.3.4. Конфликты разбора типа сдвиг-свертка
- •3.3.5. Восстановление после синтаксических ошибок
- •Глава 4. Промежуточные представления программы
- •4.1. Представление в виде ориентированного графа
- •4.2. Трехадресный код
- •4.3. Линеаризованные представления
- •4.4. Организация информации в генераторе кода
- •4.5. Уровень промежуточного представления
- •Глава 5. Элементы теории перевода
- •5.1. Преобразователи с магазинной памятью
- •5.2. Синтаксически управляемый перевод
- •5.3. Атрибутные грамматики
- •5.3.1. Определение атрибутных грамматик
- •5.3.2. Атрибутированное дерево разбора
- •5.3.3. Язык описания атрибутных грамматик
- •Глава 6. Контекстные условия языков программирования
- •6.1. Описание областей видимости и блочной структуры
- •6.2. Структура среды Модулы-2
- •6.3. Занесение в среду и поиск объектов
- •Глава 7. Организация таблиц символов компилятора
- •7.1. Таблицы идентификаторов и таблицы символов
- •7.2. Таблицы идентификаторов
- •7.3. Таблицы символов и таблицы расстановки
- •7.4. Функции расстановки.
- •7.5. Таблицы на деревьях
- •7.7. Сравнение различных методов реализации таблиц
- •Глава 8. Генерация кода
- •8.1. Модель машины
- •8.2. Динамическая организация памяти
- •8.3. Назначение адресов
- •8.4. Трансляция переменных.
7.5. Таблицы на деревьях
Рассмотрим еще один способ организации таблиц с использованием
двоичных деревьев. Будем считать, что на множестве идентификаторов
задан некоторый линейный порядок (например, лексикографический), т.е.
задано некоторое отношение '<', транзитивное, антисимметричное и
антирефлексивное. Каждой вершине двоичного дерева, представляющего
таблицу символов, сопоставлен идентификатор. Вершина может иметь нуль,
одного (правого или левого) или двух (правого и левого) потомков. Если
вершина имеет левого потомка, то идентификатор, сопоставленный левому
потомку, меньше идентификатора, сопоставленного самой вершине; если
имеет правого потомка, то ее идентификатор больше. Элемент таблицы
изображен на рис. 7.6.
+---------------+
TP --->| | | -+--->Ident
+-/----\--------+
/ \
v v
Left Right
Рис. 7.6
Поиск в такой таблице может быть описан следующей процедурой:
function SearchTree(Id,TP):Pointer;
begin if Id=TP^.Ident then return(TP)
elsif IdTP^Ident
then return(Search_tree(Id,TP^.Right))
else return(nil)
end end;
Занесение в таблицу осуществляется процедурой
function Insert_tree(Id,TP):Pointer;
function fill(var P):Pointer;
begin if P=nil then
P:=new(Element);
P^.Ident:=include(Id);
P^.Left:=nil; P^.Right:=nil;
return(P);
else return(Insert_tree(Id,P))
end end;
begin if Id=TP^.Ident then return(TP)
elsif Id| | -+----->| | -+---->| | |
+------+ +------+ +-------+
T1 T2 Tn
Рис. 7.11
7.7. Сравнение различных методов реализации таблиц
Рассмотрим преимущества и недостатки тех или иных методов
реализации таблиц с точки зрения техники использования памяти. Если
таблица размещается в массиве, то, с одной стороны, отпадает
необходимость использования динамической памяти, а с другой -
появляется ряд осложнений. Использование динамической памяти, как
правило, довольно дорогая операция, поскольку механизмы поддержания
работы с динамической памятью довольно сложны. Необходимо поддерживать
списки свободной и занятой памяти, выбирать наиболее подходящий кусок
памяти при запросе, включать освободившийся кусок в список свободной
памяти и, возможно, склеивать куски свободной памяти в списке. С
другой стороны, использование массива требует отведения заранее
довольно большой памяти, а это означает, что значительная память
вообще не будет использоваться. Кроме того, часто приходится заполнять
не все элементы массива (например, в таблице идентификаторов или в тех
случаях, когда в массиве фактически хранятся записи переменной длины,
например если в таблице символов записи для различных объектов имеют
различный состав полей). Обращение к элементам массива может означать
использование операции умножения при вычислении индексов, что может
замедлить исполнение. Наилучшим, по-видимому, является механизм
доступа по указателям и использование факта магазинной организации
памяти в компиляторе. Для этого процедура выделения памяти выдает
необходимый кусок из подряд идущей памяти, а при выходе из процедуры
вся память, связанная с этой процедурой, освобождается простой
перестановкой указателя свободной памяти в состояние перед началом
обработки процедуры. В чистом виде это не всегда, однако, возможно.
Например, локальный модуль в Модуле-2 может экспортировать
некоторые объекты наружу. При этом схему реализации приходится
"подгонять" под механизм распределения памяти. В данном случае,
например, необходимо экспортированные объекты вынести в среду
охватывающего блока и свернуть блок локального модуля.