
- •Введение
- •1 Предварительные математические сведения
- •1.1 Множества
- •1.2 Операции и отношения
- •1.2.1 Операции над множествами
- •1.2.2 Отношения на множествах
- •1.3.1 Цепочки
- •1.3.2 Операции над цепочками
- •1.4.2 Операции над языком
- •1.5 Алгоритмы
- •1.5.1 Частичные алгоритмы
- •1.5.2 Всюду определенные алгоритмы
- •1.5.3 Рекурсивные алгоритмы
- •1.5.4 Задание алгоритмов
- •1.5.5 Проблемы
- •1.6 Некоторые понятия теории графов
- •1.6.1 Ориентированные графы
- •1.6.2 Ориентированные ациклические графы
- •1.6.3 Деревья
- •1.6.4 Упорядоченные графы
- •2 Введение в компиляцию
- •2.1 Задание языков программирования
- •2.2 Синтаксис и семантика
- •2.3 Процесс компиляции
- •2.4 Лексический анализ
- •2.5 Работа с таблицами
- •2.6 Синтаксический анализ
- •2.7 Генератор кода
- •2.8 Оптимизация кода
- •2.9 Исправление ошибок
- •2.10 Резюме
- •3 Теория языков
- •3.1 Способы определения языков
- •3.2 Грамматики
- •3.3 Грамматики с ограничениями на правила
- •3.4 Распознаватели
- •3.5.1 Определения
- •3.8 Конечные автоматы и регулярные множества
- •3.9.1 Постановка задачи
- •3.10 Контекстно-свободные языки
- •3.10.2 Преобразование КС-грамматик
- •3.10.2.1. Алгоритм проверки пустоты языка
- •3.10.2.2. Алгоритм устранения недостижимых символов
- •3.10.2.3. Алгоритм устранения бесполезных символов
- •3.10.2.5. Алгоритм устранения цепных правил
- •3.10.3 Грамматика без циклов
- •3.10.4 Нормальная форма Хомского
- •3.10.5 Нормальная форма Грейбах
- •3.11 Автоматы с магазинной памятью
- •3.11.1 Основные определения
- •4.1 LL(k)-грамматики
- •4.2.2 Алгоритм поиска направляющих символов
- •4.2.2.1 Множество предшествующих символов
- •4.2.2.2 Множество последующих символов
- •4.2.2.3 Множество направляющих символов
- •4.3 LL(1)-таблица разбора
- •4.3.1 Построение таблицы
- •5 Синтаксический анализ снизу вверх
- •5.1 LR(k)-грамматики
- •5.2 LR(1)-грамматики
- •5.3 LR(1)-таблица разбора
- •5.3.1 Состояния анализатора
- •5.3.2 Построение таблицы
- •5.3.3 LR-конфликты
- •5.3.4 Разбор цепочки по таблице
- •5.4 Сравнение LL- и LR-методов разбора
- •6 Включение действий в синтаксис
- •6.2 Работа с таблицей символов
- •7 Проектирование компиляторов
- •7.1 Число проходов
- •7.2 Таблицы символов
- •7.2.2 Бинарное дерево
- •7.4.1 Стек времени прогона
- •7.4.2 Методы вызова параметров
- •7.4.3 Обстановка выполнения процедур
- •8 Генерация кода
- •8.1 Генерация промежуточного кода
- •8.2 Структура данных для генерации кода
- •8.3.1 Присвоение
- •8.3.2 Условные зависимости
- •8.3.3 Описание идентификаторов
- •8.3.4 Циклы
- •8.3.5 Вход и выход из блока
- •8.3.6 Прикладные реализации
- •8.4 Проблемы, связанные с типами
- •8.5 Время компиляции и время прогона
- •9 Исправление и диагностика ошибок
- •9.1 Типы ошибок
- •9.2 Лексические ошибки
- •9.3 Ошибки в употреблении скобок
- •9.4 Синтаксические ошибки
- •9.4.1 Методы исправления синтаксических ошибок
- •9.4.2 Предупреждения
- •9.4.3 Сообщения о синтаксических ошибках
- •9.5 Контекстно-зависимые ошибки
- •9.6 Ошибки, связанные с употреблением типов
- •9.7 Ошибки, допускаемые во время прогона
- •9.8 Ошибки, связанные с нарушением ограничений
- •Заключение
- •Список литературы
- •Глоссарий
128
4.2.2.2 Множество последующих символов
Множество терминальных последующих символов (от англ. follow)
определяется следующим образом:
a F(A) αAβ * αAaγ,
где:
–a – терминал или признак конца цепочки, а Σ{ };
–α, β и γ – произвольные цепочки терминалов и/или нетерминалов, α,
β, γ(N Σ)*;
–А – нетерминал, A N;
–F(A) – множество последующих символов для нетерминала A.
Множество последующих символов включает в себя символы, которые могут следовать в выводимых цепочках за нетерминалом A. При этом данное множество не может содержать пустую цепочку. Но если при выводе после нетерминала A входная цепочка может заканчиваться, то множество F(A) бу-
дет содержать специальный символ – маркер конца цепочки. Это может быть любой символ, не входящий в алфавит языка Σ, а также не совпадающий по написанию с нетерминалами грамматики N и символом пустой цепочки e.
Для нахождения множества последующих символов некоторого нетерминала A также строятся деревья вывода. При этом:
1)деревья строятся для правой части порождающего правила грамма-
тики, содержащего нетерминал A, т.е. для правил вида B αAβ;
2)используются не самые левые выводы, как это было при нахожде-
нии множества символов-предшественников, а вывод из первого элемента цепочки β;
3)если после нереминала A в цепочке появился терминал, дальнейшее поддерево строить не следует, а терминал включается в искомое множество;

129
4)если после нетерминала A цепочка β может заканчиваться (β * e),
тогда дальнейшее поддерево строится для всех цепочек правых ча-
стей правил грамматики, содержащих нетерминал B, причем в них B
заменяется полученной ранее при выводе цепочкой;
5)если после нетерминала A цепочка β может заканчиваться (β * e) и
A = S (т.е. A – это стартовый нетерминал), то в искомое множество включается маркер конца цепочки.
· · · · · · · · · · · · · · · · · · · · · · · · |
|
Пример · · · · · · · · · · · · · · · · · · · · · · · |
|
|
|
Например, найдем для грамматики:
A BCD
B aCx | bCd | F C zBD | c | e
D Fx | e F y
символы, следующие за нетерминалом B (рис. 4.2). Стрелкой «↑» показаны позиции, в которых должны выводиться искомые терминалы. Жирным выде-
лены найденные последующие символы.

130
B↑CD |
zB↑D |
B↑D |
zB↑Fx |
zB↑ |
B↑zBDD B↑cD
B↑Fx |
B↑ |
BzB↑D |
zB↑yx |
azB↑x |
bzB↑d |
BzB↑Fx |
BzB↑ |
|
B↑yx
BzB↑yx
Рисунок 4.2 – Деревья вывода для F(B) и F(zB)
Для первого дерева, используя пункты 1–3, получаем:
B↑CD 1 B↑zBDD B↑CD 1 B↑cD B↑CD 3 B↑yx
Но BCD 2 B (или CD 2 e), т.е. нетерминал B оказывается в конце цепочки. В левой части правила A BCD находится нетерминал A, но в пра-
вых частях правил он не встречается, поэтому пункт 4 неприменим. Так как при этом A является стартовым символом, то добавляем к множеству F(B)
маркер конца цепочки согласно пункту 5. Получили F(B) = {c, y, z, }.
Во втором дереве по пунктам 1–3 получаем вывод zB↑D 2 zB↑yx
131
Далее, т.к. zB↑D 1 zB↑ (или D 1 e) и в левой части правила C zBD
находится нетерминал C, находим все правые части порождающих правил,
содержащих данный нетерминал. Таких правил три:
A BCD
B aCx
B bCd
Используя пункт 4, подставляем в них вместо C цепочку zB и продол-
жаем процесс. Таким образом, для первого вхождения C получаем:
BCD * BzB↑D BzB↑Fx BzB↑yx BCD * BzB↑D BzB↑
Снова, как и выше, получается ситуация, когда во множество F(B) сле-
дует добавить маркер конца цепочки (в левой части правила A BCD нахо-
дится стартовый символ). Для второго и третьего вхождения C в грамматику: aCx * azB↑x
bCd * bzB↑d
В результате, учитывая полученные ранее элементы, F(B) = {d, x, y, } {c, y, z, } = {c, d, x, y, z, }.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
По сути, для правила грамматики вида B αAβ во множество F(A)
входят элементы множества S(β), кроме e, а если после нетерминала A цепоч-
ка β может заканчиваться (β * e), то также и элементы множества F(B):
|
|
|
|
|
|
|
|
|
|
|
|
|
B |
|
β e e S β , |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
F |
|
A |
|
|
|
S |
|
β |
|
e |
|
F |
|
|||
|
|
|
|
|||||||||||||
|
|
|
|
|
|
|
|
|
β e e S β . |
|||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Для нахождения множества последующих символов для нетерминалов грамматики G = (N, Σ, P, S) существует следующий нерекурсивный алгоритм:
1.Для всех нетерминалов грамматики A N положить F(A) = . Для стартового нетерминала положить F(S) = { }.

132
2.Для каждого вхождения нетерминала A в правую часть порождаю-
щих правил грамматики вида (B αAβ) P добавить к множеству
F(A) новые элементы, найденные по приведенной выше формуле:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
B |
|
β e e S β , |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
F |
|
A |
|
F |
|
A |
|
|
|
S |
|
β |
|
e |
|
F |
|
|||
|
|
|
|
|||||||||||||||||
|
|
|
|
|
|
|
|
|
|
|
β e e S β . |
|||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3.Если при выполнении шага 2 хотя бы в одном множестве F(A) по-
явился хотя бы один новый элемент, вернуться на шаг 2. Иначе ал-
горитм заканчивает свою работу.
· · · · · · · · · · · · · · · · · · · · · · · · |
|
Пример · · · · · · · · · · · · · · · · · · · · · · · |
|
|
|
Рассмотрим работу этого алгоритма на примере грамматики:
E T E′
E′ + T E′ | e T F T′
T′ * F T′ | e F ( E ) | a
В табл. 4.8 приведены результаты последовательных проходов второго шага алгоритма.
Таблица 4.8 – Результаты проходов алгоритма поиска последующих символов
|
F |
1-й проход |
2-й проход |
N |
|
||
|
|
|
|
|
|
|
|
E |
|
, ) |
, ) |
|
|
|
|
E′ |
|
, |
, ) |
|
|
|
|
T |
|
, + |
, +, ) |
|
|
|
|
T′ |
|
, + |
, +, ) |
|
|
|
|
F |
|
, +, * |
, +, *, ) |
|
|
|
|