- •Тема 2. Основные принципы построения трансляторов
- •Трансляторы, компиляторы и интерпретаторы - общая схема работы
- •Формальное определение транслятора
- •Определение компилятора. Отличие компилятора от транслятора
- •Многопроходные и однопроходные компиляторы
- •Определение интерпретатора. Разница между интерпретаторами и трансляторами
- •Трансляция в ассемблер
- •Методики создания компилятора
- •Метод раскрутки
- •Кросс-транслятор
- •Виртуальная машина
- •Компиляция "на лету"
- •Этапы трансляции. Общая схема работы транслятора
- •Лексический анализ
- •Синтаксический анализ
- •Семантический анализ
- •Оптимизация кода
- •Генерация кода
- •Просмотры
Трансляция в ассемблер
Трансляция программы в ассемблер несколько упрощает конструирование компилятора. Однако, такой подход удлиняет технологическую цепочку выполнения программы, так как объектная программа, порожденная компилятором, должна быть впоследствии обработана ассемблером, а также редактором связей и загрузчиком.
У трансляции в ассемблер есть несколько ощутимых преимуществ:
уровень ассемблера все-таки выше, чем у машинного кода; поэтому при трансляции в ассемблер программисту не приходится возиться с некоторыми утомительными техническими деталями, например, ассемблер берет на себя разрешение операторов безусловного перехода на еще неопределенные метки (переходы вперед), распределение памяти под данные, расчет длин переходов и т.п.,
использование ассемблера позволяет отследить целый ряд ошибок, которые могут возникнуть при генерации кода (например, неправильная мнемоника команды); при генерации в машинные коды отловить такие ошибки значительно труднее,
порождаемый текст на ассемблере значительно читабельней, чем машинный код; это может помочь при отладке компилятора.
Рис. 2.5. Трансляция в Ассемблер
T-диаграммы
М
ы
обозначили компилятор, переводящий
цепочки языка L1 в
цепочки языка L2 и
написанный на языке L3,
следующим образом: KL3
: L1→L3. Для
представления компилятора используются
также так называемые Т-диаграммы:
Н
апример,
Компилятор, переводящий язык L в
язык ассемблера A и написанный на
языке A, можно представить следующими
способами: KA: L→ A или
Т-диаграммой
Т-диаграммы достаточно удобны при обсуждении различных способов получения компиляторов.
Методики создания компилятора
Написание компилятора может потребоваться в самых разнообразных условиях – для различных языков, целевых платформ, с различными требованиями к работе компилятора и т.д. Например, одна из типичных проблем написания компилятора связана с тем, что на целевой платформе могут отсутствовать подходящие средства для разработки. Именно так обычно обстоят дела при создании новой компьютерной архитектуры: на начальном этапе жизни компьютера системные программисты не имеют никаких средств разработки, кроме системы команд самого компьютера (на этом этапе даже ассемблер может отсутствовать). Бывают и такие случаи, когда компилятор создается для еще не существующей целевой платформы.
Таким образом, цели и условия написания компиляторов могут очень сильно варьироваться от одного проекта к другому. Поэтому существует целый ряд методик разработки компиляторов; мы остановимся на наиболее распространенных из них:
прямой, при котором целевым языком является язык ассемблера,
метод раскрутки,
использование кросс-трансляторов,
использование виртуальных машин,
компиляция "на лету".
Метод раскрутки
Компилятор – это весьма большая и сложная программа; на написание и отладку компилятора на языке ассемблера можно истратить слишком много времени. Для того, чтобы как-то справиться с этой проблемой, был придуман метод раскрутки, суть которого заключается в следующем.
Пусть есть компилятор KA: P→A, где P – некоторый язык более высокого уровня, чем язык ассемблера. Тогда напишем KP: L→A, а затем применим компилятор KA к компилятору KP, т.е. получим KA=KA(KP): L→A. Такая схема проиллюстрирована с помощью Т-диаграмм на рисунке и называется раскруткой (bootstrapping1).
Описанная схема может быть использована при написании компилятора некоторого языка на нем самом. Пусть у нас есть компилятор некоторого подмножества S языка L в язык A, написанный на языке A, KA: S→A. Тогда мы можем написать KL: L→A и получим новый компилятор KA = KA(KL). Мы используем это подмножество S для того, чтобы написать компилятор языка L в язык A, KS: L→A. Если теперь мы применим компилятор KA к программе KS, то получим KA=KA(KS): L→A.
Рис. 2.6. Схема «раскрутки» компилятора
Впервые такая схема была применена в 1960 году при реализации языка Neliac. В 1971 году Вирт написал с использованием раскрутки транслятор языка Pascal, причем самый первый компилятор был оттранслирован вручную. Количество шагов раскрутки было больше 1, т.е. была построена последовательность языков S1 S2 ... Sn = L и построена последовательность компиляторов: KA: S1→A, KA1=KA(KS1: S2→A), ...
Раскрутку можно использовать и в следующей ситуации. Пусть у нас есть недостаточно эффективный компилятор KA: L→A. Можно написать более эффективный компилятор KL: L→A, а затем применить раскрутку.
