この章では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