Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ТЯП-2 Основные принципы построения трансляторов...docx
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
1.65 Mб
Скачать
  1. Этапы трансляции. Общая схема работы транслятора

На рис. 2.9 представлена общая схема работы компилятора.

Из нее видно, что в целом процесс компиляции состоит из двух основных этапов — синтеза и анализа.

На этапе анализа выполняется распознавание текста исходной программы, соз­дание и заполнение таблиц идентификаторов. Результатом его работы служит некое внутреннее представление программы, понятное компилятору.

На этапе синтеза на основании внутреннего представления программы и инфор­мации, содержащейся в таблице (таблицах) идентификаторов, порождается текст результирующей программы. Результатом этого этапа является объектный код.

Таблица переходов

Рис. 2.9. Общая схема работы компилятора

Кроме того, в составе компилятора присутствует часть, ответственная за анализ и исправление ошибок, которая при наличии ошибки в тексте исходной про­граммы должна максимально полно информировать пользователя о типе ошиб­ки и месте ее возникновения. В лучшем случае компилятор может предложить пользователю вариант исправления ошибки.

Эти этапы, в свою очередь, состоят из более мелких этапов, называемых фазами компиляции. Состав фаз компиляции приведен в самом общем виде, их конкрет­ная реализация и процесс взаимодействия могут, конечно, различаться в зави­симости от версии компилятора. Однако в том или ином виде все представлен­ные фазы практически всегда присутствуют в каждом конкретном компиляторе.

Компилятор в целом с точки зрения теории формальных языков выступает в «двух ипостасях», выполняет две основные функции.

Во-первых, он является распознавателем для языка исходной программы. То есть он должен получить на вход цепочку символов входного языка, проверить ее принадлежность языку и, более того, выявить правила, по которым эта цепочка была построена (поскольку сам ответ на вопрос о принадлежности «да» или «нет» представляет мало интереса). Интересно, что генератором цепочек входно­го языка выступает пользователь — автор входной программы.

Во-вторых, компилятор является генератором для языка результирующей про­граммы. Он должен построить на выходе цепочку выходного языка по опреде­ленным правилам, предполагаемым языком машинных команд или языком ас­семблера. Распознавателем этой цепочки будет выступать уже вычислительная система, под которую создается результирующая программа.

Рассмотрим вкратце основные фазы компиляции и краткое описание их функций.

Лексический анализ (сканер) — это часть компилятора, которая читает текст исходной программы и выделяет слова (лексемы) исходного язы­ка. Выходная информация передается для дальнейшей обработки компилятором на этапе синтаксического разбора. С теоретической точки зрения лексический анализатор не является обязательной, необходимой частью компилятора. Одна­ко его присутствие позволяет упростить синтаксический анализатор.

Синтаксический разбор — это основная часть компилятора на этапе анализа. Она выполняет выделение синтаксических конструкций в тексте исходной програм­мы, обработанном лексическим анализатором. На этой же фазе компиляции проверяется синтаксическая правильность программы. Синтаксический разбор играет главную роль — роль распознавателя текста входного языка программи­рования

Семантический анализ — это часть компилятора, проверяющая правильность текста исходной программы с точки зрения семантики входного языка. Кроме непосредственно проверки, семантический анализ должен выполнять преобра­зования текста, требуемые семантикой входного языка (такие, как добавление функций неявного преобразования типов). В различных реализациях компиля­торов семантический анализ может частично входить в фазу синтаксического разбора, частично — в фазу подготовки к генерации кода.

Подготовка к генерации кода — это фаза, на которой компилятором выполняют­ся предварительные действия, непосредственно связанные с синтезом текста ре­зультирующей программы, но еще не ведущие к порождению текста на выход­ном языке. Обычно в эту фазу входят действия, связанные с идентификацией элементов языка, распределением памяти и т. п.

Генерация кода — это фаза, непосредственно связанная с порождением команд, составляющих предложения выходного языка и в целом текст результирующей программы. Это основная фаза на этапе синтеза результирующей программы. Кроме непосредственного порождения текста результирующей программы, гене­рация обычно включает в себя также оптимизацию — процесс, связанный с обра­боткой уже порожденного текста. Иногда оптимизацию выделяют в отдельную фазу компиляции, так как она оказывает существенное влияние на качество и эффективность результирующей программы

Таблицы идентификаторов (иногда — «таблицы символов») — это специальным образом организованные наборы данных, служащие для хранения информации об элементах исходной программы, которые затем используются для порожде­ния текста результирующей программы. Таблица идентификаторов в конкрет­ной реализации компилятора может быть одна, или же таких таблиц может быть несколько. Элементами исходной программы, информацию о которых нужно хра­нить в процессе компиляции, являются переменные, константы, функции и т. п. — конкретный состав набора элементов зависит от используемого входного языка программирования. Понятие «таблицы» вовсе не предполагает, что это хранилище данных должно быть организовано именно в виде таблиц или других массивов информации — часто таблицы идентификаторов строят в виде сбалансированных двоичных деревьев или с использованием хэш-функций.

Примерный состав таблицы идентификаторов:

  1. для переменных:

  • имя переменной,

  • тип данных,

  • область памяти, связанная с переменной;

  1. для констант:

  • имя константы,

  • значение,

  • тип данных;

  1. для функций:

  • имя функции,

  • количество и типы формальных параметров,

  • типы возвращаемых данных,

  • адрес кода функции (точка входа).

Проблемы – большой объем таблиц, основной объем вычислений падает на поиск по имени идентификатора

Методы построения таблиц:

  1. таблица – список; новый элемент добавляется в список (лучше в начало)

  • заполнение таблицы – О(1),

  • поиск – O(n);

  1. таблица – сбалансированное бинарное дерево

  • заполнение таблицы – О(n2),

  • поиск – O(log n);

  1. использование Хэш-функций

  • заполнение таблицы – О(1),

  • поиск – O(1).

Недостаток – возможны одинаковые значения хэш-функций для разных идентификаторов.

Продемонстрируем преобразования, которым подвергается исходная программа на перечисленных фазах компиляции, на небольшом примере — мы рассмотрим оператор присваивания position = initial + rate * 60, причем предположим, что все переменные вещественные.

position = initial + rate * 60;