Doxygen内部结构

下图展示了Doxygen处理源文件的流程。

Doxygen内部结构

Config parser(配置解析器)

工程的参数配置文件被Doxygen解析,并将参数存储到一个单独的类Config(src/config.h)文件中。解析器是采用的FLEX框架编写的,源码在src/config.l目录下。因为解析器也被doxywizard(前端向导)直接调用,所以把解析器做成一个单独的库。

每一个配置选项都是五种类型中的一种:String,List,Enum,Int,Bool。这些配置选项的值都可以使用全局函数Config_getXXX()获取到(XXX指选项)。这些函数的参数,是以配置文件中选项的名称命名的字符串。例如:Config_getBool("GENERATE_TESTLIST"),如果test列表启用,返回布尔类型TRUE。

src/doxygen.cpp中的函数readConfiguration()读取了命令行参数,之后调用配置解析器。

 

C Preprocessor

配置文件里配置的输入文件(默认情况下)被送到C语言预处理器(通过用户定义的筛选器传输)。

预处理器的工作方式不同于标准的C预处理器。默认情况下,它不展开宏,虽然它可以被配置为展开所有宏。典型的用法是只扩展用户指定的宏集合。这是为了允许宏名称出现在函数参数的类型中。

另一个区别是,预处理器分析,但实际上不包含#include的代码(除了{...}里的#include代码)。这么做的原因是为了避免让doxygen的解析器去处理函数/类的重定义的情况。例如当所有源文件都包含一个公共头文件,类和类型定义(以及注释)将存于在每个翻译单元中。

解析器是基于FLEX框架,源码在src/pre.l。条件控制块(#if)需要使用常量表达式。因而使用了基于yacc的解析器,源码在src/constexp.y和src/constexp.l。

C 预处理器会为每个文件调用preprocessFile()函数,并且把处理结果追加到字符缓冲区,缓冲区的格式是

0x06 file name 1
0x06 preprocessed contents of file 1
...
0x06 file name n

0x06 preprocessed contents of file n

Language parser(语言解析器)

预处理器的缓冲区被送到语言解析器,后者使用flex(一个词法分析器)实现了一个大型状态机。源码在src/scanner。一种解析器解析所有语言(C/C++/Java/IDL)。状态变量insideIDL和insideJava在某些地方用用于选择特定语言。

解析器的工作是把输入缓冲区转换成条目树(抽象的语法树),在src/entry定义了条目的内容,是一种松散的结构信息。最重要的字段section描述了条目里包含的信息。

为了在以下几个方面可能会改善:
    每种语言各使用一种解析器,而不是一个大型扫描器。
    将文档的第一道解析移动到一个独立的模块。
    解析预定义define(目前预处理器只是把他们集合起来,语言解析器会把他们忽略掉)。


    
Data organizer(数据组织器)

这一步包含了很多小步骤,为提取的类、文件、命名空间、变量、函数、包、页面、组创建文件目录,除此之外,在这一步过程中条目之间关系会被计算出来。

在src/doxygen.cpp里有每个一个步骤的函数,这些函数操作条目数,在语言解析期间构建目录。详情需查看“Gathering information”一节。

这一步的结果是许多目录,目录结构在src/doxygen.h的doxygen的“命名空间”中。目录的大部分属性是从类Definition继承而来的。MemberDef类保存了一个成员的全部信息,FileDef类保存了文件的,ClassDef类保存了类,NamespaceDef保存了命名空间,GroupDef保存了组,PackageDef类保存了Java包信息。

Tag file parser(标签文件解析器)
如果在配置文件中指明标签文件,那么将使用基于SAX的XML解析器来解析它们,源码在src/tagreader.cpp中。解析标签文件后,会像条目树中插入Entry对象。字段Entry::tagInfo用来标记条目是外部的,并保存了标签文件里的信息。

Documentation parser(注释解析器)
特定的注释块会以字符串形式存储在条目中。简述信息和详细信息都有各自的字符串。文档解析器读取字符串并且执行字符串里的命令(这是注释解析的第二部分)。它会把结果写给输出生成器。

解析器是C++编写,源码在src/docparser.cpp中。解析器处理的符号在src/doctokenizer.l中定义。注释块里的代码段会被传递给源代码解析器。

文档解析器的入口函数是src/docparser.h里的validatingParseDoc()。带有特殊命令的简单文本会使用validatingParseText()函数。

Source parser(源码解析器)

如果浏览源代码被启用,或者注释汇中出现代码段,源码解析器会被调用。

代码解析试图对文档化的条目生成交叉引用,同样会为源代码做语法高亮,输出直接写给了输出生成器。

代码解析入口程序parseCode()在src/code.h声明。

Output generators(输出生成器)

在数据被收集和交叉引用之后,Doxygen产生各种格式的输出。为此,输出生成器使用抽象类OutputGenerator提供的方法。为了能生成多种格式的输出,OutputList会被代替。该类维护一个具体的输出生成器列表,其中调用的每个方法都被委派给列表中的所有生成器。

为了让每个特定的输出生成器允许在输出中写入小偏差,可以暂时禁用某些生成器。输出列表类包含了大量disable()和enable()方法。OutputList::pushGeneratorState() 和OutputList::popGeneratorState()用于临时保存堆栈上的启用/禁用输出生成器集。

XML是直接从数据结构中生成的。未来,XML将会被用做中间语言(IL),然后,输出生成器将使用这个IL作为起点来生成特定的输出格式。拥有IL的优点是,各种独立开发的工具以各种语言编写,可以从XML输出中提取信息。

独立工具可能是:
    交互式源码浏览器
    类图生成工具
    计算代码度量

Debugging 调试
    由于doxygen使用了大量的flex代码,所以我们需要了解flex的运行规则以及flex如何处理输入。幸运的是,当Flex与-d选项一起使用时,它输出匹配的规则。这使得很容易跟踪特定输入片段的内容。为了更容易切换给定的Flex文件的调试信息,我编写了以下Perl脚本,它会自动在makefile文件中添加或移除-d。

#!/usr/bin/perl
$file = shift @ARGV;
print "Toggle debugging mode for $file\n";
if (!-e "../src/${file}.l")
{
print STDERR "Error: file ../src/${file}.l does not exist!\n";
exit 1;
}
system("touch ../src/${file}.l");
unless (rename "src/CMakeFiles/_doxygen.dir/build.make","src/CMakeFiles/_doxygen.dir/build.make.old") {
print STDERR "Error: cannot rename src/CMakeFiles/_doxygen.dir/build.make!\n";
exit 1;
}
if (open(F,"<src/CMakeFiles/_doxygen.dir/build.make.old")) {
unless (open(G,">src/CMakeFiles/_doxygen.dir/build.make")) {
print STDERR "Error: opening file build.make for writing\n";
exit 1;
}
print "Processing build.make...\n";
while (<F>) {
if ( s/flex \$\(LEX_FLAGS\) -d(.*) ${file}.l/flex \$(LEX_FLAGS)$1 ${file}.l/ ) {
print "Disabling debug info for $file\n";
}
elsif ( s/flex \$\(LEX_FLAGS\)(.*) ${file}.l$/flex \$(LEX_FLAGS) -d$1 ${file}.l/ ) {
print "Enabling debug info for $file.l\n";
}
print G "$_";
}
close F;
unlink "src/CMakeFiles/_doxygen.dir/build.make.old";
}
else {
print STDERR "Warning file src/CMakeFiles/_doxygen.dir/build.make does not exist!\n";
}
# touch the file
$now = time;
utime $now, $now, $file;

另一种从flex代码中获取规则匹配/调试信息的方法是,使用make 设置LEX_FLAGS。(make LEX_FLAGS=-d)。

运行doxygen -d lex 你会得知哪里使用了 flex codefile。