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 中定义,是一个包含松散结构信息的“数据块”。最重要的字段是 section,它指定了条目中包含的信息类型。

未来版本的可能改进

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

数据组织器

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

每个步骤都有一个在 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_FLAGSmake LEX_FLAGS=-d)。

默认情况下,Doxygen 的调试版本(即使用 CMake 设置 -DCMAKE_BUILD_TYPE=Debug 创建的可执行文件)将自动包含所有 flex codefileflex 调试信息。

请注意,运行 Doxygen 时使用 -d lex 会获得有关使用了哪个 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 类型替换变量。

返回索引