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 codefileflex 调试信息。

请注意,通过运行带有 -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 配置文件设置不同的配置设置,则可以运行 Doxygen 命令:使用 -x 选项和配置文件的名称(默认为 Doxyfile)。输出将是不默认设置的列表(以 Doxyfile 格式)。或者,也可以使用 -x_noenv,它与 -x 选项相同,但不会替换环境变量和 CMake 类型替换变量。

返回索引