今回はただの命令ではない、プリプロセッサ命令(preprocessor directive)についてです。
プリプロセッサ命令というのはHSP独自の呼び方で、一般的にはプリプロセッサ・ディレクティブといいます。
名前の最初についている記号 # が目印です。
# ちなみに、この記号 # はナンバーサインです。シャープ(♯)ではありません。
# いくつかの言語ではコメントを表す記号なので、このように、コメントとしてもよく使われます (HSPの「;」や「//」と同じ)。
このプリプロセッサ命令ですが、一番重要な点は「実行するときには何もしない」ということです。
「実行するとき」ではなく——実行する前に、プリプロセッサ命令の仕事はなされるのです。
HSPを実行する手順は、大きく分けて次の3つに分かれています。
HSPのプリプロセッサの役割は大きく分けて3通りほどありますが、今回は「スクリプトを機械的に変更する」ものたちをみていきましょう。
文章で説明してもわかりにくいので、ここからはスクリプトを交えて説明してみます。
プリプロセッサが行う代表的な仕事の1つに、条件コンパイルがあります。
与えられた条件によって、スクリプトの一部を削除し、動作を変更するものです。
プリプロセッサ命令 #if は、引数が 0 (= 偽) なら、#endif までを無視する命令です。
if 文と似たような感じですが、前述のとおり #if は実行する前に処理されるため、条件式には「静的な式」(実行しなくても計算できる式)だけを書くことができます。変数などは使用できません。
// 条件コンパイル #1 #if 0 // 偽 mes "このメッセージは表示されません" #endif mes "Hello, world!" stop
この例では、#if 0 から #endif までの間が無視されて、その間にあるスクリプトが実行されません。
一方、次の例では条件式が真 (= 0でない値) なので、#if 1 から #endif の間は有効です。
// 条件コンパイル #2 #if (1 < 2)// この式は真 mes "このメッセージも表示されます" #endif mes "Hello, world!" stop
続いて、#else を使ってみましょう。
これは単純に、else 命令のプリプロセッサ命令版です。
// 条件コンパイル #3 #if 1 // 真 mes "Hello, world!" #else mes "このメッセージは表示されません" #endif stop
実際のところ、#if はあまりHSPでは使われていません。
重要なのは if の亜種である、#ifdef, #ifndef です。
これらの説明の前に、まずマクロ作成講座の前半部分を読んでみてください。(「特殊展開マクロ」は読まなくていいです。)
さて、マクロについてわかりましたか。
#ifdef は、マクロが「定義されているとき」という条件の #if 命令です。[脚注]
#ifndef はその否定、つまり「マクロが定義されていないとき」[脚注]になります。
// 条件コンパイル #4 #define MACRO_X // 定義する #ifdef MACRO_X // 定義済み mes "MACRO_X が定義されています" #endif mes "Hello, world!" stop
MACRO_X が定義されている場合、スクリプトが有効です。
MACRO_X は直前で定義されているので、当然これは両方の mes が実行されますね。
// 条件コンパイル #5 ;#define MACRO_X // 定義しない #ifdef MACRO_X // 未定義 mes "MACRO_X が定義されています" #endif mes "Hello, world!" stop
MACRO_X の定義を消すと、#ifdef の条件は偽ということになり、その中のスクリプトが無視されます。
ちなみに、マクロで定義された定数値は、変数と違ってプリプロセスの時点で値が決定しているため、#if の式に使用することができます。
// 条件コンパイル #6 text = "strrep 命令はありません。" //strrep 命令は、HSP3.5から標準命令として追加された。 #if __hspver__ >= 0x3500 strrep text, "ません", "ます" #endif mes text
プログラミングでは普通、ソースコードは一定の単位で分割して、ソースコード(スクリプト)を複数のファイルに保存するものです。
すると、プログラミング言語には「他のファイルを参照する機能」が必要になります。
HSPにおけるその機能は、プリプロセッサ命令 #include です。
といっても難しいことをするわけではありません。
#include 命令にファイルを指定すると、そのファイルの中身をスクリプトに貼り付けてくれます。
// ファイル結合 #include "hsp3imp.as" // common フォルダにあるファイル #ifdef hspini // hsp3imp.as で定義されるマクロの1つ mes "hsp3imp.as が結合されています。" #else mes "hsp3imp.as が結合されていません。" #endif stop
これのプリプロセッサ命令を処理すると、次のような感じになります。
// ファイル結合 ; hsp3imp.dll header #uselib "hsp3imp.dll" #func global hspini hspini 0 #func global hspbye hspbye 0 #func global hspprm hspprm 0 #func global hspexec hspexec 0 mes "hsp3imp.as が結合されています。" stop
※hsp3imp.as の中のプリプロセッサ命令まで消すと、意味不明になるので、これらは残しておきました。 hsp3imp.as を選んだ理由は、短かったからです。
イメージをつかめたでしょうか。
#include は指定したファイルに置き換わる、と覚えておきましょう。
ちなみに、HSPには #include に似た存在として #addition があります。
#include では、指定したファイルが見つからなければエラーとなりますが、#addition の場合は単に何も起こりません。
おそらく使わないので、覚えておく必要はないと思います。
ここまでに書いた知識を使って、面白いことができます。
インクルード・ガード(include guard)、つまり多重結合防止です。
// インクルード・ガード main #include "header.as" #include "header.as" // 同じファイルを複数結合する mes "header.as を2回結合してみました。" stop
// インクルード・ガード header // ※ファイル名は header.as とする。 #define MACRO_X // 便利なマクロを定義
これで、main の方のスクリプトを実行すると、どうなるでしょうか。
すぐに答えを言うと、エラーが出てコンパイルに失敗します。
これは、「header.as」で定義されているマクロが、すべて重複定義されてしまうからです。
よく使うものを1つのファイルにまとめておき、使いたいときに #include する、というのは非常によくあることです。[脚注]
同じファイルを2個も include してしまう、というのは一見ただのミスのように見えますが、「include したファイルが他に何か include していて、さらにそれも何か他のファイルを include していて、……」という連鎖構造があると、同じファイルを複数回結合してしまう、ということも起こりえます。むしろ日常茶飯事です。
このような問題を防ぐためには、次のようなインクルード・ガードを書きます。
// インクルード・ガード 見本 #ifndef ig_header_as // このマクロ名はファイル名と同じにするとよい [脚注]
#define ig_header_as /* ファイルの内容 */ #endif
見ればわかるかもしれませんが、動作原理は単純で、「1回読み込むと、2回目以降はファイル全体が飛ばされてしまう」というものです。
まず始めに、#ifndef でマクロが定義されているかどうか調べます。この時点では未定義なので、ここでの #ifndef は何もしません。
次に、#define で #ifndef の時と同じマクロを定義します。
そして、このファイルが2回目以降に結合されたときは、#ifndef の時点でマクロが定義済みとなり、ファイル全体が無視されます。
よって、多重結合が行われることはありません。
さっきの例にインクルード・ガードを追加すると次のようになります。
// インクルード・ガード header #2 // ※ファイル名は header.as とする。 #ifndef ig_header_as #define ig_header_as #define MACRO_X // 便利なマクロを定義 #endif
これなら、このファイルを何度 #include しても問題ありません。
今回は、基本的なプリプロセッサ命令の使い方を扱ってみました。
途中で書いたとおり、HSPのプリプロセッサには、他にも「モジュール」「命令定義」「コンパイラへのオプション指定」などの機能があります。というか、そっちのほうが重要だったりします。
では、また次回。