可视化抽象语法树

抽象语法树(AST)在计算机科学领域有广泛的应用。

有时,你无法直接用眼睛观察它,只能想象它的结构。如果能以可视化的方式展现出来就好了。这样不仅便于分析和调试,也能让你对程序更有信心。

上周,我就遇到了这个问题。COSStyleParser 会把 CSS 解析成一个语法树,我想看看它到底长什么样子。虽然可以遍历语法树来打印出每个节点的信息,但要观察其树形结构就比较困难了。

这时,我想到了之前使用过的 GraphViz。GraphViz 是贝尔实验室研发的图可视化软件。它接受 DOT 格式的输入,然后将其渲染成图像。用它来观察语法树的结构再合适不过了。

因此,首先得生成语法树的 DOT 表示。好在 DOT 语言非常简单,对语法树进行一次遍历就能生成。

在 COSStyleParser 中,以下几个函数会把 CSS 语法树打印成 DOT 表示:

void COSStylePrintAstAsDot(COSStyleAST *astp) {
    printf("digraph G {\n");
    printf("node[shape=rect]\n");

    COSStylePrintAstNodes(astp);

    printf("}");
}

void COSStylePrintAstNodes(COSStyleAST *astp) {
    if (astp == NULL) return;

    printf("_%p[label=%s]\n", astp, COSStyleNodeTypeToStr(astp->nodeType));

    COSStyleAST *l = astp->l;
    COSStyleAST *r = astp->r;

    if (l != NULL) printf("_%p -> _%p\n", astp, l);
    if (r != NULL) printf("_%p -> _%p\n", astp, r);

    COSStylePrintAstNodes(l);
    COSStylePrintAstNodes(r);
}

char *COSStyleNodeTypeToStr(COSStyleNodeType nodeType) {
    switch (nodeType) {
    case COSStyleNodeTypeVal      : return "val";
    case COSStyleNodeTypeProp     : return "prop";
    case COSStyleNodeTypeDecl     : return "decl";
    case COSStyleNodeTypeDeclList : return "decllist";
    case COSStyleNodeTypeCls      : return "cls";
    case COSStyleNodeTypeClsList  : return "clslist";
    case COSStyleNodeTypeSel      : return "sel";
    case COSStyleNodeTypeSelList  : return "sellist";
    case COSStyleNodeTypeRule     : return "rule";
    case COSStyleNodeTypeRuleList : return "rulelist";
    case COSStyleNodeTypeSheet    : return "sheet";
    default: break;
    }

    return "undefined";
}

假设解析的 CSS 如下:

.foo.bar, .baz {
    left: 10;
    width: 100% - 20;
    background-color: red;
}

函数 COSStylePrintAstAsDot 的打印结果是:

digraph G {
node[shape=rect]
_0x7fb18277a050[label=sheet]
_0x7fb18277a050 -> _0x7fb18277a020
_0x7fb18277a020[label=rulelist]
_0x7fb18277a020 -> _0x7fb182779ff0
_0x7fb182779ff0[label=rule]
_0x7fb182779ff0 -> _0x7fb182779d30
_0x7fb182779ff0 -> _0x7fb182779fc0
_0x7fb182779d30[label=sellist]
_0x7fb182779d30 -> _0x7fb182779c70
_0x7fb182779d30 -> _0x7fb182779d00
_0x7fb182779c70[label=sellist]
_0x7fb182779c70 -> _0x7fb182779c40
_0x7fb182779c40[label=sel]
_0x7fb182779c40 -> _0x7fb182779c10
_0x7fb182779c10[label=clslist]
_0x7fb182779c10 -> _0x7fb182779bb0
_0x7fb182779c10 -> _0x7fb182779be0
_0x7fb182779bb0[label=clslist]
_0x7fb182779bb0 -> _0x7fb182779b80
_0x7fb182779b80[label=cls]
_0x7fb182779be0[label=cls]
_0x7fb182779d00[label=sel]
_0x7fb182779d00 -> _0x7fb182779cd0
_0x7fb182779cd0[label=clslist]
_0x7fb182779cd0 -> _0x7fb182779ca0
_0x7fb182779ca0[label=cls]
_0x7fb182779fc0[label=decllist]
_0x7fb182779fc0 -> _0x7fb182779f00
_0x7fb182779fc0 -> _0x7fb182779f90
_0x7fb182779f00[label=decllist]
_0x7fb182779f00 -> _0x7fb182779df0
_0x7fb182779f00 -> _0x7fb182779e90
_0x7fb182779df0[label=decllist]
_0x7fb182779df0 -> _0x7fb182779d90
_0x7fb182779d90[label=decl]
_0x7fb182779d90 -> _0x7fb182779d60
_0x7fb182779d90 -> _0x7fb182779dc0
_0x7fb182779d60[label=prop]
_0x7fb182779dc0[label=val]
_0x7fb182779e90[label=decl]
_0x7fb182779e90 -> _0x7fb182779e20
_0x7fb182779e90 -> _0x7fb182779ed0
_0x7fb182779e20[label=prop]
_0x7fb182779ed0[label=val]
_0x7fb182779f90[label=decl]
_0x7fb182779f90 -> _0x7fb182779f30
_0x7fb182779f90 -> _0x7fb182779f60
_0x7fb182779f30[label=prop]
_0x7fb182779f60[label=val]
}

最后,用 GraphViz 渲染以上 DOT 表示,即可得到下面这张图:

CSS-AST