[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

5. 条件分岐、ループ、再帰

単純なテキストに展開されるようなマクロだけでは、 引数を取ることができるとしても、十分ではありません。 実行時に下される判断にもとづいて、 異なる展開がおこなわれるようなマクロが必要でしょう。 たとえば、何らかの条件構文が必要です。 また、ある処理を何回も繰り返したり、条件が真の間だけ繰り返したりするために、 ある種のループ構文も必要でしょう。


5.1 マクロが定義済みかを判定する

m4には2つの異なる条件構文が組み込まれています。 その1つはifdefです。

 
ifdef(name, string-1, opt string-2)

これにより、あるマクロが定義されているかどうかをテストできるようになります。 nameがマクロとして定義されていれば ifdefstring-1に展開され、 そうでないときはstring-2に展開されます。 string-2が省略されたときは (通常の規則に従い)空文字列として解釈されます。

 
ifdef(`foo', ``foo' is defined', ``foo' is not defined')
⇒foo is not defined
define(`foo', `')
⇒
ifdef(`foo', ``foo' is defined', ``foo' is not defined')
⇒foo is defined

マクロifdefは引数を与えたときだけ認識されます。


5.2 文字列の比較

もう一方の条件構文ifelseはずっと強力です。 与える引数の個数によって長いコメントの挿入のためや、 if-else構文、多重分岐などとして使うことができます。

 
ifelse(comment)
ifelse(string-1, string-2, equal, opt not-equal)
ifelse(string-1, string-2, equal, ...)

ifelseに1つだけ引数を与えた場合、 それは単に捨てられて、何も出力されません。 これはdnlを何度も使わずにブロックコメントを 挿入するために良く使われる、m4におけるイディオムです。 GNU m4ではこの特殊な使用法が認められているので、 引数が足りないことに対する警告はこのケースでは発せられません。

ifelseに3つ、または4つの引数を与えて呼び出すと、 string-1string-2が(文字毎に比べて)等しければ equalに展開されます。 等しくなければnot-equalに展開されます。

 
ifelse(foo, bar, `true')
⇒
ifelse(foo, foo, `true')
⇒true
ifelse(foo, bar, `true', `false')
⇒false
ifelse(foo, foo, `true', `false')
⇒true

またifelseには4つ以上の引数を与えることができます。 この場合、ifelseは伝統的なプログラミング言語におけるcase文や switch文と同じように機能します。 string-1string-2が等しければifelseequalに 展開され、等しくなければ最初の3つの引数が捨てられたあと、 まったくおなじ手続きが繰り返されます。例で示したほうがいいでしょう。

 
ifelse(foo, bar, `third', gnu, gnats, `sixth', `seventh')
⇒seventh

もちろん、通常はこれらの例よりもうすこし高度な使い方をするでしょう。 ifelseのよくある使い方のひとつは、さまざまな種類のループ処理を 実装するマクロの中で使用する場合です。

マクロifelseは引数を与えたときだけ認識されます。


5.3 ループと再帰

m4ではループ処理が直接的にはサポートされていませんが、 再帰的なマクロを定義することはできます。 使用しているハードウェアとオペレーティングシステムによるもの以外、 再帰の深さに制限はありません。

ループ処理は再帰とすでに説明した条件構文を使うことで実現できます。

マクロの実引数を反復処理するときには組み込みマクロshiftを 使うことができます。

 
shift(...)

このマクロは任意の個数の引数を受け取り、 最初の引数を除く残りの引数をそれぞれクォートしてから、 それらをコンマで区切ったものに展開します。

 
shift(bar)
⇒
shift(foo, bar, baz)
⇒bar,baz

shiftを使った次の例では引数の順番を逆にするマクロを定義しています。

 
define(`reverse', `ifelse($#, 0, , $#, 1, ``$1'',
			  `reverse(shift($@)), `$1'')')
⇒
reverse
⇒
reverse(foo)
⇒foo
reverse(foo, bar, gnats, and gnus)
⇒and gnus, gnats, bar, foo

それほど興味深いマクロではありませんが、 shiftifelseそして再帰を使えばループ処理をどんなに簡単に 実現できるか示しています。

次に挙げるのは、単純なforループを実現するマクロの例です。 単純な数え上げのためなどに使えます。

 
forloop(`i', 1, 8, `i ')
⇒1 2 3 4 5 6 7 8

それぞれの引数は順に、反復変数(iteration variable)の名前、 開始値、終了値、そして反復するたびに展開されるテキストです。 このマクロにおいて、マクロiはループ処理の内部でだけ定義されています。 iが以前に値を持っていた場合は、ループが終ればまたその値にもどります。

forloopは次のように入れ子にすることもできます。

 
forloop(`i', 1, 4, `forloop(`j', 1, 8, `(i, j) ')
')
⇒(1, 1) (1, 2) (1, 3) (1, 4) (1, 5) (1, 6) (1, 7) (1, 8)
⇒(2, 1) (2, 2) (2, 3) (2, 4) (2, 5) (2, 6) (2, 7) (2, 8)
⇒(3, 1) (3, 2) (3, 3) (3, 4) (3, 5) (3, 6) (3, 7) (3, 8)
⇒(4, 1) (4, 2) (4, 3) (4, 4) (4, 5) (4, 6) (4, 7) (4, 8)
⇒

forloopマクロはとても簡潔に実装することができます。 forloopマクロ自体は単なるラッパー(wrapper)で、 第1引数が持つ元の定義を保存してから、内部マクロ_forloopを呼び、 再び保存しておいた第1引数の定義を再確立します。

マクロ_forloopは第4引数を一度展開し、これで反復が終りかどうか調べます。 もし終りでなければ、反復変数を(すでに定義済みのマクロincrを使って) 1増やしてから、自分自身を再帰的に呼び出します。

forloopの実際の実装は次のようになります:

 
define(`forloop',
       `pushdef(`$1', `$2')_forloop(`$1', `$2', `$3', `$4')popdef(`$1')')
define(`_forloop',
       `$4`'ifelse($1, `$3', ,
		   `define(`$1', incr($1))_forloop(`$1', `$2', `$3', `$4')')')

注意深い引用符の使い方に注目してください。 マクロの引数でクォートされていないのは3つだけで、 それぞれに固有の理由があります。これら3つの引数がクォート されていないのはなぜか、その理由を見つけてください、 これらがクォートされていると、どうなるのかも確かめてみてください。

これら2つのマクロは便利ではありますが、 一般の使用に耐えるほど堅牢ではありません。 開始値が終了値より小さい場合や、第1引数が名前でなかった場合など、 初歩的なエラー対策さえ欠いています。 これら不備の訂正は、読者への課題として残しておきます。


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Akihiro Sagawa on June, 15 2005 using texi2html 1.70.