Подразделы

Другие разделы

Дата и время

09/05/2024 06:54:55

Авторизация

Имя:
Пароль:
Зарегистрироваться
Восстановить пароль
 

printРазработка компиляторов и интерпретаторов

printСоздание и обработка деревьев разбора

На следующем этапе трансляции необходимо обработать дерево разбора. Но сначала на этапе синтаксического анализа необходимо создать это дерево в форме, удобной для последующей обработки. Такая форма называется абстрактное синтаксическое дерево (AST) и отличается от дерева разбора тем, что в нем отсутствуют элементы, которые не влияют на семантику программы (разделители, группирующие скобки).
Программа CAIO (входит в состав MinIDE) позволяет сгенерировать по описанию структуры дерева все необходимые классы и функции для создания AST. Обычно узлы AST обычно делятся на типы, так как разные виды узлов могут появляться только в определенных частях дерева.
Рассмотрим как выглядит описание для бинарного числового дерева:
%option ast
%type <Tree> node(int,Tree,Tree) empty()
%%
В дереве могут быть два типа узлов: node и empty. Узел node содержит целое число и ссылки на два поддерева. По этому описанию будет сгенерирована иерархия из трех классов и две функции node и empty, которые создают объекты соответствующих классов.
38301.png
Для создания дерева из семи узлов (3 числа и 4 пустых узла)
38302.png
необходимо написать:
Tree sample_tree=node(5,
   node(7,empty(),empty()),
   node(3,empty(),empty()));
Для обхода дерева в программу добавляются операторы match `e` (аналог switch) и rule `n` (аналог case), где `e` – ссылка на узел дерева, а `n` – конкретный тип узла. Эти операторы будут заменены Caio на условные операторы и преобразования с проверкой dynamic_cast. Например, для получения суммы элементов бинарного числового дерева нужно написать следующую функцию:
int sum1(Tree t)
{ match t {
    rule node(v,l,r): 
      return v+sum1(l)+sum1(r);
    rule empty():
      return 0;
  }
}
...
cout<<sum1(sample_tree)<<"\n";
Если какой-то тип узла не был обработан одним из предложений rule, то будет выведено сообщение об ошибке (можно убрать эту проверку опцией nomatcherror).
Альтернативным вариантом для обхода дерева является конструкция visitor `v`<`T`,`R`> { }, где `v` – имя посетителя, `T` – тип узлов, `R` – тип результата. В скобках указывается конструкции visit `n` для обработки узлов дерева, как в match. По данному описанию будет создан класс-посетитель `v`_visitor и конкретный объект этого класса `v`. В данном классе перегружается операция (), поэтому его можно вызывать как функцию. Этот вариант более эффективен, чем match, при большом количестве типов узлов, но менее гибок.
visitor sum2<Tree,int>
{ visit node(v,l,r): 
    return v+sum2(l)+sum2(r);
  visit empty():
    return 0;
}
...
cout<<sum2(sample_tree)<<"\n";
Перед первой конструкцией visit можно указать вспомогательные элементы структуры, которые можно использовать для накопления результата во время обхода:
visitor sum3<Tree,void>
{ int s=0;
  visit node(v,l,r):
    s+=v; sum3(l); sum3(r);
}
...
sum3(sample_tree);
cout<<sum3.s<<"\n";
Можно отделить описание типов от функций для их обработки, тогда при определении функций нужно подключить описание типов с помощью команды %using имя. Опция astprint добавляет в программу семейство функций astprint(ostream&, `T`), которое позволяет вывести дерево в отладочных целях.
loading