280
СОВСЕМ КАК КЛАССИЧЕСКИЙ?
Прежде чем мы продолжим, однако, я думаю что должен разъяснить связи между
модулями и функциональные возможности этих модулей. Те из вас, кто знаком с теорией
компиляции как обучавшиеся в университетах, конечно распознают имена Scanner, Parser
и CodeGen, все из которых являются компонентами классической реализации
компилятора. Вы можете думать, что я отказался от своих обязательств по отношению к
философии KISS и отдрейфовал к более стандартной архитектуре чем мы имели. Более
пристальный взгляд, однако, должен убедить вас, что хотя имена схожи,
функциональность совершенно различна.
Вместе, сканер и парсер классической реализации составляют так называемый "front
end", а генератор кода "back end". Подпрограммы "front end" обрабатывают
языкозависимые, связанные с синтаксисом аспекты исходного языка, в то время как
генератор кода, или "back end", работает с зависимыми от целевой машины частями
проблемы. В классических компиляторах два конца (ends) сообщаются через файл
инструкций, написанный на промежуточном языке (IL).
Как правило, классический сканер это одиночная процедура, оперирующая как
сопроцедура с синтаксическим анализатором. Она "токенизирует" исходный файл,
считывая его символ за символом, распознавая элементы языка, транслируя их в токены
и передавая их синтаксическому анализатору. Вы можете думать о синтаксическом
анализаторе как об абстрактной машине, выполняющей "op кода", которыми являются
токены. Точно также, синтаксический анализатор генерирует "op кода" второй
абстрактной машины, которая механизирует IL. Как правило, IL файл записывается на
диск синтаксическим анализатором и считывается снова генератором кода.
Наша организация совершенно другая. Мы не имеем лексического анализатора в
классическом смысле; наш модуль Scanner, хотя и имеет схожее имя, не является
одиночной процедурой или сопроцедурой, а просто набором раздельных подпрограмм,
которые вызываются синтаксическим анализатором когда необходимо.
Аналогично, классический генератор кода, "back end", в своем роде тоже транслятор,
считывающий "исходный" IL файл и выдающий объектный файл. Наш генератор кода не
работает таким способом. В нашем компиляторе нет никакого промежуточного языка;
каждая конструкция в синтаксисе исходного языка преобразуется в ассемблер как только
она распознана синтаксическим анализатором. Подобно Scanner, модуль CodeGen
состоит из индивидуальных процедур, которые вызываются синтаксическим
анализатором когда необходимо.
Философия "кодируй как только найдешь" не может производить самый эффективный
код в мире - например, мы не обеспечили (пока!) удобное место для оптимизатора - но
она несомненно упрощает компилятор, не правда ли?
И этот наблюдение заставляет меня повторить снова то, как нам удавалось сводить
функции компилятора к таким сравнительно простым условиям. Я набрался
красноречивости на эту тему в прошлых главах, поэтому здесь я не бу ду слишком ее
трогать. Однако, из-за времени, прошедшего с этих последних монологов, я надеюсь что
вы предоставите мне совсем немного времени напомнить себе, так же как и вам, как мы
попали сюда. Мы дошли до этого применяя несколько принципов, которые создатели
коммерческих компилятором редко имеют роскошь использовать. Вот они: