C/C++プリプロセッサライブラリ boost::wave の簡単な解説ページです。
boost version 1.55.0 を基にして記述されています。
boost::wave は、構文解析や字句解析を行うライブラリである boost::spirit を用いて実装されたC/C++プリプロセッサライブラリです。
このライブラリは、C/C++ソースコード中の #include や #define といったディレクティブを展開することができます。
展開した結果のソースコードはトークン単位のイテレータによって取得することができます。
また、展開処理をフック(コールバック)によって制御することも可能です。
このライブラリは次のような事例に用いることができます。
当文書では主要なクラスのメンバ関数に重点を置いて解説します。
それ以外の内容については本家マニュアルを参照してください。
なお、当文書内では名前空間 boost::wave を wave と略して記述しています。
boost::wave の Hello, World 的なコードは次のようになります。
なお、このコードは後述のサンプルにも入っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | - ! - ! - ! - | | | | - ! - | | | | | ! | - ! | | | | | | | | - ! | | | | | - | ! | | | | | - - ! | - | ! ! | - - ! | | ! | | ! | |
このソースをコンパイルしてできたプログラムの第一引数に、次のコードが書かれたファイルを渡してみます。
|
すると次のように出力されます(前後の改行は省略しています)。
ご覧の通り、 RET_CODE マクロが展開されていることがわかると思います。
int main(int, char**)
{
return 0;
}
このように、 boost::wave を用いたソースコードの解析は基本的に次の手順で行います。
wave::context のインスタンスを作成する。コンテキストクラスである wave::context について解説します。
wave::context は次のようにテンプレートで定義されており、5つのテンプレート引数を持ちます。
|
各テンプレート引数の定義は次の通りです。
| 引数名 | 定義 |
|---|---|
| IteratorT | このクラスに渡すソースコード文字列のイテレータ型。 通常は std::string::const_iterator を用います。 |
| LexIteratorT | 字句解析してトークンを取り出すイテレータクラス型。 通常は wave::cpplexer::lex_iterator< wave::cpplexer::lex_token<> > を用います。 |
| InputPolicyT | #include によって別のファイルを読み取る際に用いられるクラス型。普通にファイルを読むならば既定の wave::iteration_context_policies::load_file_to_string を用います。ただしソースファイルの文字コードがシフトJISである場合はこのクラスをそのまま使うと問題があります。 シフトJIS対応の項を参照してください。 |
| HooksT | 種々の解析の際に呼び出されるフック関数群を定義したクラス型。 パブリックなコピーコンストラクタを持つクラスである必要があります*1。 既定の wave::context_policies::eat_whitespace<...> を用いると余分な空白文字が除去されます。特に何もさせたくない場合は wave::context_policies::default_preprocessing_hooks を用います。 |
| DerivedT | このクラスを継承したクラスを作成する場合に、継承先のクラス型を指定します(CRTP)。 継承せずに用いるならば既定の wave::this_type を用います。 |
wave::context のコンストラクタは次のように定義されています。
|
各引数の定義は次の通りです。
| 引数名 | 定義 |
|---|---|
| first_ | 解析するソースコード文字列イテレータの開始位置と終端位置。 ちなみにこれらの値はイテレータ取得時に渡すこともできます(後述)。 その場合、コンストラクタには無意味なイテレータ*2を渡しても問題ありません。 |
| last_ | |
| fname_ | 解析対象のファイルパス文字列。__FILE__ マクロの展開値や解析例外発生時の位置情報に用いられます。 |
| hooks_ | フッククラスのインスタンス。 フッククラスがパブリックなデフォルトコンストラクタを持つならば省略可能です。 この引数はコピーされる為、コンストラクタへ渡した後に内容を変更してもそれは反映されません。 |
主要なメンバ関数のみ解説します。
完全な一覧は本家マニュアルの The Context Object 等を参照してください。
|
|
|
begin でソースコードのトークン単位解析用イテレータを、 end でその終端を表すイテレータを取得します。
begin には2引数を受け取るオーバロードがあり、こちらを用いる場合は解析するソースコード文字列イテレータの開始位置と終端位置を渡します。
これらはコンストラクタの第1引数および第2引数に渡す値と同様であり、このメンバ関数オーバロードを用いるならばコンストラクタ側には無意味な値を渡しても問題ありません。
戻り値の iterator_type はクラステンプレート引数 LexIteratorT に渡した型となり、トークン情報を保持しています。
トークン情報について詳しくは後述のトークン情報クラスを参照してください。
|
|
|
|
#include のパス解決に用いるインクルードパスを追加します。
add_include_path で独自パスを、 add_sysinclude_path でシステム標準パスを追加でき、追加した順に検索されます。
set_sysinclude_delimiter を呼び出した場合、以降 add_include_path は add_sysinclude_path と同等の処理を行うようになります。
set_current_directory で、相対パスを追加する場合に適用されるカレントディレクトリを設定できます。
カレントディレクトリはパスの追加時に付与されるため、パスを追加した後に set_current_directory を呼び出しても意味はありません。
実際のパスの検索方法は #include <...> の場合と #include "..." の場合とで異なり、次のようになります。
#include <...> の場合add_sysinclude_path で追加したパスを検索する。#include "..." の場合add_include_path で追加したパスを検索する。ただし、 set_sysinclude_delimiter を呼び出していた場合は検索方法が変わり、次のようになります。
#include <...> の場合add_sysinclude_path で追加したパス、および set_sysinclude_delimiter 呼び出し後に add_include_path で追加したパスを検索する。#include "..." の場合set_sysinclude_delimiter 呼び出し前に add_include_path で追加したパスを検索する。 |
マクロ定義を追加します。
引数 macrostring には単純なキーワード文字列もしくは "=" 区切りで値を指定した文字列を渡します。
例えば "_WIN32" を渡すと #define _WIN32 と同等になり、 "_MSC_VER=1500" を渡すと #define _MSC_VER 1500 と同等になります。
引数 is_predefined を true にすると、 __FILE__ や __LINE__ といった組み込みマクロ定義と同等の定義が行えます。
C++標準のマクロ定義を置き換えることもできます。
|
|
解析処理時に考慮する言語サポートオプションの設定および取得を行います。
set_language の引数 language_ には wave::language_support 列挙値の論理和で設定します。
引数 reset_macros が既定値 true である場合、同時にそれまでに追加したマクロ定義のクリアが行われます。
主要な wave::language_support 列挙値とその定義は次の通りです。
| 列挙値 | 定義 |
|---|---|
support_normal | C++98の言語仕様をサポートします。 |
support_cpp | |
support_option_long_long | long long 型をサポートします。 |
support_option_variadics | 可変長引数マクロをサポートします。 |
support_c99 | C99の言語仕様をサポートします。support_option_long_long および support_option_variadics が含まれます。 |
support_cpp0x | C++0x(C++11の原形)の言語仕様をサポートします。support_option_long_long および support_option_variadics が含まれます。 |
support_option_insert_whitespace | 連続する2トークンを連結するとC++キーワード等になる場合、間に空白文字トークンを挿入します。 |
support_option_preserve_comments | コメント文を除去しないようにします。 |
support_option_convert_trigraphs | Trigraphを有効にします。 詳しくは Trigraph - みねこあ 等を参照してください。 |
support_option_emit_line_directives | 出力されるトークン列に #line を挿入し、元の行番号を保持します。 |
support_option_include_guard_detection | インクルードガードを検出し、検出されたファイルを2回以上開かないようにします。 |
support_option_emit_pragma_directives | 不明な #pragma をそのままトークン列として出力するようにします。 |
|
|
フックインスタンスの参照を取得します。
コンストラクタ呼び出し後にフックインスタンスの内容を取得したり変更したりする場合、この関数で取得した参照に対して操作を行います。
コンテキストクラスのクラステンプレート引数 LexIteratorT に指定したイテレータクラスのインスタンスからトークン情報を取得することができます。
通常の wave::cpplexer::lex_iterator< wave::cpplexer::lex_token<> > を用いた場合、トークン情報の型は wave::cpplexer::lex_token<> となります。
トークン情報は解析用イテレータから取得できる他、後述するフッククラスの多くのメンバ関数に TokenT 型の引数として渡されます。
通常用いられるトークン情報クラスである wave::cpplexer::lex_token<> の主要なメンバ関数を解説します。
|
トークンIDを表す wave::token_id 列挙型への型変換が行えます。
トークンIDはそのトークンがどのようなC++言語要素であるかを表し、本家マニュアルの The Token Identifiers の項にある T_ で始まる値のいずれかになります。
ただし、いくつかマニュアルに記載されていないトークンIDや解説の間違っているトークンIDがあります。
私が気付いたものを次の表にまとめています。
| 内容 | トークンID | 詳細 |
|---|---|---|
| 記載漏れ | T_EOI | End Of Input、即ち全入力の終端(=ソースコード文字列イテレータの終端)を表します。 このトークンIDは解析用イテレータからは取得できません。 フッククラスのトークン生成時において wave::T_EOF に続いて渡されます。 |
| 記載ミス | T_PP_INCLUDE | と記載されていますが、 #include ... の誤りです。強制インクルード設定により生成されるトークンです。 |
T_PP_QHEADER | と記載されていますが、 #include "..." の誤りです。 | |
T_PP_HHEADER | と記載されていますが、 #include <...> の誤りです。 |
|
トークン文字列値を取得します。
戻り値の string_type は BOOST_WAVE_STRINGTYPE マクロで定義されており、コンパイラによって std::string または boost::wave::util::flex_string<...> となります。
いずれの型であっても出力ストリームにはそのまま渡すことができます。
戻り値を std::string で受け取りたい場合、 boost::wave::util::flex_string<...> をそのまま代入することはできないため、いずれの型であっても定義されているメンバ関数 c_str を用いて次のように代入します。
|
|
トークンのファイル内における位置情報を取得します。
戻り値の position_type はデフォルトでは wave::util::file_position_type となり、行番号を取得する get_line や行内位置を取得する get_column などのメンバ関数が使えます。
詳しくは本家マニュアルの The File Position の項を参照してください。
|
トークンが全入力の終端(=ソースコード文字列イテレータの終端)を表すものであるか否かを取得します。
トークンIDが wave::T_EOI であるか否か調べることと同じです。
|
トークンが有効な値を保持しているか否かを取得します。
この関数が false を返す場合、 get_value や get_position の呼び出しを行ってはいけません。
呼び出した場合、NULL参照になる可能性があります。
コンテキストクラスのクラステンプレート引数 HooksT に独自のフッククラス型を指定し、コンストラクタの第4引数にそのクラスのインスタンスを渡す*3ことで、ソースコード解析中に次のようなタイミングで処理内容を捕捉することができます。
#include 、 #define 等)発見時。#include で指定されたファイルの解析開始時および完了時。また、捕捉種別によっては処理内容の操作を行うことも可能です(#include を無視する等)。
独自のフッククラス型を定義する場合、通常は wave::context_policies::default_preprocessing_hooks を継承して作成します。
wave::context_policies::default_preprocessing_hooks は特別なことを行わないフッククラスであり、このクラスを継承することで不要な捕捉処理を記述する必要が無くなります。
あらゆるディレクティブを処理しないようにするフッククラスのコーディング例は次のようになります。
なお、このコードは後述のサンプルにも入っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | - ! - ! - | - ! | - - ! ! ! | |
後述するフッククラスのメンバ関数はテンプレートで定義されており、いくつかのテンプレート引数型を関数の引数として受け取ります。
主要なテンプレート引数の定義は次の通りです。
| 引数名 | 定義 |
|---|---|
| ContextT | コンテキストクラス型。 |
| TokenT | トークン情報クラス型。 通常は wave::cpplexer::lex_token<> です。 |
| ContainerT | トークン列を表す、トークン情報クラスのリストを保持するSTLコンテナ型。std::list<TokenT> 相当の型になります。 |
フック処理で呼び出されるメンバ関数の一覧です。
すべてのメンバ関数の第1引数には処理対象であるコンテキストクラス ContextT のインスタンス参照が渡されます。
|
|
|
|
|
found_directive は、あらゆるディレクティブの発見時に最初に呼び出されます。
引数 directive に発見したディレクティブのトークン情報が渡されます。
found_include_directive は #include の発見時に呼び出されます。
引数 filename に #include 直後のファイル名指定が "foo.h" や <cstdio> といった形の文字列で渡されます。
引数 include_next は、 #include ではなく boost::wave 独自ディレクティブ #include_next が使われた場合に true となります。
found_warning_directive は #warning の発見時、 found_error_directive は #error の発見時に呼び出されます(#warning は非標準のディレクティブであり、 Visual C++ 等では使えません。)。
引数 message に警告/エラーメッセージ指定のトークン列が渡されます。
found_line_directive は #line の発見時に呼び出されます。
引数 arguments に行番号指定およびファイル名指定(省略可能)のトークン列が渡されます。
引数 line および filename には arguments を解釈した結果の値が渡されます。
ファイル名指定を省略した場合、 filename には空の文字列が渡されます。
found_line_directive 以外は、 false を返すと発見したディレクティブの解析処理が継続されます。
true を返すと、発見したディレクティブの解析処理は中止され、ディレクティブが記述されていなかったものとして扱われます(即ち空行扱いになります)。
wave::context_policies::default_preprocessing_hooks の既定の動作ではいずれの関数も false を返します。
なお、 #warning および #error の解析処理を継続させると、続けて例外送出処理が呼び出されます。
|
次のいずれかの条件分岐ディレクティブが判定された時に呼び出されます。
#if#ifdef#ifndef#elif引数 directive には上記のいずれかのディレクティブのトークン情報が渡されます。
引数 expression にはディレクティブ直後の条件式のトークン列が渡されます。
引数 expression_value には条件式の判定結果を表す真偽値が渡されます。
この関数が false を返すと判定結果に基づいて以降のコードを展開もしくはスキップします。
true を返すと条件式の判定が再度行われ、再びこの関数が呼び出されます。
wave::context_policies::default_preprocessing_hooks の既定の動作では false を返します。
|
#if や #else などの条件分岐ディレクティブによってトークンがスキップされた時に呼び出されます。
引数 token にスキップされたトークン情報が渡されます。
|
解析用イテレータの取得やインクリメントによって新しいトークン情報が生成される際に呼び出されます。
引数 token に生成されたトークン情報が渡されます。
戻り値には実際にイテレータから取得されるトークン情報の参照を返します。
wave::context_policies::default_preprocessing_hooks の既定の動作では token をそのまま返します。
なお、この関数は解析用イテレータが終端に到達した際にも呼び出されます。
その際の引数 token には、 (token.is_eoi() == true) かつ (token.is_valid() == false) であるトークン情報(EOIトークン)が渡されます。
このとき token.get_value() や token.get_position() を呼び出そうとするとNULL参照でプログラムが強制終了してしまうため、必ず token.is_valid() で有効なトークン情報であるか否か確かめてからそれらのメンバ関数を呼び出すようにしてください。
|
#define によってマクロが定義された際に呼び出されます。
#define XYZ 100 のような文字列置換マクロと、 #define MUL(x,y) ((x)*(y)) のような引数リスト付きマクロの両方で呼び出されます。
引数 macro_name には定義されたマクロ名のトークン情報が渡されます。
引数リスト付きマクロの場合、例えば MUL(x,y) ならば MUL のトークン情報が渡されます。
引数 is_functionlike には、文字列置換マクロであれば false 、引数リスト付きマクロであれば true が渡されます。
引数 parameters には、引数リスト付きマクロの場合に各引数のトークン情報が配列で渡されます。
文字列置換マクロの場合は空の配列が渡されます。
引数の型 ParametersT は std::vector<TokenT> 相当の型です。
引数 definition には置換内容のトークン列が渡されます。
#define XYZ 100 ならば 100 、 #define MUL(x,y) ((x)*(y)) ならば ((x)*(y)) の部分になります。
引数の型 DefinitionT は std::list<TokenT> 相当の型です。
引数 is_predefined には、マクロが __FILE__ や __LINE__ といった組み込みマクロ定義であれば true が渡されます。
|
#undef によってマクロが削除された際に呼び出されます。
引数 macro_name に削除されたマクロ名のトークン情報が渡されます。
|
|
expanding_object_like_macro は文字列置換マクロの展開開始時、 expanding_function_like_macro は引数リスト付きマクロの展開開始時に呼び出されます。
引数 macro_name 、 parameters 、 definition には展開対象マクロのマクロ定義時と同じ内容が渡されます。
各々のトークン情報の位置情報もマクロ定義時のものになります。
引数 macrocall にはマクロ呼び出し位置におけるマクロ名のトークン情報が渡されます。
引数 arguments には、引数リスト付きマクロの各引数に渡されたトークン列の std::vector 配列が渡されます。
例えば MUL(x,y) というマクロを MUL(100,1+2) のように呼び出した場合、トークン列 100 とトークン列 1+2 が std::vector 配列の要素になります。
引数 seqstart および seqend は、引数リスト付きマクロ呼び出しにおける引数リスト部分のトークンイテレータが渡されます。
例えば MUL(100,1+2) という呼び出しならば、 seqstart には最初の ( を指すイテレータ、 seqend には最後の ) を指すイテレータがそれぞれ渡されます。
これらの関数が false を返すとマクロの展開が継続されます。
true を返すとマクロの展開が中止され、その時点でのコード文字列がそのまま出力されるトークン列となります。
wave::context_policies::default_preprocessing_hooks の既定の動作では false を返します。
これらの関数は、マクロが入れ子になっている場合は各々の展開時に呼び出されます。
例えば ABC というマクロを展開すると XYZ というマクロになる場合、 ABC の展開時と XYZ の展開時にそれぞれ呼び出されます。
また、それぞれの呼び出しの間にはマクロ展開完了時の呼び出しが挟まります。
詳細な呼び出し順序についてはマクロ展開完了時の項を参照してください。
|
|
expanded_macro はマクロの展開完了時、 rescanned_macro は入れ子になっているマクロも含めた完全な展開完了時に呼び出されます。
引数 result には展開されたトークン列が渡されます。
これらの関数およびマクロ展開開始時に呼び出される関数の呼び出し順序について説明します。
ここでは説明のために次のマクロ定義がされているものとします。
|
このとき、 ABC および MUL(MUL(1,2),3) の展開処理における各関数の呼び出し順序は次のようになります。
ABC の展開時| 順序 | 呼び出し関数 | マクロ展開状態 | 備考 |
|---|---|---|---|
| 1 | expanding_object_like_macro | ABC | ABC に対する呼び出し。 |
| 2 | expanded_macro | (X+Y) | |
| 3 | expanding_object_like_macro | (X+Y) | X に対する呼び出し。 |
| 4 | expanded_macro | (100+Y) | |
| 5 | rescanned_macro | (100+Y) | |
| 6 | expanding_object_like_macro | (100+Y) | Y に対する呼び出し。 |
| 7 | expanded_macro | (100+50) | |
| 8 | rescanned_macro | (100+50) | |
| 9 | rescanned_macro | (100+50) | ABC に対する呼び出し。 |
MUL(MUL(1,2),3) の展開時| 順序 | 呼び出し関数 | マクロ展開状態 | 備考 |
|---|---|---|---|
| 1 | expanding_function_like_macro | MUL(MUL(1,2),3) | MUL(MUL(1,2),3) に対する呼び出し。 |
| 2 | expanding_function_like_macro | MUL(MUL(1,2),3) | MUL(1,2) に対する呼び出し。 |
| 3 | expanded_macro | MUL(((1)*(2)),3) | |
| 4 | rescanned_macro | MUL(((1)*(2)),3) | |
| 5 | expanded_macro | (((1)*(2)))*(3)) | MUL(MUL(1,2),3) に対する呼び出し。 |
| 6 | rescanned_macro | (((1)*(2)))*(3)) |
|
インクルードファイルのパスを設定するために呼び出されます。
他の多くのメンバ関数は「行おうとしている処理」や「行われた処理」を捕捉するものですが、この関数はそれ自身が処理を行います。
引数 file_path には #include で指定されたファイル名文字列が渡されます。
ディレクティブ発見時とは異なり、ファイル名を囲む "" や <> は文字列に含まれません。
引数 is_system には、 #include <...> ならば true 、 #include "..." ならば false が渡されます。
引数 current_name には、 boost::wave 独自ディレクティブ #include_next が用いられた際に呼び出し元ファイルのパスが渡されます。
#include が用いられた場合は NULL が渡されます。
引数 dir_path に発見したインクルードファイルのディレクトリパスを、引数 native_name に同ファイルのフルパスを設定します。
ただ、 wave::context_policies::default_preprocessing_hooks の既定の動作では、 dir_path にはディレクトリパスが設定されるわけではないようです。
#include <...> ならばファイルのフルパスが、 #include "..." ならば指定したファイル名文字列が設定されるようです。
ファイルパスの設定に成功したならば true を、失敗したならば false を返します。
wave::context_policies::default_preprocessing_hooks の既定の動作では、コンテキストクラスに設定されたインクルードパスを基にファイルの検索を行い、見つかったならばそのフルパスを native_name に設定して true を返し、見つからなければ false を返します。
|
|
opened_include_file はインクルードファイルの解析を開始した時に呼び出されます。
引数 relname には #include で指定されたファイル名文字列が渡され…て欲しいのですが、実際には locate_include_file の引数 dir_path に設定された文字列が渡されるようです。
引数 absname にはインクルードファイルパス設定処理によって設定されたインクルードファイルのフルパスが渡されます。
引数 is_system_include には、 #include <...> ならば true 、 #include "..." ならば false が渡されます。
returning_from_include_file は現在解析中だったインクルードファイルの解析完了時に呼び出されます。
|
|
これらの関数は、コンテキストクラスの言語サポートオプション設定で wave::support_option_include_guard_detection が設定された場合のみ呼び出されます。
detected_include_guard は、インクルードファイルの内容全体が次のような記述で囲まれていた場合に呼び出されます。
呼び出されるタイミングは returning_from_include_file の直後です。
|
|
#ifndef や #if の前、もしくは #endif の後に空白文字やコメント以外の有効なC++トークンが存在する場合には呼び出されません。
引数 filename にはインクルードガードの設定されているファイルのフルパスが渡されます。
引数 include_guard にはインクルードガードに用いられたマクロ名(上記の例ならば FOO_H)の文字列が渡されます。
detected_pragma_once は、インクルードファイル内で #pragma once の記述が見つかった場合に呼び出されます。
引数 pragma_token には #pragma のトークン情報が渡されます。
引数 filename には #pragma once の記述されているファイルのフルパスが渡されます。
|
例外を送出すべき際に呼び出され、例外送出処理を行います。
この関数で例外を送出しないようにすると、例外を無視して解析を続行させることができます。
引数 e に送出すべき例外インスタンスが渡されます。
引数の型 ExceptionT は wave::preprocess_exception または wave::cpplexer::lexing_exception となり、いずれの型も std::exception から派生しています。
wave::context_policies::default_preprocessing_hooks の既定の動作では e をそのまま throw します。
|
generated_token 呼び出しの直前に呼び出され、トークンをスキップすべきか否かの判断を行います。
通常は関数名の通り余分な空白文字をスキップするか否かの判断に用いますが、実際は空白文字以外もスキップさせることができます。
引数 token にスキップするか否か判断する対象のトークン情報が渡されます。
このトークン情報は非 const であり、自由に書き換えることができます。
引数 skipped_newline には false で初期化された bool の参照が渡されます。
この関数によって改行をスキップする場合、 skipped_newline に true を設定するようにします。
トークンをスキップするならば true を、スキップしないならば false を返します。
wave::context_policies::default_preprocessing_hooks の既定の動作では、何も行わずに常に false を返します。
なお、この関数には generated_token と同様に一番最後にEOIトークンが渡されます。
EOIトークンをスキップしても再びEOIトークンが渡され、 false を返すまで無限ループとなります。
EOIトークンはスキップしないようにしましょう。
ソースファイルの文字コードがシフトJISである場合の問題点とその対処方法。
boost::wave で文字コードがシフトJIS*4のソースファイルを解析しようとするとうまく解析できないことがあります。
例えば次のようなソースコードです。
|
「表」や「十」といった文字はシフトJISだと2バイト目が 0x5C 、即ちバックスラッシュ(\)となります。
ご存じの通り、C++ではバックスラッシュはエスケープ文字として利用されています。
Visual C++ 等、シフトJISに対応している開発環境やコンパイラはこれらの文字の2バイト目をエスケープ文字として扱うことなく処理してくれますが、 boost::wave では当然ながらシフトJIS対応などされていません。
C++のソースコード中で2バイト文字を使えるのは主にコメント中と文字列中ですが、これらの場所で上述のような2バイト文字を使うと次のような問題が発生します。
\ があると、C++の仕様で次の行と連結されます。これにより次の行もコメント扱いされてしまいます。\ があるとエスケープ文字として解釈されます。特に文字列末尾にある場合、ダブルクォートをエスケープしてしまい、文字列が閉じられていない扱いになってしまいます。即ち上述のコードは次のように書かれているようなものとして解釈されてしまいます。(あくまでイメージです)
|
2バイト目が 0x5C となる文字には「ソ」などもあり、コメント中はともかく、文字列中で使わないように制限することは困難です。
文字コードがシフトJISであることが問題なので、 boost::wave による解析前にソースコード文字列の文字コードを変換してしまえば問題ありません。
boost::wave はワイド文字列(wchar_t)を扱えないため、変換先文字コードとしてはUTF-8が最適でしょう。
解析するソースコードそのものは、ファイルから読み込んだ後に文字コード変換すればいいでしょう。
しかしそれだけでは、ソースコード内の #include ディレクティブによって boost::wave が内部で読み取るヘッダファイルは文字コード変換できません。
そこで、 boost::wave のヘッダファイル読み込み処理をプログラマ側で用意する必要があります。
boost::wave は、コンテキストクラスのクラステンプレート引数 InputPolicyT に渡したクラスを用いてヘッダファイルを読み取ります。
通常では wave::iteration_context_policies::load_file_to_string クラスを用いますが、この引数に文字コード変換対応したクラスを渡すようにします。
なお、この方法でソースコードを解析する場合、トークン文字列を始めとするソースコード内から得た文字列は当然ながらUTF-8になっています。
トークン文字列を出力したり元の文字コードで処理したい場合、下記のいずれかの方法で文字コードを元に戻す必要があります。
get_value メンバ関数から得た文字列を文字コード変換する。may_skip_whitespace メンバ関数で引数に渡されたトークンを書き換える。後述のサンプル 02_SjisBasic にコーディング例があります。
ここではサンプルのコードについて要点のみ解説します。
まず、シフトJISからUTF-8への文字コード変換処理を用意する必要があります。
方法は色々あるかと思いますが、サンプルでは私が過去に作った簡易的な文字コード変換ライブラリである encoding を用いています。*5
ファイルから文字列を読み取ってUTF-8に変換する処理は関数化しておいた方がいいでしょう。
そしてその関数を利用してコンテキストクラスのクラステンプレート引数 InputPolicyT に渡すクラスを実装します。
加えて、読み取ったトークン文字列を出力したりしたい場合にはUTF-8から標準文字コードへの変換処理も必要になります。
それらをまとめたヘッダファイルがサンプルの common/wave_utf8.hpp になります。
Utf8InputPolicy クラスの記述に戸惑うかもしれませんが、これはこう書くものだと思ってしまっていいです。
wave::iteration_context_policies::load_file_to_string クラスの記述を参考にして、UTF-8への文字コード変換を行う以外は同等の動作になるように記述しています。
そして、作成したファイル読み取り関数を使って解析対象のソースコードを読み込むようにします。
21 22 | |
コンテキストクラスのクラステンプレート引数 InputPolicyT には上述の Utf8InputPolicy クラスを渡します。
24 25 26 27 28 29 30 31 | |
解析結果のトークン文字列を出力する際には標準文字コードへ戻すようにします。
50 51 52 53 54 55 56 | |
これでシフトJISのソースコードを問題なく解析することができます。
ただし、このサンプルでは逆にシフトJIS以外のソースコードをうまく解析できません。
どちらも問題なく解析できるようにするには、元の文字コードを判別して処理を切り分ける必要があるでしょう。
Visual C++ 2012/2013 用のサンプルコードをGitHubにて公開しています。
以下に述べる5つのサンプルプログラムのソースコードが入っています。
いずれのサンプルも、起動パラメータに渡されたソースファイルの内容を解析します。
基本的な使い方に載せたコードそのものです。
シフトJIS対応の対処方法に書いた方法を 01_Basic のコードに対して適用したサンプルです。
フッククラスのクラス概要に載せた、あらゆるディレクティブを処理しないようにするフッククラス IgnoreDirectiveHook を用いたサンプルです。
フッククラスの各メンバ関数が呼び出された際に各引数の情報を出力するサンプルです。
フッククラスの各メンバ関数がどのようなタイミングで呼び出されているか調べる上で役立ちます。
他よりも少し実用的なサンプルです。
渡されたソースファイル中の #include を辿り、 #include のツリーを boost::property_tree::ptree 型のインスタンスに構築しています。
サンプルでは構築したツリーを boost::property_tree::info_parser::write_info を用いて出力しています。
例えばこのサンプルプログラムに #include <cstdio> とだけ書かれたファイルを渡すと、次のように出力されます(環境により出力内容は異なります)。
<cstdio> "C:/Program Files\\Microsoft Visual Studio 12.0\\VC\\include\\cstdio"
{
<yvals.h> "C:/Program Files\\Microsoft Visual Studio 12.0\\VC\\include\\yvals.h"
{
<xkeycheck.h> "C:/Program Files\\Microsoft Visual Studio 12.0\\VC\\include\\xkeycheck.h"
<crtdefs.h> "C:/Program Files\\Microsoft Visual Studio 12.0\\VC\\include\\crtdefs.h"
{
<sal.h> "C:/Program Files\\Microsoft Visual Studio 12.0\\VC\\include\\sal.h"
{
<ConcurrencySal.h> "C:/Program Files\\Microsoft Visual Studio 12.0\\VC\\include\\ConcurrencySal.h"
}
<vadefs.h> "C:/Program Files\\Microsoft Visual Studio 12.0\\VC\\include\\vadefs.h"
}
<use_ansi.h> "C:/Program Files\\Microsoft Visual Studio 12.0\\VC\\include\\use_ansi.h"
}
<stdio.h> "C:/Program Files\\Microsoft Visual Studio 12.0\\VC\\include\\stdio.h"
{
<crtdefs.h> "C:/Program Files\\Microsoft Visual Studio 12.0\\VC\\include\\crtdefs.h"
<swprintf.inl> "C:/Program Files\\Microsoft Visual Studio 12.0\\VC\\include\\swprintf.inl"
}
}
boost::property_tree については次のサイトが参考になります。
その他、実装する上で留意すべき点など。
警告 C4996 は、セキュリティ上問題のある(古い)関数が使われている場合に出る警告です。
詳しくはMSDNライブラリを参照してください。
とはいえ boost::wave のソースコードを書き換えるわけにもいかないので、現状ではプロジェクトのプロパティなどでこの警告を無視するように設定するしかありません。
次のような例外が出力されていませんか?
これらはいずれも、最終行が改行で終わっていないファイルが存在するために発生する例外です。
boost::wave では最終行が改行で終わっていないファイルを不正なフォーマットとして扱います。
最終行が改行で終わっていないファイルも問題なく解析したい場合、読み込んだソースコード文字列の末尾に強制的に改行を付与してしまうという手があります。
その場合、シフトJIS対応の対処方法でも書いたように、コンテキストクラスのクラステンプレート引数 InputPolicyT に渡すクラスも変更する必要があります。