- •Университет наяновой
- •М. А. Шамашов основные структуры данных и алгоритмы компиляции
- •Предисловие
- •Введение
- •1. Краткий обзор процесса компиляции
- •2. Лексический анализ
- •0123...9 Пробел
- •3. Организация таблиц компилятора
- •3.1. Общий вид таблиц
- •3.2. Прямой доступ к таблице или метод индексов
- •3.3. Неупорядоченная таблица или метод линейного списка
- •3.4. Упорядоченная таблица. Бинарный, двоичный или логарифмический поиск
- •3.5. Сбалансированные деревья
- •3.6. Деревья оптимального поиска
- •3.7.1. Рехеширование
- •3.7.3. Метод цепочек или гроздей
- •4. Общие методы синтаксического анализа
- •4.1. Нисходящий разбор с возвратами
- •4.2. Восходящий разбор с возвратами
- •4.3. Символьный препроцессор на основе бэктрекинга
- •4.3.1. Фаза анализа и перевода грамматики во внутреннее представление
- •4.3.2. Лексичекий анализ в сп
- •4.3.3. Синтаксический анализ в сп
- •4.3.4. Выполнение семантических действий
- •5. Однопроходный синтаксический анализ без возвратов
- •5.1. Ll(k) языки и грамматики
- •5.1.1. Предсказывающие алгоритмы разбора и разбор для ll(1)-грамматик
- •5.1.2. Рекурсивный спуск
- •5.2. Языки и грамматики простого предшествования
- •Xy, если u xy
- •X y, если u xU1) (y l(u1))
- •X y, если (u u1y) (X r(u1)) or
- •5.2.1. Алгоритм Вирта–Вебера для анализа языков простого предшествования
- •5.2.2. Функции предшествования.
- •5.2.3. Проблемы построения грамматик предшествования
- •5.3. Операторная грамматика предшествования
- •6. Введение в семантику
- •6.1. Внутренние формы исходной программы
- •6.1.1. Польская инверсная запись
- •If выр then инстр 1 else инстр 2
- •6.1.2. Интерпретация полиЗа
- •6.1.3. Генерирование команд по полиЗу
- •6.1.4. Тетрады и триады
- •6.2. Семантические подпрограммы перевода инфиксной записи в полиз и аспекты их реализации
- •6.3. Семантические подпрограммы для перевода в тетрады
- •6.4. Метод замельсона–бауэра для перевода в полиз и тетрады
- •6.5. Нейтрализация ошибок
- •6.5.1. Исправления орфографических ошибок
- •6.5.2. Нейтрализация семантических ошибок
- •6.5.3. Нейтрализация синтаксических ошибок
- •7. Машинно-независимая оптимизация программ
- •7.1. Исключение общих подвыражений
- •7.2. Вычисления во время компиляции
- •7.3. Оптимизация булевых выражений
- •7.4. Вынесение инвариантных вычислений за цикл
- •8. Машинно-зависимые фазы компиляции
- •8.1. Распределение памяти
- •8.2. Генерация кода и сборка
- •8.3. Трансляция с языка ассемблера
- •Заключение
- •Список литературы
- •Содержание
- •1. Краткий обзор процесса компиляции 5
- •2. Лексический анализ 10
- •3. Организация таблиц компилятора 16
- •4. Общие методы синтаксического анализа 28
- •5. Однопроходный синтаксический анализ без возвратов 52
- •6. Введение в семантику 78
- •7. Машинно-независимая оптимизация программ 102
- •8. Машинно-зависимые фазы компиляции 109
5.2.3. Проблемы построения грамматик предшествования
Из определения грамматик простого предшествования следует, что далеко не всякая КС–грамматика является грамматикой простого предшествования. В частности, в произвольной КС–грамматике может не выполняться условие однозначной обратимости. Наличие нескольких одинаковых правых частей в продукциях грамматики приведет к неоднозначности при свертке основ, а следовательно и алгоритма разбора по такой грамматике станет недетерминированным. Но это не фатальная проблема, так как нетрудно показать, что каждый КС–язык порождается по крайней мере одной обратимой КС–грамматикой.
Действительно, если в грамматике имеются правила вида: A и B , то одно из них, например B можно удалить, а все правила вида C B заменить парой правил: C и C B. Причем, последнее правило сохраняется только в том случае, если у нетерминала B есть и другая альтернатива, кроме B .
Пример 5.14. Рассмотрим фрагмент правил грамматики, определяющий синтаксис заголовка процедуры:
Заголовок проц. PROCEDURE имя проц. ( список параметров );
имя проц. идентификатор
список параметров идентификатор
идентификатор , список параметров
Устраним правило имя проц. идентификатор и в результате получим обратимую грамматику:
Заголовок проц. PROCEDURE идентификатор ( список параметров );
список параметров идентификатор
идентификатор , список параметров
Вторая проблема более существенна. Очень часто между двумя символами грамматики имеет место более одного отношения предшествования. Единственное, что мы можем тогда сделать, – это обработать и изменить грамматику так, чтобы обойти конфликт.
Обычно, наличие двух отношений между символами грамматики – это следствие рекурсии, в частности левосторонней. Предположим, что в грамматике существует некоторое правило U U... . Если есть другое правило вида V ...XU... , то одновременно XU и в силу того, что U L(U) , – X будет U. Иногда можно избавиться от такого конфликта, заменив правило
V ... XU ...
парой правил:
V ... XU1 ... , U1 U ,
где U1 новый нетерминал. При этом получим, что XU1 и X U. Такой прием называют стратификацией или разделением. Заметим, что аналогично может решаться и конфликт с отношениями и при правосторонней рекурсии.
Пример 5.15. Чтобы показать, как делается стратификация, воспользуемся привычной уже грамматикой арифметических выражений:
E ETETTT
T TMTMM
M (E)i
Из первой группы правил следует, что ‘’ и ‘’T, а так как T леворекурсивен, получаем также, что ‘’ и ‘’ T. Аналогичная проблема возникает и с символами ‘(’ и ‘E’. Без ущерба для структуры цепочек языка изменим заданную грамматику на следующую:
E ET1ET1T1T1
Т1 T
T TMTMM
M (E1)i
E1 E .
Начальным символом грамматики при этом станет E1, множество самых левых и самых правых символов для нетерминалов полученной грамматики представлено на рис. 5.17, а матрица и функции предшествования на рис. 5.18.
Этот пример может создать впечатления, что при стратификации изменения не столь значительны. Однако, если в грамматике 100 правил и более 100 символов (а так оно и есть в языках типа Паскаль), то даже искушенный специалист затратит немало времени на то, чтобы переделать такую грамматику в грамматику простого предшествования. В результате может измениться вся структура языка, не говоря о том, что грамматика станет неудобочитаемой. Кроме того, стратификация не всегда спасает, так как она часто приводит к конфликтам иного рода. Если одновременно для двух символов грамматики x и y выполняются отношения x y и x y, то лучший выход – применить другую технику.
Первопричина проблем метода простого предшествования состоит в том, что решения принимаются с учетом весьма ограниченного контекста возможной основы. В сущности, в каждом случае во внимание принимается только два соседних символа (не случайно грамматика простого предшествования называется грамматикой (1,1) предшествования). Если же рассматривать и другие символы или большее количество символов, то можно надеяться, что конфликтных ситуаций станет меньше.
Проиллюстрируем это на примере сентенциальной формы ETF исходной грамматики из примера 5.15. Поскольку отношения T и T противоречивы, мы не можем всего по двум символам, и T , сделать вывод о том является ли T головой основы или и T одновременно входят в основу и нужно выполнить сложение. Если же известно два символа и или же три символа T, то интуиция подскажет, что складывать нельзя и следовательно символ в основу не входит.