- •Университет наяновой
- •М. А. Шамашов основные структуры данных и алгоритмы компиляции
- •Предисловие
- •Введение
- •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. Однопроходный синтаксический анализ без возвратов
Как уже отмечалось, рассмотренные выше недетерминированные, переборные алгоритмы синтаксического анализа с возвратами способны анализировать практически все КС-языки, но их эффективность оставляет желать много лучшего. В общем случае время работы таких алгоритмов экспоненциально зависит от длины анализируемой цепочки. В этой главе мы рассмотрим классы КС-грамматик, для которых можно построить эффективные анализаторы, тратящие на обработку цепочек линейное время. За эту эффективность приходится платить тем, что такие анализаторы не могут обрабатывать все КС-языки без исключения. Однако эти ограниченные классы грамматик и языков адекватно отражают синтаксические черты всех известных языков программирования.
Излагаемые в этой главе алгоритмы разбора характеризуются тем, что входная цепочка считывается один раз слева направо и процесс разбора полностью детерминирован. Другими словами класс КС-грамматик здесь ограничивается так, чтобы для них можно было построить детерминированный левый или правый анализатор.
5.1. Ll(k) языки и грамматики
Дадим вначале неформальное определение LL(k) грамматики. Напомним, что в левостороннем анализаторе дерево вывода цепочки строится по заданной грамматике, начиная от корня (аксиомы грамматики), сверху вниз. Пусть на каком-то шаге анализа уже построено частичное дерево вывода с кроной A (см. рис. 5.1). Для продолжения разбора требуется заменить нетерминал A по одному из правил вида A. Если для однозначного выбора этого правила окажется достаточно знать только и первые k символов цепочки , то заданная грамматика является LL(k)–грамматикой.
Дадим более строгое определение. Определим два множества цепочек:
FIRST k () - множество терминальных цепочек, выводимых из , укороченных до k символов.
FOLLOW k (A)- множество укороченных до k символов терминальных цепочек, которые могут следовать непосредственно за A в выводимых цепочках.
КС-грамматика называется LL(k)-грамматикой для некоторого фиксированного k, если из существования двух левых выводов
-
S A
и
-
S A ,
для которых FIRST k () FIRST k (), следует, что .
Пример 5.1. Пусть грамматика G1 состоит из правил S aASb , A abSA . Интуитивно G1 является LL(1) грамматикой, так как если дан самый левый нетерминал C в левовыводимой цепочке и следующий входной символ с, то существует не более одного правила, применимого к C и приводящего к терминальной цепочке, начинающейся символом c. Переходя к определению LL(1) грамматики мы видим, что если S S и S S и цепочки и начинаются символом a, то в выводе участвует правило S aAS и = = aAS. Альтернатива S b здесь невозможна. С другой стороны, если и начинаются с b, то должно применяться правило S b и = = b. Заметим, что случай = = здесь невозможен, так как из S не выводится пустая цепочка .
Когда рассматриваются два других вывода с нетерминалом A, то рассуждение аналогично.
Пример 5.2. Рассмотрим более сложный случай - грамматику G2, определяемую правилами S abA , A Saab . Это не LL(1) грамматика, так как, пройдя часть левого вывода S abA abSaa для входных цепочек abaa или ababbaa и, имея на входе символ a, не ясно какое правило надо применить: S или S abA. Покажем, что G2 – это LL(2)-грамматика.
Допустим, что S S и S S и первые два символа цепочки (если они есть) совпадают с первыми двумя символами цепочки . Нетрудно видеть, что здесь нет иных возможностей, кроме = = , и начинается с aa, и начинается с ab. В первых двух случаях в обоих выводах применяется правило S и = = . В третьем случае должно применяться S abA и = = abA.
Пример 5.3. Рассмотрим грамматику G3 = ({S, A, B}, {0, 1, a, b}, P3, S), где P3 состоит из правил:
S AB
A aAb0
B aBbb1
Здесь L(G3) = {an0bnn 0}{ an1b2 nn 0}. G3 не является LL(k)-грамматика ни для какого k. Интуитивно, если мы начинаем с чтения достаточно длиной цепочки, начинающейся с символов a, то не знаем, какое из правил S A или S B было применено первым, пока не встретим 0 или 1. Обращаясь к точному определению LL(k)-грамматики, положим , A, B, ak0bk и ak1b2 k. Тогда выводы
S 0 S A ak0bk
S 0 S B ak1b2 k
соответствуют выводам (1) и (2) определения. Первые k символов цепочек и совпадают, однако заключение ложно. Так как k здесь выбрано произвольно, то G3 не является LL-грамматикой. Можно показать, что для языка L(G3) вообще не существует LL(k)-грамматики.
Из определения LL(k) грамматики может показаться, что для определения нужного правила надо помнить уже всю проанализированную часть входной цепочки . Но это не так. Рассмотрим теорему, очень важную для понимания LL(k)-грамматик, которая тривиально доказывается исходя из определения LL(k)-грамматики.
Теорема 5.1. КС-грамматика G = (N, , P, S) является LL(k)-грамматикой тогда и только тогда, когда для двух различных правил A и A из P пересечение FIRSTk() FIRSTk() пусто при всех таких A, что S A.
Одно из важных следствий определения LL(k)-грамматик состоит в том, что леворекурсивная грамматика не может быть LL(k)-грамматикой ни для какого k.
Пример 5.4. Пусть грамматика G определяется двумя правилами S Sab. Возьмем, как и в теореме 5.1, вывод S i Sa i, где i 0, A = S, = , = Sa и = b. Тогда для i k
FIRST k (Saa i ) FIRSTk(ba i ) = ba k-1
Таким образом, G не может быть LL(k)-грамматикой ни для какого k.
Еще одно следствие теоремы 5.1 состоит в том, что если КС-грамматика G не содержит аннулирующих правил, то она будет LL(1)-грамматикой только в том случае, когда для всех AN каждое множество A-правил A 12 n из P таково, что FIRST1(1), FIRST1(2), , FIRST1(n) попарно не пересекаются. (Отсутствие -правил здесь существенно).
Введенная выше функция FOLLOW k (A) как раз и нужна для грамматик с аннулирующими правилами. Для LL(1)-грамматик справедливо следующее утверждение.
Теорема 5.2. КС-грамматика G = (N, , P, S) является LL(1)-грамматикой тогда и только тогда, когда для двух различных правил A и A пересечение FIRST1(FOLLOW 1 (A)) FIRST1(FOLLOW 1 (A)) пусто при всех AN.
Другими словами G является LL(1)-грамматикой, если для каждого множества A-правил A 12 n
(1) множества FIRST1(1), FIRST1(2), , FIRST1( n) попарно не пересекаются,
(2) если i , то FIRST1( j) FOLLOW 1 (A) = 0 для 1 j n, i j.
Таким образом, в случае k = 1 для однозначного выбора правила для нетерминала А, достаточно знать только нетерминал A и а – первый символ нерассмотренной части входной цепочки :
следует выбрать правило A , если а входит в FIRST1()
следует выбрать правило A , если а входит в FOLLOW1(A).
Прежде чем рассмотреть алгоритм разбора для LL(1)-грамматик отметим, что неразрешима проблема распознавания существования LL(k)-грамматики, эквивалентной КС-грамматике G, которая не является LL(k)-грамматикой. Тем не менее существуют ситуации, в которых отдельные преобразования позволяют из не LL(1)-грамматики получить эквивалентную LL(1)-грамматику. Проиллюстрируем два таких преобразования на примерах.
Пример 5.5. Пусть G – леворекурсивная грамматика S Sab , которая, как видно из примера 5.4 не является LL-грамматикой. Устраняя левую рекурсию, заменим два эти правила на следующие три:
S bS
S aS
получив при этом эквивалентную грамматику G. С помощью теоремы 5.2 легко показать, что G – LL(1)-грамматика.
Пример 5.6. Рассмотрим LL(2)-грамматику G – с двумя правилами S aSa. Проведем левую факторизацию , “вынеся влево за скобку” символ a и, записав правила в виде S a(S). Иными словами, мы считаем, что операция конкатенации дистрибутивна относительно операции выбора альтернативы. Заменив эти правила на
S aA
A S
получим тем самым эквивалентную LL(1)-грамматику.