Алгоритмы на деревьях
Теория: AST-деревья
У каждого языка программирования есть свой набор ключевых слов, с помощью которых мы определяем выражения, задаем инструкции и преобразуем входные данные в выходные.
При этом у всех современных языков программирования есть одно сходство — они разрабатываются с упором на человекочитаемость. Другими словами, код на таких языках должен быть понятен в первую очередь человеку, а не машине.
Современные языки с упором на человекочитаемость называются языками высокого уровня. Они пришли на смену языкам низкого уровня, в которых программисты подстраивали код под поведение исполнителя — того или иного процессора. Задачу усложнял еще и тот факт, что процессоры сильно отличались с точки зрения поддерживаемых команд и других важных аспектов.
Переход на языки высокого уровня развивает отрасль высоких технологий:
- Языки высокого уровня проще изучать, поэтому снизился порог входа в отрасль для новичков
- Появилась возможность запускать программы на разных процессорах без изменений в исходном коде, поэтому на разработку стало уходить меньше времени
- Код стал понятнее, поэтому стало проще работать совместно — читать чужой код и набираться опыта, присоединяться к новым проектам, поддерживать и исправлять чужие программы
С другой стороны, у высокоуровневых языков есть один основной недостаток — компьютеры не умеют работать с ними напрямую. До сих пор процессоры понимают только тот набор команд, для которого его спроектировали инженеры. Программа на языке высокого уровня не запустится, если мы не переведем ее код на язык низкого уровня. Только в этом случае процессор поймет, какой набор команд нужно выполнить. За этот перевод отвечают два процесса — компиляция и интерпретация.
Разобрать код на высокоуровневом языке и превратить его в список низкоуровневых команд позволяют AST-деревья — структур данных, о которых мы поговорим в этом уроке.
Что такое AST-деревья
AST (Abstract Syntax Tree) — это абстрактное синтаксическое дерево, которое работает как один из промежуточных слоев при преобразовании языков высокого уровня. AST-дерево устроено так:
- В качестве промежуточных узлов выступают инструкции, функции, методы или операторы
- В листовых узлах содержатся константы, переменные и имена методов, то есть входные аргументы для соответствующих промежуточных узлов
AST-дерево не учитывает синтаксические особенности языка, вроде фигурных скобок или правил оформления лямбда-выражений. Это отличает их от другой похожей структуры — деревьев разбора.
Таким образом, AST-дерево занимает промежуточную позицию между деревом разбора и внутренним представлением программы внутри конкретных компиляторов или интерпретаторов. Для примера рассмотрим небольшую программу, которая занимается перемещением блока по экрану:
Javascript
Теперь попробуем преобразовать функцию из примера выше в дерево:
- В узле хранится информация о выполняемом операторе языка и список дочерних узлов
- В листовых узлах лежит сопоставление с конечными операндами выполняемого оператора
- Дерево ориентировано по порядку выполнения операций, поэтому вершиной дерева будет самая последняя операция по порядку вызова
Соответственно, функция из нашего примера принимает такой вид:
Можно сделать вывод, что AST — это не бинарное дерево. Оно может содержать в себе узлы нескольких подвидов, чем напоминает DOM-деревья.
Какие операции можно проводить над AST-деревом
Ранее в курсе мы описывали деревья с точки зрения структуры узла и реализации основных операций над ним — построения, поиска, вставки и удаления. Но в этом уроке мы пойдем другим путем.
Дело в том, что с AST-деревьями операции поиска, добавления и удаления возможны в теории, на практике не имеют смысла. AST-дерево может измениться только по одной причине — появилось что-то новое в исходном коде программы. Такое изменение требует не вносить правки в старое дерево, а перестраивать его заново.
Поэтому в изучении AST-деревьев мы ограничимся только определением структуры и реализацией самого построения. Структура нашего узла принимает следующий вид:
Javascript
Python
PHP
Java
Чтобы определить тип хранимых в узле данных, опишем перечисление instructionType. Оно будет описывать инструкции языка программирования в нескольких возможных вариантах. Воспользуемся этим перечислением, чтобы заполнить дерево. Нам понадобятся следующие варианты:
- Унарный оператор
UNARYс одним аргументом — например,a++. В таком случае полеchildrenсодержит один элемент типаASTree - Оператор
MULTIPLE, принимающий несколько аргументов — например,a == b. В таком случае полеchildrenсодержит массив элементов типаASTree - Аргумент метода
ARGUMENT, который является определенной переменной или константой. В таком случае полеchildrenне имеет значения, а в полеvalueдолжно храниться значение переменной или константы
Теперь опишем метод построения такого дерева:
Javascript
Python
PHP
Java
Здесь мы видим подход к построению через статический метод. Он позволит нам описать входные параметры дерева в виде массива. Для наглядности возьмем такой пример:
a = 5 * 10 + sqrt(6)
Его можно записать так:
Javascript
Такой способ записи программы называется польская нотация. Ее часто используют при разборе операций в различных вычислительных машинах. Сама ее структура наглядно показывает последовательность выполнения операций.
Чтобы построить дерево для нашего примера, вызовем наш класс с помощью следующей команды:
Javascript
Python
PHP
Java
Выводы
В этом уроке мы познакомились со специальным видом деревьев — AST-деревьями. С их помощью можно описать полное содержимое программы без лишних деталей — комментариев, фигурных скобок, отступов и других требований к оформлению исходного кода. Основная сфера применения этих деревьев — компиляторы и интерпретаторы исходного кода, которые переводят вашу программу на машинопонятный язык.
AST-деревья не зависят от конкретного языка, поэтому их можно использовать в инструментах автоматического перевода программы на другой язык. Кроме того, GitHub Copilot и другие современные средства искусственного интеллекта могут использовать такие деревья. Они предсказывают действия разработчика, предлагают ему уже готовую реализацию будущей функции и таким образом позволяют писать качественный код гораздо быстрее.

