Конвейер компиляции: этапы
Компилятор — это конвейер: каждый этап получает одно представление программы и выдаёт следующее, более близкое к машине.
Фаза компилятора — отдельный шаг обработки, преобразующий программу из одного представления в другое (например, текст в токены или токены в дерево).
Удобнее всего представлять компилятор как трубу, по которой течёт программа, постепенно меняя форму. Разделение на фазы — не прихоть, а инженерное решение: каждый этап решает одну задачу, его легче писать, тестировать и переиспользовать.
Передняя и задняя часть
Компилятор делят на 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, оптимизация, кодогенерация.
- Каждая фаза решает одну задачу — это упрощает разработку и тестирование.