- •Университет наяновой
- •М. А. Шамашов основные структуры данных и алгоритмы компиляции
- •Предисловие
- •Введение
- •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
4.1. Нисходящий разбор с возвратами
В алгоритме используется два магазина L1 и L2 и счетчик с текущей позицией входного указателя. Для описания алгоритма воспользуемся стилизованными обозначениями, похожими на описания конфигурации МП-автомата.
Алгоритм 4.1. Алгоритм нисходящего разбора с возвратами.
Вход. Не леворекурсивная КС-грамматика G = (N, , P, S) и входная цепочка = a1, a2, , an (n 0). Предполагается что правила из P пронумерованы числами 1, 2 , , p.
Выход. Левый разбор цепочки , если он существует, или слово “ошибка” в противном случае.
Метод.
(1) Для каждого нетерминала А N упорядочить его альтернативы. Пусть А j - индекс j-ой альтернативы нетерминала А.
(2) Четверкой (q, i, , ) будем обозначать конфигурацию алгоритма, где:
(а) q - состояние алгоритма;
(б) i - позиция входного указателя (предполагается, что (n+1)-ым “входным символом” служит правый концевой маркер $);
(в) - содержимое первого магазина (L1);
(г) - содержимое второго магазина (L2).
Будем считать, что верх первого магазина расположен справа, а второго - слева.
Магазин L2 служит для представления “текущей” левовыводимой цепочки, то есть той сентенциальной формы, которая получилась к данному моменту разбора в результате развертки нетерминалов. Верхний символ в L2 будет символом, помечающим активную вершину порожденного к данному моменту частично дерева левого выхода. В L1 представлена текущая история проделанных выборов альтернатив и входные символы, по которым прошла входная головка. Алгоритм имеет три состояния: q- нормальной деятельности, b-возврата, t – заключительное состояние.
(3) Начальная конфигурация алгоритма: (q, 1, , S$).
(4) Существует шесть типов шагов. Эти шаги будут описаны в терминах их воздействия на конфигурацию алгоритма. Запись (q, i, , ) (q, i, , ) означает что если (q, i, , ) - текущая конфигурация, то нужно перейти в следующую конфигурацию (q, i, , ). Если не оговорено противное, то и i - число от 1 до (n+1), (I)*, где I - множество индексов альтернатив, (N)*.
Шесть шагов определяется так:
(а) Разрастание дерева
(q, i, , A) (q, i, A1, 1), где A1 правило из P и 1 - первая альтернатива нетерминала A, а A1 – ее индекс. Этот шаг соответствует разрастанию частичного дерева вывода, при котором применяется первая альтернатива самого левого нетерминала дерева.
(б) Успешное сравнение входного символа с порожденным символом
(q, i, , a) (q, i+1, а, ), при условии, что а i= а (i n). Если i-ый входной символ совпадает с очередным порожденным терминалом, то терминал передается из L2 в L1, а позиция указателя увеличивается на единицу.
(в) Успешное завершение
(q, n+1, , $) (t, n+1, , ). Достигнут конец входной цепочки и найдена левовыводимая цепочка, совпадающая с входной. Левый разбор входной цепочки восстанавливается по цепочке с помощью гомоморфизма h, где h(a)=, для всех а и h(A j)=p, где p - номер правила А и - j-ая альтернатива нетерминала А.
(г) Неудачное сравнение входного символа с порожденным символом
(q, i, , а) (b, i, , a), при условии, что а i а. Надо перейти в состояние возврата b, как только обнаружится, что порожденная левовыводимая цепочка не совпадает с входной цепочкой.
(д) Возврат по входу
(b, i, a, ) (b, i1, , a) для всех а. В состоянии возврата входные символы переносятся назад из L1 в L2.
(е) Испытание очередной альтернативы
(b, i, Aj, j)
(е1) (q, i, Aj+1, j+1), если j+1 - (j+1)-ая альтернатива нетерминала А. (j в L2 заменяется на j+1).
(е2) Следующая конфигурация невозможна, если i=1, A=S и S имеет только j альтернатив. (Это условие указывает, что все возможные левовыводимые цепочки, совместимые с входной цепочкой , уже исчерпаны, а ее разбор не найден).
(е3) (b, i, , A) в оставшихся случаях. (Здесь исчерпаны все альтернативы нетерминала A и дальнейший возврат происходит путем удаления Aj из L1 и замены в L2 цепочки j на A).
Алгоритм выполняется следующим образом:
Шаг 1: Исходя из начальной конфигурации, вычислить последующие конфигурации C0 C1 Ci , пока их можно вычислить.
Шаг 2: Если последняя конфигурация (t, n+1, , ), - выдать h() и остановиться, иначе выдать сообщение об ошибке.
Пример 4.1.
Рассмотрим работу алгоритма на примере грамматикой G для упрошенных арифметических выражений с правилами:
-
(1)
Е ТE
(E1) -
индекс для альтернативы ТE
(2)
Е Т
(E2)
(3)
T FТ
(T1)
(4)
T F
(T2)
(5)
F a
(F1)
Для входа aa алгоритм вычислит следующую последовательность конфигураций (над символом перехода указан индекс шага алгоритма) :
(q, 1, , E$) a (q, 1, E1, TE$) a (q, 1, E1T1, FTE$)
a (q, 1, E1T1F1, aTE$) б (q, 2, E1T1F1а, TE$)
г (b, 2, E1T1F1а, TE$) д (b, 1, E1T1F1, аTE$)
е3 (b, 1, E1T1, FTE$) е1 (q, 1, E1T2, FE$)
а (q, 1, E1T2F1, aE$) б (q, 2, E1T2F1a, E$)
б (q, 3, E1T2F1a, E$) а (q, 3, E1T2F1aE1, ТE$)
а (q, 3, E1T2F1aE1T1, FТE$) а (q, 3, E1T2F1aE1T1F1, aТE$)
б (q, 4, E1T2F1aE1T1F1a, ТE$) г (b, 4, E1T2F1aE1T1F1a, ТE$)
д (b, 3, E1T2F1aE1T1F1, aТE$) е3 (b, 3, E1T2F1aE1T1, FТE$)
е1 (q, 3, E1T2F1aE1T2, FE$) а (q, 3, E1T2F1aE1T2F1, aE$)
б (q, 4, E1T2F1aE1T2F1a, E$) г (b, 4, E1T2F1aE1T2F1a, E$)
д (b, 3, E1T2F1aE1T2F1, aE$) е3 (b, 3, E1T2F1aE1T2, FE$)
е3 (b, 3, E1T2F1aE1, TE$) е1 (q, 3, E1T2F1aE2, T$)
а (q, 3, E1T2F1aE2T1, FТ$) а (q, 3, E1T2F1aE2T1F1, аТ$)
б (q, 4, E1T2F1aE2T1F1а, Т$) г (b, 4, E1T2F1aE2T1F1а, Т$)
д (b, 3, E1T2F1aE2T1F1, aТ$) е3 (b, 3, E1T2F1aE2T1, FТ$)
е1 (q, 3, E1T2F1aE2T2, F$) а (q, 3, E1T2F1aE2T2F1, a$)
б (q, 4, E1T2F1aE2T2F1a, $) в (t, 4, E1T2F1aE2T2F1a, )
В результате получается левый разбор h(E1T2F1aE2T2F1a)=145245.
Корректность приведенного алгоритма можно строго доказать [1].