Как стать автором
Обновить

Синергия Graphviz и препроцессора C/C++

Время на прочтение4 мин
Количество просмотров5.2K

Это статья посвящена тому, как использовать популярный инструмент рисования графов Graphviz в кооперации с препроцессором C/C++ (далее просто препроцессор).


Ключевым моментом является то, что язык описания графов dot, который использует Graphviz, по своему синтаксису пригоден для обработки препроцессором. Так задумано разработчиками. Благодаря их прозорливости, мы при описании графов можем использовать следующие возможности (цитирую википедию по памяти):


  • замена соответствующих диграфов и триграфов на эквивалентные символы «#» и «\»;
  • удаление экранированных символов перевода строки;
  • замена строчных и блочных комментариев пустыми строками (с удалением окружающих пробелов и символов табуляции);
  • вставка (включение) содержимого произвольного файла (#include);
  • макроподстановки (#define);
  • условная компиляция (#if, #ifdef, #elif, #else, #endif);

Теперь продемонстрируем открывшиеся возможности на практике. В качестве примера возьмем граф из моей статьи о Медиастримере2, он на рисунке ниже.



Граф достаточно большой и если его описывать "в лоб" то потребуется много ручной безошибочной работы. Если приглядеться, то можно вычленить повторяющиеся элементы с разницей только в содержании некоторых полей. Так выглядят узлы графа обозначенные как m1, m2, m2_1, m2_2, m3, m4. Это первые кандидаты на применение малой механизации с помощью макросов. Создаем заголовочный файл для нашего основного dot-файла. Назовем его common.dot:


// Файл common.dot  содержит определения для основного файла графа.

#define Q(x) #x         // Вспомогательное макро для "закавычивания" строки внутри макро.

#define QUOTE(x) Q(x)   // Макро для "закавычивания" строки внутри другого макро.

// Определяем макрос для условного изображения внутренних полей структуры mblk_t.
// макрос будет показывать только некоторые поля структуры. 
#define msg_staff \
   <p> *prev \
  |<n> *next \
  |<c> *cont  \
  |<d> *data \
  | other\nstuff

// Определяем макрос для условного изображения самой структуры mblk_t.
#define msg(name, ... ) \
name[xlabel=mblk_t label=QUOTE(<h> name | msg_staff) \
];

// Определяем макрос для условного изображения самой структуры dblk_t.
#define dbuf(name ...) \
name[label=QUOTE(<h> name) xlabel="dblk_t"];

// Определяем макрос для условного изображения структуры queue_t.
// Часть полей этой структуры совпадают с полями структуры mblk_t, 
// поэтому в определение подставлен макрос msg_staff.
#define queue(name, ...) \
name[ xlabel="queue_t" label=QUOTE(<h> name | \
msg_staff)];

Теперь подошло время написать основной файл графа. Назовем его my_graph.dot:


// Файл my_graph.dot

// Подключаем наш файл с определением макросов.
#include "common.dot"

digraph queue_demo
{
    rankdir=LR;
    node[shape=Mrecord];

    // Используя определенные нами выше макросы,
    // создаем узлы, которые будут на графе символизировать блоки данных.
    dbuf(d1);
    dbuf(d2);
    dbuf(d2_1);
    dbuf(d2_2);
    dbuf(d3);
    dbuf(d4);

    // создаем узлы, которые будут на графе символизировать сообщения,
    // к которым привязаны блоки данных.
    msg(m1);
    msg(m2);
    msg(m2_1);
    msg(m2_2);
    msg(m3);
    msg(m4);

    // Создаем экземпляр структуры управляющей очередью.
    // Узел будет иметь имя q1.
    queue(q1);

    // Описываем соединения блоков данных с узлами сообщений.
    m1:d->d1;
    m2:d->d2;
    m2_1:d->d2_1;
    m2_2:d->d2_2;
    m3:d->d3;
    m4:d->d4;

    // Описываем соединения сообщений между собой.
    m1:n -> m2:h;
    m1:p -> q1:h;
    m2:n -> m3:h;
    m2:c->m2_1:h;
    m2_1:c->m2_2:h;
    m3:n -> m4:h;

    m2:p -> m1:h;
    m3:p -> m2:h;
    m4:p -> m3:h;

    // Описываем соединения сообщений со структурой очереди.
    q1:n->m1:h;  
    q1:p->m4:h;
    m4:n -> q1:h[color=blue]; // Подкрашиваем ребро синим цветом.
}

Обрабатываем файл препроцессором:


$ cpp my_graph.dot -o my_graph_res.dot

Результат будет помещен в файл my_graph_res.dot. В результате работы препроцессора файл описания графа примет вид:


# 1 "<built-in>"
# 1 "<command-line>"
# 1 "my_graph.dot"

# 1 "common.dot" 1
# 3 "my_graph.dot" 2

digraph queue_demo
{
    rankdir=LR;
    node[shape=Mrecord];

    d1[label="<h> d1" xlabel="dblk_t"];;
    d2[label="<h> d2" xlabel="dblk_t"];;
    d2_1[label="<h> d2_1" xlabel="dblk_t"];;
    d2_2[label="<h> d2_2" xlabel="dblk_t"];;
    d3[label="<h> d3" xlabel="dblk_t"];;
    d4[label="<h> d4" xlabel="dblk_t"];;

    m1[xlabel=mblk_t label="<h> m1 | <p> *prev |<n> *next |<c> *cont |<d> *data | other\nstuff" ];;
    m2[xlabel=mblk_t label="<h> m2 | <p> *prev |<n> *next |<c> *cont |<d> *data | other\nstuff" ];;
    m2_1[xlabel=mblk_t label="<h> m2_1 | <p> *prev |<n> *next |<c> *cont |<d> *data | other\nstuff" ];;
    m2_2[xlabel=mblk_t label="<h> m2_2 | <p> *prev |<n> *next |<c> *cont |<d> *data | other\nstuff" ];;
    m3[xlabel=mblk_t label="<h> m3 | <p> *prev |<n> *next |<c> *cont |<d> *data | other\nstuff" ];;
    m4[xlabel=mblk_t label="<h> m4 | <p> *prev |<n> *next |<c> *cont |<d> *data | other\nstuff" ];;

    q1[ xlabel="queue_t" label="<h> q1 | <p> *prev |<n> *next |<c> *cont |<d> *data | other\nstuff"];;

    m1:d->d1;
    m2:d->d2;
    m2_1:d->d2_1;
    m2_2:d->d2_2;
    m3:d->d3;
    m4:d->d4;

    m1:n -> m2:h;
    m1:p -> q1:h;
    m2:n -> m3:h;
    m2:c->m2_1:h;
    m2_1:c->m2_2:h;
    m3:n -> m4:h;

    m2:p -> m1:h;
    m3:p -> m2:h;
    m4:p -> m3:h;

    q1:n->m1:h;
    q1:p->m4:h;
    m4:n -> q1:h[color=blue];
}

Как можно видеть, все макросы раскрыты и подставлены. Строчки стали длиннее и сложнее. Остается передать файл для рендеринга одной из утилит пакета Graphviz (например dot) или отрендерить на сайте: https://sketchviz.com/new


Результат будет таким:



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


При желании можно пойти дальше и убрать под макрос соединения узлов очереди.

Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
+11
Комментарии9

Публикации

Изменить настройки темы

Истории

Работа

Ближайшие события