TL; DR
-
>>define
ディレクティブでコンパイル時変数を使用 -
COPY
文を関数の代わりに使用 - 再帰ができないのでチューリング完全ではない
(残念!)
はじめに
人々を魅了してやまないコンパイル時計算 (主語デカ)
C++やRust等で多用される技術ですが、本記事では COBOLのコンパイル時計算 について紹介したいと思います。
読み終わるころには、貴方も DATA DIVISION
のしがらみから解放されることでしょう。
おことわり
著者はCOBOL超初心者なので、内容に誤りがありましたらコメント等でご指摘いただけますとありがたいです。
そもそもテーマからふざけた記事におことわりも何もありませんが...
実行環境
-
gcobol
(GCC15.0.1)
GCOBOLについては以前の記事でも紹介しています。
コンパイル時計算のやり方
コンパイル時計算には、 defineディレクティブを使用します。
define
defineディレクティブを使用すると、コンパイル時に変数を定義することができます。
>>define n 0
override
で値を更新(再定義)することも可能です。コンパイル時計算では基本的にこの override
を使用します。
>>define n as n + 1 override
COPY文
上記を繰り返すだけでも計算はできますが、処理をもう少しスマートに管理したいです。COPY文を使用することで、コンパイル時にソースコードを別ファイルから挿入できます。
以下は階乗の計算をするプログラムです。
COPY文 copy "fact.cpy".
を使用した箇所には、コンパイル時に原文ファイル fact.cpy
に記載されたソースコードが挿入されます。
identification division.
program-id. fact.
procedure division.
>>define n 0
>>define fact 1
* 10!を計算
* copy文を使用すると、コンパイル時に該当箇所に原文ファイルのソースコードが挿入される
copy "fact.cpy".
copy "fact.cpy".
copy "fact.cpy".
copy "fact.cpy".
copy "fact.cpy".
copy "fact.cpy".
copy "fact.cpy".
copy "fact.cpy".
copy "fact.cpy".
copy "fact.cpy".
display fact.
stop run.
>>define n as n + 1 override
>>define fact as fact * n override
こうすることで、以下のように階乗計算のプログラムが作成されます。
>>define n 0
>>define fact 1
* copy文を展開
>>define n as n + 1 override
>>define fact as fact * n override
* copy文を展開
>>define n as n + 1 override
>>define fact as fact * n override
* ...
コンパイル時プログラミングに詳しい方は「COPYでループが作れるのでは?」と思われたかもしれませんが、残念ながら COPYでは自分自身を指定することができないため不可能でした(後述)。
コンパイル時変数を参照
最後に、計算されたコンパイル時変数を(通常のデータ項目のように) display
に指定することで内容を表示します。実行されるプログラムでは実質計算結果の数値定数をdisplayしているのと同じです。
display fact.
表示時は左端に符号が付きます。
+3628800
別のデータ項目へmoveすれば任意の書式で出力可能です。今回はdata division禁止縛りなので使いませんが
* ...
data division.
working-storage section.
01 fact-printing pic 9(8).
procedure division.
* ...
move fact to fact-printing.
display fact-printing.
stop run.
03628800
条件分岐
より複雑な処理をするためには条件分岐を使用します。 >>if
や >>evaluate
が使用可能です。
通常の if
や evaluate
と同じ構文で、先頭に >>
を付けるだけでコンパイル時処理になります。
通常のif文と異なるのは、コンパイル時計算なので条件を満たした行だけがプログラムに含まれるという点です。
>>define n as 90
>>if n > 60
display "OK".
>>else
display "NG".
>>end-if
display "OK".
実際にプログラム中にif文があるわけではないため、then節、else節の各文の文末は;
ではなく.
にする必要があります 1。
以下はfizzbuzzの分岐処理を書いた例です。階乗計算同様COPY文を使用し処理を原文ファイルにまとめています。
>>if n / 15 * 15 = n
display "FizzBuzz".
>>else
>>if n / 3 * 3 = n
display "Fizz".
>>else
>>if n / 5 * 5 = n
display "Buzz".
>> else
display n.
>>end-if
>>end-if
>>end-if
>>define n as n + 1 override
(>>evaluate
も使用できるはずなのですが、when節が上手くパースできませんでした...)
COPY文で呼び出すとこうなります。
identification division.
program-id fizzbuzz.
procedure division.
>>define n 1
copy "fizzbuzz.cpy".
* ... (計15行)
copy "fizzbuzz.cpy".
stop run.
+1
+2
Fizz
+4
Buzz
Fizz
+7
+8
Fizz
Buzz
+11
Fizz
+13
+14
FizzBuzz
想定通り出力されました。
制約
ここまで簡単なプログラムをコンパイル時計算で実装してきましたが、この手法にはいくつか制約があります。
COPY文の再帰はできない
先にも述べたように、原文ファイル内のCOPY文で自身を参照することはできません。
別の原文ファイルを経由して間接的に参照する(a.cpy
→ b.cpy
→ a.cpy
)こともできません。
looper.cpy:6:1: error: recursive copybook: 'looper.cpy' includes itself
6 | end-if
| ^
cobol1: error: failed compiling copy.cbl
- エラー定義個所
言い換えると、処理を繰り返す場合はその回数分だけ COPY
を記載する必要があります。無限ループを作ることはできません。
言語の安全機構としては素晴らしいですが、コンパイル時計算はチューリング完全ではなくなってしまいました。COBOLのコンパイル時brainf*ck等は実現困難です2。
コンパイル時変数はint32
もう1点の制約として、コンパイル時変数はint32なので、大きな数値を扱うことができません。扱いたい場合は上位nビット、下位nビットのように変数を分ける必要があります。
identification division.
program-id define-max.
procedure division.
>>define n as 2147483648
display n.
stop run.
-2147483648
おわりに
以上、COBOLのコンパイル時計算についての紹介でした。皆さんも、究極の省メモリ、高速実行を体感してみてはいかがでしょうか?