>トップ
簡単なものでしたら @ksmakoto まで。量があるようでしたらメイル( ksmakoto ◎ dd.iij4u.or.jp )で。
杉浦 K.さんによるSuper Technique 講座~m4 チュートリアル(アーカイブ)も参考にするとよいでしょう。
M4はマクロプロセッサで、テキストファイルに対してマクロの処理を行うソフトウェアです。Unixの世界で古くから使われてきました。マニュアルのHISTORYの節による記述には揺れがあるのですが、V6かV7かPWB UNIXの頃UNIXに登場したようです。
「(略)、遊び仲間は選ぶの。遊び相手は選べないけれど」(『星界の紋章 Ⅱ』より)
M4 の歴史は以下のようにして追ってゆくことができます。まずFreeBSDでは /usr/share/doc/psd/17.m4/paper.ascii.gz というファイルに、PSDことUNIX Programmer's Supplementary Documentsの17番目のドキュメント"The M4 Macro Processor"が収録されています(他の環境の方はネットなどでさがしてください)。GNU M4 のマニュアルはよく整備されていますが、分量が多いので、とりあえず一通り読んでなんとなく全貌を把握するには向いていません。
次に、"The M4 Macro Processor"は、ほぼself-containedなドキュメントですが、深い内容についてはカーニハン&プラウガーの"Software Tools"の8章が(唯一の)参照文献となっています。日本語版『ソフトウェア作法』(さくほう、と読む。 http://www.kyoritsu-pub.co.jp/bookdetail/9784320021426 )がありますのでそちらを読んでも良いでしょう。Ratforで書かれているという点が、慣れないプログラミング言語で書かれた文献に慣れていない我々には少々辛いですが、すぐ慣れるでしょう。
これはほぼM4のはらわたを解説しているものと言って良く「実装を読んで仕様を理解する」向きには最適なテキストです。
章末の「文献メモ」には複数の参考文献が挙げられていますが、M4自体とのつながりは薄いようです。M4自体の由来についてはGNU M4のマニュアルの"1.2 Historical references"が詳しく、ストレイチーのGPMにはじまり、M6やm3という先行の実装が挙げられています。特にm3は"Software Tools"の8章によるインスパイヤを受けて書かれたもの、とあります(The M4 Macro Processorにもそうあるのだが、一方で、『ソフトウェア作法』の章末の記述によれば順序が逆なので、そのへんよくわからない)。
その後のM4ですが、C言語のプリプロセッサとして使われたりcppに影響を与えたりしたわけですが、実際に言語と本格的に組み合わせて使う用途には、たとえば文字列リテラルの認識など、言語専用に設計されたcppに分があります。そんなわけでcppのほうがむしろ有名になり、C言語はもっぱらcppでプリプロセスされるようになります。
そして冒頭の引用ではありませんが、なぜか(?)、前世紀には sendmail.cf の生成に使うものとして、今世紀には Autoconf が生成に使うものとして、と「悪名高いもの」との相性が良いようですが(既存の書籍中での解説としても、これを書いている筆者が確認しているものとしては、オライリーの『sendmail』の中のものと、オーム社の『GNU Autoconf/Automake/Libtool』の中のものと、という感じです)、M4自体は筋が良いように私は思います。
M4の主な実装には以下の3種類があります。それと標準があります。
heirloomプロジェクトのCVSリポジトリ(のアーカイブ)から取得できます。heirloom-devtoolsをcoして、その中のm4というディレクトリにあります。
FreeBSDプロジェクトのウェブサイトにあるマニュアルページ検索では、2系BSDの2.9.1と2.10と、Unix Seventh Edition、他は確かめていませんがおそらく正統系UNIXで、この版のマニュアルページが出てきます。heirloomプロジェクトのソースツリー中にはなぜかマニュアルページのソースは無いので、マニュアルページはこちらを見ると良いでしょう。それかV7 UNIXのtarballの中にもあります。
(原)作者のOzan S. Yigitさんのウェブページは https://www.eecs.yorku.ca/~oz/ です。1990年頃MINIX(?)コミュニティに放流されたコードのようで、386BSDの子孫の*BSDなどのM4は皆このM4がベースです。FreeBSDとOpenBSDのソースツリーでは元はNetBSD版からのforkでしたが、現在はOpenBSD版がNetBSDやFreeBSDのM4のソースコードのベースになっています。
FreeBSDプロジェクトのウェブサイトにあるマニュアルページ検索では、2系BSDでは2.11と、4系BSDではなぜか4.3BSD RenoにだけM4のマニュアルがあるのですが、この版のマニュアルページが出てきます。
https://www.gnu.org/software/m4/ Autotoolsにはこの版が必要です。やはり1990年頃、René Seindalさんによって書かれたコードから出発したものです。作者のウェブページは https://www.seindal.dk/ です。あとアーカイブ https://web.archive.org/web/20190926163245/https://www.seindal.dk/rene/gnu/
簡単な例から始めます。
次のようなテキストファイルを作ってください。
define(`HELLO', ``Kon-NichiWa'') define(`WORLD', ``Sekai'') HELLO, WORLD
名前は適当でいいですが hello_world.m4 とします。これを次のようにして処理します。
$ m4 hello_world.m4 (空行) (空行) Kon-NichiWa, Sekai
一行目はコマンドラインです(以降の説明では、特に必要がある場合以外は省略します)。標準出力に空行が2行出力されて、その後 HELLO, WORLD という文字列がマクロにより置換された結果が出力されます。define
の直後には空白を入れずに (
を続けるようにしてください。
これが最も簡単なM4の使い方の例です。空行は抑制できないのか、とか、なぜバッククウォート-クウォートで囲むの? とか、ひとつめの引数のクウォートが1重で、ふたつめは2重だったりするのも、一見謎のおまじないっぽいですがちゃんとわけがあります。そのへんをおいおい説明してゆきます。というか、そのへんの仕掛けをきちんと理解するだけで、M4はマスターしたと言っていいんじゃないかと思います。バッドノウハウ的ではありますが、奥は深くありません(「奥が深い症候群」の意味で)。
(こういった古いUNIXのツールには、入力元はコマンドラインで指定できるが、出力先は標準出力のみ、というものが多いようです。『ソフトウェア作法』によると、ファイルに出力すると、ファイルシステムを滅茶苦茶にしてしまうことがあった、とか(昔はディレクトリもファイルとしてオープンして書き込めました。当然壊れます))
M4はマクロプロセッサで、フィルタとして動作するテキストプロセッサすなわちテキストフィルタの一種ですから、基本動作としては入力をそのまま出力します。ですのでまずはその動作を、そこらへんの適当なテキストで試してみることにします。
サンプルとして、筆者の手元のFreeBSDの /usr/src/COPYRIGHT から主要部を取り出したもので試してみます。バージョン管理システム関係のヘッダと、2項目BSDライセンスに至る経緯の部分を取り除きました。
The compilation of software known as FreeBSD is distributed under the following terms: Copyright (c) 1992-2013 The FreeBSD Project. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
これを m4 copyright.txt のようにして処理してみると、一見そのまま出力されているように見えます。しかしちゃんと確認するために sha256 でハッシュをとってみますと、
$ cat copyright.txt | /sbin/sha256 f2cb6e3237db6440292b6cdf8c2a795140e2453ca095f0631ae788a3aef55db9 $ m4 copyright.txt | /sbin/sha256 5b36086749c97a7d674101c50a74e446190aeaf7587d7ead844171d55556f2fc
このように、違っています。
diff で違いを確認してみると、
--- a 2014-03-08 20:21:27.000000000 +0900 +++ b 2014-03-08 20:21:31.000000000 +0900 @@ -12,7 +12,7 @@ notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS `AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
このように一目瞭然、強調表現のために使われていた、バッククウォート-クウォート対が、M4のエスケープのためのクウォートとみなされて1段剥がされてしまっていました。このようにM4ではマクロ以外に、クウォートもあらゆる場所で有効になっています。これはdefineのようなトークンがテキスト中に出現するけど、これは解釈してほしくない、というような場合に有効に働きます。が、この例のように、不意のアンエスケープが起きることもあります。
また「1段」剥がされた、という表現を使いましたが、この例から他にもわかることとして、クウォートの入れ子は認識されます(たとえば、C言語のコメントなどとは異なっています)。マクロの中にさらにマクロがある、ということを可能にするためには必要な仕様です。
M4のクウォートのデフォルトが "`" と "'" なのは、多分設計当時、主な利用対象として考えていたプログラミング言語で、あまり使われていない記号だった、とかそんな理由なのではないかと思います。
現代だとたとえばVerilog HDLではコンパイラ指示子とぶつかるので困ります、が、幸いなことに、クウォートに使う記号は次のようにして変えられます。Hello worldで例を示します。
changequote(`[', `]') define([HELLO], [[Kon-NichiWa]]) define([WORLD], [[Sekai]]) HELLO, WORLD
この例のように "[" と "]" に変えるのは、Autoconfのマクロにおいてコンベンションになっています。また、『ソフトウェア作法』本文中でもバッククウォートではなくこちらを使っていました。
続いて、defineについて考えます。
まず、defineの引数にクウォートは必要なのか(他の多くのM4入門では、最初のサンプルではクウォートを付けていません)という点をさぐってみます。まずクウォートが一段のひとつめの引数のほうから。
次の3個のファイルをそれぞれM4に掛けてみます。すると、
define(FOO, ``bar'') FOO -- define(`FOO', ``bar'') FOO -- define(``FOO'', ``bar'') FOO
1個目と2個目はマクロによる置換が同じように行われます。3個目は置換が行われませんから、ひとつめの引数をクウォートして意味があるのは1段ということになります。では、ひとつめの引数をクウォートすることにはどんな意味があるのでしょうか?
次のようなファイルをM4に掛けてみると、
define(FOO, ``baz'') define(FOO, ``bar'') FOO baz
次のようになります。
(空行) (空行) baz bar
なぜこうなるのか、少し考えてみるとわかると思いますが、2個目のdefineの引数の FOO
が、まずその場で baz
にマクロ置換されてしまい、define(`baz', ``bar'')
というようなマクロ定義と同じことになってしまっているためです。また、プログラミング言語などに多い、実行前に全ての定義が済んでいる、というようなモデルではなく、入力を読んで展開などを行いながら、定義などでは逐次定義が追加・更新される、というモデルになっています。
M4では、このようにマクロの引数も他と同様に展開の対象になります。ですからdefineのひとつめの引数のような、他のマクロの影響を受けたくないトークンはクウォートで囲むのを原則としたほうが良いでしょう。
もうひとつここまで説明しないで来ましたが、defineも、組込みで定義されていてマクロを定義するという特殊な副作用があるだけの、空文字列に置き換えられるマクロです。defineだけ例外扱いすれば、名前による仮引数の宣言など、よりフレンドリーな言語にできると思われますが、そのような例外を作らないことで仕様が単純明快になっているように思います。
また、余計な感じのする空白行が出力される(出力に残る)ことも、defineもまたマクロである、ということから説明ができます。defineマクロ自体が空文字列に置き換えられた結果、残りの空行が出力されたわけです。
Autoconfでは、このような余計な空行を出力しないのがマナーということになっているようですが、その点について次の「コメントとdnl」の節で説明します。defineのふたつめの引数の2重クウォートについてはあとにまわします。
次のようなファイルをM4に掛けると、
define(`FOO', ``bar'')dnl dnl define(`BAR', ``baz'') FOO #FOO dnl FOO FOO#FOO FOO dnl BAR BAR dnl FOO eof
次のような結果が得られます。
bar #FOO bar#FOO bar BAR eof
Unixの多くのスクリプトやツール類と同様、M4も # でコメントです。コメントは、削除されたりすることなく、そのまま出力されます。
マクロ定義中などに # が現れるとちょっと変な動作をします。基本的には避けるのが無難ですが、興味のある人はいろいろなサンプルを作って試してみると良いでしょう(最初の所で詰まった人のためのヒント。M4では複数行に展開されるマクロもそのまま書けます)。なおこれについては、『Software Tools』本文中の実装の仕様はM4と異なっています。
dnlは組込みのマクロで、その場所から後、改行までを全て削除し、出力させない、という(副)作用があります。d はDelete、nl はNew Lineの意味でしょう。改行も削除されるので、dnlのあるその場所から次の行の内容が始まっている、ということに注意してください。
この例の1行目のように、defineと併用してマクロ定義だけの行は出力させない、とか、結果には残さないコメントを記述するために使う、というのが主な使い方でしょう。
ここまで正確な説明をしないで来ましたが、ここで「トークン」についてきちんと定義を示します。
M4のマクロ名や、マクロ置換の対象となるのは「トークン」で、入力のその場所から正規表現で [A-Z_a-z][0-9A-Z_a-z]*
に最長一致したもの、ということになります。大文字小文字は別のものとして扱われる、いわゆるケースセンシティブです。また、『ソフトウェア作法』の実装ではdefineでの定義中などで、カンマの後に空白を入れると、その空白も定義の一部になってしまいますが、M4ではカンマや開き括弧の直後の0個以上の空白は無視されます。
C言語のプリプロセッサでは、C言語自体の構文に関連するため、「プリプロセッサトークン」というものを定義していますが、M4では以上の単純な定義のみです。
マクロの引数もまた、展開の対象になる、と以前の節で説明しました。ここでは、ネストしたマクロで実際にどうなるのかを確認してみます。
例としてevalという算術式を計算する組込みマクロを使います(余談: evalよりもexprという名前のほうが良かったのではないかと思います)。
次のようなファイルをM4に掛けてみると、
eval(`1') eval(`1+2')
次のような結果が得られます。
1 3
eval自身はevalが含まれるような文字列を評価できませんから、
eval(`1+eval(2+3)')
をM4で処理しようとすると、
$ m4 expr_sample.m4 m4: bad constant in expr 1+eval(2+3). 0
のように、エラーと正しくない結果、になります。
内側のevalをクウォートから外して、
eval(`1+'eval(`2+3'))
あるいは、
eval(1+eval(2+3))
のようにすれば、意図したように 6 に展開されます。このように、引数はそれに含まれる全ての展開がされた上でマクロに渡される、という点で、計算モデルでいう「値渡し」に近いものと言えるでしょう。
次のようなファイルをM4に掛けてみると(2重のクウォートをしていないことに注意)、
define(`foo', `bar') define(`bar', `baz') foo bar
次のような結果が得られます。
(空行) (空行) baz baz
foo が foo → bar → baz と展開されていることからわかるように、M4の展開は、展開結果がさらに展開可能なら、何回でも展開されます。この、一種の再帰的な処理は少し面白い方法で実現されていて、この例の foo
の場合、foo
から展開された bar
は、入力を読み込んでいるバッファに「押し戻され」ます。そしてあたかも bar
という入力があったかのようにして、処理が続けられます。このように、マクロ展開の結果を入力に押し戻すことを『ソフトウェア作法』ではプッシュバックと呼んでいますので、この入門でも以降ではそのように呼びます。
このプッシュバックがわかれば、2重のクウォートの仕掛けもわかってきます。さきほどの例を2重クウォートにしてみましょう。
define(`foo', ``bar'') define(`bar', ``baz'') foo bar
次のような結果が得られます。
(空行) (空行) bar baz
foo
が bar
までしか展開されていません。この展開がどのように行われたかを追跡すると、次のようになります。
(1) まずdefineに引数が与えられる前の展開で、クウォートが1段外され、foo
が `bar'
のように展開される、というマクロが定義されます。
(2) 出現した foo
が `bar'
に展開され、プッシュバックされます。
(3) `bar'
という入力があったのと同様ということになりますから、クウォートされているのでマクロ展開は行われず、出力側にはクウォートがもう1段外されて bar
と出力されます(クウォート外しはマクロ展開ではないので、プッシュバックされないことに注意してください)。
この仕掛けから想像できる通り、(無条件で)同じ名前を展開に含むようなマクロは、展開が止まらなくなって暴走します。一応あからさまなパターンはM4がチェックして止まりますが、理論的に、完全なチェックは不可能ですから利用者が注意する必要があります。(「無条件で」という条件を付けたのは、この入門では説明しないifelseによる条件判定を使って制御すれば、再帰的なマクロも可能なため)
M4ではマクロ展開の結果は、入力に押し戻される、すなわちプッシュバックされる、ということを説明しました。また、標準化以後のC言語のcppではトークンはトークン連結演算子という特殊な演算子を使わない限り連結されることはありませんが、M4では隣接させてしまえばトークンは連結されてしまいます。
これも例を見たほうが早いでしょう。
define(`foo', `bar') define(barbaz, ``quuz'') foo()baz
これをM4に掛けると、次のようになります。
(空行) (空行) quuz
マクロ展開の結果の bar
をプッシュバックした結果、その直後にある baz
がくっついて、barbaz
というひとつのトークンとしてさらに展開がされています。ここで、()
のように0個の引数を明示しているのは(M4では引数が無いマクロには、普通はカッコを付けなくてもかまいません)、そのまま書くとトークンの切れ目にならないからです。
この例のように、展開後の文字列の後に続く側には連結が起きますが、前側には起きません(というか、それをうまく記述する方法が無い)。
ここまで、引数を使うマクロとしてはdefineなど組込みのマクロを示すだけで、引数を使うマクロを定義する方法については言及しませんでした。ここで引数を使うマクロについて説明します。ついでに複数行に展開されるマクロについても説明します。
まず複数行マクロですが、これは簡単で、書かれているままに定義、展開されます。
define(`helloWorld', int main(void) { printf("hello, world\n"); return 0; }) helloWorld
これが、
(空行) int main(void) { printf("hello, world\n"); return 0; }
このようになります。最後の改行はマクロではなく元の入力に含まれる改行がそのまま出力されたものであることなどに注意してください(マクロ定義と呼び出しの、どちら側で改行を持つか、運用で決めておく必要がある場合も出てくるでしょう。定義側で改行を持つ場合、呼び出し側は helloWorld()dnl
のようにdnlを併用します)。また、マクロの引数の中に )
が現れる例にもなっていますのでここで説明しますが、M4では (
と )
だけは特別扱いをしていて、クウォート中やコメント中など以外では、カッコが対応するかチェックして、出現するカッコが全て対応するまでは )
が現れてもマクロの引数の終わりとはしません。
続いて引数を使うマクロです。マクロ定義中の $1
〜 $9
という文字列は、実引数として与えられた文字列に置換えられます。他に特殊なものとして、マクロ名に置換えられる $0
などがあります( $
に展開される、たとえば $$
のようなものが必要なはずですが、一般的なM4の実装には存在しません)。サンプルを示します。
define(`FOO', ``$0-$1-$2-$3-$4'') FOO(`arg1', `arg2', `arg3', `arg4')
これは次のようになります。
(空行) FOO-arg1-arg2-arg3-arg4
シェルスクリプト等と同様ですからUnixに慣れている人には特に違和感は無いと思いますが、クウォートと無関係に展開されるという点は少し気になるかもしれません。これは、クウォートはM4のマクロを見つける働きをエスケープするもので、引数の展開はマクロを見つけた後のものなので無関係、と考えれば無理なく解釈できるでしょう。
マクロの引数でも、やはりトークンの連結や、プッシュバックによる再展開は起きます。例を見てみます。
define(`FOO', `$1$2') define(`barbaz', ``quuz'') FOO(`bar', `baz')
これは、次のようになります。
(空行) (空行) quuz
この入門は以上で終わりです。M4には他に、ifelseという条件判断をする組込みマクロや、それと再帰を使ったテクニック、その他いくつかの組込みマクロ(特にGNU M4には多くの組込みマクロがあります)、ダイバート(出力を一旦別のバッファに向ける)などといった話題がありますが、この入門で説明した基本を押さえておけば、あとは文献とサンプルを読んで、どうとでもできるでしょう。余談ですが、ifelseという名前も、多くのプログラミング言語ではそういう名前が予約語じゃないから、という理由ではないかと思います。
最後にサンプルのありかですが、BSDでは /usr/src/usr.bin/m4/TEST/ に5種類ほど(1個は動かないサンプルで、動く5個は test.m4 がincludeしているのでまとめて動かせます)あります。GNU M4なら、ソースツリー中の tests/ と doc/examples/ に多数のサンプルがあります。面白そうなサンプル(たとえばハノイの塔)などを動かす時の注意の一例ですが、DECRというデクリメントを計算するマクロが定義されていれば 4 → 3 → 2 ... のように変化して停止するはずのマクロが、定義されていないと 4 → DECR(4) → DECR(DECR(4)) ... のように展開され続けて止まらなくなります。
また、GNUのAutoconf Archiveプロジェクトで、Autoconf用のマクロを集めたアーカイブがメンテされています。
M4の仕様で良くないのでは、と思う点ですが、まだ実装して評価をしていないのですが、マクロ呼び出しにはマクロ名の前(および後)に記号を付けて @FOO@
のようにしてマクロを呼び出すような仕様のほうが良かったのでは、と思います。既存のソースコードにお手軽にパッチを当てる、みたいなことができなくなりますが、マクロ展開される部分が見た目に「ひっかかる」ほうが良いはずです。他に、引数を囲むカッコなども全てカスタマイズ可能にすべきじゃないのか、というのもあります。いずれにしても、実装して評価してみないとはっきりとは言えませんが。
その後(というか、このページの執筆と並行して進めていたのですが)m55 を実装しながらいくつかの文法を試してみましたが、結論としては、マクロ名の前に開きカッコを付けるのがベストではないか、という点に落ち着きました。マクロ名と引数のセパレータは、引数と引数のセパレータと共通になります。