Построение и обход абстрактного синтаксического дерева

мне неясна структура абстрактных синтаксических деревьев. Чтобы перейти "вниз (вперед)" в источнике программы, которую представляет AST, вы идете прямо на самый верхний узел или спускаетесь вниз? Например, будет ли пример program

a = 1
b = 2
c = 3
d = 4
e = 5

результат в AST, который выглядит так: enter image description here

или это: enter image description here

где в первом, идя "направо" на main node продвинет вас через программу, но во втором просто после next указатель на каждом узле будет делать то же самое.

кажется, что второй будет более правильным, так как вам не нужно что-то вроде специального типа узла с потенциально очень длинным массивом указателей для самого первого узла. Хотя, я вижу, что второй становится более сложным, чем первый, когда вы попадаете в for петли и if филиалов и более сложные вещи.

5 ответов


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

считаем:

enter image description here

Это ваш первый пример, за исключением сокращенного и с" главным "узлом (концептуальный соломенный человек), более подходящим именем "блок", чтобы отразить общую конструкцию "блока", содержащего последовательность высказываний на императивном языке программирования. Различные типы узлов имеют разные типы дочерних узлов, и иногда эти дочерние узлы включают коллекции вспомогательных узлов, порядок которых важен, как в случае с "блоком". То же самое может возникнуть, скажем, при инициализации массива:

int[] arr = {1, 2}

рассмотрим, как это может быть представлено в виде дерева синтаксис:

enter image description here

здесь узел array-literal-type также имеет несколько дочерних элементов того же типа, чей порядок важен.


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

кажется, что второй будет более правильно, так как вам не нужно что-то вроде специального типа узла с потенциально весьма длиной массив указателей на первые узел

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

Я думаю, что в целом проще, чтобы все объекты происходили из общего базового класса, подобного этому:

abstract class Expr { }

class Block : Expr
{
    Expr[] Statements { get; set; }
    public Block(Expr[] statements) { ... }
}

class Assign : Expr
{
    Var Variable { get; set; }
    Expr Expression { get; set; }
    public Assign(Var variable, Expr expression) { ... }
}

class Var : Expr
{
    string Name { get; set; }
    public Variable(string name) { ... }
}

class Int : Expr
{
    int Value { get; set; }
    public Int(int value) { ... }
}

результирующий AST выглядит следующим образом:

Expr program =
    new Block(new Expr[]
        {
            new Assign(new Var("a"), new Int(1)),
            new Assign(new Var("b"), new Int(2)),
            new Assign(new Var("c"), new Int(3)),
            new Assign(new Var("d"), new Int(4)),
            new Assign(new Var("e"), new Int(5)),
        });

Это зависит от языка. В C вам нужно будет использовать первую форму для захвата понятия блока, так как блок имеет переменную область:

{
    {
        int a = 1;
    }
    // a doesn't exist here
}

область переменных будет атрибутом того, что вы называете "основным узлом".


Я считаю, что ваша первая версия имеет больше смысла, по нескольким причинам.

во-первых, первый более наглядно демонстрирует "вложенность" программы, а также четко реализован в виде корневого дерева (что является обычным понятием дерева).

вторая и более важная причина заключается в том, что ваш "главный узел" действительно мог быть "узлом ветви" (например), который может быть просто другим узлом в более крупном AST. Таким образом, ваш AST можно просмотреть в рекурсивный смысл, где каждый AST является узлом с другими AST как его дочерними элементами. Это делает дизайн первого гораздо проще, более общим и очень однородным.


предложение: при работе с древовидными структурами данных, связанными с компилятором AST или другим видом, всегда используйте один "корневой" узел, это может помочь вам выполнять операции и иметь больше контроля:

class ASTTreeNode {
  bool isRoot() {...}

  string display() { ... }  
  // ...
}

void main ()
{
  ASTTreeNode MyRoot = new ASTTreeNode();

  // ...

  // prints the root node, plus each subnode recursively
  MyRoot.Show();
}

Ура.