Устройство языков программирования
Все объяснения, задачи и лабораторная в одном месте.
Можно сохранить страницу в PDF через печать браузера. Для печати разборы ответов раскрываются автоматически.
1. Лексер, parser и абстрактное синтаксическое дерево
Объяснение
Лексер превращает символы в токены, parser строит дерево по грамматике. Приоритет операций задаётся грамматикой или алгоритмом разбора. AST сохраняет смысл, но обычно отбрасывает лишние скобки и пробелы.
Задача для самостоятельного решения
Постройте дерево для 2+3*4.
Показать разбор ответа
Корень — сложение: слева 2, справа умножение 3 и 4. Вычисление даёт 14. Линейное выполнение слева направо ошибочно дало бы 20.
2. Области видимости, closures и окружения
Объяснение
Лексическая область видимости определяется местом объявления. Замыкание объединяет функцию с окружением, нужным для её исполнения. Захват значения и захват изменяемой ячейки имеют разное поведение.
Задача для самостоятельного решения
Счётчик хранит n=0 и возвращает функцию, увеличивающую n. Что дают два вызова?
Показать разбор ответа
1 и 2, если захвачена одна изменяемая ячейка. Новый вызов фабрики создаёт независимую ячейку. Это объясняет, почему разные счётчики не обязаны разделять состояние.
3. Статические и динамические системы типов
Объяснение
Тип задаёт допустимые значения и операции. Статическая проверка работает до исполнения, динамическая — во время. Наличие статических типов не исключает логические ошибки и неверные внешние данные.
Задача для самостоятельного решения
Почему типизированный JSON-клиент всё равно должен проверять ответ сети?
Показать разбор ответа
Типы описывают предположения программы; сервер присылает байты и может нарушить схему. Нужна runtime-валидация на границе, а не только приведение типа.
4. Управление памятью и сборка мусора
Объяснение
Сборщик мусора освобождает недостижимые объекты. Цикл ссылок не обязательно является утечкой для трассирующего GC. Удерживаемая из глобального кеша ссылка делает объект достижимым, даже если он больше не нужен продукту.
Задача для самостоятельного решения
Почему бесконечно растущий кеш не очищается GC?
Показать разбор ответа
Его записи достижимы из корня программы. Нужны ограничение размера, TTL или вытеснение. Сборщик не знает бизнес-смысла слова «ненужный».
5. Функциональная, объектная и логическая парадигмы
Объяснение
Чистая функция возвращает одинаковый результат для одинакового входа и не меняет внешнее состояние. Объектная модель группирует состояние и поведение. Парадигмы можно сочетать, отделяя вычисления от эффектов.
Задача для самостоятельного решения
Как сделать расчёт стоимости тестируемым?
Показать разбор ответа
Передавать цены и ставки аргументами, возвращать результат, а чтение БД и отправку счёта вынести наружу. Тогда тест не зависит от сети и текущего времени.
6. Байткод, JIT и оптимизация
Объяснение
Байткод исполняется виртуальной машиной. JIT компилирует горячие участки во время работы, используя наблюдаемые типы и частоты. Прогрев влияет на результаты измерений, а оптимизация может отменяться.
Задача для самостоятельного решения
Почему первый запуск метода медленнее следующих?
Показать разбор ответа
Он может включать загрузку классов, интерпретацию и JIT-компиляцию. Сравнивайте прогретую нагрузку отдельно от cold start: оба показателя важны, но отвечают на разные вопросы.
Лабораторная работа
Подготовка
Python 3. Пример — небольшой интерпретатор явно заданного AST, не parser произвольного ввода.
Учебный пример
def evaluate(node):
if isinstance(node, int):
return node
op,left,right = node
a,b = evaluate(left),evaluate(right)
if op == "+": return a+b
if op == "*": return a*b
raise ValueError("Неизвестная операция")
print(evaluate(("+",2,("*",3,4))))Как работает пример и что ожидать
Результат 14: рекурсивный evaluator сначала получает значения дочерних узлов. Структура дерева уже задаёт приоритет; код не разбирает строку. Для реального языка нужно отдельно ограничить глубину дерева, типы и объём вычислений.
Итоговая работа
Добавьте lexer, parser со скобками, переменные и понятные сообщения об ошибке позиции. Не используйте eval для недоверенного текста. Проверяйте неверный токен, неизвестное имя и деление на ноль.
Проверка результата
1. Опишите исходные данные и условия запуска, чтобы другой человек мог повторить работу.
2. Приложите результат обычного сценария и сравните его с ожидаемым.
3. Проверьте неверный вход, граничный случай и отказ зависимости, если она есть.
4. Объясните выбранное решение и известное ограничение.
5. Сохраните исправления после самопроверки вместе с примером, который раньше не работал.
Как оценить работу
По каждому пункту поставьте 0 (не выполнено), 1 (выполнено с пробелами) или 2 (результат воспроизводим и объяснён). Если обязательный сценарий не работает, вернитесь к нему независимо от общей суммы. Это рубрика самопроверки: сайт не исполняет присланный код и не выдаёт автоматическую оценку проекта.