プログラミング/boost/wave のバックアップソース(No.3)

C/C++プリプロセッサライブラリ boost::wave の簡単な解説ページです。~
boost version 1.47.0 を基にして記述されています。

#contents

*ライブラリ概要 [#abstract]

boost::wave は、構文解析や字句解析を行うライブラリである boost::spirit を用いて実装されたC/C++プリプロセッサライブラリです。

このライブラリは、C/C++ソースコード中の @code{#include}; や @code{#define}; といったディレクティブを展開することができます。~
展開した結果のソースコードはトークン単位のイテレータによって取得することができます。~
また、展開処理をフック(コールバック)によって制御することも可能です。

このライブラリは次のような事例に用いることができます。

-ソースコード文字列のトークン単位での取得。
-あるソースコードからインクルードされているファイルのリストアップ。

当文書では主要なクラスのメンバ関数に重点を置いて解説します。~
それ以外の内容については本家マニュアルを参照してください。

-[[Wave V2.0 - boost.org>http://www.boost.org/doc/libs/1_47_0/libs/wave/index.html]]

なお、当文書内では名前空間 @code{boost::wave}; を @code{wave}; と略して記述しています。

*基本的な使い方 [#basic]

boost::wave の Hello, World 的なコードは次のようになります。~
なお、このコードは後述の[[サンプル>#sample]]にも入っています。

#code(c){{
#include <iostream>
#include <fstream>
#include <string>

// boost::wave 関連ヘッダをインクルード
#include <boost/wave.hpp>
#include <boost/wave/preprocessing_hooks.hpp>
#include <boost/wave/cpplexer/cpp_lex_token.hpp>
#include <boost/wave/cpplexer/cpp_lex_iterator.hpp>

// 名前空間のエイリアス名定義
namespace wave = boost::wave;

//! メインエントリポイント
int main(int argc, char* argv[])
{
    using namespace std;

    if (argc < 2) { return 1; }

    // ソースファイルを読み込む
    std::string code;
    {
        std::ifstream fs(argv[1]);
        fs.unsetf(std::ios::skipws);
        code.assign(
            std::istreambuf_iterator<char>(fs.rdbuf()),
            std::istreambuf_iterator<char>());
    }

    // コンテキスト用意
    typedef
        wave::context<
            std::string::const_iterator,
            wave::cpplexer::lex_iterator< wave::cpplexer::lex_token<> >,
            wave::iteration_context_policies::load_file_to_string,
            wave::context_policies::default_preprocessing_hooks>
        Context;
    Context ctx(code.begin(), code.end(), argv[1]);

    // サポートオプション設定
    ctx.set_language(
        wave::language_support(
            wave::support_cpp               |   // C++として処理
            wave::support_option_long_long  |   // long long 型サポート
            wave::support_option_variadics));   // 可変長引数マクロサポート

    // コンパイルオプション設定
    ctx.add_macro_definition("_WIN32");
    ctx.add_macro_definition("_MSC_VER=1500");
    ctx.add_sysinclude_path(
        "C:/Program Files/Microsoft Visual Studio 9.0/VC/include");

    try
    {
        // ソースコード解析しつつ結果のトークンを出力
        Context::iterator_type itrEnd = ctx.end();
        for (Context::iterator_type itr = ctx.begin(); itr != itrEnd; ++itr)
        {
            cout << (*itr).get_value();
        }
    }
    catch (const wave::cpp_exception& ex)
    {
        // 例外処理
        cerr << ex.file_name() << " : " << ex.line_no() << endl;
        cerr << "  -> " << ex.description() << endl;
        return 1;
    }

    return 0;
}
}}

このソースをコンパイルしてできたプログラムの第一引数に、次のコードが書かれたファイルを渡してみます。

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
#define RET_CODE 0

// サンプル
int main(int, char**)
{
    return RET_CODE;
}
}}

すると次のように出力されます(前後の改行は省略しています)。~
ご覧の通り、 @code{RET_CODE}; マクロが展開されていることがわかると思います。

#pre{{
int main(int, char**)
{
    return 0;
}
}}

このように、 boost::wave を用いたソースコードの解析は基本的に次の手順で行います。

+ソースコード文字列を用意する。
+ソースコード文字列とその基となったファイルパスを渡して[[コンテキストクラス>#context]] @code{wave::context}; のインスタンスを作成する。
+コンテキストクラスのインスタンスに各種オプションを設定する。
+コンテキストクラスのインスタンスから取得したイテレータをインクリメントすることで、コードをトークン単位で解析する。

*コンテキストクラス [#context]

コンテキストクラスである @code{wave::context}; について解説します。

**クラステンプレート引数 [#context-tparam]

@code{wave::context}; は次のようにテンプレートで定義されており、5つのテンプレート引数を持ちます。

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
namespace boost {
namespace wave {

template <
    typename IteratorT,
    typename LexIteratorT, 
    typename InputPolicyT = iteration_context_policies::load_file_to_string,
    typename HooksT = context_policies::eat_whitespace<typename LexIteratorT::token_type>,
    typename DerivedT = this_type
>
class context : private boost::noncopyable
{
    // …略…
};

}   // namespace wave
}   // namespace boost
}}

各テンプレート引数の定義は次の通りです。

|~引数名|~定義|h
|LEFT:||c
|~@var{IteratorT};|このクラスに渡すソースコード文字列のイテレータ型。&br;通常は @code{std::string::const_iterator}; を用います。|
|~@var{LexIteratorT};|字句解析してトークンを取り出すイテレータクラス型。&br;通常は @code{wave::cpplexer::lex_iterator< wave::cpplexer::lex_token<> >}; を用います。|
|~@var{InputPolicyT};|@code{#include}; によって別のファイルを読み取る際に用いられるクラス型。&br;普通にファイルを読むならば既定の @code{wave::iteration_context_policies::load_file_to_string}; を用います。|
|~@var{HooksT};|種々の解析の際に呼び出されるフック関数群を定義したクラス型。&br;パブリックなコピーコンストラクタを持つクラスである必要があります((特にメンバを持たないクラスであるならば既定のコピーコンストラクタで問題ありません。))。&br;既定の @code{wave::context_policies::eat_whitespace<...>}; を用いると余分な空白文字が除去されます。&br;特に何もさせたくない場合は @code{wave::context_policies::default_preprocessing_hooks}; を用います。|
|~@var{DerivedT};|このクラスを継承したクラスを作成する場合に、継承先のクラス型を指定します(CRTP)。&br;継承せずに用いるならば既定の @code{wave::this_type}; を用います。|

**コンストラクタ [#context-ctor]

@code{wave::context}; のコンストラクタは次のように定義されています。

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
context(
    target_iterator_type const &first_,
    target_iterator_type const &last_,
    char const *fname_ = "<Unknown>",
    HooksT const &hooks_ = HooksT());
}}

各引数の定義は次の通りです。

|~引数名|~定義|h
|LEFT:||c
|~@var{first_};|解析するソースコード文字列イテレータの開始位置と終端位置。&br;ちなみにこれらの値はイテレータ取得時に渡すこともできます([[後述>#context-member-begin-end]])。&br;その場合、コンストラクタには無意味なイテレータ((例えば空文字列のイテレータなど。))を渡しても問題ありません。|
|~@var{last_};|~|
|~@var{fname_};|解析対象のファイルパス文字列。&br;@code{__FILE__}; マクロの展開値や解析例外発生時の位置情報に用いられます。|
|~@var{hooks_};|フッククラスのインスタンス。&br;フッククラスがパブリックなデフォルトコンストラクタを持つならば省略可能です。&br;この引数はコピーされる為、コンストラクタへ渡した後に内容を変更してもそれは反映されません。|

**メンバ関数 [#context-member]

主要なメンバ関数のみ解説します。~
完全な一覧は本家マニュアルの [[The Context Object>BoostWaveDoc:class_reference_context]] 等を参照してください。

***解析用イテレータ取得 [#context-member-begin-end]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
iterator_type begin();
}}
#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
iterator_type begin(
    target_iterator_type const &first_,
    target_iterator_type const &last_);
}}
#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
iterator_type end() const;
}}

@code{begin}; でソースコードのトークン単位解析用イテレータを、 @code{end}; でその終端を表すイテレータを取得します。

@code{begin}; には2引数を受け取るオーバロードがあり、こちらを用いる場合は解析するソースコード文字列イテレータの開始位置と終端位置を渡します。~
これらは[[コンストラクタ>#context-ctor]]の第1引数および第2引数に渡す値と同様であり、このメンバ関数オーバロードを用いるならばコンストラクタ側には無意味な値を渡しても問題ありません。

戻り値の @code{iterator_type}; は[[クラステンプレート引数>#context-tparam]] @var{LexIteratorT}; に渡した型となり、トークン情報を保持しています。~
トークン情報について詳しくは後述の[[トークン情報クラス>#token]]を参照してください。

***インクルードパス追加 [#cpntext-member-include-path]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
bool add_include_path(char const *path_);
}}
#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
bool add_sysinclude_path(char const *path_);
}}
#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
void set_sysinclude_delimiter();
}}
#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
void set_current_directory(char const *path_);
}}

@code{#include}; のパス解決に用いるインクルードパスを追加します。

@code{add_include_path}; で独自パスを、 @code{set_sysinclude_delimiter}; でシステム標準パスを追加でき、追加した順に検索されます。~
@code{set_sysinclude_delimiter}; を呼び出した場合、以降 @code{add_include_path}; は @code{add_sysinclude_path}; と同等の処理を行うようになります。

@code{set_current_directory}; で、相対パスを追加する場合に適用されるカレントディレクトリを設定できます。~
カレントディレクトリはパスの追加時に付与されるため、パスを追加した後に @code{set_current_directory}; を呼び出しても意味はありません。

実際のパスの検索方法は @code{#include <...>}; の場合と @code{#include "..."}; の場合とで異なり、次のようになります。

:@code{#include <...>}; の場合|
@code{add_sysinclude_path}; で追加したパスを検索する。
:@code{#include "..."}; の場合|
まずインクルード元ファイルと同じ位置を検索し、見つからなければ @code{add_include_path}; で追加したパスを検索する。

ただし、 @code{set_sysinclude_delimiter}; を呼び出していた場合は検索方法が変わり、次のようになります。

:@code{#include <...>}; の場合|
@code{add_sysinclude_path}; で追加したパス、および @code{set_sysinclude_delimiter}; 呼び出し後に @code{add_include_path}; で追加したパスを検索する。
:@code{#include "..."}; の場合|
@code{set_sysinclude_delimiter}; 呼び出し前に @code{add_include_path}; で追加したパスを検索する。~
インクルード元ファイルと同じ位置は検索されなくなる。

***マクロ定義追加 [#cpntext-member-macro-definition]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<typename StringT>
bool add_macro_definition(
    StringT macrostring,
    bool is_predefined = false);
}}

マクロ定義を追加します。

引数 @var{macrostring}; には単純なキーワード文字列もしくは @code{"="}; 区切りで値を指定した文字列を渡します。~
例えば @code{"_WIN32"}; を渡すと @code{#define _WIN32}; と同等になり、 @code{"_MSC_VER=1500"}; を渡すと @code{#define _MSC_VER 1500}; と同等になります。

引数 @var{is_predefined}; を @code{true}; にすると、 @code{__FILE__}; や @code{__LINE__}; といった組み込みマクロ定義と同等の定義が行えます。~
C++標準のマクロ定義を置き換えることもできます。

***言語サポートオプション設定 [#context-member-language]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
void set_language(
    wave::language_support language_,
    bool reset_macros = true);
}}
#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
wave::language_support get_language() const;
}}

解析処理時に考慮する言語サポートオプションの設定および取得を行います。

@code{set_language}; の引数 @var{language_}; には @code{wave::language_support}; 列挙値の論理和で設定します。~
引数 @var{reset_macros}; が既定値 @code{true}; である場合、同時にそれまでに追加した[[マクロ定義>#cpntext-member-macro-definition]]のクリアが行われます。

主要な @code{wave::language_support}; 列挙値とその定義は次の通りです。

|~列挙値|~定義|h
|LEFT:||c
|~@code{support_normal};|C++98の言語仕様をサポートします。|
|~@code{support_cpp};|~|
|~@code{support_option_long_long};|@code{long long}; 型をサポートします。|
|~@code{support_option_variadics};|可変長引数マクロをサポートします。|
|~@code{support_c99};|C99の言語仕様をサポートします。&br;@code{support_option_long_long}; および @code{support_option_variadics}; が含まれます。|
|~@code{support_cpp0x};|C++0x(C++11の原形)の言語仕様をサポートします。&br;@code{support_option_long_long}; および @code{support_option_variadics}; が含まれます。|
|~@code{support_option_insert_whitespace};|連続する2トークンを連結するとC++キーワード等になる場合、間に空白文字トークンを挿入します。|
|~@code{support_option_preserve_comments};|コメント文を除去しないようにします。|
|~@code{support_option_convert_trigraphs};|Trigraphを有効にします。&br;詳しくは [[Trigraph - みねこあ>http://d.hatena.ne.jp/minekoa/20071130/1196436534]] 等を参照してください。|
|~@code{support_option_emit_line_directives};|出力されるトークン列に @code{#line}; を挿入し、元の行番号を保持します。|
|~@code{support_option_include_guard_detection};|インクルードガードを検出し、検出されたファイルを2回以上開かないようにします。|
|~@code{support_option_emit_pragma_directives};|不明な @code{#pragma}; をそのままトークン列として出力するようにします。|

***フックインスタンス参照取得 [#context-member-hooks]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
hook_policy_type &get_hooks();
}}
#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
hook_policy_type const &get_hooks() const;
}}

フックインスタンスの参照を取得します。~
コンストラクタ呼び出し後にフックインスタンスの内容を取得したり変更したりする場合、この関数で取得した参照に対して操作を行います。

*トークン情報クラス [#token]

**クラス概要 [#token-about]

コンテキストクラスの[[クラステンプレート引数>#context-tparam]] @var{LexIteratorT}; に指定したイテレータクラスのインスタンスからトークン情報を取得することができます。~
通常の @code{wave::cpplexer::lex_iterator< wave::cpplexer::lex_token<> >}; を用いた場合、トークン情報の型は @code{wave::cpplexer::lex_token<>}; となります。

トークン情報は[[解析用イテレータ>#context-member-begin-end]]から取得できる他、後述する[[フッククラス>#hooks]]の多くのメンバ関数に @var{TokenT}; 型の引数として渡されます。

**メンバ関数 [#token-member]

通常用いられるトークン情報クラスである @code{wave::cpplexer::lex_token<>}; の主要なメンバ関数を解説します。

***トークンID型変換 [#token-member-op-token-id]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
operator token_id() const;
}}

トークンIDを表す @code{wave::token_id}; 列挙型への型変換が行えます。

トークンIDはそのトークンがどのようなC++言語要素であるかを表し、本家マニュアルの [[The Token Identifiers>BoostWaveDoc:token_ids]] の項にある @code{T_}; で始まる値のいずれかになります。~
ただし、いくつかマニュアルに記載されていないトークンIDや解説の間違っているトークンIDがあります。~
私が気付いたものを次の表にまとめています。

|~内容|~トークンID|~詳細|h
|LEFT:|LEFT:||c
|~記載漏れ|~@code{T_EOI};|End Of Input、即ち全入力の終端(=ソースコード文字列イテレータの終端)を表します。&br;このトークンIDは[[解析用イテレータ>#context-member-begin-end]]からは取得できません。&br;フッククラスの[[トークン生成時>#hooks-member-generated-token]]において @code{wave::T_EOF}; に続いて渡されます。|
|~記載ミス|~@code{T_PP_INCLUDE};|@q(The Token Identifiers - Wave V2.0){@code(#include "...");}; と記載されていますが、 @code{#include ...}; の誤りです。&br;強制インクルード設定により生成されるトークンです。|
|~|~@code{T_PP_QHEADER};|@q(The Token Identifiers - Wave V2.0){@code(#include <...>);}; と記載されていますが、 @code{#include "..."}; の誤りです。|
|~|~@code{T_PP_HHEADER};|@q(The Token Identifiers - Wave V2.0){@code(#include ...);}; と記載されていますが、 @code{#include <...>}; の誤りです。|

***トークン文字列値取得 [#token-member-value]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
string_type const &get_value() const;
}}

トークン文字列値を取得します。

戻り値の @code{string_type}; は @code{BOOST_WAVE_STRINGTYPE}; マクロで定義されており、コンパイラによって @code{std::string}; または @code{boost::wave::util::flex_string<...>}; となります。~
いずれの型であっても出力ストリームにはそのまま渡すことができます。~
戻り値を @code{std::string}; で受け取りたい場合、 @code{boost::wave::util::flex_string<...>}; をそのまま代入することはできないため、いずれの型であっても定義されているメンバ関数 @code{c_str}; を用いて次のように代入します。

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
// トークン情報 token からトークン文字列値を std::string に取得
std::string value = token.get_value().c_str();
}}

***トークン位置情報取得 [#token-member-position]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
position_type const &get_position() const;
}}

トークンのファイル内における位置情報を取得します。

戻り値の @code{position_type}; はデフォルトでは @code{wave::util::file_position_type}; となり、行番号を取得する @code{get_line}; や行内位置を取得する @code{get_column}; などのメンバ関数が使えます。~
詳しくは本家マニュアルの [[The File Position>BoostWaveDoc:class_reference_filepos]] の項を参照してください。

***EOI(End Of Input; 入力の終端)判定 [#token-member-eoi]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
bool is_eoi() const;
}}

トークンが全入力の終端(=ソースコード文字列イテレータの終端)を表すものであるか否かを取得します。~
トークンIDが @code{wave::T_EOI}; であるか否か調べることと同じです。

***トークン有効判定 [#token-member-valid]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
bool is_valid() const;
}}

トークンが有効な値を保持しているか否かを取得します。

この関数が @code{false}; を返す場合、 @code{get_value}; や @code{get_position}; の呼び出しを行ってはいけません。~
呼び出した場合、NULL参照になる可能性があります。

*フッククラス [#hooks]

**クラス概要 [#hooks-abstract]

コンテキストクラスの[[クラステンプレート引数>#context-tparam]] @var{HooksT}; に独自のフッククラス型を指定し、[[コンストラクタ>#context-ctor]]の第4引数にそのクラスのインスタンスを渡す((デフォルトコンストラクタを用いるならば引数指定は省略可能です。))ことで、ソースコード解析中に次のようなタイミングで処理内容を捕捉することができます。

-各種ディレクティブ(@code{#include}; 、 @code{#define}; 等)発見時。
-@code{#include}; で指定されたファイルの解析開始時および完了時。
-マクロ展開時。
-トークン生成時。
-解析例外発生時。

また、捕捉種別によっては処理内容の操作を行うことも可能です(@code{#include}; を無視する等)。

独自のフッククラス型を定義する場合、通常は @code{wave::context_policies::default_preprocessing_hooks}; を継承して作成します。~
@code{wave::context_policies::default_preprocessing_hooks}; は特別なことを行わないフッククラスであり、このクラスを継承することで不要な捕捉処理を記述する必要が無くなります。

あらゆるディレクティブを処理しないようにするフッククラスのコーディング例は次のようになります。~
なお、このコードは後述の[[サンプル>#sample]]にも入っています。

#code(c){{
#pragma once

// boost::wave 関連ヘッダをインクルード
#include <boost/wave.hpp>
#include <boost/wave/preprocessing_hooks.hpp>

/// すべてのディレクティブを処理しないようにするフッククラス
class IgnoreDirectiveHook
    :
    public boost::wave::context_policies::default_preprocessing_hooks
{
public:
    /// ディレクティブ発見時の捕捉関数
    template<typename ContextT, typename TokenT>
    bool found_directive(ContextT const&, TokenT const&)
    {
        // true を返すと処理がスキップされる
        return true;
    }
};
}}

**メンバ関数テンプレート引数 [#hooks-tparam]

後述するフッククラスの[[メンバ関数>#hooks-member]]はテンプレートで定義されており、いくつかのテンプレート引数型を関数の引数として受け取ります。~
主要なテンプレート引数の定義は次の通りです。

|~引数名|~定義|h
|LEFT:||c
|~@var{ContextT};|[[コンテキストクラス>#context]]型。|
|~@var{TokenT};|[[トークン情報クラス>#token]]型。&br;通常は @code{wave::cpplexer::lex_token<>}; です。|
|~@var{ContainerT};|トークン列を表す、[[トークン情報クラス>#token]]のリストを保持するSTLコンテナ型。&br;@code{std::list<TokenT>}; 相当の型になります。|

**メンバ関数 [#hooks-member]

フック処理で呼び出されるメンバ関数の一覧です。~
すべてのメンバ関数の第1引数には処理対象である[[コンテキストクラス>#context]] @var{ContextT}; のインスタンス参照が渡されます。

***各種ディレクティブ発見時 [#hooks-member-found-directive]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<
    typename ContextT,
    typename TokenT
>
bool found_directive(
    ContextT const& ctx,
    TokenT const& directive);
}}
#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<typename ContextT>
bool found_include_directive(
    ContextT const& ctx,
    std::string const& filename, 
    bool include_next);
}}
#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<
    typename ContextT,
    typename ContainerT
>
bool found_warning_directive(
    ContextT const& ctx,
    ContainerT const& message);
}}
#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<
    typename ContextT,
    typename ContainerT
>
bool found_error_directive(
    ContextT const& ctx,
    ContainerT const& message);
}}
#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<
    typename ContextT,
    typename ContainerT
>
void found_line_directive(
    ContextT const& ctx,
    ContainerT const& arguments,
    unsigned int line,
    std::string const& filename);
}}

@code{found_directive}; は、あらゆるディレクティブの発見時に最初に呼び出されます。~
引数 @var{directive}; に発見したディレクティブの[[トークン情報>#token]]が渡されます。

@code{found_include_directive}; は @code{#include}; の発見時に呼び出されます。~
引数 @var{filename}; に @code{#include}; 直後のファイル名指定が @code{"foo.h"}; や @code{<cstdio>}; といった形の文字列で渡されます。~
引数 @var{include_next}; は、 @code{#include}; ではなく boost::wave 独自ディレクティブ @code{#include_next}; が使われた場合に @code{true}; となります。

@code{found_warning_directive}; は @code{#warning}; の発見時、 @code{found_error_directive}; は @code{#error}; の発見時に呼び出されます(@code{#warning}; は非標準のディレクティブであり、 Visual C++ 等では使えません。)。~
引数 @var{message}; に警告/エラーメッセージ指定のトークン列が渡されます。

@code{found_line_directive}; は @code{#line}; の発見時に呼び出されます。~
引数 @var{arguments}; に行番号指定およびファイル名指定(省略可能)のトークン列が渡されます。~
引数 @var{line}; および @var{filename}; には @var{arguments}; を解釈した結果の値が渡されます。~
ファイル名指定を省略した場合、 @var{filename}; には空の文字列が渡されます。

@code{found_line_directive}; 以外は、 @code{false}; を返すと発見したディレクティブの解析処理が継続されます。~
@code{true}; を返すと、発見したディレクティブの解析処理は中止され、ディレクティブが記述されていなかったものとして扱われます(即ち空行扱いになります)。~
@code{wave::context_policies::default_preprocessing_hooks}; の既定の動作ではいずれの関数も @code{false}; を返します。

なお、 @code{#warning}; および @code{#error}; の解析処理を継続させると、続けて[[例外送出処理>#hooks-member-throw-exception]]が呼び出されます。

***条件分岐ディレクティブ判定時 [#hooks-member-evaluated-conditional]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<
    typename ContextT,
    typename TokenT,
    typename ContainerT
>
bool evaluated_conditional_expression(
    ContextT const& ctx, 
    TokenT const& directive,
    ContainerT const& expression, 
    bool expression_value);
}}

次のいずれかの条件分岐ディレクティブが判定された時に呼び出されます。

-@code{#if};
-@code{#ifdef};
-@code{#ifndef};
-@code{#elif};

引数 @var{directive}; には上記のいずれかのディレクティブの[[トークン情報>#token]]が渡されます。~
引数 @var{expression}; にはディレクティブ直後の条件式のトークン列が渡されます。~
引数 @var{expression_value}; には条件式の判定結果を表す真偽値が渡されます。

この関数が @code{false}; を返すと判定結果に基づいて以降のコードを展開もしくはスキップします。~
@code{true}; を返すと条件式の判定が再度行われ、再びこの関数が呼び出されます。~
@code{wave::context_policies::default_preprocessing_hooks}; の既定の動作では @code{false}; を返します。

***トークンスキップ時 [#hooks-member-skipped-token]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<
    typename ContextT,
    typename TokenT
>
void skipped_token(
    ContextT const& ctx,
    TokenT const& token);
}}

@code{#if}; や @code{#else}; などの条件分岐ディレクティブによってトークンがスキップされた時に呼び出されます。~
引数 @code{token}; にスキップされた[[トークン情報>#token]]が渡されます。

***トークン生成時 [#hooks-member-generated-token]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<
    typename ContextT,
    typename TokenT
>
TokenT const& generated_token(
    ContextT const& ctx,
    TokenT const& token);
}}

[[解析用イテレータ>#context-member-begin-end]]の取得やインクリメントによって新しいトークン情報が生成される際に呼び出されます。~
引数 @var{token}; に生成された[[トークン情報>#token]]が渡されます。

戻り値には実際にイテレータから取得されるトークン情報の参照を返します。~
@code{wave::context_policies::default_preprocessing_hooks}; の既定の動作では @var{token}; をそのまま返します。

なお、この関数は解析用イテレータが終端に到達した際にも呼び出されます。~
その際の引数 @var{token}; には、 @code{(token.[[is_eoi>#token-member-eoi]]() == true)}; かつ @code{(token.[[is_valid>#token-member-valid]]() == false)}; であるトークン情報(EOIトークン)が渡されます。~
このとき @var{token.[[get_value>#token-member-value]]()}; や @code{token.[[get_position>#token-member-position]]()}; を呼び出そうとするとNULL参照でプログラムが強制終了してしまうため、必ず @code{token.is_valid()}; で有効なトークン情報であるか否か確かめてからそれらのメンバ関数を呼び出すようにしてください。

***マクロ定義時 [#hooks-member-defined-macro]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<
    typename ContextT,
    typename TokenT,
    typename ParametersT, 
    typename DefinitionT
>
void defined_macro(
    ContextT const& ctx,
    TokenT const& macro_name, 
    bool is_functionlike,
    ParametersT const& parameters, 
    DefinitionT const& definition,
    bool is_predefined);
}}

@code{#define}; によってマクロが定義された際に呼び出されます。~
@code{#define XYZ 100}; のような文字列置換マクロと、 @code{#define MUL(x,y) &#40;(x)*(y))}; のような引数リスト付きマクロの両方で呼び出されます。

引数 @var{macro_name}; には定義されたマクロ名の[[トークン情報>#token]]が渡されます。~
引数リスト付きマクロの場合、例えば @code{MUL(x,y)}; ならば @code{MUL}; のトークン情報が渡されます。

引数 @var{is_functionlike}; には、文字列置換マクロであれば @code{false}; 、引数リスト付きマクロであれば @code{true}; が渡されます。

引数 @var{parameters}; には、引数リスト付きマクロの場合に各引数のトークン情報が配列で渡されます。~
文字列置換マクロの場合は空の配列が渡されます。~
引数の型 @var{ParametersT}; は @code{std::vector<TokenT>}; 相当の型です。

引数 @var{definition}; には置換内容のトークン列が渡されます。~
@code{#define XYZ 100}; ならば @code{100}; 、 @code{#define MUL(x,y) &#40;(x)*(y))}; ならば @code{&#40;(x)*(y))}; の部分になります。~
引数の型 @var{DefinitionT}; は @code{std::list<TokenT>}; 相当の型です。

引数 @var{is_predefined}; には、マクロが @code{__FILE__}; や @code{__LINE__}; といった組み込みマクロ定義であれば @code{true}; が渡されます。

***マクロ削除時 [#hooks-member-undefined-macro]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<
    typename ContextT,
    typename TokenT
>
void undefined_macro(
    ContextT const& ctx,
    TokenT const& macro_name);
}}

@code{#undef}; によってマクロが削除された際に呼び出されます。~
引数 @var{macro_name}; に削除されたマクロ名の[[トークン情報>#token]]が渡されます。

***マクロ展開開始時 [#hooks-member-expanding-macro]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<
    typename ContextT,
    typename TokenT,
    typename ContainerT
>
bool expanding_object_like_macro(
    ContextT const& ctx,
    TokenT const& macro_name, 
    ContainerT const& definition,
    TokenT const& macrocall);
}}
#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<
    typename ContextT,
    typename TokenT,
    typename ContainerT,
    typename IteratorT
>
bool expanding_function_like_macro(
    ContextT const& ctx,
    TokenT const& macro_name,
    std::vector<TokenT> const& parameters, 
    ContainerT const& definition,
    TokenT const& macrocall,
    std::vector<ContainerT> const& arguments,
    IteratorT const& seqstart,
    IteratorT const& seqend);
}}

@code{expanding_object_like_macro}; は文字列置換マクロの展開開始時、 @code{expanding_function_like_macro}; は引数リスト付きマクロの展開開始時に呼び出されます。

引数 @var{macro_name}; 、 @var{parameters}; 、 @var{definition}; には展開対象マクロの[[マクロ定義時>#hooks-member-defined-macro]]と同じ内容が渡されます。~
各々のトークン情報の[[位置情報>#token-member-position]]もマクロ定義時のものになります。

引数 @var{macrocall}; にはマクロ呼び出し位置におけるマクロ名の[[トークン情報>#token]]が渡されます。

引数 @var{arguments}; には、引数リスト付きマクロの各引数に渡されたトークン列の @code{std::vector}; 配列が渡されます。~
例えば @code{MUL(x,y)}; というマクロを @code{MUL(100,1+2)}; のように呼び出した場合、トークン列 @code{100}; とトークン列 @code{1+2}; が @code{std::vector}; 配列の要素になります。

引数 @var{seqstart}; および @var{seqend}; は、引数リスト付きマクロ呼び出しにおける引数リスト部分のトークンイテレータが渡されます。~
例えば @code{MUL(100,1+2)}; という呼び出しならば、 @code{seqstart}; には最初の @code{(}; を指すイテレータ、 @code{seqend}; には最後の @code{)}; を指すイテレータがそれぞれ渡されます。

これらの関数が @code{false}; を返すとマクロの展開が継続されます。~
@code{true}; を返すとマクロの展開が中止され、その時点でのコード文字列がそのまま出力されるトークン列となります。~
@code{wave::context_policies::default_preprocessing_hooks}; の既定の動作では @code{false}; を返します。

これらの関数は、マクロが入れ子になっている場合は各々の展開時に呼び出されます。~
例えば @code{ABC}; というマクロを展開すると @code{XYZ}; というマクロになる場合、 @code{ABC}; の展開時と @code{XYZ}; の展開時にそれぞれ呼び出されます。~
また、それぞれの呼び出しの間には[[マクロ展開完了時>#hooks-member-expanded-macro]]の呼び出しが挟まります。~
詳細な呼び出し順序については[[マクロ展開完了時>#hooks-member-expanded-macro]]の項を参照してください。

***マクロ展開完了時 [#hooks-member-expanded-macro]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<
    typename ContextT,
    typename ContainerT
>
void expanded_macro(
    ContextT const& ctx,
    ContainerT const& result);
}}
#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<
    typename ContextT,
    typename ContainerT
>
void rescanned_macro(
    ContextT const& ctx,
    ContainerT const& result);
}}

@code{expanded_macro}; はマクロの展開完了時、 @code{rescanned_macro}; は入れ子になっているマクロも含めた完全な展開完了時に呼び出されます。~
引数 @code{result}; には展開されたトークン列が渡されます。

これらの関数および[[マクロ展開開始時>#hooks-member-expanding-macro]]に呼び出される関数の呼び出し順序について説明します。~
ここでは説明のために次のマクロ定義がされているものとします。

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
#define ABC (X+Y)
#define X 100
#define Y 50
#define MUL(x,y) ((x)*(y))
}}

このとき、 @code{ABC}; および @code{MUL(MUL(1,2),3)}; の展開処理における各関数の呼び出し順序は次のようになります。

:@code{ABC}; の展開時|
|~順序|~呼び出し関数|~マクロ展開状態|~備考|h
|CENTER:|LEFT:|||c
|~1|~@code{expanding_object_like_macro};|@code{''ABC''};|@code{ABC}; に対する呼び出し。|
|~2|~@code{expanded_macro};|@code{''(X+Y)''};|~|
|~3|~@code{expanding_object_like_macro};|@code{(''X''+Y)};|@code{X}; に対する呼び出し。|
|~4|~@code{expanded_macro};|@code{(''100''+Y)};|~|
|~5|~@code{rescanned_macro};|@code{(''100''+Y)};|~|
|~6|~@code{expanding_object_like_macro};|@code{(100+''Y'')};|@code{Y}; に対する呼び出し。|
|~7|~@code{expanded_macro};|@code{(100+''50'')};|~|
|~8|~@code{rescanned_macro};|@code{(100+''50'')};|~|
|~9|~@code{rescanned_macro};|@code{''(100+50)''};|@code{ABC}; に対する呼び出し。|
:@code{MUL(MUL(1,2),3)}; の展開時|
|~順序|~呼び出し関数|~マクロ展開状態|~備考|h
|CENTER:|LEFT:|||c
|~1|~@code{expanding_function_like_macro};|@code{''MUL(MUL(1,2),3)''};|@code{MUL(MUL(1,2),3)}; に対する呼び出し。|
|~2|~@code{expanding_function_like_macro};|@code{MUL(''MUL(1,2)'',3)};|@code{MUL(1,2)}; に対する呼び出し。|
|~3|~@code{expanded_macro};|@code{MUL(''&#40;(1)*(2))'',3)};|~|
|~4|~@code{rescanned_macro};|@code{MUL(''&#40;(1)*(2))'',3)};|~|
|~5|~@code{expanded_macro};|@code{''&#40;&#40;(1)*(2)))*(3))''};|@code{MUL(MUL(1,2),3)}; に対する呼び出し。|
|~6|~@code{rescanned_macro};|@code{''&#40;&#40;(1)*(2)))*(3))''};|~|

***インクルードファイルパス設定処理 [#hooks-member-locate-include]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<typename ContextT>
bool locate_include_file(
    ContextT& ctx,
    std::string& file_path, 
    bool is_system,
    char const* current_name,
    std::string& dir_path, 
    std::string& native_name);
}}

インクルードファイルのパスを設定するために呼び出されます。~
他の多くのメンバ関数は「行おうとしている処理」や「行われた処理」を捕捉するものですが、この関数はそれ自身が処理を行います。

引数 @var{file_path}; には @code{#include}; で指定されたファイル名文字列が渡されます。~
[[ディレクティブ発見時>#hooks-member-found-directive]]とは異なり、ファイル名を囲む @code{""}; や @code{<>}; は文字列に含まれません。

引数 @var{is_system}; には、 @code{#include <...>}; ならば @code{true}; 、 @code{#include "..."}; ならば @code{false}; が渡されます。

引数 @var{current_name}; には、 boost::wave 独自ディレクティブ @code{#include_next}; が用いられた際に呼び出し元ファイルのパスが渡されます。~
@code{#include}; が用いられた場合は @code{NULL}; が渡されます。

引数 @var{dir_path}; に発見したインクルードファイルのディレクトリパスを、引数 @var{native_name}; に同ファイルのフルパスを設定します。~
ただ、 @code{wave::context_policies::default_preprocessing_hooks}; の既定の動作では、 @var{dir_path}; にはディレクトリパスが設定されるわけではないようです。~
@code{#include <...>}; ならばファイルのフルパスが、 @code{#include "..."}; ならば指定したファイル名文字列が設定されるようです。

ファイルパスの設定に成功したならば @code{true}; を、失敗したならば @code{false}; を返します。~
@code{wave::context_policies::default_preprocessing_hooks}; の既定の動作では、[[コンテキストクラス>#context]]に設定されたインクルードパスを基にファイルの検索を行い、見つかったならばそのフルパスを @var{native_name}; に設定して @code{true}; を返し、見つからなければ @code{false}; を返します。

***インクルードファイル解析開始/完了時 [#hooks-member-opened-include]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<typename ContextT>
void opened_include_file(
    ContextT const& ctx,
    std::string const& relname, 
    std::string const& absname,
    bool is_system_include);
}}
#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<typename ContextT>
void returning_from_include_file(ContextT const& ctx);
}}

@code{opened_include_file}; はインクルードファイルの解析を開始した時に呼び出されます。~
引数 @code{relname}; には @code{#include}; で指定されたファイル名文字列が渡され…て欲しいのですが、実際には @code{[[locate_include_file>#hooks-member-locate-include]]}; の引数 @code{dir_path}; に設定された文字列が渡されるようです。~
引数 @code{absname}; には[[インクルードファイルパス設定処理>#hooks-member-locate-include]]によって設定されたインクルードファイルのフルパスが渡されます。~
引数 @var{is_system_include}; には、 @code{#include <...>}; ならば @code{true}; 、 @code{#include "..."}; ならば @code{false}; が渡されます。

@code{returning_from_include_file}; は現在解析中だったインクルードファイルの解析完了時に呼び出されます。

***インクルードガード検出時 [#hooks-member-include-guard]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<typename ContextT>
void detected_include_guard(
    ContextT const& ctx,
    std::string const& filename,
    std::string const& include_guard);
}}
#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<
    typename ContextT,
    typename TokenT
>
void detected_pragma_once(
    ContextT const& ctx,
    TokenT const& pragma_token,
    std::string const& filename);
}}

これらの関数は、コンテキストクラスの[[言語サポートオプション設定>#context-member-language]]で @code{wave::support_option_include_guard_detection}; が設定された場合のみ呼び出されます。

@code{detected_include_guard}; は、インクルードファイルの内容全体が次のような記述で囲まれていた場合に呼び出されます。~
呼び出されるタイミングは [[@code{returning_from_include_file};>#hooks-member-opened-include]] の直後です。

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
#ifndef FOO_H
#define FOO_H

/* ファイル内容 */

#endif // FOO_H
}}
#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
#if !defined(FOO_H)
#define FOO_H

/* ファイル内容 */

#endif // FOO_H
}}

@code{#ifndef}; や @code{#if}; の前、もしくは @code{#endif}; の後に空白文字やコメント以外の有効なC++トークンが存在する場合には呼び出されません。~
引数 @var{filename}; にはインクルードガードの設定されているファイルのフルパスが渡されます。~
引数 @var{include_guard}; にはインクルードガードに用いられたマクロ名(上記の例ならば @code{FOO_H};)の文字列が渡されます。

@code{detected_pragma_once}; は、インクルードファイル内で @code{#pragma once}; の記述が見つかった場合に呼び出されます。~
引数 @var{pragma_token}; には @code{#pragma}; の[[トークン情報>#token]]が渡されます。~
引数 @var{filename}; には @code{#pragma once}; の記述されているファイルのフルパスが渡されます。

***例外送出処理 [#hooks-member-throw-exception]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<
    typename ContextT,
    typename ExceptionT
>
void throw_exception(
    ContextT const& ctx,
    ExceptionT const& e);
}}

例外を送出すべき際に呼び出され、例外送出処理を行います。~
この関数で例外を送出しないようにすると、例外を無視して解析を続行させることができます。

引数 @var{e}; に送出すべき例外インスタンスが渡されます。~
引数の型 @var{ExceptionT}; は @code{wave::preprocess_exception}; または @code{wave::cpplexer::lexing_exception}; となり、いずれの型も @code{std::exception}; から派生しています。

@code{wave::context_policies::default_preprocessing_hooks}; の既定の動作では @var{e}; をそのまま @code{throw}; します。

***トークンスキップ処理 [#hooks-member-may-skip-ws]

#code(c,nomenu,nonumber,nooutline,noliteral,nocomment){{
template<
    typename ContextT,
    typename TokenT
>
bool may_skip_whitespace(
    ContextT const& ctx,
    TokenT& token,
    bool& skipped_newline);
}}

[[generated_token>#hooks-member-generated-token]] 呼び出しの直前に呼び出され、トークンをスキップすべきか否かの判断を行います。~
通常は関数名の通り余分な空白文字をスキップするか否かの判断に用いますが、実際は空白文字以外もスキップさせることができます。

引数 @code{token}; にスキップするか否か判断する対象の[[トークン情報>#token]]が渡されます。~
このトークン情報は非 @code{const}; であり、自由に書き換えることができます。

引数 @code{skipped_newline}; には @code{false}; で初期化された @code{bool}; の参照が渡されます。~
この関数によって改行をスキップする場合、 @code{skipped_newline}; に @code{true}; を設定するようにします。

トークンをスキップするならば @code{true}; を、スキップしないならば @code{false}; を返します。~
@code{wave::context_policies::default_preprocessing_hooks}; の既定の動作では、何も行わずに常に @code{false}; を返します。

なお、この関数には [[generated_token>#hooks-member-generated-token]] と同様に一番最後にEOIトークンが渡されます。~
EOIトークンをスキップしても再びEOIトークンが渡され、 @code{false}; を返すまで無限ループとなります。~
EOIトークンはスキップしないようにしましょう。

*サンプル [#sample]

Visual C++ 2008 Express SP1 で作成したサンプルです。

#ref(BoostWaveSamples.zip);

以下に述べる4つのプロジェクトが入っています。~
いずれのサンプルも、起動パラメータに渡されたソースファイルの内容を解析します。

**01_Basic [#sample-01]

[[基本的な使い方>#basic]]に載せたコードそのものです。

**02_IgnoreDirectiveHook [#sample-02]

[[フッククラス>#hooks]]の[[クラス概要>#hooks-abstract]]に載せた、あらゆるディレクティブを処理しないようにするフッククラス @code{IgnoreDirectiveHook}; を用いたサンプルです。

**03_OutputHookInfos [#sample-03]

[[フッククラス>#hooks]]の各[[メンバ関数>#hooks-member]]が呼び出された際に各引数の情報を出力するサンプルです。~
フッククラスの各メンバ関数がどのようなタイミングで呼び出されているか調べる上で役立ちます。

**04_MakeIncludeTree [#sample-04]

他よりも少し実用的なサンプルです。

渡されたソースファイル中の @code{#include}; を辿り、 @code{#include}; のツリーを @code{boost::property_tree::ptree}; 型のインスタンスに構築しています。~
サンプルでは構築したツリーを @code{boost::property_tree::info_parser::write_info}; を用いて出力しています。

例えばこのサンプルプログラムに @code{#include <cstdio>}; とだけ書かれたファイルを渡すと、次のように出力されます(環境により出力内容は異なります)。

#pre{{
<cstdio> "C:/Program Files\\Microsoft Visual Studio 9.0\\VC\\include\\cstdio"
{
    <yvals.h> "C:/Program Files\\Microsoft Visual Studio 9.0\\VC\\include\\yvals.h"
    {
        <crtdefs.h> "C:/Program Files\\Microsoft Visual Studio 9.0\\VC\\include\\crtdefs.h"
        {
            <sal.h> "C:/Program Files\\Microsoft Visual Studio 9.0\\VC\\include\\sal.h"
            <vadefs.h> "C:/Program Files\\Microsoft Visual Studio 9.0\\VC\\include\\vadefs.h"
        }
        <use_ansi.h> "C:/Program Files\\Microsoft Visual Studio 9.0\\VC\\include\\use_ansi.h"
    }
    <stdio.h> "C:/Program Files\\Microsoft Visual Studio 9.0\\VC\\include\\stdio.h"
    {
        <crtdefs.h> "C:/Program Files\\Microsoft Visual Studio 9.0\\VC\\include\\crtdefs.h"
        <swprintf.inl> "C:/Program Files\\Microsoft Visual Studio 9.0\\VC\\include\\swprintf.inl"
    }
}
}}

boost::property_tree については次のサイトが参考になります。

-[[boost::property_tree - Let's Boost>http://www.kmonos.net/alang/boost/classes/property_tree.html]]

*その他 [#others]

その他、実装する上で留意すべき点など。

**Visual C++ で警告 C4996 が大量に出る [#others-c4996]

警告 C4996 は、セキュリティ上問題のある(古い)関数が使われている場合に出る警告です。~
詳しくはMSDNライブラリを参照してください。

-[[コンパイラの警告 (レベル 1) C4996 - MSDNライブラリ>http://msdn.microsoft.com/ja-jp/library/ttcz0bys(v=VS.90).aspx]]

とはいえ boost::wave のソースコードを書き換えるわけにもいかないので、現状ではプロジェクトのプロパティなどでこの警告を無視するように設定するしかありません。

**コンパイルが通るはずのソースコードで例外になる [#others-newline]

次のような例外が出力されていませんか?

-@samp{error: ill formed preprocessor directive: ...};
-@samp{warning: last line of file ends without a newline};

これらはいずれも、最終行が改行で終わっていないファイルが存在するために発生する例外です。~
boost::wave では最終行が改行で終わっていないファイルを不正なフォーマットとして扱います。