この章ではGNU m4とは何か、その由来、
このマニュアルの読み方と使い方、m4を起動する方法、
バグを報告する方法などを説明します。
マニュアルの続きを読むにあたっての助言をもってこの章は終ります。
ここから後の章ではm4言語のすべての機能を詳しく説明します。
m4序説
m4の由来
m4の起動(オプション一覧)
m4序説マクロを展開しながら入力を出力へコピーするという意味で、
m4はマクロプロセッサだと言えます。
マクロにはm4に最初から組み込まれている組み込み(builtin)マクロと、
ユーザが自分で定義するユーザ定義(user-defined)マクロの2種類があります。
マクロは何個でも引数を取ることができます。
m4には単なるマクロの展開機能に加え、ファイルのインクルード、
UNIXコマンドの実行、整数演算、さまざまな方法でのテキスト操作、再帰、
その他のための関数がそろっています。
m4はコンパイラのフロントエンドとして、
またマクロプロセッサそのものとしても使うことができます。
m4マクロプロセッサは、ほとんどすべてのUNIXで利用することができます。
通常その存在に気づいているのは、ほんのわずかな人たちだけです。
しかし実際に気づいた人たちは往々にして熱心なユーザとなります。
GNU Autoconfでconfigureスクリプトを生成するには
GNU m4が必要なため、GNU Autoconfの人気が高まったのがきっかけとなって
GNU m4をインストールする人が増えました。
もっとも、そういう人が自分でm4のプログラミングをすることは無いでしょう。
GNU m4はわずかな違いを除けばSystem V, Release 3 版
とほぼ互換性があります。
詳細はSee Compatibilityを参照してください。
ユーザの中にはm4中毒になってしまった人たちもいます。
そういう人たちは最初は簡単なことにm4を使い、
徐々に複雑なm4マクロの書き方を習得しながら、
大きなこと大きなことへと挑戦していくのです。
いちど病みつきになってしまえば、簡単な問題を解くためにさえ
洗練されたm4アプリケーションを書こうとして、実際の仕事よりも
自分のm4スクリプトのデバッグに多くの時間をさくことになるのです。
熱中しやすいプログラマーはm4で健康を損なうおそれがあるので
注意しましょう。
m4の由来ここに記されている歴史に関する覚え書きはきわめて不完全なものであり、 なんら権威あるものでもありません。 事情に通じている方は、どうかこの節をふさわしいものにするのを手伝ってください。
GPMはm4の重要な祖先です。
C. Stratchey: "A General Purpose Macro generator", Computer Journal
8,3 (1965), pp. 225 ff を参照してください。
GPMは、David Gries classic
"Compiler Construction for Digital Computers"でも簡潔に説明されています。
GPMは純粋だったのに対して、
m4は実生活にまつわる本物の複雑さを扱うことを意図したものでした。
そのため、たとえばマクロは前もって宣言しなくても認識されるようになり、
改行文字などの空白(whitespace)をスキップするのは簡単になり、
多くの構成要素が借り物ではなく内蔵されるようになりました。
もともとm4はRational FORTRANプリプロセッサ
すなわちratfor(cppに相当)のエンジンでした。
m4の起動(オプション一覧)m4コマンドの形式は次のようになります。
m4 [option...] [macro-definitions...] [input-file...]
オプションは、短いオプション名を使うときは-で始め、
長いオプション名を使うときは--で始めます。
長いオプション名は、
そのオプションだと明確に特定できる先頭部分を書くだけで十分です。
m4は次のオプションを受けつけます。
--version
m4の実行を終了します。
--help
m4の実行を終了します。
-G
--traditional
-E
--fatal-warnings
m4の実行を停止し終了します。
-dflags
--debug=flags
-lnum
--arglength=num
-ofile
--error-output=file
-Idir
--include=dir
m4がdirを探すようにします。
詳細はSee Search Pathを参照してください。
-e
--interactive
m4を対話的な状態(interactive)にします。
これは、すべての出力をバッファリング無しで行ない、
割り込み(interrupt)を無視するということを意味します。
-s
--synclines
m4をコンパイラのフロントエンドとして使うときなどに便利です。
ソースファイル名と行番号の情報は、#line linenum
"filename"という形式の指令(directive)によって表され、
出力の途中へ必要に応じて挿入されます。
この指令(directive)は次の行が入力ファイルfilenameの第linenum行
そのものであるか、もしくはその行の展開によるものであることを意味します。
"filename"の部分は、ファイル名が前の指令(directive)と
変わらない場合はしばしば省かれます。
このような同期指令は、必ずそれ自身で完全な一行に対してのみ与えられます。
ある出力行の途中に同期情報の食い違いがあるときは、
対応する同期指令の出力は次の行へと持ち越されます。
-P
--prefix-builtins
m4の内部に登録されているすべての組み込みマクロの名前を
接頭辞m4_が付いたものに変更します。
このオプションを使ったときには、たとえばdefineの代わりに
m4_define、__file__の代わりにm4___file__と書かなければ
なりません。
-WREGEXP
--word-regexp=REGEXP
m4の実装も存在する可能性があります。
(see Changeword)
-Hn
--hashsize=n
-Ln
--nesting-limit=n
このオプションの正確な効力は、動的な再帰構造というよりは、
テキスト上での入れ子構造に対して発揮されるものだと理解したほうが
より正しいでしょう。
このオプションは、機械的な方法でm4への複雑な入力を生成したときに
役に立ったという例もありますが、大部分のユーザにとっては無用の長物でしょう。
目障りだということになったときは、
このオプションは(まだ試験段階です)削除されてしまうかも知れません。
このオプションを使っても、再走査(rescanning)による無限ループを抜け出す
ことはできません。その一方で、再走査ループは必ずしもメモリや
スタック領域を大量に消費するわけではありません。
再走査ループをうまく使えば、複雑で時間のかかる処理をm4に
やらせることができます。
この領域に制限をもうけるのは、m4の能力を弱めてしまうことになるでしょう。
異常な使い方の例はいくらでもあります。
define(`a', `a')aはもっとも単純なものの例です
(しかしsee Compatibility)。
GNU m4がこのようなケースを検出するのを期待するのは、
コンパイラシステムが無限ループを検出し、診断メッセージをだすのを
期待することに似ています。つまり、決定不能ではないとしても、
一般にとてもハードな問題です。
-Q
--quiet
--silent
-B
-S
-T
m4との互換性のために存在しますが、
この実装では何の効果もありません。
-Nn
--diversions=n
m4の以前のバージョンとの互換性のためだけに
存在し、同時に使うことができる出力切替え先(diversion)の数を制御するために
使われていました。現在は固定された制限値がなくなったので、
何の働きもしません。
-Dや-Uオプションを使うと、コマンドライン上でマクロを定義したり
削除したりすることができます。これらは次の形式をとります。
-Dname
-Dname=value
--define=name
--define=name=value
=valueが省略されたときは、値は空文字列として
解釈します。valueはどんな文字列でもよく、したがって、マクロは入力内で
定義するときと同じように引数を取るものとして定義することもできます。
-Uname
--undefine=name
-tname
--trace=name
-Ffile
--freeze-state file
-Rfile
--reload-state file
コマンドライン上の残りの引数は、入力ファイルの名前として解釈します。
ファイル名の指定がないときは、標準入力から読み込みます。
ファイル名-は、標準入力を意味するものとして解釈します。
入力ファイルは、指定された順番に読み込みます。
標準入力は1度しか読むことができませんので、ファイル名-は
コマンドライン上で1度しか使えません。
GNU m4に関して問題が起こったり、バグと思われることを見つけたときは、
どうか、それを報告してください。
バグを報告する前に、実際にそれが本物のバグなのか確かめてください。
注意深くドキュメントを読み直して、あなたのしようとしたことが
実際にできると本当に書いてあるか確かめてください。
もし、ある事ができるのかどうか明確でないときは、それも報告してください。
それはドキュメントのバグです!
バグを報告したり、自分で直そうとする前に、
そのバグを発現させる、できるだけ小さな入力ファイルを作れないか試してください。
作ることができたときは、その入力ファイルとm4が出す正確な結果を
私たちに送ってください。
また、あなたはどうなるはずだと思ったのかも添えてください。
これは、問題の原因が実はドキュメントにあるものなのか判断するのに役立ちます。
問題を正確に把握したら、電子メールを(インターネット)
bug-m4@gnu.orgまたは(UUCP)
mit-eddie!prep.ai.mit.edu!bug-gnu-utilsへ送ってください。
あなたが使っているm4のバージョン番号も書いてください、
コマンドm4 --versionで調べられます。
バグの報告以外の提案もいつでも歓迎します。 ドキュメントの不明確なところや分かりにくい機能についての質問があれば、 それも送ってください。
このマニュアルにはm4の入力と出力の例がたくさん含まれており、
入力、出力およびm4からのエラーメッセージを区別するための
簡単な表記法が用いられています。
これらの例は通常の文章とは間隔をあけて、
固定幅フォントで次のように表示されます。
This is an example of an example!
入力と出力を区別するため、m4からの出力にはすべて=>が、
エラーメッセージにはすべてerror-->が先頭につきます。
したがって次のようになります。
Example of input line (入力行の例) =>Output line from m4 (m4からの出力行の例) error-->and an error message (エラーメッセージの例)
m4に最初から定義されているマクロの説明では、
マクロの呼び出しのプロトタイプが、引数に分かりやすい名前をつけて示されます。
これは次のようになります。
regexp(string, regexp, opt replacement)
m4ではマクロの引数はすべて文字列ですが、
数字、ファイル名、正規表現としてなど特別な解釈のされかたをするものもあります。
3番目の引数の前にあるoptは、この引数が省略可能であることを
表しています。省略したときは、空文字列として解釈されます。
引数リストの最後にある省略記号(...)は、その後にいくつでも引数を
続けてよいことを示してます。
m4は入力を読み込むと、それをトークン(token)に分割します。
トークン(token)は名前(name)、クォートされた文字列(quoted string)、
それらの構成要素とならない個々の文字のどれか1つの種類に属します。
また、m4への入力にはコメントを含めることができます。
名前(name)はアルファベット、数字、_(アンダースコア) を自由に並べたもののうち、先頭の文字が数字でないものです。 名前にマクロの定義が存在するときは、マクロの呼び出しとして認識され、 展開の対象となります(see Macros)。
正しい名前(name)の例を挙げるとfoo, _tmp,
name01などがあります。
クォートされた文字列(quoted string)は、 引用符`と'に囲まれた文字列のうち、 文字列の内部で開始引用符`と終了引用符'の数が釣り合っているものです。 クォートされた文字列(quoted string)のトークンとしての値は、 いちばん外側にある引用符を一対だけ取った文字列です。 したがって、
`'
の値は空文字列です。そして、
``quoted''
の値は次の文字列になります。
`quoted'
引用符を表す文字は、組み込みマクロchangequoteを使って、
いつでも替えることができます。
詳細はSee Changequoteを参照してください。
名前(name)とクォートされた文字列(quoted string)の構成要素にならない 文字はすべて、それ自身で一つのトークンとなります。
m4では通常#と改行文字で区切られた部分がコメントとなります。
これらコメントデリミタ(comment delimiters)の間にあるすべての文字は
処理の対象とならず無視されます。
しかし、コメントデリミタ(comment delimiters)を含むコメント全体は、
出力へそのまま流されて行きます。
つまりm4においてコメントは破棄されません。
コメントを入れ子にすることはできません。したがって、#の後の
最初の改行文字でコメントは終りとなります。
コメント開始文字をクォートすることによって、
コメント開始文字としての働きを抑制できます。
コメントデリミタ(comment delimiters)は組み込みマクロchangecomを
使って、いつでも好きな文字列に変更できます。
詳細はSee Changecomを参照してください。
この章ではマクロの呼び出し、マクロの引数、マクロの展開が行われる過程 について説明します。
マクロを呼び出すときの形式には次のものがあります。
name
これは引数を伴わないマクロの呼び出しです。
name(arg1, arg2, ..., argn)
これはn個の引数を伴うマクロの呼び出しです。 マクロはいくつでも引数を取ることができます。 すべての引数は文字列ですが、 マクロによって引数の解釈のしかたが違うことがあります。
開きカッコ(は、
スペースを入れずにnameの直後に書かなければいけません。
そうしないと、そのマクロは引数なしで呼び出されてしまいます。
引数なしでマクロを呼び出すためには、カッコを付けてはいけません。 たとえば、
name()
これは、空の文字列を1つだけ引数として持つマクロの呼び出しであり、 引数を伴わないマクロの呼び出しではありません。
先行するマクロプロセッサ(StratcheyのGPMなど)に比べて、
m4言語の革新的なところは、
先頭に特別な文字をつけて書くといったことをしなくても、マクロの呼び出しを識別できる能力です。
この機能は多くの場合において便利なのですが、
ときには不必要なマクロの呼び出しの原因となることがあります。
そこで、GNU m4には名前(name)がマクロの呼び出しとして認識されるのを
抑制するいくつかの機構やテクニックがあります。
まず、多くの組み込みマクロは引数なしで呼び出しても意味がないので、
それらの名前の直後に開きカッコがないときは、組み込みマクロは呼び出されません。
これによって、includeやevalがマクロとして認識されてしまう
といったよくあるケースに対処できます。
後ほど、この文書に出てくる
"このマクロは引数が与えられたときだけ認識されます"という文は、
この動作を意味します。
また、コマンドオプション(--prefix-builtins, または-P)
を使うと、組み込みマクロを呼び出すときは、
その名前の先頭にm4_をつけなければ認識されなくなります。
たとえばm4_dnlや、さらにはm4_m4exitと
書かなければならなくなります。
ちなみに、このオプションはユーザ定義のマクロには何の効果ももちません。
changeword機能がコンパイル時に組み込まれたm4を
使用しているときは、マクロ名の認識に使われる字句構成規則をはるかに柔軟に
指定することができます。
この規則は組み込みマクロとユーザ定義マクロ両方の名前に作用します。
この試験的な機能の詳細はSee Changewordを参照してください。
もちろん、ある名前がマクロの呼び出しとして認識されるのを防ぐ、 もっとも単純な方法は、その名前をクォートする(引用符で囲む)ことです。 この節の残り部分では、クォートすることがマクロの呼び出しにどのように 影響するのか、またマクロの呼び出しを抑制するには それをどのように使えよいのかを、もうすこし詳しく見ていきます。
マクロの呼び出しを抑制したいときは名前全体をクォートするのが普通ですが、 名前の数文字をクォートするだけでも同じ効果があります。 また、空文字列をクォートするだけでもよいのですが、 この場合は名前の内部でないと効果はありません。たとえば、
`divert' `d'ivert di`ver't div`'ert
これらの結果はすべて文字列divertとなりますが、
`'divert divert`'
こちらは両方とも組み込みマクロdivertが呼ばれます。
マクロを評価して生じた出力は常に再走査(rescan)されます。
次の例では、m4にsubstr(abcde, 3, 2)を入力として
与えたときと同様に、文字列deが生成されます。
define(`x', `substr(ab') define(`y', `cde, 3, 2)') x`'y
クォートされた文字列(quoted string)の両端にあるクォートされていない文字列は、
マクロ名として認識される対象となります。
次の例では、空文字列をクォートすることによって
dnlマクロが認識されるようになります。
define(`macro', `di$1') macro(v)`'dnl
もし引用符がなかったら、
文字列divdnlとそれに続く改行文字が生成されるだけでしょう。
クォートすることで、マクロ展開による文字列とその周囲の文字を連結したものが マクロの名前として認識されるのを防ぐことができます。たとえば、
define(`macro', `di$1') macro(v)`ert'
この入力からは、文字列divertが生み出されます。
もし引用符がなければ、組み込みマクロdivertが呼びだされるでしょう。
ある名前(name)が認識され、その名前に対するマクロの定義が存在するとき、 それはマクロとして展開されます。
その名前の直後に開きカッコ(があるときは、
引数をすべて集めてから、マクロが呼び出されます。
足りない引数があるときは、空文字列が与えられたものとして解釈されます。
余分な引数は無視されます。
組み込みマクロの呼び出しで引数の数が不足しているとき、通常ならm4は
警告を発しますが、コマンドライン・オプション-Qを使えば、
この警告を抑制できます。
ユーザ定義マクロに対する引数の個数チェックはありません。
引数の収集が行われているときでも、通常と同じようにマクロの展開は行われます。
そして展開後のテキストに出現した、コンマ、引用符、
カッコなどはすべて個々の引数の定義に寄与します。
したがってfooが, b, cに展開されるとき、
次のマクロの呼び出し、
bar(a foo, d)
は、4つの引数、a , b, c, d
を伴うマクロの呼び出しとなります。
なぜ最初の引数に空白(whitespace)が含まれているのか理解するには、
引数の前にある空白はすべて削除されるのに対して、引数の後ろにある空白は
削除されないことを覚えておくとよいでしょう。
個々の引数の前にある、クォートされていない空白(whitespace)は削除されます。 各引数の内部では、 クォートされていないカッコはすべて対になっていなければなりません。 たとえば、fooがマクロのとき、
foo(() (`(') `(')
これはひとつの引数を伴ったマクロの呼び出しで、
その引数の値は() (() (です。
引数自体がマクロ展開の対象になって欲しいのでない限り、 マクロへの引数はすべてクォートするのが普通です。 したがって、上記のカッコを含む例の`正しい'書き方は次のようになります。
foo(`() (() (')
しかし、ときにはいくつかの引数をクォートしないでおく必要がある場合もあり、 また、そうすることに何ら問題はありません。 ただ、注意を怠ると人生がすこしばかり厳しいものになるだけです。
マクロの呼び出しが引数を伴うときはその収集が行われたあと、 マクロは展開されます。 そして展開後のテキストは入力に(クォートされずに)戻され、 そして再び読み込まれます(再走査)。 したがって、マクロを1つ呼び出したことで得られたテキストの中に、 完全なマクロの呼び出しやその一部が含まれている場合、 そこから更に多くのマクロが呼ばれることもあるわけです。
非常に簡単な例を挙げると、fooがbarへ展開され、
barがHello worldへ展開されるとすると、入力
foo
は最初にbarへ展開された後、再び走査が行われ
Hello worldへ展開されます。
マクロはいくつかの異なる方法で定義、再定義、削除することができます。 また現在の定義を失うことなく一時的にマクロを再定義しておいて、 後で元の定義に戻すこともできます。
通常はマクロを定義したり再定義するときは、
組み込みマクロdefineを使います。
define(name [, expansion])
これはnameがexpansionに展開されるように定義します。 もしexpansionが与えられなかったときは、空文字列だと見なされます。
defineは展開されると消滅します。
次の例では、マクロfooがHello World.に展開されるように
定義しています。
define(`foo', `Hello world.') => foo =>Hello world.
出力に空行がある理由は、マクロ定義の直後にある改行文字が
定義の一部ではなく、従って出力にそのままコピーされるためです。
これはdnlマクロを使うことで避けることができます。
詳しくはSee Dnlを参照してください。
マクロdefineは引数が与えられたときだけ認識されます。
マクロは引数を取ることができます。n番目の引数は$nとして
展開用テキストの中で示し、マクロが展開されるときにn番目の
実引数(actual argument)に置き換えられます。
次の例は2つの引数を取るマクロです。2つの引数の順番を単純に交換します。
define(`exch', `$2, $1') => exch(arg1, arg2) =>arg2, arg1
これはdefineへの引数の順番を逆にしたいときなどに使えます。
define(`exch', `$2, $1') => define(exch(``expansion text'', ``macro'')) => macro =>expansion text
二重になった引用符の説明についてはSee Quoting Argumentsを参照してください。
GNU m4では$に続く数字は複数の桁でもよいので、
マクロはいくつでも引数を取ることができます。
これと違いUNIXのm4では1桁の数字しか認識されません。
特殊なケースとして、0番目の引数$0は常に現在展開されているマクロの名前
となります。
define(`test', ``Macro name: $0'') => test =>Macro name: test
クォートされたテキストを展開後のテキストに含めたい時は、 クォートは入れ子にできることを思い出しましょう。したがって、
define(`foo', `This is macro `foo'.') => foo =>This is macro foo.
展開されたテキストに含まれるfooは、
クォートされた文字列(quoted string)であり名前(name)ではないので、
再走査によって展開はされず、
引用符がはぎ取られるだけです(see Syntax)。
与えられた実引数(actual arguments)の個数や全ての実引数をまとめて表すための 特別な表記方法があります。
マクロを呼び出すときに与えられた実引数の個数は、
展開用テキストの中で$#として表します。
したがって与えられた実引数の個数を表示するマクロは次のようになります。
define(`nargs', `$#') => nargs =>0 nargs() =>1 nargs(arg1, arg2, arg3) =>3
展開用テキストの中で$*という表記をすることで、
全ての実引数を(クォートはせずに)コンマで区切ったものを表すことができます。
define(`echo', `$*') => echo(arg1, arg2, arg3 , arg4) =>arg1,arg2,arg3 ,arg4
引数をそれぞれクォートしなければならないことがよくありますが、
そんなときは$@という表記を使います。
これは各引数がクォートされることを除けば$*と同じです。
define(`echo', `$@') => echo(arg1, arg2, arg3 , arg4) =>arg1,arg2,arg3 ,arg4
引用符はどこに行ったのでしょうか? もちろん展開後のテキストを
再走査したときにm4が食べてしまったのです。
違いを見るために、次のようにしてみましょう。
define(`echo1', `$*') => define(`echo2', `$@') => define(`foo', `This is macro `foo'.') => echo1(foo) =>This is macro This is macro foo.. echo2(foo) =>This is macro foo.
これが理解できないときはSee Traceを参照してください。
展開用テキストに記号$が存在し、それに続く部分がm4に理解できる
ものでないときは、$は他のテキストと同じように
マクロ展開後のテキストへ単にコピーされます。
define(`foo', `$$$ hello $$$') => foo =>$$$ hello $$$
マクロを$12などに展開させたいときは、
$の後に一組の引用符を置きます。
これによって、m4がその$記号を引数への参照だと解釈してまうのを
防ぐことができます。
undefineを使えばマクロの定義を削除することができます。
undefine(name)
これによってマクロnameが削除されます。 展開されてしまうのを防ぐためにマクロの名前は必ずクォートしなくてはなりません。
undefineは展開されると消滅します。
foo =>foo define(`foo', `expansion text') => foo =>expansion text undefine(`foo') => foo =>foo
nameがマクロとして定義されていなくても問題はありません。
その場合はundefineが何もしないだけです。
undefineは引数が与えられたときだけ認識されます。
すでに定義済みのマクロの名前を替えることができます。
それには組み込みマクロdefnが必要となります。
defn(name)
これはnameの定義をクォートしたものに展開されます。 引数が定義済みのマクロでないときは展開されると消滅します。
nameがユーザ定義マクロの場合、
クォートされた定義とは単にクォートされた展開用テキストのことです。
nameが組み込みマクロの場合、
展開後のテキストは、m4の内部にある組み込みマクロの定義を指す
特殊なトークンとなります。
このトークンは、define (および pushdef)
の第2引数としてのみ意味を持ち、その他の文脈では無視されます。
通常の使用方法は、次の例でundefineの名前をzapに換える方法を
見るのが一番分かりやすいでしょう。
define(`zap', defn(`undefine')) => zap(`undefine') => undefine(`zap') =>undefine(zap)
このようにdefnはユーザ定義マクロの定義や
組み込みマクロの定義をコピーするために使うことができます。
たとえ元のマクロが削除されても、もう一方の名前を使って
定義にアクセスすることができます。
defnは引数が与えられたときだけ認識されます。
あるマクロを一時的に再定義しておき、後で元の定義に戻すことができます。
それにはdefineとundefineに良く似た、
組み込みマクロpushdefとpopdefを使います。
pushdef(name [, expansion]) popdef(name)
これらのマクロはスタック(stack)に似た仕組みで機能します。
pushdefは、あるマクロを一時的に再定義します。
このときnameの前の定義は、新しい定義によって置き換えられる前に
保存されます。もし前の定義が存在しない場合は、
pushdefはdefineとまったく同じように機能します。
あるマクロに複数の定義が存在する場合(その中の一つだけがアクセス可能です)、
popdefを使って一番上の定義を削除することができます。
前の定義が無い場合、popdefはundefineのように機能します。
define(`foo', `Expansion one.') => foo =>Expansion one. pushdef(`foo', `Expansion two.') => foo =>Expansion two. popdef(`foo') => foo =>Expansion one. popdef(`foo') => foo =>foo
defineによって、複数の定義を持つマクロを再定義したときは、
一番上の定義が新しい定義で置き換えられます。
undefineによって定義を削除するときは、
一番上のもの一つだけではなく、すべての定義が削除されます。
define(`foo', `Expansion one.') => foo =>Expansion one. pushdef(`foo', `Expansion two.') => foo =>Expansion two. define(`foo', `Second expansion two.') => foo =>Second expansion two. undefine(`foo') => foo =>foo
pushdefとdefnを使えば、組み込みマクロを一時的に再定義することが
できます。
マクロpushdefとpopdefは引数が与えられたときだけ認識されます。
indirを使うと、どんなマクロでも間接的に呼び出すことができます。
indir(name, ...)
indirはマクロnameを残りの引数と共に呼び出します。
これを"不正な"名前を持つマクロを呼ぶのに使うことができます(define
はそういう名前でも定義できます。)
define(`$$internal$macro', `Internal macro (name `$0')') => $$internal$macro =>$$internal$macro indir(`$$internal$macro') =>Internal macro (name $$internal$macro)
ここでの要点は、大きなマクロ・パッケージで、
間違って呼ばれてしまうことのないマクロを定義できるということです。
それらは組み込みマクロindirによってだけ呼びだすことができます。
builtinを使えば、組み込みマクロを間接的に呼び出すことができます。
builtin(name, ...)
これは組み込みマクロnameを、残りの引数と共に呼び出します。 たとえnameに本来の定義を隠している別の定義が与えられていても、 本来の定義を呼び出します。
マクロbuiltinは引数が与えられたときだけ認識されます。
単純なテキストに展開されるようなマクロだけでは、 引数を取ることができるとしても、十分ではありません。 実行時に下される判断にもとづいて、 異なる展開がおこなわれるようなマクロが必要でしょう。 たとえば、何らかの条件構文が必要です。 また、ある処理を何回も繰り返したり、条件が真の間だけ繰り返したりするために、 ある種のループ構文も必要でしょう。
m4には2つの異なる条件構文が組み込まれています。
その1つはifdefです。
ifdef(name, string-1, opt string-2)
これにより、あるマクロが定義されているかどうかをテストできるようになります。
nameがマクロとして定義されていれば
ifdefはstring-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は引数が与えられたときだけ認識されます。
もう一方の条件構文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-1とstring-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-1とstring-2が等しければifelseはequalに
展開され、等しくなければ最初の3つの引数が捨てられたあと、
まったくおなじ手続きが繰り返されます。例で示したほうがいいでしょう。
ifelse(foo, bar, `third', gnu, gnats, `sixth', `seventh') =>seventh
もちろん、通常はこれらの例よりもうすこし高度な使い方をするでしょう。
ifelseのよくある使い方のひとつは、さまざまな種類のループ処理を
実装するマクロの中で使用する場合です。
マクロifelseは引数が与えられたときだけ認識されます。
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
それほど興味深いマクロではありませんが、
shiftとifelseそして再帰を使えばループ処理をどんなに簡単に
実現できるか示しています。
次に挙げるのは、単純な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引数が名前でなかった場合など、 初歩的なエラー対策さえ欠いています。 これら不備の訂正は、読者への課題として残しておきます。
m4のマクロを書いていると、
(たいていのプログラミング言語の場合と同様に)
往々にしてそれらは思ったようには動いてくれないものです。
m4にはデバッグをサポートするしくみがいくつか存在します。
ある名前(name)が展開されるとどうなるのか調べたいときは、
組み込みマクロdumpdefを使用することができます。
dumpdef(...)
このマクロは任意の個数の引数を取ります。 引数を与ずに呼びだすと、既知の名前すべての定義を表示します。 それ以外の場合は与えた引数の定義を表示します。 出力は標準エラー出力に直接行われます。
dumpdefは展開されると消滅します。
define(`foo', `Hello world.') => dumpdef(`foo') error-->foo: `Hello world.' => dumpdef(`define') error-->define: <define> =>
最後の例は組み込みマクロの定義が表示される様子です。
表示の詳細を調整するための情報についてはSee Debug Levelsを参照してください。
組み込みマクロtraceonとtraceoffを使うと、マクロの呼び出しと
展開をトレース(trace)することができます。
traceon(...) traceoff(...)
traceonとtraceoffを引数なしで呼ぶと、
すべての定義済みのマクロに対してトレースがそれぞれオンまたはオフとなります。
引数を指定した場合、その名前のマクロに対してだけ作用します。
traceonとtraceoffは展開されると消滅します。
トレース中のマクロが呼ばれるたびに、 そのマクロの呼び出しに関する情報が引数の収集が終ったあとに表示されます。 マクロの呼び出しが展開後に消滅しないときは展開の結果が表示されます。 出力は標準エラー出力へ直接行われます。
define(`foo', `Hello World.') => define(`echo', `$@') => traceon(`foo', `echo') => foo error-->m4trace: -1- foo -> `Hello World.' =>Hello World. echo(gnus, and gnats) error-->m4trace: -1- echo(`gnus', `and gnats') -> ``gnus',`and gnats'' =>gnus,and gnats
ダッシュ(-)の間にある数字は展開の深さを表します。 たいていは最も外側のレベルでの展開を表す1となりますが、 クォートされていないマクロの呼び出しを引数が含む場合には増えていきます。
表示の詳細を調整するための情報についてはSee Debug Levelsを参照してください。
m4に-dオプションを与えることにより、
これまでの節に記載されているマクロを使ったときに表示される情報の詳しさを
制御します。
このオプションに続けて次のうち1つまたは複数のフラグを指定します。
t
m4におけるマクロの呼び出しを全てトレースします。
a
tフラグと共に指定したときは全てのマクロの呼び出しが対象となりますが、
それ以外の場合はtraceonマクロによってトレースしているマクロだけが
対象となります。
e
tフラグと共に指定したときは全てのマクロの呼び出しが対象となりますが、
それ以外の場合はtraceonマクロによってトレースしているマクロだけが
対象となります。
q
c
x
cフラグを使うときに便利です。
f
l
p
i
V
-dオプションに何もフラグを指定しない場合、
デフォルトでaeqが使われます。
前2つの節にある例は、これらデフォルトのフラグを使うことを想定しています。
組み込みマクロdebugmodeを使うと、実行時にデバッグ出力のフォーマットを
制御できます。
debugmode(opt flags)
引数flagsは上に列挙されている文字をいくつか指定します。
特殊なケースとして引数を+で始めると、
それらのフラグが現在のデバッグ・フラグ群に追加されます。
また引数を-で始めると現在のデバッグ・フラグ群から削除されます。
引数をまったく与えない場合、デバッグ・フラグは(-dを与えない場合と同様に)
すべてゼロに設定されます。
引数として空文字列を与えるとフラグはデフォルトの値にリセットされます。
デバッグとトレースの出力は、m4に-oオプションを与えるか
組み込みマクロdebugfileを使うことで、
ファイルにリダイレクト(redirect)することができます。
debugfile(opt filename)
このマクロにより、ここから後に発生するデバッグとトレースの出力は
すべてfilenameへ送られます。
filenameが空文字列のときはデバッグとトレースの出力は捨てられます。
debugfileが引数無しで呼ばれたときは、デバッグとトレースの出力は
標準エラー出力へ送られます。
この章ではm4への入力を制御するための、さまざまな組み込みマクロを
説明します。
組み込みマクロdnlは最初の改行文字までにある文字をすべて読み込んでから
改行文字も含めてそれらを捨てます。
dnl
次の例のようにdefineの呼び出しの後に続く改行を取り除くために、
defineと一緒によく使います。
define(`foo', `Macro `foo'.')dnl A very simple macro, indeed. foo =>Macro foo.
コメントの扱かわれ方とは対照的に(see Comments)、 次の改行までの入力が改行を含めて捨てられます。
通常dnlの後ろには、行末もしくはその他の空白(whitespace)が続きます。
GNU m4はdnlに続いて開きカッコがあるとき、
それを警告する診断メッセージを出します。
この場合dnlは閉じカッコを探しながら、すべての引数を集め処理します。
この引数の収集が原因となる予想可能な副作用はすべて起こります。
このときdnlは何も出力しません。
閉じカッコのあとに続く次の改行までの入力は、
それがどの行にあっても、改行を含めてやはり捨てられます。
デフォルトの引用符は組み込みマクロchangequoteによって変更できます。
changequote(opt start, opt end)
startは新しい開始引用符でendは新しい終了引用符です。
もし欠けている引数があるときは、デフォルトの引用符(` と ')
がその欠けている引数の代わりに使用されます。
changequoteは展開されると消滅します。
changequote([, ]) => define([foo], [Macro [foo].]) => foo =>Macro foo.
適切な文字がないときはstartやendを好きな長さにしてかまいません。
changequote([[, ]]) => define([[foo]], [[Macro [[[foo]]].]]) => foo =>Macro [foo].
引用符を両方とも空文字列にすると、事実上クォート機構が無効になり、 テキストをクォートする方法が無くなります。
define(`foo', `Macro `FOO'.') => changequote(, ) => foo =>Macro `FOO'. `foo' =>`Macro `FOO'.'
changequoteを使って現在の引用符を替えない限り、
終了引用符と対になっていない開始引用符を含む文字列をクォートする
方法はm4には存在しません。
入力に含まれる名前と混同されてしまうので、
どちらの引用符も記号でない普通の文字や_ (アンダースコア)で
始めるべきではありません。そうした場合はクォート機構が無効になります。
デフォルトのコメントデリミタ(comment delimiters)は
組み込みマクロchangecomで変更できます。
changecom(opt start, opt end)
startが新しいコメント開始デリミタ(start-comment delimiter)で、
endが新しいコメント終了デリミタ(end-comment delimiter)です。
欠けている引数があるときは、デフォルトのコメント区切り記号
(# と 改行文字)がその欠けている引数の代わりに使用されます。
コメント区切り記号は任意の長さにすることができます。
changecomは展開されると消滅します。
define(`comment', `COMMENT') => # A normal comment =># A normal comment changecom(`/*', `*/') => # Not a comment anymore =># Not a COMMENT anymore But: /* this is a comment now */ while this is not a comment =>But: /* this is a comment now */ while this is not a COMMENT
クォートされた文字列であるかのようにコメントが出力にコピーされている 様子に注目しましょう。 コメント内部のテキストが展開されるようにしたいときは、 コメント開始デリミタをクォートしてください。
引数なしでchangecomを呼ぶとコメント機構が完全に無効になります。
define(`comment', `COMMENT') => changecom => # Not a comment anymore =># Not a COMMENT anymore
マクロchangewordとそれに関連する機能すべてが実験段階にあります。 GNUm4をインストールする際configureに--enable-changewordオプションを与えたときだけ この機能を使用することができます。 これから先、この機能が変更されたり、削除されてしまうことさえありえます。 したがって、この機能に依存した使い方はしないで下さい。 この機能について意見を寄せていただくときは バグを報告するときと同じ方法で行ってください。
m4によって処理されるファイルは、クォートされた文字列、
単語(潜在的なマクロ名)、そして単純なトークン(その他の単一の文字すべて)
に分割されます。
初めに単語は次の正規表現によって定義されています。
[_a-zA-Z][_a-zA-Z0-9]*
changewordを使えばこの正規表現を変更できます。
たとえば数字が含まれているファイルに置換をかけたい場合など、
m4の字句構成規則を緩めると便利なときもあるでしょう。
changeword(`[_a-zA-Z0-9]+') define(1, 0) =>1
字句構成規則を厳しくすると 組み込みマクロのうちいくつかが使えなくなってしまうことが多いので、 緩める場合ほど便利にはなりません。 次の例のように、間違って組み込みマクロを呼んでしまうのを避けるために 使うことはできるでしょう。
define(`_indir', defn(`indir')) changeword(`_[_a-zA-Z0-9]*') esyscmd(foo) _indir(`esyscmd', `ls')
m4は単語を一度に一文字ずつ構築するので、
changewordに渡すことができる正規表現には制限があります。
これは、もし指定した正規表現がfooを受理するなら
fとfoも受理しなければならないというものです。
changewordには、もう一つ機能があります。
指定した正規表現に角括弧でくくられた部分が1つ以上存在する場合、
最初の角括弧でくくられた部分の外側にあるテキストが、
シンボルを表引きする前に捨てられます。
changecom(`/*', `*/') changeword(`#\([_a-zA-Z0-9]*\)') #esyscmd(ls)
こうすると、m4はすべてのマクロの呼び出しの先頭に
#記号を必要とするようになるので、
m4でシェル・スクリプトを処理するときに
shiftコマンドがm4に飲み込まれてしまわないようにできます。
また種々の一般的な単語を失うことなくプレーン・テキストを
処理できるようになります。
m4のマクロ置換はテキストに基づいていますが、
TeXのものはトークンに基づいています。
changewordによって、この違いを浮き彫りにすることができます。
たとえば次の例は同じアイデアをTeXとm4で表現したものです。
初めはTeX バージョンからです。
\def\a{\message{Hello}}
\catcode`\@=0
\catcode`\\=12
=>@a
=>@bye
つぎにm4バージョンです。
define(a, `errprint(`Hello')') changeword(`@\([_a-zA-Z0-9]*\)') =>@a
TeXの例において、最初の行はマクロaがHelloメッセージを
表示するように定義しています。
2行目は<\>の代わりに<@>をエスケープ文字として使えるように
定義しています。
3行目は<\>をエスケープ文字ではなく通常の表示可能文字として
定義しています。
4行目はマクロaを呼び出しています。
したがって、このファイルに対してTeXを走らせると
Helloメッセージを表示します。
m4の例をm4に与えるとerrprint(Hello)を表示します。
この理由はTeXはマクロが定義されたときにマクロ定義の字句解析
を行うのに対し、m4は単純にテキストを保存しておき、
字句解析は実際にマクロが使われるまで後回しにするからです。
changewordを使用すると、
m4の速度が7倍ほど遅くなることに注意してください。
通常の入力が終りになるまで、テキストを`保存(save)'しておくことができます。
保存されたテキストは通常の入力が終った段階でm4に再び読み込まれます。
通常この機能は一時ファイルの削除など正常終了前に行うクリーンアップ動作を
開始するために使われます。
入力テキストを保存するためには組み込みマクロm4wrapを使います。
m4wrap(string, ...)
stringと残りの引数は入力が終端に達したときに再び読み込まれるように 安全な場所に保存されます。
define(`cleanup', `This is the `cleanup' actions. ') => m4wrap(`cleanup') => This is the first and last normal input line. =>This is the first and last normal input line. ^D =>This is the cleanup actions.
保存されている入力は通常の入力が終端に達したときだけ再び読み込まれます。
m4を終了するためにm4exitが使われたときは再読み込みは行われません。
保存されたテキストの中でm4wrapを呼びだしても差し支えありませんが、
そのとき保存されたテキストが再読み込みされる順番は決まっていません。
m4wrapが再帰的に使われていない場合、
保存された各テキストはそれぞれが保存されたのと逆の順番(LIFO--last in, first out)で再読み込みが行われます。
m4では入力のどこででも名前を指定してファイルをインクルード
(include)することができます。
m4にはファイルをインクルードするための組み込みマクロが2つあります。
include(filename) sinclude(filename)
どちらもfilenameという名前のファイルをm4に読み込ませます。
そのファイルの終りに達すると以前の入力ファイルから入力を再開します。
したがってincludeとsincludeは展開後に
filenameの内容となります。
includeに指定したファイルが存在しないとエラーとなります。
ファイルが存在しない事についてのエラー・メッセージを避けたいときは
sincludeを使います。
sincludeは、もしファイルが存在すればそのファイルをインクルードし、
存在しなければ消滅します。
include(`no-such-file') => error-->30.include:2: m4: Cannot open no-such-file: No such file or directory sinclude(`no-such-file') =>
これ以降ファイルincl.m4の内容は仮に以下のものだとします。
Include file start foo Include file end
通常、ファイルのインクルードはファイルの内容を入力ストリームに挿入するために
使用されます。
インクルードされたファイルの内容はm4によって読まれ、
そのファイルに含まれるマクロの呼び出しは展開されます。
define(`foo', `FOO') => include(`incl.m4') =>Include file start =>FOO =>Include file end =>
includeとsincludeがファイルの内容に展開されることを利用して、
ファイル全体に作用するマクロを定義することができます。
次の例ではbarがincl.m4の内容に展開されるように定義しています。
define(`bar', include(`incl.m4')) => This is `bar': >>>bar<<< =>This is bar: >>>Include file start =>foo =>Include file end =><<<
もっとも、これはincludeのごく普通の使い方ではありません。
なぜならファイルにはクォート、コンマ、括弧といった
m4のパーサ(parser)が動作する方法に干渉するものが含まれている可能性が
あるからです。
組み込みマクロincludeとsincludeは引数が与えられたときだけ認識されます。
GNU m4ではインクルード対象のファイルが現在の作業ディレクトリ
(current working directory)とは別のディレクトリにあっても
かまいません。
ファイルが現在の作業ディレクトリで見つからずファイル名が絶対ファイル名(absolute file name)でないとき、GNU m4は指定されたサーチ・パス(search path)
を使ってそのファイルを探します。
最初に-Iオプションで指定された各ディレクトリの中を
コマンド・ラインに書かれている順番に探します。
次に環境変数M4PATHが設定されているときは、
それをコロン(:)で区切られたディレクトリのリストとして解釈し、
それらの中を順に探します。
インクルード・ファイルの自動サーチがトラブルの原因となったときは、
pデバッグ・フラグ(see Debug Levels)が問題の切り分けに
役立つでしょう。
出力切替え(diversions)は出力を一時的に保存しておく方法です。
m4では好きなときに出力を一時ファイルへ切替え(divert)ておき、
後で再び出力ストリームへと逆切替え(undiverted)することができます。
切り替え先番号は0から数え上げます。切替え先0は通常の出力ストリームです。
同時に存在できる切替え先の数は主としてそれらを記述するために使われるメモリ
によって制限されます。
これはGNU m4が切替え先の情報とそこへの出力をメモリに置いておこうとするためです。
しかし全ての切替え先に必要なメモリの総量には制限があります(現在は512Kです)。
この最大値を超えそうになったときは、一番大きな切替え先の内容を入れるために
一時ファイルが作られ、その分のメモリが他の切替え先のために開放されます。
したがって理論上は切替え先の数が利用可能なファイル・ディスクリプタの数
によって制限されることがありえます。
出力はdivertを使って切替えます。
divert(opt number)
numberは使用する切替え先です。 numberを省略したときはゼロとして解釈されます。
divertは展開されると消滅します。
m4への入力がすべて処理されると、
その時点で存在するすべての切替え先が自動的に番号の順で逆切替え(undivert)
されて、そこにたまっていたテキストが出力されます。
divert(1) This text is diverted. divert => This text is not diverted. =>This text is not diverted. ^D => =>This text is diverted.
同じ引数でdivertを何回か呼び出すと、
切替え先にある以前のテキストは上書きされずに、
新しいテキストが以前のテキストの後に追加されてゆきます。
存在するはずのない切替え先へ出力を切替えるとそこから後の出力は 単に捨てられます。 よくある不要な出力の例はマクロ定義の後にある改行です。 次はそれらを避ける方法です。
divert(-1) define(`foo', `Macro `foo'.') define(`bar', `Macro `bar'.') divert =>
これはm4でのプログラミング上の一般的な慣用句のひとつです。
切替え先に出力されたテキスト(diverted text)は組み込みマクロ
undivertを使って明示的に逆切替え(undivert)することができます。
undivert(opt number, ...)
このマクロは引数で指定された切替え先を、指定された順に逆切替えして出力します。 引数が与えられなかったときは、すべての切替え先を番号順に逆切替えします。
undivertは展開されると消滅します。
divert(1) This text is diverted. divert => This text is not diverted. =>This text is not diverted. undivert(1) => =>This text is diverted. =>
最後にある2つの空行に注目してください。
1つはundivertに続く改行によるもので、
もう一方はなんとdivertに続く改行によるものです!
切替え先のテキストはしばしばこのような空行で始まります。
切替え先のテキスト(diverted text)は逆切替え(undiverted)されると、
m4によって再走査されずに、
現在の出力(切替え先)に直接コピーされます。
したがってある切替え先(diversion)に出力中に、
逆切替え(undivert)しても問題ありません。
逆切替えをすると、その切替え先にあるテキストは破棄されるので そのテキストを取り出せるのは1回だけです。
divert(1) This text is diverted first. divert(0)undivert(1)dnl => =>This text is diverted first. undivert(1) => divert(1) This text is also diverted but not appended. divert(0)undivert(1)dnl => =>This text is also diverted but not appended.
現在の切替え先(current diversion)を逆切替え(undivert)しようとしても 黙殺されます。
GNU m4では名前を指定したファイルを逆切替え(undivert)することができます。
数字以外の引数を与えると、その名前をもつファイルの内容が
現在の出力(切替え先)に解釈されずにコピーされます。
これによって組み込みマクロincludeの機能が補完されます(see Include)。
次の例で違いを説明します。ファイルfooの内容はbarだとします。
define(`bar', `BAR') => undivert(`foo') =>bar => include(`foo') =>BAR =>
組み込みマクロdivnumは現在の切替え先(current diversion)の番号に展開されます。
divnum
Initial divnum =>Initial 0 divert(1) Diversion one: divnum divert(2) Diversion two: divnum divert => ^D => =>Diversion one: 1 => =>Diversion two: 2
逆切替えされて出力されるテキスト自身が切替え先に出力されてしまうのを防ぐため
に最後にある引数無しのdivertの呼び出しが必要です。
(訳者注: 誤訳の可能性あり。原文は
The last call of divert without argument is necessary, since the
undiverted text would otherwise be diverted itself.)
出力を切替えているときは切替え先のテキストが実際に
必要となるかどうかは分からないことがよくあります。
テキストが溜っている切替え先は入力が終りに達した段階で
メインの出力ストリームにすべて出力されるので、
切替え先にたまっているテキストを破棄するためのなんらかの手段が必要です。
すべての切替え先のテキストを破棄したいときは
m4への入力をdivert(-1)とそれに続く明示的なundivert
で終えるのが最も簡単でしょう。
divert(1) Diversion one: divnum divert(2) Diversion two: divnum divert(-1) undivert ^D
このとき出力はいっさいありません。
特定の切替え先のテキストは次のマクロで消去できます。
define(`cleardivert', `pushdef(`_num', divnum)divert(-1)undivert($@)divert(_num)popdef(`_num')') =>
undivertと同じように呼び出しますが、
その効果は引数として与えられた切替え先のテキストを消去することです。
(このマクロにはひどいバグがあります!
それを見つけて直せるか挑戦してみてください。)
m4には部分文字列の抽出、検索、置換など様々な方法でテキストを
操作するための組み込みマクロがあります。
文字列の長さはlenで計算できます。
len(string)
このマクロはstringの長さを表す10進数に展開されます。
len() =>0 len(`abcdef') =>6
組み込みマクロlenは引数が与えられたときだけ認識されます。
部分文字列の検索はindexで行います。
index(string, substring)
このマクロはstringの中でsubstringが最初に出現する
位置のインデックスに展開されます。
stringの先頭にある文字のインデックスは0です。
substringがstringに含まれないとき、
indexは-1に展開されます。
index(`gnus, gnats, and armadillos', `nat') =>7 index(`gnus, gnats, and armadillos', `dag') =>-1
組み込みマクロindexは引数が与えられたときだけ認識されます。
正規表現の検索は組み込みマクロregexpで行います。
regexp(string, regexp, opt replacement)
このマクロはregexpでstringの中を検索します。 正規表現の構文はGNU Emacsのものと同じです。 See Regexps.
replacementを省略すると、regexpはstringの中で
regexpに最初にマッチした部分のインデックスに展開されます。
regexpがstringのどこにもマッチしない場合は-1に展開されます。
regexp(`GNUs not Unix', `\<[a-z]\w+') =>5 regexp(`GNUs not Unix', `\<Q\w*') =>-1
replacementを与えたときは、regexpはこの引数の値に展開されます。
このときreplacementに含まれる\nはregexp中の
n番目のカッコでくくられた部分式にマッチしたテキストに置き換えられ、
\&は正規表現全体にマッチしたテキストに置き換えられます。
regexp(`GNUs not Unix', `\w\(\w+\)$', `*** \& *** \1 ***') =>*** Unix *** nix ***
組み込みマクロregexpは引数が与えられたときだけ認識されます。
部分文字列はsubstrを使って抽出します。
substr(string, from, opt length)
このマクロはstringのインデックスfromから始まるlength文字分 の部分文字列に展開されます。 lengthを省いたときはstringの最後までになります。 文字列の最初のインデックスは常に0です。
substr(`gnus, gnats, and armadillos', 6) =>gnats, and armadillos substr(`gnus, gnats, and armadillos', 6, 5) =>gnats
組み込みマクロsubstrは引数が与えられたときだけ認識されます。
文字の置き換えはtranslitで行います。
translit(string, chars, replacement)
stringの各文字のうちcharsに出て来る文字をそれぞれ replacement中で同じ位置にある文字に置き換えたものに展開されます。
replacementがcharsより短いときは 余分な文字は展開後のテキストから削除されます。 replacementを省略すると展開後のテキストは stringからcharsに含まれる文字すべてを削除したものになります。
charsとreplacementのどちらにも文字範囲を含めることができます。
たとえばa-z (すべての小文字アルファベット)や
0-9 (すべての数字)などです。
charsやreplacementにダッシュ-そのものを含めるときは
最初か最後に置いてください。
範囲の最後の文字が最初の文字より`小さい'場合もエラーではありません。
そういうケースでは範囲が逆に広がります。
つまり9-0は文字列9876543210を意味します。
translit(`GNUs not Unix', `A-Z') =>s not nix translit(`GNUs not Unix', `a-z', `A-Z') =>GNUS NOT UNIX translit(`GNUs not Unix', `A-Z', `z-a') =>tmfs not fnix
最初の例は大文字のアルファベットをすべて削除します。 2番目の例は小文字を大文字に変換します。 3番目の例は大文字すべてを小文字に変換しながら`反射'させます。 最初の2つの例のほうがはるかに一般的です。
組み込みマクロtranslitは引数が与えられたときだけ認識されます。
文字列の全置換(global substitution)はpatsubstで行います。
patsubst(string, regexp, opt replacement)
string中でregexpにマッチする部分を探し、 それらをすべてreplacementに置換します。 正規表現の構文はGNU Emacsのものと同じです。
stringの中でregexpに適合するどの箇所にも含まれない部分は展開後の テキストにそのまま残ります。 マッチする箇所が見つかるたびに、 サーチはそのマッチした箇所の終りから続行されます。 したがってstring内のある文字が2回置換されることは決してありません。 regexpが長さ0の文字列にマッチしたときは、 無限ループを避けるためサーチの開始位置は1文字前に進められます。
置き換えが行われるときは、replacementに含まれる\nを
regexp内のカッコでくくられたn番目の部分式にマッチしたテキスト
に置き換え、\&を正規表現全体にマッチしたテキストに置き換えたものが
展開後のテキストに挿入されます。
引数replacementは省略することができます。 そのときはregexpにマッチしたテキストは削除されます。
patsubst(`GNUs not Unix', `^', `OBS: ') =>OBS: GNUs not Unix patsubst(`GNUs not Unix', `\<', `OBS: ') =>OBS: GNUs OBS: not OBS: Unix patsubst(`GNUs not Unix', `\w*', `(\&)') =>(GNUs)() (not)() (Unix) patsubst(`GNUs not Unix', `\w+', `(\&)') =>(GNUs) (not) (Unix) patsubst(`GNUs not Unix', `[A-Z][a-z]+') =>GN not
次はもうすこし現実的な例です。
文字列の中にマクロupcaseとdowncaseの呼び出しを挿入することで、
単独の単語または文全体をキャピタライズ(capitalize)します。
define(`upcase', `translit(`$*', `a-z', `A-Z')')dnl
define(`downcase', `translit(`$*', `A-Z', `a-z')')dnl
define(`capitalize1',
`regexp(`$1', `^\(\w\)\(\w*\)', `upcase(`\1')`'downcase(`\2')')')dnl
define(`capitalize',
`patsubst(`$1', `\w+', `capitalize1(`\&')')')dnl
capitalize(`GNUs not Unix')
=>Gnus Not Unix
組み込みマクロpatsubstは引数が与えられたときだけ認識されます。
書式付き出力はformatを使って行うことができます。
format(format-string, ...)
このマクロはC言語の関数printfとよく似た動作をします。
最初の引数は書式指定文字列で%指定を含めることができます。
formatは展開されると書式付の文字列になります。
2, 3の例を使って説明するのが一番でしょう。
define(`foo', `The brown fox jumped over the lazy dog') => format(`The string "%s" is %d characters long', foo, len(foo)) =>The string "The brown fox jumped over the lazy dog" is 38 characters long
See Loopsで定義されているforloopマクロを使って
表形式の出力をするときはformatを次のように使うことができます。
forloop(`i', 1, 10, `format(`%6d squared is %10d ', i, eval(i**2))') => 1 squared is 1 => 2 squared is 4 => 3 squared is 9 => 4 squared is 16 => 5 squared is 25 => 6 squared is 36 => 7 squared is 49 => 8 squared is 64 => 9 squared is 81 => 10 squared is 100
組み込みマクロformatはANSI Cのprintf関数をモデルとしており、
次の標準的な%指定をサポートしています: c, s, d,
o, x, X, u, e, E, f。
またフィールド幅と精度指定、モディファイア +, -, ,
0, #, h, lをサポートしています。
printfの動作について更に詳しいことは
C ライブラリ・マニュアルを見てください。
m4にはCに似た文法の整数演算機構が組み込まれています。
単純なインクリメントとデクリメント操作のための組み込みマクロが便利な省略記法
として用意されています。
整数のインクリメントとデクリメントは組み込みマクロincrとdecr
によってサポートされています。
incr(number) decr(number)
numberの数値を1だけ増やした値または1だけ減らした値に展開されます。
incr(4) =>5 decr(7) =>6
組み込みマクロincrおよびdecrは引数が与えられたときだけ認識されます。
整数式はevalを使って計算します。
eval(expression, opt radix, opt width)
このマクロはexpressionの値に展開されます。
式(expression)には次の演算子を含めることができます。 リストは優先順位の高い順に並んでいます。
-
**
* / %
+ -
<< >>
== != > >= < <=
!
~
&
^
|
&&
||
累乗(exponentiation)を除いたすべての演算子は左結合(left associative)をします。
m4の実装には^を累乗(exponentiation)演算子の代用として使うもの
が多くありますが、^をビットごとの排他的論理和(bitwise exclusive-or)
に使っている実装も多くあります。
GNU m4はこの点について動作の変更を行いました。
かつて^は累乗を行う演算子でしたが、
現在はビットごとの排他的論理和を行う演算子となっています。
特別な接頭辞(prefix)がついていない数字は10進数となります。
0のみの接頭辞は8進数の始まりを表します。
0xは16進数の始まりを表します。
0bは2進数の始まりを表します。
0rは1から36までの任意の基数で表現した数字の始まりを表します。
0rの後には10進数で表現した基数、コロン(:)、
および数値を表す数字列を続けなくてはなりません。
いずれの基数で表すにしても数字としては0, 1, 2,
...を使い、9から上はa, b ... zまでを
数字として使います。
アルファベットの大文字と小文字は基数を表す接頭辞および数値を表す数字列のなかで
区別なく使用することができます。
部分式をグループ化するために必要なときはカッコを使うことができます。
関係演算子は関係が真のときは1を返し、偽のときは0を返します。
evalの使用例をいくつか次に挙げます。
eval(-3 * 5) =>-15 eval(index(`Hello world', `llo') >= 0) =>1 define(`square', `eval(($1)**2)') => square(9) =>81 square(square(5)+1) =>676 define(`foo', `666') => eval(`foo'/6) error-->51.eval:14: m4: Bad expression in eval: foo/6 => eval(foo/6) =>111
最後から2番目の例が示しているようにevalがマクロ名を勝手に展開することはありません。
例えそれらが有効な式(もしくは有効な式の一部)に展開されるにしてもです。
したがって全てのマクロはevalに渡される前に展開済である必要があります。
radixを指定すると展開後のテキストではその基数が使われます。
デフォルトの基数は10です。
evalの結果は常に符号付き(signed)であると解釈されます。
引数widthは最低限の出力幅を指定します。
展開後のテキストが要求された幅になるように
結果には0が埋め草(zero-padded)として付加されます。
eval(666, 10) =>666 eval(666, 11) =>556 eval(666, 6) =>3030 eval(666, 6, 10) =>0000003030 eval(-666, 6, 10) =>-000003030
radixは36より大きくてはいけないことに注意してください。
組み込みマクロevalは引数が与えられたときだけ認識されます。
UNIXコマンドをm4内部から呼び出すための組み込みマクロが
m4にはいくつか存在します。
syscmdを使えば任意のシェル・コマンドを実行することができます。
syscmd(shell-command)
シェル・コマンドとしてshell-commandを実行します。
syscmdは展開後、shell-commandからの出力にはならずに
消滅します。shell-commandからの出力やエラー・メッセージは
m4には読み込まれません。
コマンドの出力を処理する必要があるときはSee Esyscmdを参照してください。
コマンドの実行に先立ち、m4は自分の出力バッファをフラッシュします。
shell-commandにおけるデフォルトの標準入力、標準出力、
および標準エラー出力はm4のものと同じです。
syscmdは引数が与えられたときだけ認識されます。
UNIXコマンドの出力をm4に読み込ませたいときは
esyscmdを使ってください。
esyscmd(shell-command)
シェル・コマンドshell-commandの標準出力の内容に展開されます。
m4はコマンドの実行に先立ち自分の出力バッファをフラッシュします。
shell-commandのデフォルトの標準入力および標準エラー出力は
m4のものと同じになります。
shell-commandのエラー出力は展開テキストの一部にはなりません
― m4のエラー出力と一緒に出てくるでしょう。
次の例ではGNU m4ディストリビューションのchecksディレクトリ
にいると仮定します。
define(`vice', `esyscmd(grep Vice ../COPYING)') => vice => Ty Coon, President of Vice =>
esyscmdの展開によるテキストの後ろに改行がついている様子に
注意してください。
esyscmdは引数が与えられたときだけ認識されます。
シェル・コマンドの実行が成功したか調べるときは
sysvalを使ってください。
sysval
このマクロはsyscmdやesyscmdで実行した最後のシェル・コマンドの
終了ステータス(exit status)に展開されます。
syscmd(`false') => ifelse(sysval, 0, zero, non-zero) =>non-zero syscmd(`true') => sysval =>0
syscmdやesyscmdに指定されたコマンドが出力やその他の目的で
一時ファイルを必要とすることもあるでしょう。
一時ファイルの名前を生成するために組み込みマクロmaketempが用意されています。
maketemp(template)
このマクロはtemplateを元に作られた現時点で存在しないファイルの名前に
展開されます。
templateは文字列XXXXXXで終らなければなりません。
この6つのXはファイル名をユニークにするためにm4のプロセスidを含む何らかの文字列によって置き換えられます。
maketemp(`/tmp/fooXXXXXX') =>/tmp/fooa07346 maketemp(`/tmp/fooXXXXXX') =>/tmp/fooa07346
例にあるようにmaketempを複数回呼び出すと同じ文字列に展開されることが
あります。これは選択の基準がファイルが存在するかどうかだからです。
maketempを次に呼び出すより前にファイルが作成されていないときは、
2つのmaketempマクロの呼び出しは同じ名前に展開される可能性があります。
maketempは引数が与えられたときだけ認識されます。
この章では、これまでのどの章にも分類できない様々な組み込みマクロを説明します。
エラー・メッセージはerrprintを使って表示することができます。
errprint(message, ...)
このマクロはmessageと残りの引数の標準エラー出力への表示だけを行います。
errprintは展開されると消滅します。
errprint(`Illegal arguments to forloop ') error-->Illegal arguments to forloop =>
末尾の改行は自動的に印字されません。したがって例にあるように
引数の一部として与えなければなりません。
(BSDフレーバーのm4はerrprint呼び出しごとに
改行を1つ末尾に付加します。)
エラーの場所を特定するための組み込みユーティリティ・マクロが2つあります。
__file__ __line__
これらは現在の入力ファイルの名前をクォートしたものと そのファイル内での現在の入力行番号に展開されます。
errprint(`m4:'__file__:__line__: `Input error ') error-->m4:56.errprint:2: Input error =>
m4を終了させるすべての入力を読み込んでしまう前にm4を終了したいときは
m4exitを使うことができます。
m4exit(opt code)
このマクロはcodeを終了コード(exit code)としてm4を終了させます。
codeを省略したときは終了コードは0になります。
define(`fatal_error', `errprint(`m4: '__file__: __line__`: fatal error: $* ')m4exit(1)') => fatal_error(`This is a BAD one, buster') error-->m4: 57.m4exit: 5: fatal error: This is a BAD one, buster
この例ではfatal_errorマクロが呼び出された後、
m4は終了コード1で終了します。
このマクロはエラー終了を行うためだけのものです、なぜなら通常の終了手続き、
例えば出力切替え先のテキスト(diverted text)の
逆切替え(undivert)や保存されているテキスト(see M4wrap)の再読み込みなど
が行われないからです。
何百もの定義や手間がかかるその他の初期化を含んだ共通の基盤を土台として、
複数のさらに大きなm4アプリケーションを構築することができます。
通常はその共通の基盤を1つ以上のファイルに格納しておいて
m4を起動するたびにユーザの入力ファイル名の前に
それらのファイル名を羅列するか、ユーザの入力ファイルからincludeします。
巨大なアプリケーションの共通基盤を何度も何度も繰り返し読み込むのは
時間がかかることでしょう。
m4には大きな共通基盤を使うアプリケーションの開始時間をスピードアップ
するための機構が用意されています。
ユーザが次のコマンドラインを繰り返し使うとします。
m4 base.m4 input.m4
ここでinput.m4は起動のたびにいろいろな内容を持ち、
base.m4はかなり固定的な内容を持っているとします。
こういうときは、次のようにした方がいいでしょう。
m4 -F base.m4f base.m4
このように一度実行しておき、必要になるたびに次のように実行します。
m4 -R base.m4f input.m4
最初の-Fオプションを含んでいる呼び出しは
base.m4を読んで実行することで、
さまざまなアプリケーション・マクロの定義やその他の初期化を行います。
入力ファイルbase.m4の処理が完全に終ってから、
GNU m4は凍結(frozen)ファイルをbase.m4fとして生成します。
このファイルはm4の内部状態のある種のスナップ・ショットとなっています。
後者の-Rオプションを含んでいる呼び出しでは、
どの入力ファイルが読み込まれるよりも前に、
base.m4fからm4のメモリの内部状態をリロード(reload)しておく
ことができます。
このようにして、まっさらな状態のm4から始めるかわりに
前に起動したときの結果を効率的に回復したあとで入力を読み込みます。
この例ではbase.m4を新たに読んだときと効果は同じですが、
それをずっと速く行うことができます。
m4の1回の起動で作ったり読み込んだりできる凍結ファイルはそれぞれ
1つだけです。
一度に2つの凍結ファイルの内容を回復することはできません。
しかし-Rと-Fオプションを同時につかうことで、
凍結ファイルをすこしずつ更新していくことはできます。
m4 file1.m4 file2.m4 file3.m4 file4.m4
これは多少の注意を払えば同じ出力を段々と蓄積していく 次の一連のコマンドに分けることができるでしょう。
m4 -F file1.m4f file1.m4 m4 -R file1.m4f -F file2.m4f file2.m4 m4 -R file2.m4f -F file3.m4f file3.m4 m4 -R file3.m4f file4.m4
多少の注意を払う必要があるというのは、
これがどんな場合にもうまく動くようにするための
あらゆる対策がなされているわけではないからです。
とりわけ、マクロのトレース属性には対応していませんし
changewordの現在の設定に関してもそうです。
m4のいくつかのオプションが1度目で使用されて、次は使用されなかった
場合にどのように作用するかも完全には検討されていません。
一方でpushdefされた定義のスタックが正しく扱われることは
保証されています。
またundefineされた定義、組み込みマクロの名前変更、
引用符やコメント記号の変更についても同様です。
m4の実行が凍結されるとき、
実行終了時に起こる自動的な出力の逆切替え(undiversion)は抑制されます。
そのかわり全ての正の番号をもつ出力切替え先の内容は凍結ファイルに
保存されます。使用中の出力切替え先の番号も伝えられます。
リロード(reload)しようとしている凍結ファイルが
カレント・ディレクトリにある必要はありません。
凍結ファイルを探す方法はインクルード・ファイル(include)の場合と同じです。
(see Search Path)
凍結ファイルは複数のアーキテクチャで共有することができます。
1つのマシンで凍結ファイルを作り、それを他のマシンで使うときは
2番目のマシンで同じか新しいバージョンのGNU m4を使っているなら確実です。
これらのファイルは単純な(編集可能な)テキストファイルで、
アルファベット大文字で始まり改行文字(<NL>)で終る指令から成り立っています。
指令があるはずの場所に#があるときはコメント行の始まりとなり、
空行とあわせて無視されます。
次の説明ではlengthは常に対応するstringを参照します。
数字は常に10進数で表されます。
指令の一覧です。
V number <NL>
C length1 , length2 <NL> string1 string2 <NL>
Q length1 , length2 <NL> string1 string2 <NL>
F length1 , length2 <NL> string1 string2 <NL>
pushdefを使い、string1が
組み込みマクロとして名前string2をもつ関数に展開されるように定義します。
T length1 , length2 <NL> string1 string2 <NL>
pushdefを使い、string1がテキストstring2に展開される
ように定義します。
D number, length <NL> string <NL>
m4内部からこのようにこのコマンドが生成されることはありません。
m4との互換性この章ではm4の本実装とUNIXとりわけSystem V, Release 3における実装
との相違点を説明します。
m4で拡張された機能本バージョンのm4にはSystem V m4に存在しない機能が
いくつかあります。
これらの追加された機能はコマンドライン・オプション-Gを使うことで、
他のコマンドライン・オプションによって無効にされない限り、すべて抑制されます。
$n表記において、
nは複数の数字を含むことができますが、
System V m4は1つの数字しか受けつけません。
これによりGNU m4ではマクロが引数を9つだけでなく
いくつでも取ることができます。(see Arguments)
includeとsincludeでインクルードされるファイルは、
作業ディレクトリで見つからないときは指定されたサーチ・パスのなかから
捜し出されます。サーチ・パスは-Iオプションと環境引数M4PATHで
指定します。(see Search Path)
undivertへの引数は数字以外でもよく、
そのときはその名前を持つファイルを解釈しないまま出力に含めます。
(see Undivert)
printfをモデルとした
組み込みマクロformatを通じてサポートされています。(see Format)
regexp (see Regexp)と
patsubst (see Patsubst)によってサポートされています。
esyscmd (see Esyscmd)で
m4に読み込むことができます。
builtin (see Builtin)により任意の組み込みマクロへ
間接的にアクセスできます。
indir (see Indir)を通じてマクロを間接的に呼び出せます。
__file__と__line__ (see Errprint)
を通じて現在の入力ファイルの名前と現在の入力行番号へアクセスできます。
dumpdefとマクロ・トレースの出力書式を
debugmode (see Debug Levels)で制御することができます。
debugfile (see Debug Output)
で制御可能です。
上記の拡張に加えGNU m4には次のコマンドライン・オプションが
実装されています ― -F, -G, -I,
-L, -R, -V, -W, -d,
-l, -o, -t。
これらオプションの説明はSee Invoking m4を参照してください。
またGNU m4のデバッグとトレース機構は
他バージョンのm4にあるものより遥かに大規模です。
m4 にあってGNU m4にない機能System Vバージョンのm4にはGNU m4にまだ実装されていない
機能がいくつかあります。
m4はdefnへの複数の引数をサポートしています。
GNU m4はこれを実装していません。
この機能の有用性が筆者にははっきりしません。
System Vバージョンのm4と本実装との間には他にいくつか非互換な
部分があります。
m4とSystem V m4では異なります。
GNU m4はテキストが出力切替え先に送られるときに同期行を出力しますが、
System V m4ではこれが、出力切替え先のテキストが引き戻されるときになります。
問題は出力切替え先に送られる、または送られていたテキストに
どの行番号とファイル名をつけ加えるかです。
System V m4は出力切替え先に送られていたすべてのテキストが
undivertの呼び出しを含むソース行によって生成されたと見なしますが、
GNU m4は出力切替え先に送られる時にテキストが生成されたと見なします。
私は同期行オプションを使うのは大抵m4をコンパイラのフロントエンド
として使うときだと考えます。
もし出力切替え先に送られた行がコンパイラエラーを引き起こしたら、
エラーメッセージは出力切替え先のテキストが挿入しなおされた場所ではなく、
出力切替え先に送られた場所をおそらく指し示すべきでしょう。
m4は自己参照的な定義を防ごうとはしません。
define(`x', `x') define(`x', `x ')
xがxを返すように定義することに、本質的に間違っているところは
ありません。
間違っているのはクォートされていないxを展開することです。
他のプログラミング言語で変数を使うのと同じように、
m4で文字列を保持するためにマクロ使い、
さらにそれを次のようにチェックする人もいるでしょう:
ifelse(defn(`holder'), `value', ...)
このような場合、マクロが自分自身の名前を保持するのを禁止するのは
余計なお世話でしょう。
もちろん、これはGNU m4ユーザが自分で首をくくるためのロープを
放置しておくことにはなります!
再走査によるハング(hang)は、
伝統的なプログラミング言語において無限ループに対するときのような、
注意深いプログラミングによって避けることができるでしょう。
m4は-Gオプション無しだと、
__gnu__が空行に展開されるように定義します。
UNIX システム上においてGNU m4は-Gオプションをつけると
マクロ__unix__、つけないときはマクロunixを定義します。
両方とも空文字列に展開されます。
m4: M4exit
参照先は組み込みマクロが最初に紹介されている場所だけです。
索引では始めや終りに__のある名前はそれらが取り除かれています。
builtin: Builtin
changecom: Changecom
changequote: Changequote
changeword: Changeword
debugfile: Debug Output
debugmode: Debug Levels
decr: Incr
define: Define
defn: Defn
divert: Divert
divnum: Divnum
dnl: Dnl
dumpdef: Dumpdef
errprint: Errprint
esyscmd: Esyscmd
eval: Eval
file: Errprint
format: Format
gnu: Other Incompat
ifdef: Ifdef
ifelse: Ifelse
include: Include
incr: Incr
index: Index
indir: Indir
len: Len
line: Errprint
m4exit: M4exit
m4wrap: M4wrap
maketemp: Maketemp
patsubst: Patsubst
popdef: Pushdef
pushdef: Pushdef
regexp: Regexp
shift: Loops
sinclude: Include
substr: Substr
syscmd: Syscmd
sysval: Sysval
traceoff: Trace
traceon: Trace
translit: Translit
undefine: Undefine
undivert: Undivert
unix: Other Incompat