Doxygen 内部结构

Doxygen 的内部结构

请注意,本节仍在建设中!

下图显示了 Doxygen 如何处理源文件。

数据流概述

以下部分将更详细地解释上述步骤。

配置解析器

控制项目设置的配置文件被解析,设置存储在 src/config.h 中的单例类 Config 中。解析器本身是使用 flex 编写的,可以在 src/config.l 中找到。该解析器也直接被 Doxywizard 使用,因此它被放在一个单独的库中。

每个配置选项都有 5 种可能的类型之一:StringListEnumIntBool。这些选项的值可以通过全局函数 Config_getXXX() 获得,其中 XXX 是选项的类型。这些函数的参数是一个字符串,该字符串命名该选项在配置文件中出现时的名称。例如:Config_getBool(GENERATE_TESTLIST) 返回一个布尔值的引用,如果测试列表在配置文件中启用,则该值为 TRUE

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

C 预处理器

配置文件中提到的输入文件(默认情况下)被馈送到 C 预处理器(如果可用,则在通过用户定义的过滤器管道传输之后)。

预处理器的工作方式与标准的 C 预处理器略有不同。默认情况下,它不执行宏展开,尽管可以配置为展开所有宏。典型的用法是只展开用户指定的一组宏。例如,这是为了允许宏名称出现在函数参数的类型中。

另一个区别是预处理器在遇到 #include 时会解析代码,但实际上并不包含代码(在 { ... } 块中找到的 #include 除外)。这种与标准偏差的原因是为了防止将相同函数/类的多个定义馈送到 Doxygen 的解析器。例如,如果所有源文件都包含一个公共头文件,则类和类型定义(及其文档)将存在于每个翻译单元中。

预处理器是使用 flex 编写的,可以在 src/pre.l 中找到。对于条件块(#if),需要评估常量表达式。为此,使用基于 yacc 的解析器,该解析器可以在 src/constexp.ysrc/constexp.l 中找到。

使用 src/pre.h 中声明的 preprocessFile() 函数为每个文件调用预处理器,并将预处理结果附加到字符缓冲区。字符缓冲区的格式是

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

语言解析器

预处理后的输入缓冲区被馈送到语言解析器,该解析器使用 flex 实现为一个大型状态机。它可以在文件 src/scanner.l 中找到。所有语言(C/C++/Java/IDL)都有一个解析器。状态变量 insideIDLinsideJava 在某些地方用于特定于语言的选择。

解析器的任务是将输入缓冲区转换为条目树(基本上是一个抽象语法树)。条目在 src/entry.h 中定义,是一个松散结构的信息 blob。最重要的字段是 section,它指定条目中包含的信息类型。

未来版本的可能改进

  • 为每种语言使用一个扫描器/解析器,而不是一个大型扫描器。
  • 将文档块的第一遍解析移动到单独的模块。
  • 解析定义(这些定义当前由预处理器收集,并被语言解析器忽略)。

数据组织器

此步骤由许多较小的步骤组成,这些步骤构建提取的类、文件、命名空间、变量、函数、包、页面和组的字典。除了构建字典外,在此步骤中,还会计算提取的实体之间的关系(例如继承关系)。

每个步骤在 src/doxygen.cpp 中都有一个定义的函数,该函数在语言解析期间构建的条目树上运行。有关详细信息,请查看 parseInput() 的“收集信息”部分。

此步骤的结果是多个字典,可以在 src/doxygen.h 中定义的 Doxygen “命名空间”中找到。这些字典的大多数元素都派生自 Definition 类;例如,MemberDef 类保存成员的所有信息。此类的实例可以是文件(类 FileDef )、类(类 ClassDef )、命名空间(类 NamespaceDef )、组(类 GroupDef )或 Java 包(类 PackageDef )的一部分。

标签文件解析器

如果在配置文件中指定了标签文件,则这些标签文件将由基于 SAX 的 XML 解析器解析,该解析器可以在 src/tagreader.cpp 中找到。解析标签文件的结果是在条目树中插入 Entry 对象。字段 Entry::tagInfo 用于将条目标记为外部条目,并保存有关标签文件的信息。

文档解析器

特殊的注释块以字符串的形式存储在它们所文档的实体中。有一个用于简要描述的字符串和一个用于详细描述的字符串。文档解析器读取这些字符串并执行在其中找到的命令(这是解析文档的第二遍)。它将结果直接写入输出生成器。

解析器是用 C++ 编写的,可以在 src/docparser.cpp 中找到。解析器所消耗的标记来自 src/doctokenizer.l。在注释块中找到的代码片段会传递给源代码解析器。

文档解析器的主要入口点是 src/docparser.h 中声明的 validatingParseDoc()。对于带有特殊命令的简单文本,使用 validatingParseText()

源代码解析器

如果启用了源代码浏览,或者如果在文档中遇到代码片段,则会调用源代码解析器。

代码解析器尝试将其解析的源代码与文档化的实体进行交叉引用。它还对源代码进行语法高亮显示。输出直接写入输出生成器。

代码解析器的主要入口点是 src/code.h 中声明的 parseCode()

输出生成器

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

为了允许为每个具体的输出生成器写入输出的内容略有偏差,可以暂时禁用某些生成器。OutputList 类为此包含各种 disable()enable() 方法。方法 OutputList::pushGeneratorState()OutputList::popGeneratorState() 用于暂时将启用/禁用的输出生成器集合保存在堆栈上。

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

  • 交互式源代码浏览器
  • 类图生成器
  • 计算代码度量。

调试

由于 Doxygen 使用了大量的 flex 代码,因此了解 flex 的工作原理(为此,应该阅读 man 页面)并了解当 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/doxymain.dir/build.make","src/CMakeFiles/doxymain.dir/build.make.old") {
  print STDERR "Error: cannot rename src/CMakeFiles/doxymain.dir/build.make!\n";
  exit 1;
}
if (open(F,"<src/CMakeFiles/doxymain.dir/build.make.old")) {
  unless (open(G,">src/CMakeFiles/doxymain.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/doxymain.dir/build.make.old";
}
else {
  print STDERR "Warning file src/CMakeFiles/doxymain.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 的调试版本(即使用 CMake 设置 -DCMAKE_BUILD_TYPE=Debug 创建的可执行文件)将自动为所有 flex codefile 提供 flex 调试信息。

请注意,通过使用 -d lex 运行 Doxygen,您可以获得有关使用哪个 flex codefile 的信息。要查看使用 flex 调试选项编译的 flex 解析器的信息,您必须在运行 Doxygen 时指定 -d lex:<flex codefile>

请注意,关于 lex 解析的信息将发送到 stderr,并且其他调试输出默认发送到 stdout,除非使用 -d stderr

测试

Doxygen 有一小组可用于测试的测试,这些测试用于测试一些代码完整性。可以通过命令 make tests 运行测试。当只需要一个或几个测试时,可以在运行测试时设置变量 TEST_FLAGS,例如 make TEST_FLAGS="--id 5" tests 或对于多个测试 make TEST_FLAGS="--id 5 --id 7" tests。对于完整的可能性,请给出命令 make TEST_FLAGS="--help" tests。也可以将 TEST_FLAGS 指定为环境变量(也适用于通过 Visual Studio 项目进行测试),例如 setenv TEST_FLAGS "--id 5 --id 7"make tests

Doxyfile 差异

如果必须通过例如论坛交流与标准 Doxygen 配置文件设置不同的配置设置,则可以使用 -x 选项和配置文件名称(默认为 Doxyfile)运行 Doxygen 命令。输出将是未默认设置的列表(采用 Doxyfile 格式)。或者,也可以使用 -x_noenv,它与 -x 选项相同,但不替换环境变量和 CMake 类型替换变量。

返回 索引