Обновить 3. Библиотека Sage++ и внутреннее представление программы.

2023-09-14 17:01:07 +00:00
parent 4af7e4f515
commit 6a4b69f9b1

@@ -1 +1,86 @@
в
Внутреннее представление программы с языка Фортран представляет собой AST (абстрактное синтаксическое дерево). Библиотека Sage++ представляет собой реализацию данного дерева разбора. Все высокоуровневые функции, структуры и классы в Sage++ начинаются с префикса **Sg**, за исключением некоторых функций, которые проверяют некоторые свойства, например, `isSgForStmt(SgStatement*)` - функция, проверяющая является ли выбранный узел циклом или нет.
Внутреннее представление создается с помощью парсера. Парсер использует грамматику для синтаксического анализа. Данная грамматика расширена для поддержки директив DVMH и SAPFOR. Для того, чтобы начать работать с внутренним представлением, необходимо загрузить в память все файлы проекта, созданные парсером. Это делается с помощью класса **SgProject**, в конструктор, которому передается имя текстового файла, в котором перечислен список *.dep файлов. После создания проекта можно использовать следующий уровень абстракции - файл. Файл является единицей трансляции, поэтому для того, чтобы переключиться на конкретный файл, необходимо использовать следующий вызов:` SgFile *file = &(project.file(0));` - данная функция получает файл с индексом 0. Также, класс SgProject содержит в себе весь необходимый функционал для работы с проектом, например, количество файлов в проекте ( `project.numberOfFiles()` ), или имя каждого файла в проекте ( `project.fileName(0)` ). Таким образом, стандартная фаза компиляции представляет из себя проход по всем файлам для анализа кода программы пользователя.
В текущей реализации SAPFOR уже определен механизм проходов, открытия проекта и выбора файлов. Каждый проход (фаза анализа) может обрабатывать каждый файл отдельно, а также, если необходима агрегация результатов анализа, может иметь функцию, которая работает после обработки всех файлов проекта.
Каждый файл проекта представляет из себя набор связанных между собой операторов или **SgStatement**. Данный класс является абстрактным представлением всех операторов (базовым классом), от которого наследуются остальные классы для реализации специальных возможностей, присущие отдельно взятому оператору. Например, **SgForStmt**, является производным классом от **SgStatement** и содержит весь необходимый функционал для работы с оператором цикла. Согласно правилам языка С++, любой производный класс может быть приведен к базовому классу.
В дереве разбора пользователь "видит" только **SgStatement**. К примеру, заголовок функции, оператор присваивания или оператор цикла - все это содержит в себе **SgStatement**. У каждого такого оператора есть вариант ( `SgStatement->variant()` ), который и задает вид конкретного оператора. Узнав вариант рассматриваемого оператора, можно использовать производное представление данного класса, преобразовав базовый класс к производному. Гарантируется, что данный оператор с правильным вариантом содержит всю необходимую информацию для производного класса. Например, чтобы узнать, является ли данный оператор оператором цикла, можно использовать следующий код:
```
SgStatement *st = currSt;
if (st->variant() == FOR_NODE)
SgForStmt *forSt = (SgForStmt*) st;
```
либо можно использовать такой код:
```
SgStatement *st = currSt;
SgForStmt *forSt = isSgForStmt(st);
if (forSt)
DoSomth();
```
Все варианты ( `*->variant()` ) описаны в файле **tag** и **dvm_tag.h**. Каждый узел обязан иметь какой-либо вариант. Помимо класса **SgStatement**, есть класс **SgExpression**, представляющий собой выражения. Данный класс реализует выражения, которые есть у операторов. Например, следующий оператор присваивания:
`A[i] = B[i] + C[i]`
содержит в себе два выражения - то, что находится слева от оператора присваивания, и то, что находится справа. Для того, чтобы получить доступ к этим выражениям, необходимо использовать соответствующие функции у класса **SgStatement**: например, **expr(N)** позволяет получить выражение N. Если выражения с номером N не существует, то вернется пустой указатель (NULL). Всего оператор может содержать не более трех выражений (то есть N = 0, 1, 2). Данные выражения представляю собой **SgExpression**, которыми наполняется оператор.
Класс **SgExpression** также является базовым классом для представления выражений. У данного класса есть такая же функция для взятия варианта ( `SgExpression->variant()` ). Используя данную функцию, можно узнать, с каким именно выражением необходимо работать и выполнить соответствующее преобразование к производному. Способ преобразования и проверки для выражений такой же, как и для операторов (_см. пример выше_).
Все операторы файла (**SgStatement**) связаны между собой, есть понятие следующего оператора за данным в лексическом порядке, и предыдущего оператора перед данным в лексическом порядке. Также у каждого оператора есть родительский оператор, который задает уровни вложенности операторов. Таким образом, следующий оператор, который идет за данным, не обязательно должен принадлежать текущей области вложенности (иметь одного и того же родителя). Например,
```
if (condition) then
op1
op2
endif
op3
```
за первым оператором, лексически следует оператор 2, за вторым - конец IF блока, а за концом IF блока - оператор 3. Узнать, какой оператор является родителем для данного оператора, можно с помощью функции `controlParent()`.
Стоит отметить оператор с вариантом CONTROL_END. Данный оператор определяет конец блока операторов языка фортран, например, END IF, END DO, END FUNCTION и т.д. Для определения родителя для данного оператора нужно использовать функцию `controlParent()`. У каждого оператора есть функция определения последнего оператора для данного - `lastNodeOfStatement()`. Если оператор является составным, например, IF END IF, то последним оператором будет ENDIF с вариантом CONTROL_END.
В отличие от операторов, выражения связаны в правое рекурсивное двоичное дерево. У каждого узла есть левый потомок ( `SgExpression->lhs()` ) и/или правый потомок ( `SgExpression->rhs()` ). Каждый потомок также является **SgExpression**. У какого узла может быть только левый потомок, либо только правый, либо вообще может не быть потомков (_в данном случае соответствующие функции вернут пустой указатель_). Для работы с такими деревьями требуется понимание рекурсии и двоичного дерева. Рекурсивно обойти такое дерево из выражений можно, например, следующим образом:
```
static void recExpression(SgExpression *exp, const int lvl) {
if (exp) {
SgExpression *lhs = exp->lhs();
SgExpression *rhs = exp->rhs();
doSmth()
recExpression(lhs, lvl + 1);
recExpression(rhs, lvl + 1);
}
}
```
У каждого оператора и выражения есть возможность получения его исходного кода на языке Фортран, то есть можно выполнить генерацию кода отдельного взятого оператора и выражения. Соответствующая функция называется `unparsestdout()`. Данная функция позволяет выполнить генерацию кода в консоль. Она служит в основном для отладки. Стоит заметить, что если вызвать данную функцию для оператора "Функция" или "IF блок", то вместе с этим оператором будут сгенерированы все вложенные операторы в данный (или все те операторы, у которых родитель - данный оператор). Аналогично и для выражений - будет сгенерирован код для всего бинарного дерева, начиная от текущего узла и ниже.
Для удобства отладки, в SAPFOR есть функция `recExpressionPrint (SgExpression *exp);`, которая позволяет получить наглядное представление бинарного дерева разбора для выражений в формате GraphViz, именуются узлы графа по такому правилу: **NODENUM_LVL_LR_TAGNAME_VALUE**:
- **NODENUM** - номер узла,
- **LVL** - глубина узла в дереве,
- **LR** - левое или правое это поддерево,
- **TAGNAME** - имя варианта, соответствующее предопределенным макросам в файлах tag и dvm_tag.h,
- **VALUE** - значение, которое было в исходном коде, если оно доступно (например, имя символа, функции или операции).
Данная функция на начальном этапе может **существенно упростить процесс отладки** и понимания того, как устроено внутреннее представление.
Рассмотрим такой оператор: `B(I, J, K) = A(I, J, K-1) + A(I, J-1, K) + A(I-1, J, K)`. Для него с помощью функции `recExpressionPrint()` можно получить представление для левого выражения (_стоящего слева от присваивания_) и для правого. Данная функция (`recExpressionPrint`) выводит код для GraphViz в стандартный поток вывода (_консоль_). Код для визуализации правого выражения будет выглядеть так:
```
digraph G{
"0_0_L_ADD_OP_(+)" -> "1_1_L_ADD_OP_(+)";
"0_0_L_ADD_OP_(+)" -> "2_1_R_ARRAY_REF_(a)";
"1_1_L_ADD_OP_(+)" -> "3_2_L_ARRAY_REF_(a)";
"1_1_L_ADD_OP_(+)" -> "4_2_R_ARRAY_REF_(a)";
и т.д.
};
```
Визуализировать данный код можно с помощью Web GraphViz по этой [ссылке ](https://dreampuf.github.io/GraphvizOnline/) или по этой [ссылке](http://webgraphviz.com/) , либо можно скачать с сайта по последней ссылке программу для визуализации графов. По построенному графу легко сопоставить исходное выражение с внутренним представлением.
Помимо операторов и выражений есть таблицы символов (**SgSymbol**) и типов (**SgType**), которые свои для каждого файла и общие для всех операторов и выражений в данном файле. Символы представляют собой наполнение выражений и операторов. Например, в приведенном выше выражении, есть символы A, B - которые являются символами следующего типа: трехмерный массив из double (базовый тип double, производный - массив из трех измерений), а символы I, J, K являются символами с типом Integer. В данном выражении для всех обращений к I, J, K будет ссылка на таблицу символов к единственным экземплярам I, J, K. Таблицы символов и типов доступны по соответствующей функции класса **SgFile**. На базе встроенных типов можно строить производные типы. Таблица типов доступна на уровне файла.
На текущий момент существует две версии библиотеки Sage++. Компилятором FDVMH и SAPFOR используется первая версия. На сайте Sage++ также доступна вторая версия. Описание классов, приведенных на сайте, может отличаться в зависимости от версии. [По этой ссылке](https://extreme.indiana.edu/) доступна некоторая документация и иерархия классов первой версии, а также доступно более наглядное представление всех интерфейсов второй версии.
Некоторые примеры можно найти на сайте этой библиотеки. Также можно рассмотреть некоторые простые проходы, реализованные в SAPFOR, которые описаны ниже.
Так как данная библиотека разрабатывалась в зарубежных университетах в том числе студентами, можно встретить какие-то некорректности или ошибки. Периодически мы стараемся вносить изменения и исправлять некоторые ошибки и некорректности как на высоком уровне, так и на низком. Также в интерфейс Sage++ можно добавлять некоторые возможности, которые могут упростить его использование. Изменение уже существующих классов крайне не рекомендуется и в большинстве случаев не практикуется, а расширение функциональности - наоборот приветствуется.