
- •Введение
- •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 Ошибки, связанные с нарушением ограничений
- •Заключение
- •Список литературы
- •Глоссарий

180
В рассматриваемом примере нам не пришлось сравнивать приоритеты двух операций, так как эти приоритеты уже заложены в правилах грамматики.
6.2 РАБОТА С ТАБЛИЦЕЙ СИМВОЛОВ
Поскольку синтаксические анализаторы обычно используют контекст-
но-свободную грамматику, необходимо найти метод определения контекст-
но-зависимых частей языка. Например, во многих языках идентификаторы не могут применяться, если они ранее не описаны и имеются ограничения в от-
ношении способов употребления в программе значений различного типа.
Кроме того, в языках программирования имеются ограничения на употребле-
ние различных знаков. Для запоминания описанных идентификаторов и их типов большинство компиляторов использует таблицу символов [2].
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
В принятой формализации описание int x
является определяющей реализацией x, а использование x в дру-
гом контексте
x := 4 или x + y или read(y)
говорит, что имеется прикладная реализация x.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Во многих языках программирования один и тот же идентификатор может использоваться для представления в различных частях программы различных объектов (например, в «голове» – int, а в подпрограмме char). В
этом случае в таблице символов – это два разных объекта.
Таблица символов имеет ту же блочную структуру, что и сама про-
грамма, чтобы различать виды употребления одного и того же идентифика-
тора. При построении таблицы символов учитываются основные свойства большинства языков:

181
1)определяющая реализация идентификатора появляется раньше лю-
бой прикладной реализации;
2)все описания в блоке помещаются раньше всех операторов и пред-
ложений;
3)при наличии прикладной реализации идентификатора соответству-
ющая определяющая реализация находится в наименьшем включа-
ющем блоке, в котором содержится описание этого идентификатора;
4)в одном и том же блоке идентификатор не может описываться более одного раза.
· · · · · · · · · · · · · · · · · · · · · · · · |
|
Пример · · · · · · · · · · · · · · · · · · · · · · · |
|
|
|
Пусть синтаксис описания идентификаторов задается правилами:
DEC real IDS | integer IDS | boolean IDS IDS ID
IDS IDS, ID
а блок определяется как
BLOCK begin DECS; STATS end,
где:
DECS DECS; DEC DECS DEC STATS STATS; stat STATS stat
В этом случае структуру таблицы символов можно представить в виде рис. 6.1.

182
levno |
|
|
|
levno |
|
|
|
levno |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type |
|
|
|
type |
|
|
|
type |
|
|
|
|
|
|
|
|
|
|
|
|
|
type |
|
|
|
type |
|
|
|
|
|
|
|
|
type
Рисунок 6.1 – Структура таблицы символов
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Таким образом, в любой точке разбора в цепи находятся те блоки, в ко-
торые делается текущее вхождение, а уже описанные идентификаторы по-
мещаются в список идентификаторов для того блока, где они описаны.
Для описания таблиц задаются структуры строго фиксированной кон-
фигурации. Обычно в этих структурах идентификаторы и типы представля-
ются целыми числами. Имеется указатель на элемент таблицы символов, со-
ответствующих наименьшему включающему блоку.
В языке, обладающем описанными выше четырьмя свойствами, в каче-
стве структуры данных для таблицы символов удобно использовать стек,
каждым элементом которого служит элемент этой таблицы символов.
При встрече с описанием соответствующий элемент таблицы символов помещается в верхнюю часть стека, а при выходе из блока все элементы таб-
лицы символов, соответствующие описаниям в этом блоке, удаляются из сте-
ка. Указатель стека понижается до положения, которое он имел до вхождения в блок. В результате в любой момент разбора элементы таблицы символов,
соответствующие всем текущим идентификаторам, находятся в стеке, а свя-

183
занные с ними прикладные и определяющие реализации идентификаторов требуют поиска в стеке в направлении сверху вниз.
· · · · · · · · · · · · · · · · · · · · · · · · |
|
Пример · · · · · · · · · · · · · · · · · · · · · · · |
||
|
|
|
||
Рассмотренный метод иллюстрируется следующим примером (табл. |
||||
6.2). |
|
|
|
|
Таблица 6.2 – Стек с элементами таблицы символов |
|
|||
|
|
|
|
|
Вид программы |
|
Элемент таблицы |
||
|
|
|
||
|
|
|
Имя |
Тип |
|
|
|
|
|
begin |
|
b |
int |
|
int a, b |
|
a |
int |
|
|
|
|
|
|
… |
|
|
|
|
|
|
|
|
|
begin |
|
d |
char |
|
char c, d |
|
c |
char |
|
|
|
|
b |
int |
|
|
|
a |
int |
|
|
|
|
|
… |
|
|
|
|
|
|
|
|
|
end |
|
b |
int |
|
|
|
|
a |
int |
|
|
|
|
|
begin |
|
f |
int |
|
int e, f |
|
e |
int |
|
|
|
|
b |
int |
|
|
|
a |
int |
|
|
|
|
|
begin |
|
g |
char |
|
char g |
|
f |
int |
|
|
|
|
e |
int |
|
|
|
b |
int |
|
|
|
|
|

184
|
a |
int |
|
|
|
… |
|
|
|
|
|
end |
f |
int |
|
e |
int |
|
b |
int |
|
a |
int |
|
|
|
end |
b |
int |
|
a |
int |
end
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Таким образом, включение действий в грамматику позволяет получить простой и элегантный компилятор. При этом действия выполняются на соот-
ветствующем уровне в грамматике.
· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
Контрольные вопросы по главе 6
·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
1.Технология включения действий в грамматику.
2.Получение четверок, грамматика для четверок.
3.Разбор арифметического выражения с одновременной генерацией кода.
4.Метод определения контекстно-зависимых частей языка.
5.Синтаксис описания идентификаторов и блоков.
6.Структура таблицы символов.
7.Программная реализация таблицы символов.