OSDN Git Service

翻訳文章追加. リンクの貼り直し
[omake-japanese/omake_trans.git] / language-examples.rst
1 .. 7-language-examples
2
3 .. index:
4    single: osh
5 .. _label7:
6
7 7. さらなる言語例
8 =======================================
9 この章では、一連の例を通して言語の核心へと迫ります(ビルドシステムのサンプルについては :ref:`label3` を参照してください)。
10
11 私たちはこれらの例のほとんどに ``osh`` インタープリターを用いています。また、簡単にするため、 ``osh`` によって出力された値は省略されています。
12
13 .. _label7.1:
14
15 7.1 文字列と配列
16 ---------------------------------------
17 OMakeの基本となる型は文字列とシーケンス、そして値からなる配列です。シーケンスはホワイトスペースによって分割された配列のようなもので、関数の要求に応じて分割されます。 ::
18
19    osh> X = 1 2
20    - : "1 2" : Sequence
21    osh> addsuffix(.c, $X)
22    - : <array 1.c 2.c> : Array
23
24 時々あなたは明示的に配列を定義したいと思うでしょう。この場合、 ``[]`` を変数名の後に追加し、配列の成分をそれぞれインデントした行に書くことで実現できます。 ::
25
26    osh> A[] =
27            Hello world
28            $(getenv HOME)
29    - : <array "Hello world" "/home/jyh"> : Array
30
31 成分にホワイトスペースを含めることができるという配列の主要機能は重要です。これはホワイトスペースを含むファイル名にとって特に役立ちます。 ::
32
33    # ディレクトリ中の現在のファイルを並べる
34     osh> ls -Q
35     "fee"  "fi"  "foo"  "fum"
36     osh> NAME[] = 
37             Hello world
38     - : <array "Hello world"> : Array
39     osh> touch $(NAME)
40     osh> ls -Q
41     "fee"  "fi"  "foo"  "fum"  "Hello world"
42
43 .. _label7.2:
44
45 7.2 クオート文字列
46 ---------------------------------------
47 ``String`` は単一の値です。文字列の中でホワイトスペースは重要な意味を持っています(訳注: 英語圏ではそうだと思います)。文字列をクオートするには4通りの方法があります。まず一番目にクオートをつけることが挙げられるでしょう。シンボル ``'`` (シングルクオート)と ``"`` (ダブルクオート)を用いることで、シェルを扱うときのように成分をクオートすることができます。クオーテーションシンボルは結果の文字列に **含まれます** 。変数は常に内部にクオートを含んだ状態で展開されます。 ``osh(1)`` (15章で説明)は文字列にダブルクオートをつけて出力しますが、これは出力時だけであり、文字列の中には含まれていないことに注意してください。 ::
48
49     osh> A = 'Hello "world"'
50     - : "'Hello \"world\"'" : String
51     osh> B = "$(A)"
52     - : "\"'Hello \"world\"'\"" : String
53     osh> C = 'Hello \'world\''
54     - : "'Hello 'world''" : String
55
56 二番目の方法は ``$'`` と ``$"`` クオートを導入することです。始まりと終わりのクオートシンボルの数は任意です。また、これらのクオーテーションはいくつかの性質をもっています。
57
58 * クオートデリミタは文字列の一部では **ありません** 。
59 * 文字列中のバックスラッシュ ``\`` 文字は通常の文字として扱われます。
60 * 文字列は何行にわたって書くこともできます。
61 * 変数は ``$"`` を用いて展開することができます。ただし、 ``$'`` では展開できません。
62
63 ::
64
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)
70     - : 38 : Int
71     osh> value $(A.nth 5)
72     - : "$" : String
73     osh> value $(A.rev)
74     - : "[gnirts )\\elpmaxe(\\ '''' na )SI($ ereH" : String
75
76 文字列とシーケンスの両方はホワイトスペースが含まれていない文字列とくっつけることができます。 ::
77
78     osh> A = a b c
79     - : "a b c" : Sequence
80     osh> B = $(A).c
81     - : <sequence "a b c" : Sequence ".c" : Sequence> : Sequence
82     osh> value $(nth 2, $(B))
83     - : "c.c" : String
84     osh> value $(length $(B))
85     - : 3 : Int
86
87 配列は異なります。配列の成分はどのような方法を用いても文字列とくっつけることができません。配列は括弧 ``[]`` を変数名につけ、インデントした中身を記述することで成分を定義できます。各成分にはホワイトスペースを含めることもできます。 ::
88
89     osh> A[] =
90             a b
91             foo bar
92     - : <array
93            "a b" : Sequence
94            "foo bar" : Sequence>
95            : Array
96     osh> echo $(A).c
97     a b foo bar .c
98     osh> value $(A.length)
99     - : 2 : Int
100     osh> value $(A.nth 1)
101     - : "foo bar" : Sequence
102
103 配列はしばしばシステム上にホワイトスペースを含んだファイル名を使う場合において、非常に有用なツールとなります。 ::
104
105     osh> FILES[] =
106              c:\Documents and Settings\jyh\one file
107              c:\Program Files\omake\second file
108
109     osh> CFILES = $(addsuffix .c, $(FILES))
110     osh> echo $(CFILES)
111     c:\Documents and Settings\jyh\one file.c c:\Program Files\omake\second file.c
112
113 .. _label7.3:
114
115 7.3 ファイルとディレクトリ
116 ---------------------------------------
117 複数のディレクトリにまたがっており、かつ異なったパートに分かれているOMakeのプロジェクト上では、まったく違うディレクトリ上でコマンドが実行されます。これはファイル、あるいはディレクトリの名前が位置的に独立して定義されている必要があることを表しています。
118
119 この問題は ``$(file <names>)`` や ``$(dir <names>)`` 関数を用いて解決できます。 ::
120
121    osh> mkdir tmp
122    osh> F = $(file fee)
123    osh> section:
124             cd tmp
125             echo $F
126    ../fee
127    osh> echo $F
128    fee
129
130 ``section:`` を用いて ``cd`` コマンドのスコープに制限を加えていることに注意してください。このセクションでは一時的に ``tmp`` ディレクトリに移動しているので、ファイルの名前には ``../fee`` が用いられます。このセクションが終了してカレントディレクトリに戻ってきたとき、ファイルの名前には ``fee`` が用いられます。
131
132 ファイル関数を使う主な目的は、あなたのプロジェクトの ``OMakefile`` で、ファイル名が正しく定義されるようにするためです。これを用いればプロジェクト上の様々なパートに移動したとしても、変数は同一のファイルを指し示します。 ::
133
134     osh> cat OMakefile
135     ROOT = $(dir .)
136     TMP  = $(dir tmp)
137     BIN  = $(dir bin)
138     ...
139
140 (訳注: file, dirについて詳しく知りたい方は ":ref:`label4.15`" を参照してください。)
141
142 .. index::
143    single: mapprefix()
144    single: addprefix()
145    single: addsuffix()
146    single: mapsuffix()
147    single: foreach()
148 .. _label7.4:
149
150 7.4 イテレーション、マップ、foreach
151 ---------------------------------------
152 ほとんどのビルドイン関数では配列を何も考慮することなく処理できます。 ::
153
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
160
161 ``mapprefix`` と ``addprefix`` 関数は全く異なります( ``addsuffix`` と ``mapsuffix`` 関数も同様です)。 ``addprefix`` 関数は接頭辞を各々の成分くっつけます。 ``mapprefix`` 関数は元の成分の前に新しい接頭辞を成分として加えるので、配列の長さは2倍になります。
162
163 ほとんどの関数は配列でも動きますが、あなた自身が配列にも対応した関数を作りたいと思うこともあるでしょう。 ``foreach`` 関数はその要望を実現します。 ``foreach`` 関数は二つの表記を使いますが、この中身を伴った表記法はとても便利です。この関数は二つの引数と中身が必要です。まず、一つ目の引数は変数で、二つ目のは配列を指定します。そして ``foreach`` の中身は各々の成分を対象に、引数で指定された変数がその成分に束縛された状態で、配列の長さぶんだけ実行されます。さあ、それでは値を持った配列の各々の成分に1を加える関数を定義してみましょう。 ::
164
165    osh> add1(l) =
166             foreach(i, $l):
167                 add($i, 1)
168    osh> add1(7 21 75)
169    - : 8 22 76 : Array
170
171 あなたはファイル名を持った配列を持ち、そのそれぞれにビルドルールを定義したいと思うこともあるでしょう。ビルドルールは特別なものではなく、あなたはどの箇所でもビルドルールを定義することができます。さて、私たちは配列にある、各々のファイルの処理について記述し、さらに結果を ``tmp/`` ディレクトリの中に置く関数を書きたいものとします。 ::
172
173    TMP = $(dir tmp)
174
175    my-special-rule(files) =
176       foreach(name, $(files))
177          $(TMP)/$(name): $(name)
178             process $< > $@
179
180 後に、プロジェクトの他の部分で、私たちはこの関数を用いていくつかのファイルを処理することを決めたとしましょう。 ::
181
182    # src/libに処理するためのファイルが入っています
183    MY_SPECIAL_FILES[] =
184        fee.src
185        fi.src
186        file with spaces in its name.src
187    my-special-rule($(MY_SPECIAL_FILES))
188
189 ``my-special-rule`` を呼んだ結果は、以下の3つのルールを明示的に書いた場合と全く同じになります。 ::
190
191     $(TMP)/fee.src: fee.src
192         process fee > $@
193     $(TMP)/fi.src: fi.src
194         process fi.src > $@
195     $(TMP)/$"file with spaces in its name.src": $"file with spaces in its name.src"
196         process $< > $@
197
198 もちろん、これらのルールを記述することは関数を呼ぶことより好ましいものではありません。関数を抽象化することによる普通の特性は、普通の利点となります。ビルドルールを定義するためのコードが一つだけで済み、コードはさらに短くなります。後でもし私たちがルールを修正したりアップデートしたいと思ったときも、単純に一つのルールを修正するだけでよいのです。
199
200 .. index::
201    single: 遅延評価変数
202 .. _label7.5:
203
204 7.5 遅延評価式
205 ---------------------------------------
206 omakeでの評価は通常の場合先行して行われます。これは、omakeが式に遭遇した場合、即座に評価が行われることを意味してみます。この効果の一つとして、変数定義式の右側は、変数が定義される時点で展開されることが挙げられます。
207
208 この振る舞いをコントロールするための2つの方法があります。 ``$`(v)`` は遅延評価を行うための、 ``$,(v)`` は先行評価に戻すための表記法です。以下のシーケンスについて考えてみましょう。 ::
209
210     osh> A = 1
211     - : "1" : Sequence
212     osh> B = 2
213     - : "2" : Sequence
214     osh> C = $`(add $(A), $,(B))
215     - : $(apply add $(apply A) "2" : Sequence)
216     osh> println(C = $(C))
217     C = 3
218     osh> A = 5
219     - : "5" : Sequence
220     osh> B = 6
221     - : "6" : Sequence
222     osh> println(C = $(C))
223     C = 7
224
225 ``C = $`(add $(A), $,(B))`` では遅延評価を定義しています。 ``add`` 関数はこの場合、実際に値が必要となるときまで評価しません。上の式を見てみると、 ``$,(B)`` は ``B`` が即座に評価する変数であることを指定します。これが遅延評価式の中で定義されているにもかかわらずです。
226
227 最初私たちが ``C`` の値を出力したとき、 ``A`` は1で ``B`` が2であったので、結果は3と評価されました。次に私たちが同様に ``C`` を出力したとき、 ``A`` は5に再定義されていたので、結果は7と評価されました。二回目の ``B`` は ``C`` の定義時に評価されているため、なんの影響も与えていません。
228
229 .. _label7.5.1:
230
231 7.5.1 遅延評価式についての追加例
232 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
233 遅延評価式は実際に結果が必要とされるときまで評価されません。筆者を含む結構な人数のプログラマは、遅延評価が多用しているコードを見ると眉をひそめます。なぜならこれは実際にどこで評価が行われているのか分かりにくくなるからです。しかしながら、これらの難点をペイオフできるケースも確かに存在します。
234
235 一つの例としてオプションの処理が挙げられます。Cコンパイラに"include"ディレクトリの指定をコマンドライン上から指定する場合を考えましょう。もし私たちが"/home/jyh/include"と"../foo"上のファイルをインクルードしたい場合、コマンドラインにはオプション ``-I/home/jyh/include -I../foo`` を指定する必要があります。
236
237 Cファイルをビルドするための通常のルールを定義する場合について考えましょう。私たちはインクルードされるディレクトリを指定するため、 ``INCLUDES`` 配列を定義し、ルートのOMakefileに通常用いる暗黙のルールを定義しました。 ::
238
239     # Cファイルをコンパイルする通常の設定
240     CFLAGS = -g
241     INCLUDES[] =
242     %.o: %.c
243        $(CC) $(CFLAGS) $(INCLUDES) -c $<
244
245     # srcディレクトリは4つのソースファイルからmy_widgetをビルドします。
246     # これはインクルードディレクトリからインクルードファイルを読み込みます。
247     .SUBDIRS: src
248         FILES = fee fi foo fum
249         OFILES = $(addsuffix .o, $(FILES))
250         INCLUDES[] += -I../include
251         my_widget: $(OFILES)
252            $(CC) $(CFLAGS) -o $@ $(OFILES)
253
254 しかしこれは全く正しいというわけではありません。問題としては、 ``INCLUDES`` はオプションが格納されてある配列であり、ディレクトリではないという点です。もし私たちが後にディレクトリ名を変更したい場合は、まず ``-I`` 接頭辞を配列から分割する必要があり、これは混乱の元となります。さらに、私たちはディレクトリの絶対パスを使用していません。この問題を解決する方法は、遅延評価式を使うことです。まず私たちは ``INCLUDES`` をディレクトリの配列として定義し、さらに新しい変数 ``PREFIXED_INCLUDES`` を定義することによって ``-I`` 接頭辞を追加します。 ``PREFIXED_INCLUDES`` は遅延評価を行うことで、最新のINCLUDES変数値が使われることを保証してくれます。 ::
255
256     # Cファイルをコンパイルする通常の設定
257     CFLAGS = -g
258     INCLUDES[] =
259     PREFIXED_INCLUDES[] = $`(addprefix -I, $(INCLUDES))
260     %.o: %.c
261        $(CC) $(CFLAGS) $(PREFIXED_INCLUDES) -c $<
262
263     # 今回の例では、私たちはインクルードディレクトリの絶対パスを定義しました。
264     STDINCLUDE = $(dir include)
265
266     # srcディレクトリは4つのソースファイルからmy_widgetをビルドします。
267     # これはインクルードディレクトリからインクルードファイルを読み込みます。
268     .SUBDIRS: src
269         FILES = fee fi foo fum
270         OFILES = $(addsuffix .o, $(FILES))
271         INCLUDES[] += $(STDINCLUDE)
272         my_widget: $(OFILES)
273            $(CC) $(CFLAGS) -o $@ $(OFILES)
274
275 遅延する値と関数が密接に繋がっていることに注目してください。上の例では、私たちは ``PREFIXED_INCLUDES`` を、引数を持たない関数として定義しているのと同じことを行っています。 ::
276
277     PREFIXED_INCLUDES() =
278         addprefix(-I, $(INCLUDES))
279
280 .. index::
281    single: while
282 .. _label7.6:
283
284 7.6 スコープとエクスポート
285 ---------------------------------------
286 OMakeの言語は(IOとシェルコマンドは除きますが)関数型言語となっています。これは、まず関数が最上級のものであることと、変数が不変なものである(代入という操作が存在しない)という2つから、そうであると言えるでしょう。後者に関しては、おそらく従来のGNU makeを使っていたユーザからすると奇妙なものに思えるかもしれません。しかしこれは実際にOMakeを使う上で非常に重要な点となります。変数は修正できませんので、プロジェクトの一部分が他の部分に干渉することは不可能(あるいは非常に困難)です。
287
288 これを従来の純粋な関数型言語のように実装してしまうと、非常に使いにくいものになるかもしれません。OMakeでは、インデントすることでレベルを一つ挙げた場合、新しいスコープが導入されます。そしてスコープが終わると、そのスコープで新しく定義された変数は消去されます。もしOMakeが馬鹿真面目にスコープに関して厳格な仕様であったならば、おそらくコードはもっと複雑なものになったでしょう。 ::
289
290    osh> X = 1
291    osh> setenv(BOO, 12)
292    osh> if $(equal $(OSTYPE), Win32)
293             setenv(BOO, 17)
294             X = 2
295    osh> println($X $(getenv BOO))
296    1 12
297
298 ``export`` コマンドはこの制限を外に出します。このコマンドは内部のスコープの値(あるいは全体の変数環境)を外部に「エクスポート」するお世話をします。 ::
299
300    osh> X = 1
301    osh> setenv(BOO, 12)
302    osh> if $(equal $(OSTYPE), Win32)
303             setenv(BOO, 17)
304             X = 2
305             export
306    osh> println($X $(getenv BOO))
307    2 17
308
309 エクスポートは、ループ中のイテレーションから次のイテレーションへ値をエクスポートするのに特に役立ちます。 ::
310
311    # オーケー、それでは配列の各成分を足し合わせてみよう
312    osh>sum(l) =
313            total = 0
314            foreach(i, $l)
315                total = $(add $(total), $i)
316            value $(total)
317    osh>sum(1 2 3)
318    - : 0 : Int
319
320    # うーん、正常に動いていないじゃないか!
321    osh>sum(l) =
322            total = 0
323            foreach(i, $l)
324                total = $(add $(total), $i)
325                export
326            value $(total)
327    osh>sum(1 2 3)
328    - : 6 : Int
329
330 ``while`` ループは自動的にエクスポートしてくれる、別の形のループ文です。 ::
331
332     osh>i = 0
333     osh>total = 0
334     osh>while $(lt $i, 10)
335             total = $(add $(total), $i)
336             i = $(add $i, 1)
337     osh>println($(total))
338     45
339
340 .. index::
341    single: シェルエイリアス
342    single: Shell
343 .. _label7.7:
344
345 7.7 シェルエイリアス
346 ---------------------------------------
347 ときどきあなたは *エイリアス* を定義したいと思うことがあるかもしれません。そのためにOMakeでは、実際にシェルコマンドが存在しているかのようにふるまうコマンドが存在します。あなたはこれを、 ``Shell`` オブジェクトに対象の関数を追加することで実現できます。
348
349 例えば、 ``awk`` 関数を用いて、あるファイル中のすべてのコメントを出力する場合について考えてみましょう。 ::
350
351     osh>cat comment.om
352     # Comment function
353     comments(filename) =
354         awk($(filename))
355         case $'^#'
356             println($0)
357     # File finished
358     osh>include comment
359     osh>comments(comment.om)
360     # Comment function
361     # File finished
362
363 これをエイリアスとして追加するには、 ``Shell`` オブジェクトにメソッドを追加します。 ``+=`` を用いて、シェルの既存の内容を保存している点に注意してください。 ::
364
365    osh>Shell. +=
366            printcom(argv) =
367                comments($(nth 0, $(argv)))
368    osh>printcom comment.om > output.txt
369    osh>cat output.txt
370    # Comment function
371    # File finished
372
373 シェルコマンドは引数として配列 ``argv`` が渡されます。これはエイリアスの名前には *含まれていません* 。
374
375 .. index::
376    single: リダイレクション
377    single: stdin
378    single: stdout
379    single: stderr
380 .. _label7.8:
381
382 7.8 簡単に入出力のリダイレクションを行う
383 -----------------------------------------
384 結果的に、スコーピングによってリダイレクションの実行に関しての良い代替案も表れることとなりました。それでは、既に標準の出力先に出力するコードが大量にあるが、この出力先のリダイレクションを行いたいというような場合について考えてみましょう。まず一つ目の方法としては、前回のテクニックを用いることが挙げられます。具体的には、関数をエイリアスとして定義し、あなたが望む出力先にすることでシェルのリダイレクションを行うといった方法です。
385
386 別の方法については、前者の方法よりも簡単な場合があります。変数 ``stdin`` ``stdout`` ``stderr`` は標準I/Oの出力先について定義しています。出力先をリダイレクトするには、これらの変数をあなたが望むように再定義します。もちろん、あなたはこれを普通にネストされたスコープ上で行うことができるので、外部の出力先に影響を与えることはありません。 ::
387
388     osh>f() =
389             println(Hello world)
390     osh>f()
391     Hello world
392     osh>section:
393             stdout = $(fopen output.txt, w)
394             f()
395             close($(stdout))
396     osh>cat output.txt
397     Hello world
398
399 これはシェルコマンドに対しても同様です。もしあなたがギャンブル好きであるならば、以下の例を試してみるのもいいでしょう。 ::
400
401     osh>f() =
402             println(Hello world)
403     osh>f()
404     Hello world
405     osh>section:
406             stdout = $(fopen output.txt, w)
407             f()
408             cat output.txt
409             close($(stdout))
410     osh>cat output.txt
411     Hello world
412     Hello world
413
414