8 =======================================
9 この章では、一連の例を通して言語の核心へと迫ります(ビルドシステムのサンプルについては :ref:`label3` を参照してください)。
11 私たちはこれらの例のほとんどに ``osh`` インタープリターを用いています。また、簡単にするため、 ``osh`` によって出力された値は省略されています。
16 ---------------------------------------
17 OMakeの基本となる型は文字列とシーケンス、そして値からなる配列です。シーケンスはホワイトスペースによって分割された配列のようなもので、関数の要求に応じて分割されます。 ::
21 osh> addsuffix(.c, $X)
22 - : <array 1.c 2.c> : Array
24 時々あなたは明示的に配列を定義したいと思うでしょう。この場合、 ``[]`` を変数名の後に追加し、配列の成分をそれぞれインデントした行に書くことで実現できます。 ::
29 - : <array "Hello world" "/home/jyh"> : Array
31 成分にホワイトスペースを含めることができるという配列の主要機能は重要です。これはホワイトスペースを含むファイル名にとって特に役立ちます。 ::
35 "fee" "fi" "foo" "fum"
38 - : <array "Hello world"> : Array
41 "fee" "fi" "foo" "fum" "Hello world"
46 ---------------------------------------
47 ``String`` は単一の値です。文字列の中でホワイトスペースは重要な意味を持っています(訳注: 英語圏ではそうだと思います)。文字列をクオートするには4通りの方法があります。まず一番目にクオートをつけることが挙げられるでしょう。シンボル ``'`` (シングルクオート)と ``"`` (ダブルクオート)を用いることで、シェルを扱うときのように成分をクオートすることができます。クオーテーションシンボルは結果の文字列に **含まれます** 。変数は常に内部にクオートを含んだ状態で展開されます。 ``osh(1)`` (15章で説明)は文字列にダブルクオートをつけて出力しますが、これは出力時だけであり、文字列の中には含まれていないことに注意してください。 ::
49 osh> A = 'Hello "world"'
50 - : "'Hello \"world\"'" : String
52 - : "\"'Hello \"world\"'\"" : String
53 osh> C = 'Hello \'world\''
54 - : "'Hello 'world''" : String
56 二番目の方法は ``$'`` と ``$"`` クオートを導入することです。始まりと終わりのクオートシンボルの数は任意です。また、これらのクオーテーションはいくつかの性質をもっています。
58 * クオートデリミタは文字列の一部では **ありません** 。
59 * 文字列中のバックスラッシュ ``\`` 文字は通常の文字として扱われます。
60 * 文字列は何行にわたって書くこともできます。
61 * 変数は ``$"`` を用いて展開することができます。ただし、 ``$'`` では展開できません。
65 osh> A = $'''Here $(IS) an '''' \(example\) string['''
66 - : "Here $(IS) an '''' \\(example\\) string[" : String
67 osh> B = $""""A is "$(A)" """"
68 - : "A is \"Here $(IS) an '''' \\(example\\) string[\" " : String
69 osh> value $(A.length)
74 - : "[gnirts )\\elpmaxe(\\ '''' na )SI($ ereH" : String
76 文字列とシーケンスの両方はホワイトスペースが含まれていない文字列とくっつけることができます。 ::
79 - : "a b c" : Sequence
81 - : <sequence "a b c" : Sequence ".c" : Sequence> : Sequence
82 osh> value $(nth 2, $(B))
84 osh> value $(length $(B))
87 配列は異なります。配列の成分はどのような方法を用いても文字列とくっつけることができません。配列は括弧 ``[]`` を変数名につけ、インデントした中身を記述することで成分を定義できます。各成分にはホワイトスペースを含めることもできます。 ::
98 osh> value $(A.length)
100 osh> value $(A.nth 1)
101 - : "foo bar" : Sequence
103 配列はしばしばシステム上にホワイトスペースを含んだファイル名を使う場合において、非常に有用なツールとなります。 ::
106 c:\Documents and Settings\jyh\one file
107 c:\Program Files\omake\second file
109 osh> CFILES = $(addsuffix .c, $(FILES))
111 c:\Documents and Settings\jyh\one file.c c:\Program Files\omake\second file.c
116 ---------------------------------------
117 複数のディレクトリにまたがっており、かつ異なったパートに分かれているOMakeのプロジェクト上では、まったく違うディレクトリ上でコマンドが実行されます。これはファイル、あるいはディレクトリの名前が位置的に独立して定義されている必要があることを表しています。
119 この問題は ``$(file <names>)`` や ``$(dir <names>)`` 関数を用いて解決できます。 ::
130 ``section:`` を用いて ``cd`` コマンドのスコープに制限を加えていることに注意してください。このセクションでは一時的に ``tmp`` ディレクトリに移動しているので、ファイルの名前には ``../fee`` が用いられます。このセクションが終了してカレントディレクトリに戻ってきたとき、ファイルの名前には ``fee`` が用いられます。
132 ファイル関数を使う主な目的は、あなたのプロジェクトの ``OMakefile`` で、ファイル名が正しく定義されるようにするためです。これを用いればプロジェクト上の様々なパートに移動したとしても、変数は同一のファイルを指し示します。 ::
140 (訳注: file, dirについて詳しく知りたい方は ":ref:`label4.15`" を参照してください。)
150 7.4 イテレーション、マップ、foreach
151 ---------------------------------------
152 ほとんどのビルドイン関数では配列を何も考慮することなく処理できます。 ::
154 osh> addprefix(-D, DEBUG WIN32)
155 - : -DDEBUG -DWIN32 : Array
156 osh> mapprefix(-I, /etc /tmp)
157 - : -I /etc -I /tmp : Array
158 osh> uppercase(fee fi foo fum)
159 - : FEE FI FOO FUM : Array
161 ``mapprefix`` と ``addprefix`` 関数は全く異なります( ``addsuffix`` と ``mapsuffix`` 関数も同様です)。 ``addprefix`` 関数は接頭辞を各々の成分くっつけます。 ``mapprefix`` 関数は元の成分の前に新しい接頭辞を成分として加えるので、配列の長さは2倍になります。
163 ほとんどの関数は配列でも動きますが、あなた自身が配列にも対応した関数を作りたいと思うこともあるでしょう。 ``foreach`` 関数はその要望を実現します。 ``foreach`` 関数は二つの表記を使いますが、この中身を伴った表記法はとても便利です。この関数は二つの引数と中身が必要です。まず、一つ目の引数は変数で、二つ目のは配列を指定します。そして ``foreach`` の中身は各々の成分を対象に、引数で指定された変数がその成分に束縛された状態で、配列の長さぶんだけ実行されます。さあ、それでは値を持った配列の各々の成分に1を加える関数を定義してみましょう。 ::
171 あなたはファイル名を持った配列を持ち、そのそれぞれにビルドルールを定義したいと思うこともあるでしょう。ビルドルールは特別なものではなく、あなたはどの箇所でもビルドルールを定義することができます。さて、私たちは配列にある、各々のファイルの処理について記述し、さらに結果を ``tmp/`` ディレクトリの中に置く関数を書きたいものとします。 ::
175 my-special-rule(files) =
176 foreach(name, $(files))
177 $(TMP)/$(name): $(name)
180 後に、プロジェクトの他の部分で、私たちはこの関数を用いていくつかのファイルを処理することを決めたとしましょう。 ::
182 # src/libに処理するためのファイルが入っています
186 file with spaces in its name.src
187 my-special-rule($(MY_SPECIAL_FILES))
189 ``my-special-rule`` を呼んだ結果は、以下の3つのルールを明示的に書いた場合と全く同じになります。 ::
191 $(TMP)/fee.src: fee.src
193 $(TMP)/fi.src: fi.src
195 $(TMP)/$"file with spaces in its name.src": $"file with spaces in its name.src"
198 もちろん、これらのルールを記述することは関数を呼ぶことより好ましいものではありません。関数を抽象化することによる普通の特性は、普通の利点となります。ビルドルールを定義するためのコードが一つだけで済み、コードはさらに短くなります。後でもし私たちがルールを修正したりアップデートしたいと思ったときも、単純に一つのルールを修正するだけでよいのです。
205 ---------------------------------------
206 omakeでの評価は通常の場合先行して行われます。これは、omakeが式に遭遇した場合、即座に評価が行われることを意味してみます。この効果の一つとして、変数定義式の右側は、変数が定義される時点で展開されることが挙げられます。
208 この振る舞いをコントロールするための2つの方法があります。 ``$`(v)`` は遅延評価を行うための、 ``$,(v)`` は先行評価に戻すための表記法です。以下のシーケンスについて考えてみましょう。 ::
214 osh> C = $`(add $(A), $,(B))
215 - : $(apply add $(apply A) "2" : Sequence)
216 osh> println(C = $(C))
222 osh> println(C = $(C))
225 ``C = $`(add $(A), $,(B))`` では遅延評価を定義しています。 ``add`` 関数はこの場合、実際に値が必要となるときまで評価しません。上の式を見てみると、 ``$,(B)`` は ``B`` が即座に評価する変数であることを指定します。これが遅延評価式の中で定義されているにもかかわらずです。
227 最初私たちが ``C`` の値を出力したとき、 ``A`` は1で ``B`` が2であったので、結果は3と評価されました。次に私たちが同様に ``C`` を出力したとき、 ``A`` は5に再定義されていたので、結果は7と評価されました。二回目の ``B`` は ``C`` の定義時に評価されているため、なんの影響も与えていません。
232 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
233 遅延評価式は実際に結果が必要とされるときまで評価されません。筆者を含む結構な人数のプログラマは、遅延評価が多用しているコードを見ると眉をひそめます。なぜならこれは実際にどこで評価が行われているのか分かりにくくなるからです。しかしながら、これらの難点をペイオフできるケースも確かに存在します。
235 一つの例としてオプションの処理が挙げられます。Cコンパイラに"include"ディレクトリの指定をコマンドライン上から指定する場合を考えましょう。もし私たちが"/home/jyh/include"と"../foo"上のファイルをインクルードしたい場合、コマンドラインにはオプション ``-I/home/jyh/include -I../foo`` を指定する必要があります。
237 Cファイルをビルドするための通常のルールを定義する場合について考えましょう。私たちはインクルードされるディレクトリを指定するため、 ``INCLUDES`` 配列を定義し、ルートのOMakefileに通常用いる暗黙のルールを定義しました。 ::
243 $(CC) $(CFLAGS) $(INCLUDES) -c $<
245 # srcディレクトリは4つのソースファイルからmy_widgetをビルドします。
246 # これはインクルードディレクトリからインクルードファイルを読み込みます。
248 FILES = fee fi foo fum
249 OFILES = $(addsuffix .o, $(FILES))
250 INCLUDES[] += -I../include
252 $(CC) $(CFLAGS) -o $@ $(OFILES)
254 しかしこれは全く正しいというわけではありません。問題としては、 ``INCLUDES`` はオプションが格納されてある配列であり、ディレクトリではないという点です。もし私たちが後にディレクトリ名を変更したい場合は、まず ``-I`` 接頭辞を配列から分割する必要があり、これは混乱の元となります。さらに、私たちはディレクトリの絶対パスを使用していません。この問題を解決する方法は、遅延評価式を使うことです。まず私たちは ``INCLUDES`` をディレクトリの配列として定義し、さらに新しい変数 ``PREFIXED_INCLUDES`` を定義することによって ``-I`` 接頭辞を追加します。 ``PREFIXED_INCLUDES`` は遅延評価を行うことで、最新のINCLUDES変数値が使われることを保証してくれます。 ::
259 PREFIXED_INCLUDES[] = $`(addprefix -I, $(INCLUDES))
261 $(CC) $(CFLAGS) $(PREFIXED_INCLUDES) -c $<
263 # 今回の例では、私たちはインクルードディレクトリの絶対パスを定義しました。
264 STDINCLUDE = $(dir include)
266 # srcディレクトリは4つのソースファイルからmy_widgetをビルドします。
267 # これはインクルードディレクトリからインクルードファイルを読み込みます。
269 FILES = fee fi foo fum
270 OFILES = $(addsuffix .o, $(FILES))
271 INCLUDES[] += $(STDINCLUDE)
273 $(CC) $(CFLAGS) -o $@ $(OFILES)
275 遅延する値と関数が密接に繋がっていることに注目してください。上の例では、私たちは ``PREFIXED_INCLUDES`` を、引数を持たない関数として定義しているのと同じことを行っています。 ::
277 PREFIXED_INCLUDES() =
278 addprefix(-I, $(INCLUDES))
285 ---------------------------------------
286 OMakeの言語は(IOとシェルコマンドは除きますが)関数型言語となっています。これは、まず関数が最上級のものであることと、変数が不変なものである(代入という操作が存在しない)という2つから、そうであると言えるでしょう。後者に関しては、おそらく従来のGNU makeを使っていたユーザからすると奇妙なものに思えるかもしれません。しかしこれは実際にOMakeを使う上で非常に重要な点となります。変数は修正できませんので、プロジェクトの一部分が他の部分に干渉することは不可能(あるいは非常に困難)です。
288 これを従来の純粋な関数型言語のように実装してしまうと、非常に使いにくいものになるかもしれません。OMakeでは、インデントすることでレベルを一つ挙げた場合、新しいスコープが導入されます。そしてスコープが終わると、そのスコープで新しく定義された変数は消去されます。もしOMakeが馬鹿真面目にスコープに関して厳格な仕様であったならば、おそらくコードはもっと複雑なものになったでしょう。 ::
292 osh> if $(equal $(OSTYPE), Win32)
295 osh> println($X $(getenv BOO))
298 ``export`` コマンドはこの制限を外に出します。このコマンドは内部のスコープの値(あるいは全体の変数環境)を外部に「エクスポート」するお世話をします。 ::
302 osh> if $(equal $(OSTYPE), Win32)
306 osh> println($X $(getenv BOO))
309 エクスポートは、ループ中のイテレーションから次のイテレーションへ値をエクスポートするのに特に役立ちます。 ::
311 # オーケー、それでは配列の各成分を足し合わせてみよう
315 total = $(add $(total), $i)
320 # うーん、正常に動いていないじゃないか!
324 total = $(add $(total), $i)
330 ``while`` ループは自動的にエクスポートしてくれる、別の形のループ文です。 ::
334 osh>while $(lt $i, 10)
335 total = $(add $(total), $i)
337 osh>println($(total))
346 ---------------------------------------
347 ときどきあなたは *エイリアス* を定義したいと思うことがあるかもしれません。そのためにOMakeでは、実際にシェルコマンドが存在しているかのようにふるまうコマンドが存在します。あなたはこれを、 ``Shell`` オブジェクトに対象の関数を追加することで実現できます。
349 例えば、 ``awk`` 関数を用いて、あるファイル中のすべてのコメントを出力する場合について考えてみましょう。 ::
359 osh>comments(comment.om)
363 これをエイリアスとして追加するには、 ``Shell`` オブジェクトにメソッドを追加します。 ``+=`` を用いて、シェルの既存の内容を保存している点に注意してください。 ::
367 comments($(nth 0, $(argv)))
368 osh>printcom comment.om > output.txt
373 シェルコマンドは引数として配列 ``argv`` が渡されます。これはエイリアスの名前には *含まれていません* 。
382 7.8 簡単に入出力のリダイレクションを行う
383 -----------------------------------------
384 結果的に、スコーピングによってリダイレクションの実行に関しての良い代替案も表れることとなりました。それでは、既に標準の出力先に出力するコードが大量にあるが、この出力先のリダイレクションを行いたいというような場合について考えてみましょう。まず一つ目の方法としては、前回のテクニックを用いることが挙げられます。具体的には、関数をエイリアスとして定義し、あなたが望む出力先にすることでシェルのリダイレクションを行うといった方法です。
386 別の方法については、前者の方法よりも簡単な場合があります。変数 ``stdin`` ``stdout`` ``stderr`` は標準I/Oの出力先について定義しています。出力先をリダイレクトするには、これらの変数をあなたが望むように再定義します。もちろん、あなたはこれを普通にネストされたスコープ上で行うことができるので、外部の出力先に影響を与えることはありません。 ::
393 stdout = $(fopen output.txt, w)
399 これはシェルコマンドに対しても同様です。もしあなたがギャンブル好きであるならば、以下の例を試してみるのもいいでしょう。 ::
406 stdout = $(fopen output.txt, w)