Конвейер компиляции: этапы

Компилятор — это конвейер: каждый этап получает одно представление программы и выдаёт следующее, более близкое к машине.

Фаза компилятора — отдельный шаг обработки, преобразующий программу из одного представления в другое (например, текст в токены или токены в дерево).

Удобнее всего представлять компилятор как трубу, по которой течёт программа, постепенно меняя форму. Разделение на фазы — не прихоть, а инженерное решение: каждый этап решает одну задачу, его легче писать, тестировать и переиспользовать.

Передняя и задняя часть

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

Шесть классических этапов

исходный код
   |
   v
1. ЛЕКСИЧЕСКИЙ АНАЛИЗ   --> поток токенов
   |
   v
2. СИНТАКСИЧЕСКИЙ АНАЛИЗ --> дерево разбора / AST
   |
   v
3. СЕМАНТИЧЕСКИЙ АНАЛИЗ  --> проверенное дерево + таблица символов
   |
   v
4. ПРОМЕЖУТОЧНЫЙ КОД     --> IR
   |
   v
5. ОПТИМИЗАЦИЯ           --> улучшенный IR
   |
   v
6. ГЕНЕРАЦИЯ КОДА        --> машинный код / байткод

1. Лексический анализ

Лексер режет текст на токены: числа, имена, операторы, скобки. Пробелы и комментарии отбрасываются.

2. Синтаксический анализ

Парсер проверяет, что токены складываются в допустимые конструкции по грамматике, и строит дерево.

3. Семантический анализ

Проверяются правила, которые нельзя выразить грамматикой: объявлены ли переменные, совпадают ли типы, не вызвана ли функция с лишними аргументами.

4–6. Кодогенерация и оптимизация

Дерево переводится в промежуточное представление, оно оптимизируется (свёртка констант, удаление мёртвого кода), а затем превращается в целевой код.

Как работает под капотом

Возьмём строку x = 2 + 3 * 4. Лексер выдаст токены x, =, 2, +, 3, *, 4. Парсер построит дерево, где умножение лежит глубже сложения (из-за приоритета). Семантика проверит, что x можно присваивать. Оптимизатор заметит, что 2 + 3 * 4 — константа, и заранее вычислит 14. Кодогенератор выдаст инструкцию «положить 14 в x».

Частые ошибки

Главная путаница новичков — считать, что лексер уже «понимает» структуру. Нет: лексер не знает про приоритет операторов и вложенность, он лишь нарезает символы на токены. Структуру строит парсер. Смешение этих ролей приводит к запутанному коду компилятора.

Ещё ошибка — пытаться делать всё в один проход без чётких фаз. Для учебного калькулятора это сойдёт, но для реального языка слияние фаз превращает компилятор в нечитаемый клубок.

Итог

  • Компилятор — конвейер фаз, каждая меняет представление программы.
  • Front-end анализирует язык, back-end генерирует код, между ними — IR.
  • Классические этапы: лексический, синтаксический, семантический анализ, IR, оптимизация, кодогенерация.
  • Каждая фаза решает одну задачу — это упрощает разработку и тестирование.
Проверьте себя
1. Какая фаза превращает текст программы в поток токенов?
AСинтаксический анализ
BЛексический анализ
CСемантический анализ
DГенерация кода
2. За что отвечает back-end компилятора?
AЗа анализ исходного языка
BЗа генерацию кода под целевую платформу
CЗа удаление комментариев
DЗа чтение файла с диска
3. Почему компилятор делят на отдельные фазы?
AТак требует процессор
BЧтобы каждая фаза решала одну задачу и её было легче писать и тестировать
CЧтобы программа работала быстрее на этапе запуска
DЭто требование операционной системы