关于 graphviz 的使用(残卷)

最近学习编译原理,苦于使用 draw.io 这样的软件绘制示意图的时候特别麻烦,于是试图找到更为简单的方案,而 graphviz 就是一个很舒爽的解决方案——它提供了一个名为 dot 的 DSL 用以生成 svg,png 以及其他图像格式的文件。所以这里对它进行一些学习。这软件或许在其他地方也能够使用,比如绘制流程图,状态转移图之类的时候。

先尝尝鲜——下面是一个表示(19+2)*2的语法树的 dot 代码——

1
2
3
4
5
6
7
8
9
10
11
12
13
// 单行和多行注释同 Java
// graph 定义无向图
graph {
// 节点可以预先定义,也可以直接进行使用,这时其显示的内容就是其名称
MUL [label="*"]; // [] 中的内容对节点进行描述,显然这里定义了节点所显示的标签
PLUS [label="+"];
NUM2 [label=2];
NUM2_1 [label=2];

PLUS -- NUM2; // --标识相连,绘制树的话,左边深度较浅
PLUS -- 19; // 直接定义和描述了一个新节点
MUL -- {PLUS NUM2_1}; // 等价于 MUL -- PLUS; MUL -- NUM2_1;
}

其使用 dot 生成的对应的图如下——

%0 MUL * PLUS + MUL--PLUS NUM2_1 2 MUL--NUM2_1 NUM2 2 PLUS--NUM2 19 19 PLUS--19

说使用 dot 生成,是因为 graphviz 实际上提供了多种命令来生成图,其代表各种不同的布局引擎,其各有特点——

命令 特性
dot 渲染的图具有明确方向性。
neato 渲染的图缺乏方向性。
twopi 渲染的图采用放射性布局。
circo 渲染的图采用环型布局。
fdp 渲染的图缺乏方向性。
sfdp 渲染大型的图,图片缺乏方向性。

下面通过一些实例和注释描述其使用。

List

这里试图展示 Lisp 中的 cons/list 的结构。一般来说这种结构使用所谓的“箱子表示法 ”(box notation),如下图来自《ANSI Common Lisp》。

这里,每一个 node 都有两个“箱子”(之后都这么称呼),dot 中使用 record 类型的 shape 可以让 node 如此展示。其使用方法如下。

1
2
3
4
5
6
digraph g {
node [shape = record]; // 可以使用这种方法设定所有 node 的属性
node0[label = "{\<interface\> \n AInterface| |+ hello():void}"]; // 可以使用一些标识符
node1[label = "<f0> A|<f1> BCD"];
node0 -> node1:f0;
}
g node0 <interface> AInterface + hello():void node1 A BCD node0->node1:f0

上面的代码试图展示一个 UML 类图。需要注意的是 node0 中 label 的内容。其包含三个元符号——{},<>和|。

其中,|可以认为是各个箱子的分隔符;{}则是对特定数量箱子起作用,其将更改箱子排列的方向(这个元符号用以构造结构更为复杂的箱子)。<>用来定义“锚点”,使可以被 edge 所引用。

为实现箱型表示法,还有一点需要实现——默认排列是从上到下的,需要改成从左到右,这是通过 graph 的 rankdir 属性实现的。需要注意的是,rankdir 会改变 record 的默认方向。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
digraph g {
rankdir=LR; // 方向
node [shape = record]; // 默认所有 node 都是 record
edge [tailclip=false]; // edge 不会被 clip——这是为了保证箭头从内部伸出
node0[label = "{a | <A>}"];
node1_0[label = "{<A>|<B>}"];
node1_1[label = "{b|<B>}"];
node2[label = "{c|nil}"];
node3[label = "{d|nil}"];
node0:A:c -> node1_0; // :c 是 node 默认提供的“锚点”,表示从中心开始
node1_0:A:c -> node1_1;
node1_1:A:c -> node2;
node1_0:B:c -> node3;
// {rank=same;node1_0;node1_1;} 这玩意有 bug,对 record 会报错!
}

……看起来要表达规则的图形的时候这玩意并不太适合。

先就这样吧……这玩意出现的 bug 浇灭了我继续的兴趣。反正当前学到的东西足够画 AST 了。