Теория языков программирования
AST и семантический анализ
AST - это мост между текстом программы и её смыслом. После построения AST компилятор работает не со строками, а со структурами данных. Каждая IDE feature - автодополнение, рефакторинг, hover types - работает с AST.
- **TypeScript Language Server**: построение и кеширование AST для каждого файла - основа instant type checking в VSCode. Инкрементальное обновление при каждом нажатии клавиши
- **Babel**: AST-трансформации превращают ES2023 в ES5. Каждый плагин - посетитель (Visitor), трансформирующий узлы AST
- **ESLint**: правила - это проверки AST узлов. no-unused-vars проверяет, что каждое определение в symbol table используется
Построение AST
AST (Abstract Syntax Tree) - дерево, представляющее структуру программы без деталей синтаксиса (скобки, запятые, точки с запятой). Каждый узел - операция или значение, дочерние узлы - подвыражения.
Что такое 'абстрактное' в аббревиатуре AST?
Разрешение имён
Name resolution (разрешение имён) - фаза после построения AST, где каждое использование имени (переменной, функции, типа) связывается с его определением. Результат - таблица символов (symbol table).
Что происходит при shadowing переменной в новом scope?
Проверка типов
Type checking - проверка корректности типов после разрешения имён. Бывает structural (типы совместимы если одинаковая структура - TypeScript) и nominal (типы совместимы если одно имя/иерархия - Java). Bidirectional type checking - современный стандарт.
В чём разница между structural и nominal type checking?
Scope и lifetime
Scope - лексическая область видимости переменной. Lexical scope (статический) - видимость определяется текстом программы. Dynamic scope - видимость определяется call stack. Почти все современные языки используют lexical scope.
AST - это только для компиляторов, в обычной разработке не используется
AST используется в babel (JS transpilation), eslint (linting), prettier (formatting), ts-morph (code generation), IDE hover/completion
Любой инструмент, анализирующий или трансформирующий код, работает с AST. TypeScript Compiler API предоставляет доступ к AST для любого TS кода
Почему Rust требует явные lifetime аннотации в некоторых функциях?
Итоги
- **AST** отбрасывает синтаксис (скобки, запятые), сохраняет структуру и приоритет. Узлы - операции, листья - значения
- **Name resolution** строит symbol table: каждое использование имени -> определение. Shadowing разрешено через scope chain
- **Type checking**: structural (TypeScript) vs nominal (Java). H-M inference выводит типы через унификацию
- **Scope**: lexical scope - видимость по тексту. Rust lifetimes - явная аннотация для borrow checker
Связанные темы
AST - центр пайплайна компилятора:
- Парсер — Парсер строит AST из потока токенов
- IR и оптимизации — Из AST генерируется IR для оптимизаций
Вопросы для размышления
- Closure захватывает переменные из lexical scope. Как это реализовано на уровне машинного кода - где хранятся захваченные переменные?
- Rust lifetimes - это аннотации в AST/HIR, проверяемые borrow checker. Почему нельзя автоматически вывести все lifetimes без аннотаций?
- TypeScript structural typing означает, что два разных типа с одинаковыми полями взаимозаменяемы. Это Feature или Bug? Приведите пример, где это создаёт проблему.