Doxygen latest release v1.8.3.1 - last update Sat Aug 24 2013

Doxygenの内部

Doxygenの内部

このセクションは、まだ製作中です。

次の図では、ソースファイルの処理の流れを示しています。

archoverview.gif
Data flow overview

以下のセクションでは、各ステップを詳しく説明します。

設定の解析

プロジェクト設定を制御する設定ファイルを解析し、その結果を src/config.h に存在する単一のクラス Config に格納します。このパーサーは、 flex で書かれており、src/config.l にあります。このパーサーは、doxywizard も直接使うので、別のライブラリーに置いてあります。

各設定オプションには、次の5つのタイプがあります。String, List, Enum, Int, Bool です。これらオプションの値は、Config_getXXX() というグローバル関数を通して取得できます。ここで XXX は、オプションのタイプを指します。引数は、設定ファイルに現れる、オプション名の文字列です。例えば、Config_getBool("GENERATE_TESTLIST") は、設定ファイルでテストリストがイネーブルにされていれば TRUE となる、ブール値への参照を返します。

src/doxygen.cpp 内の readConfiguration() 関数は、コマンドラインオプションを読み出し、設定パーサーを呼び出します。

C プリプロセッサ

設定ファイルで指定された入力ファイルは、デフォルトで、Cプリプロセッサに任されます(ユーザー定義のフィルタがあれば、まずそれを通します)。

プリプロセッサの処理方法は、標準のCプリプロセッサとは少し違います。 デフォルトでは、マクロ展開をしません。ただし、すべてのマクロを展開するように設定を変えることはできます。典型的な使用法は、ユーザー定義のマクロを展開するだけのことです。この方法は、例えば、関数パラメータのタイプにマクロ名を出現させるなどといったことに使います。

あと一つの違いは、#include を見つけても、プリプロセッサはそれを解析はするものの、実際にはコードをインクルードしないところです(ただし、{ ... } ブロック内の場合は違います)。このように標準と違う理由は、同じ関数やクラスに関する複数の定義を doxygen パーサーに与えるのを避けるためです。たとえば、すべてのソースファイルが共通のヘッダファイルをインクルードするとしたら、そのクラスやタイプ定義(及び文書)が、翻訳単位それぞれに存在することになるからです。

プリプロセッサは flex で書かれており、src/pre.l にあります。#if 条件ブロックの場合、定数式の評価が必要です。そのため、yacc ベースのパーサーを使用します。これは、 src/constexp.ysrc/constexp.l にあります。

プリプロセッサは、ファイルごとに src/pre.h 内の preprocessFile() 関数を使って起動されます。結果は、キャラクターバッファに追加されます。キャラクターバッファのフォーマットを示します。

0x06 ファイル名 1 
0x06 プリプロセスされた、ファイル1の内容
...
0x06 ファイル名 n
0x06 プリプロセスされた、ファイルnの内容

言語パーサー

プリプロセスされた入力バッファは、言語パーサーに与えられます。言語パーサーは、 flex で書かれ、大状態装置として実装されています。言語パーサーは src/scanner.l にあります。C/C++/Java/IDL すべての言語に対して、一つのパーサーが対応します。状態変数 insideIDL, insideJava が、言語特有の処理にあたることがあります。

パーサーの役割は、入力バッファを木要素(基本的に抽象的なシンタックスツリー)に変換することです。要素は、src/entry.h に定義されています。要素は、大雑把に体系化された情報の集まりです。要素の最重要なフィールドは、要素に含まれる情報の種類を特定する section です。

将来の改善に向けて可能なこと

  • 一つの大スキャナでなく、言語ごとにスキャナとパーサーを使う
  • ドキュメントブロックの最初の解析は、別のモジュールに移す
  • defineの解析(現状、defineは、プリプロセッサが集めており、言語パーサーは無視します)

データのまとめ

このステップには、多くの小さなステップがあり、取り出したクラス、ファイル、ネームスペース、変数、関数、パッケージ、ページ、グループの辞書を構築します。辞書を構築するほかに、取り出した要素間の関係(継承などの)を計算します。

各ステップには、src/doxygen.cpp で定義された関数があり、言語パーサーが構築した要素木に作用します。詳細は、 parseInput() の "Gathering information" の箇所を参照してください。

このステップでは、たくさんの辞書を出力します。辞書は、 src/doxygen.h に定義されている Doxygen "namespace" にあります。これら辞書の多くの要素は、 Definition クラスから導き出されます。例えば、 MemberDef クラスには、メンバーの情報がすべて含まれます。同様のクラスのインスタンスには、ファイル(FileDef クラス)、クラス(ClassDef クラス)、ネームスペース(NamespaceDef クラス)、グループ(GroupDef クラス)、Javaパッケージ(PackageDef クラス)があります。

タグファイルパーサー

タグファイルが設定ファイルに指定されていると、SAX ベースの XML パーサーが解析します。このパーサーは、 src/tagreader.cpp にあります。タグファイルを解析すると、要素木へ Entry オブジェクトが挿入されます。 Entry::tagInfo フィールドは、要素を外部としてマークするのに使われ、タグファイルについての情報を持ちます。

ドキュメントパーサー

特殊コメントブロックは、それらで記述されるエンティティーに、文字列として格納されます。文字列には、要約説明と、詳細説明があります。ドキュメントパーサーは、これら文字列を読み込み、中で見つけたコマンドを実行します(これが、ドキュメント解析の第2段階です)。結果は、直接、出力ジェネレーターに書き込まれます。

パーサーは、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";

# add or remove the -d flex flag in the makefile
unless (rename "Makefile.libdoxygen","Makefile.libdoxygen.old") {
  print STDERR "Error: cannot rename Makefile.libdoxygen!\n";
  exit 1;
}
if (open(F,"<Makefile.libdoxygen.old")) {
  unless (open(G,">Makefile.libdoxygen")) {
    print STDERR "Error: opening file Makefile.libdoxygen for writing\n";
    exit 1; 
  }
  print "Processing Makefile.libdoxygen...\n";
  while (<F>) {
    if ( s/\(LEX\) (-i )?-P([a-zA-Z]+)YY -t $file/(LEX) -d \1-P\2YY -t $file/g ) {
      print "Enabling debug info for $file\n";
    }
    elsif ( s/\(LEX\) -d (-i )?-P([a-zA-Z]+)YY -t $file/(LEX) \1-P\2YY -t $file/g ) {
      print "Disabling debug info for $file\n";
    }
    print G "$_";
  }
  close F;
  unlink "Makefile.libdoxygen.old";
}
else {
  print STDERR "Warning file Makefile.libdoxygen.old does not exist!\n"; 
}

# touch the file
$now = time;
utime $now, $now, $file
のセクションに行く / インデックス に戻る

This page was last modified on Sat Aug 24 2013.
© 1997-2014 Dimitri van Heesch, first release 27 oct 1997.
© 2001 OKA Toshiyuki (Japanese translation).
© 2006-2014 TSUJI Takahiro (Japanese translation).
© 2006-2014 TAKAGI Nobuhisa (Japanese translation).