Многопроходный компилятор — это тип компилятора , который обрабатывает исходный код или абстрактное синтаксическое дерево программы несколько раз. Это отличается от однопроходного компилятора , который проходит программу только один раз. Каждый проход принимает результат предыдущего прохода в качестве входных данных и создает промежуточный выход. Таким образом, (промежуточный) код улучшается проход за проходом, пока последний проход не произведет окончательный код.
Многопроходные компиляторы иногда называют широкими компиляторами [1] , имея в виду больший охват проходов: они могут «видеть» всю компилируемую программу, а не только небольшую ее часть. Более широкий охват, доступный таким образом этим компиляторам, позволяет лучше генерировать код (например, меньший размер кода, более быстрый код) по сравнению с выводом однопроходных компиляторов, за счет большего времени компиляции и потребления памяти. Кроме того, некоторые языки не могут быть скомпилированы за один проход из-за их конструкции.
Этот этап многопроходного компилятора предназначен для удаления нерелевантной информации из исходной программы, которую синтаксический анализ не сможет использовать или интерпретировать. Нерелевантная информация может включать в себя такие вещи, как комментарии и пробелы. Помимо удаления нерелевантной информации, лексический анализ определяет лексические токены языка. Этот этап означает, что предварительное объявление , как правило, не требуется, если используется многопроходный компилятор. Этот этап сосредоточен на разбиении последовательности символов на токены с такими атрибутами, как вид, тип, значение и потенциально другие.
Синтаксический анализ отвечает за рассмотрение синтаксических правил языка (часто как контекстно-свободной грамматики ) и построение некоторого промежуточного представления языка. Примером этого промежуточного представления может быть что-то вроде абстрактного синтаксического дерева или направленного ациклического графа .
Семантический анализ берет представление, созданное синтаксическим анализом, и применяет семантические правила к представлению, чтобы убедиться, что программа соответствует требованиям семантических правил языка. Например, в примере ниже на этапе семантического анализа, если язык требует, чтобы условия в операторах if были булевыми выражениями, cond будет проверен на тип, чтобы убедиться, что это будет допустимое булево выражение.
если ( условие ) { ... } иначе { ... }
Помимо выполнения семантического анализа на этом этапе компиляции часто создаются таблицы символов для помощи в генерации кода.
Этот последний этап типичного компилятора преобразует промежуточное представление программы в исполняемый набор инструкций (часто ассемблерный ). Этот последний этап является единственным этапом компиляции, который зависит от машины. На этом этапе компиляции также может быть выполнена оптимизация , которая делает программу более эффективной.
Другие проходы компилятора включают промежуточную фазу генерации кода, которая происходит перед генерацией кода, и фазу оптимизации кода, которая может происходить при написании исходной программы, или после промежуточной фазы генерации кода, или после фазы генерации кода.
Независимость от машины : поскольку множественные проходы имеют модульную структуру, а генерация кода отделена от других шагов компилятора, проходы можно повторно использовать для разного оборудования/машин.
Более выразительные языки : множественные проходы устраняют необходимость в предварительных объявлениях, позволяя элегантно реализовать взаимную рекурсию. Яркие примеры языков, требующих предварительных объявлений из-за требования компиляции за один проход, включают C и Pascal , тогда как Java не имеет предварительных объявлений.