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 程式碼檔案提供 flex 除錯資訊。

請注意,藉由執行帶有 -d lex 的 Doxygen,您可以取得有關使用了哪個 flex 程式碼檔案的資訊。若要查看使用 flex 除錯選項編譯的 flex 解析器的資訊,您必須在執行 Doxygen 時指定 -d lex:<flex 程式碼檔案>

請注意,有關 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 類型替換變數。

返回索引