- •Университет наяновой
- •М. А. Шамашов основные структуры данных и алгоритмы компиляции
- •Предисловие
- •Введение
- •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.1.1. Предсказывающие алгоритмы разбора и разбор для ll(1)-грамматик
Разбор для LL(k)-грамматики удобно осуществлять с помощью k-предсказывающего алгоритма разбора. Такой алгоритм для КС-грамматики G=(N, , P, S), используя входную ленту, магазин и выходную ленту (см. рис. 5.2), пытается проследить левый вывод цепочки, записанной на его входной ленте.
При чтении анализируемой цепочки, находящейся на входной ленте, входная головка может “заглядывать вперед” на k очередных символов. Эту цепочку из k символов, увиденную впереди входной головкой, принято называть аванцепочкой. На рис. 5.2 аванцепочкой служит подцепочка u входной цепочки u .
Магазин содержит цепочку X$, где X – цепочка магазинных символов, X – верхний символ магазина, а $ - специальный символ, используемый в качестве маркера дна магазина. Алфавит магазинных символов (без $) обозначим через .
Выходная лента содержит цепочку , состоящую из номеров правил грамматики, применяемых при левом выводе.
Конфигурацию предсказывающего алгоритма разбора будем представлять в виде тройки (, X, ), где
(1) – еще не проанализированная часть входной цепочки,
(2) X – цепочка в магазине (X – верхний символ магазина),
(3) – цепочка на выходной ленте.
Например, на рис. 5.2 изображена конфигурация (u , X, ).
Работой k-предсказывающего алгоритма A руководит управляющая таблица М, задающая отображение множества ({$}) k в множество, содержащее:
(1) (,i), где , а i – номер правила (предполагается, что будет либо правой частью i-го правила, либо некоторым ее представлением),
(2) выброс (извлечение из магазина),
(3) допуск,
(4) ошибка.
Алгоритм анализирует входную цепочку, проделывая последовательность тактов, очень похожих на такты преобразователя с магазинной памятью (см. [10] или раздел 4.1 данного пособия). На каждом такте сначала определяется аванцепочка u и верхний символ магазина X. Затем рассматривается элемент M(X, u) управляющей таблицы. Такты алгоритма A мы опишем в терминах отношения перехода , определенного на множестве конфигураций. Пусть u = FIRSTk(), тогда в алгоритме A возможны следующие такты:
(1) (, X, ) (, , i), если M(X, u) (,i). Здесь верхний символ магазина X заменяется цепочкой (правой частью правила X ) и к выходу добавляется номер этого правила i . Входная головка не сдвигается.
(2) (, a , ) (, , ), если M(X, u) выброс и a. Когда верхний символ магазина совпадает с текущим входным символом (первым символом аванцепочки), он удаляется из магазина и входная головка сдвигается на один символ вправо.
(3) Если алгоритм достигает конфигурации (, $, ), работа прекращается и выходная цепочка называется левым разбором исходной входной цепочки. Будем предполагать, что всегда M($, ) = допуск, и конфигурацию (, $, ) будем называть допускающей.
(4) Если алгоритм достигает конфигурации (, X, ) и M(X, u) = ошибка, то разбор прекращается и выдается сообщение об ошибке. Эту конфигурацию (, X, ) называют ошибочной.
Алгоритм построения управляющих таблиц для LL(k)-грамматик в случае k >1 довольно сложен, управляющие таблицы имеют большой объем и на практике такие k-предсказывающие алгоритмы не нашли применения. Синтаксис большинства известных языков программирования описывается LL(1)-грамматиками. Поэтому ниже мы и рассмотрим только один важный частный случай, когда G – LL(1)-грамматика.
Алгоритм 5.1. Построение управляющей таблицы для LL(1)-грамматики.
Вход. LL(1)-грамматика G = (N, , P, S).
Выход. Управляющая таблица M для грамматики G.
Метод. Будем считать, что $ – маркер дна магазина. Таблица M определяется на множестве (N {$}) ( {}) следующим образом:
(1) Если A – правило из P с номером i, то M(A, a) = (, i) для всех а , принадлежащих FIRST1(). Если FIRST1(), то M(A, b) = (, i) для всех b FOLLOW1(A).
(2) M(a, a) = выброс для всех a .
(3) M($, ) = допуск.
(4) В остальных случаях M(X, a) = ошибка для X N {$} и a {}.
Пример 5.7. Рассмотрим построение управляющей таблицы для грамматики G с набором правил:
(1) |
E T E |
(2) |
E T E |
(3) |
E |
(4) |
T F T |
(5) |
T F T |
(6) |
T |
(7) |
F ( E ) |
(8) |
F a |
С помощью теоремы 5.2 можно проверить, что G – LL(1)-грамматика. Предложенная грамматика ни что иное, как результат устранения левой рекурсии из фрагмента хорошо известной нам не LL-грамматики арифметических выражений с правилами:
Е ETT T TFF F Ea
На шаге (1) алгоритма 5.1 найдем элементы строки таблицы для нетерминала E. Здесь FIRST1(TE) = {(, a}, так что M ( E, ( ) = (TE, 1) и M ( E, a ) = (TE, 1). Все остальные элементы этой строки – ошибки. Вычислим теперь строку для нетерминала E. Заметим, что FIRST1(TE) = , так что M ( E, ) = (TE, 2). Так как есть правило E , мы должны вычислить FOLLOW1(E) = {, ) }. Таким образом, M ( E, ) = M ( E, ) ) = (, 3). Каждый из остальных элементов строки для E – ошибка. Продолжая в том же духе, получим управляющую таблицу для G, представленную на рис. 5.3, где ячейки, в которых должна стоять ошибка, оставлены пустыми.
1-предсказывающий алгоритм разбора проанализирует цепочку (aa) следующим образом:
[(aa), E$, ] [(aa), TE$, 1] [(aa), FTE$, 14]
[(aa), (E)TE$, 147] [aa), E)TE$, 147] [aa), TE)TE$, 1471]
[aa), FTE)TE$, 14714] [aa), aTE)TE$, 147148]
[a), TE)TE$, 147148] [a), FTE)TE$, 1471485]
[a), FTE)TE$, 1471485] [a), aTE)TE$, 14714858]
[ ), TE)TE$, 14714858] [ ), E)TE$, 147148586]
[ ), )TE$, 1471485863] [, TE$, 1471485863]
[, E$, 14714858636] [, $, 147148586363]
Поскольку действия при LL(1)-разборе зависят только от пары “очередной нетерминал - очередной символ”, этот разбор легко запрограммировать, используя и другой не универсальный, но зато довольно прозрачный метод рекурсивного спуска.