Что такое абстрактное синтаксическое дерево/нужно ли оно?

меня интересовал дизайн/реализация компилятора/интерпретатора, пока я программировал (всего 5 лет), и это всегда казалось "волшебством" за кулисами, о котором никто не говорит (Я знаю, по крайней мере, 2 форума для разработки операционной системы, но я не знаю никакого сообщества для разработки компилятора/интерпретатора/языка). В любом случае, недавно я решил начать работать самостоятельно, в надежде расширить свои знания о программировании в целом (и Эй, это довольно весело :). Итак, основываясь на ограниченном количестве материала для чтения, который у меня есть, и Википедии, я разработал эту концепцию компонентов для компилятора / интерпретатора:

исходный код - > лексический анализ - > абстрактное дерево синтаксиса - > синтаксический анализ - > семантический анализ - > генерация кода - > исполняемый код.

(Я знаю, что есть больше для генерации кода и исполняемого кода, но я еще не дошел до этого:)

и с этим знанием, я создал очень простой lexer (в Java), чтобы взять вход из исходного файла и вывести токены в другой файл. Пример ввода/вывода будет выглядеть следующим образом:

вход:

int a := 2
if(a = 3) then
    print "Yay!"
endif

выход (лексер):

INTEGER
A
ASSIGN
2
IF
L_PAR
A
COMP
3
R_PAR
THEN
PRINT
YAY!
ENDIF

лично я думаю, что было бы очень легко перейти оттуда к синтаксическому / семантическому анализу и, возможно, даже генерации кода, что приводит меня к вопросу: Зачем использовать AST, когда кажется, что мой лексер делает такую же хорошую работу? Однако, 100% мои источники, которые я использую для исследования этой темы, все кажутся непреклонными, что это необходимая часть любого компилятора / интерпретатора. Я упускаю смысл того, что такое AST (дерево, которое показывает логический поток программы)?

TL; DR: В настоящее время в маршруте для разработки компилятора, закончил лексер, мне кажется, что вывод сделает для легкого синтаксического анализа/семантического анализа, а не для выполнения AST. Так зачем использовать его? Я упускаю смысл одного из них?

спасибо!

2 ответов


во-первых, одна вещь о вашем списке компонентов не имеет смысла. Объект АСТ is (в значительной степени) синтаксический анализ, поэтому его либо не должно быть там, либо, по крайней мере, прийти до АСТ.

то, что у вас есть, - это лексер. Все, что он дает вам, - это отдельные жетоны. В любом случае вам понадобится фактический парсер, потому что обычные языки не так интересно программировать. Вы даже не можете (правильно) вложить выражения. Черт, ты даже не можешь ручка операторов. Поток токенов не дает вам:

  1. идея, где операторы и выражения начинаются и заканчиваются.
  2. представление о том, как операторы группируются в блоки.
  3. идея, какая часть выражения имеет какой приоритет, ассоциативность и т. д.
  4. четкое, лаконичное представление о фактической структуре программы.
  5. структура которую можно пройти через мириады преобразований, без каждого один проход зная и имея код для размещения что условие if заключено в скобки.
  6. ... в более общем плане, любое понимание выше уровня одного токена.

Предположим, у вас есть два прохода в вашем компиляторе, которые оптимизируют определенные типы операторов, применимые к определенным аргументам (скажем, постоянное складывание и алгебраические упрощения, такие как x - x -> 0). Если вы передадите им токены для выражения x - x * 1, эти проходы загромождены выяснением того, что x * 1 часть первая. И они!--6-->есть знать это, чтобы преобразование не было неправильным (рассмотрим 1 + 2 * 3).

эти вещи достаточно сложно, чтобы получить право, как это, так что вы не хотите, чтобы вас беспокоили проблемы разбора, а также. Вот почему вы решаете проблему разбора первый, в отдельном шаге разбора. затем вы можете, скажем, заменить вызов функции его определением, без беспокоиться о добавлении круглых скобок, чтобы смысл остался прежним. Вы экономите время,вы отделяете проблемы, вы избегаете повторения, вы включаете более простой код во многих других местах и т. д.

парсер вычисляет все это и строит АСТ, который, следовательно, содержит всю эту информацию. Без каких-либо дополнительных данных об узлах форма AST сама по себе не дает вам никаких. 1, 2, 3 и многое другое, Бесплатно. нет из bazillion проходит, которые следуют должны беспокоиться об этом больше.

это не значит, что вы всегда должны иметь АСТ. Для достаточно простых языков можно сделать компилятор с одним проходом. Вместо создания AST или какого-либо другого промежуточного представления во время синтаксического анализа вы выдаете код по ходу. Однако это становится сложнее для менее простых языков, и вы не можете разумно делать много вещей (например, 70% всех оптимизаций и диагностики-и да, я только что придумал это число). Вообще, я бы не советовал вам этого делать. Там хорошие причины однопроходные компиляторы в основном мертвы. Даже языки, которые позволяют их (например, C), в настоящее время реализованы с несколькими проходами и AST. Это простой способ начать работу, но сильно ограничит вас (и язык, если вы его создадите) позже.


У вас есть AST в неправильной точке вашей схемы. Как правило, выход лексера представляет собой серию токенов (как у вас есть в вашем выходе), и они подаются в синтаксический анализатор/синтаксический анализатор, который генерирует AST. Таким образом, выход вашего лексера отличается от AST, потому что они используются в разных точках процесса компиляции и выполняют разные цели.

следующий логический вопрос: Что же такое АСТ? Ну, цель синтаксический анализ/синтаксический анализ заключается в том, чтобы превратить ряд токенов, генерируемых лексером, в AST (или дерево синтаксического анализа). AST-это промежуточное представление, которое фиксирует связь между синтаксическими элементами таким образом, что с ним проще работать программно. Один из способов мышления об этом заключается в том, что текстовая программа является одномерной конструкцией и может представлять идеи только как последовательность элементов, в то время как AST освобождается от этого ограничения и может представлять лежащую в основе отношения между этими элементами в 2 измерениях (как обычно рисуется) или любое пространство более высокого измерения, если вы решите думать об этом таким образом.

например, двоичный оператор имеет два операнда, назовем их A и B. В коде это может быть написано 'A * B' (предполагая, что оператор infix-другое преимущество AST-скрыть такие различия, которые могут быть важны синтаксически, но не семантически), но для компилятора, чтобы "понять" это выражение, он должен прочитать 5 символы последовательно, и эта логика может быстро стать громоздкой, учитывая множество возможностей даже на небольшом языке. Однако в представлении AST у нас есть узел "двоичный оператор", значение которого"*", и этот узел имеет два дочерних элемента, значения " A " и "B".

по мере продвижения вашего проекта компилятора, я думаю, вы начнете видеть преимущества этого представления.