今年最後の更新となりました、山本です。

さて、今回のお題は「COBOL」です。

なんでCOBOLなんでしょう。

実は、このブログは事前にお題の幾つか考え、社長のOKが出たものだけを記事にするものとなっています。

その中でCOBOLが出たのは、お題のネタを出し尽くして、他に浮かばなかったからにほかなりません。

というか、出したの私なんですよ。

なんで、これ通ったんでしょうね?

COBOLってなに?

何でしょうね。

  • 金融系で使われているイメージ
  • 小数点に強い?

とか、実のところ殆ど分かりません。

そもそもなんですが、どんな環境で動くの?という疑問があります。
又、この言語コンパイルするの?って疑問もあります。
まさか、インタプリタではないと思うのですが。

その辺を調べつつやっていこうと思います。

環境を作ろう

まず、環境を作らないことには始まりません。

とはいえ、汎用機が必須とかになると困ってしまいます。
経費で落ちるか怪しいですし、自腹も厳しいので。

最悪、エミュレータ等でなんとかなればいいのですが。。。

え~と、調べたところOpenCOBOLなるものがあるようです。
これがあればLinuxで動かせるのかな?

あと、コンパイルするようです。

よく考えるとコンパイルするのって数年ぶりな気がします。

最後にやったのってJavaだったかな?C++じゃなかったとは思うのですが。。。

で、インストール方法を調べていたのですがどうやらWindowsで環境を作れるようです。
なのでWindowsでやってみましょう。

Windows環境にOpenCOBOLをインストールする

良い情報がありました。

参考サイト
http://kunst1080.hatenablog.com/entry/2015/05/07/235848

ありがたく、参考にさせて頂きます。

さて、まずMinGWが必要なようです。

なので、それをインストールします。

そして手順に従い、システムパッケージの更新を行い、組み込みパッケージの更新を行ったこところ、エラーを吐きました。

cobol_001

cygwin DLL?

今のPCにはcygwinはインストールしていなかったはずなんですが。

つまりcygwin1.dllを消せば良いのでしょう。

ただ、全く所在に心当たりがありませんので検索してみます。

cobol_002

見つかりました。

因みに同じファイルが複数あるのは、Cドライブの特定ディレクトリを仮想のDドライブにしているためです。
なので、実際には2ファイルあることになります。

見る限り、どちらも消して問題なさそうなので削除して進めます。

時に疑問なのですが、該当のディレクトリにパスを通しているわけでもないのにcygwin1.dllのエラーが出るんでしょうね。

さて、次はnkfのインストールです。

cobol_003

なんか、また失敗しました。。。

何が良くないのか分かりませんが、どうやらエラーは出るがインストールは出来ているようです。

cobol_004

では、今回の本題のopen COBOLをインストールします。

残念ながらソースからコンパイルになるようですが、やってみます。

cobol_005

configureは通ったのですが、makeが通りません。

原因がさっぱり分からないので、色々調べたのですが、必要とする情報が見つかりませんでした。

どうしたものかなと思ったのですが、そもそもWindowsでやろうと思ったのはLinuxではつまらないかな~ぐらいの気持ちなので、別に拘る必要も無い気がします。

ええ、動かないと意味がないですからね。

Linux 環境にOpenCOBOLをインストールする

今回はCentOS6でやってみます。

参考サイト
http://a4dosanddos.hatenablog.com/entry/2013/11/02/153153

yum install gmp gmp-devel db4 db4-devel

必要パッケージをインストールします。

wget http://sourceforge.net/projects/open-cobol/files/open-cobol/2.0/open-cobol-2.0-feb2012.tar.gz/download
tar zxvf open-cobol-2.0-feb2012.tar.gz
cd open-cobol-2.0
./configure
make && make install
[root@localhost /]# cobc -version
cobc (OpenCOBOL) 2.0.0
Copyright (C) 2001,2002,2003,2004,2005,2006,2007 Keisuke Nishida
Copyright (C) 2006-2012 Roger While
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Built     Dec 14 2015 17:02:34
Packaged  Feb 11 2012 12:36:31 UTC
C version "4.4.7 20120313 (Red Hat 4.4.7-11)"

動いた!

Linuxとても簡単!

ハロワ

では、まずはHello worldからやっていきましょう。

参考としたサイトのHello worldを試してみます。

       identification division.
       program-id.    sample.
      *
       environment    division.
      *
       data           division.
      *
       procedure      division.
       main.
           accept num from console.
           display "Hello World" upon console.
           stop run.

hoge.cobで作成して

cobc -x hoge.cob

コンパイル

エラー!

[localhost@adjust:cobol]$ cobc -x hoge.cob 
hoge.cob: In paragraph 'main':
hoge.cob: 10: Error: 'num' is not defined

なんで?

まあいいです、なんかもう面倒くさいので他のハロワを探します。

ぐぐったら、別のが見つかりましたのでこれでいきます。

000010 IDENTIFICATION                   DIVISION.
000020 PROGRAM-ID.                      SAMPLE-01.
000030*
000040 ENVIRONMENT                      DIVISION.
000050*
000060 DATA                             DIVISION.
000070*
000080 PROCEDURE                        DIVISION.
000090 MAIN.
000100     DISPLAY "Hello world!"  UPON CONSOLE.
000110     STOP RUN.

左のは行番号?行番号は流石に不要だと思うので削除して実行します。

[localhost@adjust:cobol]$ cobc -x hoge.cob 
hoge.cob: 1: Error: Invalid indicator 'F' at column 7

………、料理が下手な人ほどアレンジを加えたがるものです。

そのままやってみましょう。

[localhost@adjust:cobol]$ cobc -x hoge.cob 

何も出ない、ナンデ?

[localhost@adjust:cobol]$ ll
合計 16
-rwxrwxr-x 1 adjust adjust 9524 12月 14 18:04 2015 hoge
-rw-rw-r-- 1 adjust adjust  359 12月 14 18:03 2015 hoge.cob

ちゃんと、実行ファイルが出来てますね。

何も考えずに、そのまま実行されると勘違いしていました。

コンパイルするんだから、あたりまえなんですが、失念していました。

気を取り直して実行。

[localhost@adjust:cobol]$ ./hoge 
./hoge: error while loading shared libraries: libcob.so.4: cannot open shared object file: No such file or directory

またか!

パスかな?

設定を読み込みなおして

sudo ldconfig
[localhost@adjust:cobol]$ ./hoge 
Hello world!

やった動いたw

ソースを読み解いてみる

次回に続く!としたいですが、流石に尺が短いのでもう少し進めてみましょう。

まず、先頭の2行ですが

IDENTIFICATION                   DIVISION.
PROGRAM-ID.                      SAMPLE-01.

IDENTIFICATIONは見出し部の宣言文だそうです。

#include "stdio.h"

的なものかとも思いましたが、特に意味はないそうです。

そして、PROGRAM-IDでID指定が必要であるようです。

プログラム名の最初の8字が,プログラムを識別する名前として用いられる.
このコンパイラでは,プログラム名は英字で始まる英数字で構成される文字列とする.

最初の8文字?
長いとエラーになるのか?
と思ったのですが「SAMPLE-01」、サンプルの時点で9文字あります。
超過した場合は切り捨てのようですね。

次に、左の行番号とか分からないものが多々ありますのでそこを調べてみましょう。

1~6文字目 行番号
7文字目 区分
8~72文字目 本文
73~80文字目 コメント

だそうです。

それを踏まえ、ソースをもう一度みてみましょう

000010 IDENTIFICATION                   DIVISION.
000020 PROGRAM-ID.                      SAMPLE-01.
000030*

6文字目までが行番号で、区分が一文字なので、*が区分でしょうか?

区分が何かはよく分かりませんが、その次の8文字目からが本文のようです。

あと.が行の終わりのようです。

ふと思ったのですが、DIVISIONの前のスペースは必要なのでしょうか?

カラムの定義を見る限り必要なさそうです。
あと、行番号は省略出来ないのでしょうか?

これでは後から一行追加する度に行番号を変更する必要が出てきます。
それは避けてたいですね。

       IDENTIFICATION DIVISION.
       PROGRAM-ID. SAMPLE-01.
      *
       ENVIRONMENT DIVISION.
      *
       DATA DIVISION.
      *
       PROCEDURE DIVISION.
       MAIN.
           DISPLAY "Hello world!"  UPON CONSOLE.
           STOP RUN.

試してみましたが、コンパイル、実行ともに問題なく動きました。

カラムの数さえ間違わなければ、大丈夫のようです。

73~80文字目のコメントですが、コメントって8文字で終わらないような気がします。。。

この一行80文字制限って、パンチカード時代の名残っぽいですね。

古い言語、そういえばFortranも同じ80文字制限があったような気がします。

次に、区分を見てみましょう。

*(アスタリスク)
コメント行とし、以下のカラムをコメントとして使用できます。
-(ハイフン)
文字列のつなぎとみなし、以下のカラム記述を前行からの続きだとみなします。

なるほど、コメントを書きたいときは7文字目に*を使えば、長く書けそうですね。

あと、「-」ですが思い返してみればBasicも複数行を使用する場合文末に「_」を使った記憶があります。
同じことなのでしょう。

ENVIRONMENTとDATAは必須ではないので後回しとして
「PROCEDURE DIVISION.」が命令の記述箇所になるようです。

       MAIN.
           DISPLAY "Hello world!"  UPON CONSOLE.
           STOP RUN.

え~と、main関数があって「Hello world!」を表示、対象は「UPON CONSOLE」という意味っぽいですね。

「STOP RUN.」で処理を終了と……、exit or returnみたいなもの?

とここで疑問なのですが、COBOLもMAINあるの?

調べたのですが、mainは見つかりませんでした。

そもそも、この行いらないのではと思い消してみましたが

        IDENTIFICATION DIVISION.
        PROGRAM-ID. SAMPLE-01.
        PROCEDURE DIVISION.
            DISPLAY "Hello world!"  UPON CONSOLE.
            STOP RUN.

消しても動きますね。
おかげでずいぶん短くなりました。

FizzBuzz

そろそろ、今回の記事の終わらせかたを考えなければいけません。

なので、FizzBuzzをやってみましょう。

まだ、一から作るのは難しいのでサンプルを探してきました。

ありがたく借用させて頂きます。

参考サイト
http://quill3.hatenablog.com/entry/20070511/p1

       IDENTIFICATION  DIVISION.
       PROGRAM-ID.     FizzBuzz.
       DATA            DIVISION.
       WORKING-STORAGE SECTION.
       01 IX           PIC 9(03).
       01 TBL-ALL.
          05 TBL       PIC X(08) OCCURS 100.
       PROCEDURE    DIVISION.
       MOVE SPACE TO TBL-ALL
       PERFORM WITH TEST BEFORE VARYING IX FROM 3 BY 3 UNTIL IX > 100
         MOVE 'Fizz    ' TO TBL(IX)
       END-PERFORM
       PERFORM WITH TEST BEFORE VARYING IX FROM 5 BY 5 UNTIL IX > 100
         IF TBL(IX) = 'Fizz    '
           MOVE 'FizzBuzz' TO TBL(IX)
         ELSE
           MOVE 'Buzz    ' TO TBL(IX) 
         END-IF
       END-PERFORM
       PERFORM WITH TEST BEFORE VARYING IX FROM 1 BY 1 UNTIL IX > 100
         IF TBL(IX) = SPACE
           DISPLAY IX
         ELSE
           DISPLAY TBL(IX)
         END-IF
       END-PERFORM
       STOP RUN.

cobol_006

動いた!

まあコピペしただけなので、動くのは当然なのですが、今回は上手くいかない事が多かったので動いて安心しました。

FizzBuzzを読んでみる

       DATA            DIVISION.
       WORKING-STORAGE SECTION.
       01 IX           PIC 9(03).
       01 TBL-ALL.
          05 TBL       PIC X(08) OCCURS 100.

DATA DIVISIONの子要素に、WORKING-STORAGEのSECTIONがあり、その下に変数?の定義のように思われますね。

あと、代入までしているのでしょうか?

IXとかTBL-ALLとかは変数名になるのでしょうか?
あとこの左の番号はなんでしょうか?
PICって何の略称?
9とかXの横の数字ってバイト数?

等など、短い記述の中に謎が詰まっています。

さて、ドキュメントを調べてですが
まず、左の数字ですが、これはレベル番号だそうです。

1~49まで使うことが出来て、これで階層構造を制御出来るようですね。

え?数字で階層管理するの?

レベル1とレベル2を使っていて、今のレベル2を3へと変更したくなった場合、レベルを置換しないといけなくなるのでは?

なんでも、そうならないように、ある程度番号を繰り上げて使うらしいですけどそういう問題か?

それとも、そんなに階層は深くならないってことなのでしょうか?

昔、Pythonのインデントで階層管理するというのに驚きましたが、この方式の方が驚きがありますね。

次に、変数名の定義っぽいものがあります。
十中八九これが変数名だと思うのですが、ググってもレベルとか型の情報しかなく断言できるものが見つかりませんでした。

一応、英数と一部記号と30文字まで使えて、それを超過すると切り捨てをするみたいです。

最後にPICというのはPICTURE句らしく、PICはその省略形だそうです。

そこで、符号、データ型等を指定すると。

9が符号無しの数字一桁でその次の数字がバイト数を指定するようです。
つまり、3バイトの自然数となるわけなんですね。

過去の経験上、Short intやLong intでサイズを選ぶことはあるのですが、型の名前ではなく直接バイト数を選ぶのは新鮮です。

ようやく、ここで初めてCOBOLすげーと思いました。

で、Xは任意の字を指し、その次の数字がバイト数になるようです。

まあ、これはそんなに驚きはありませんね。
Char型でバイトを指定するとってのはありがちな話です。

OCCURSは繰り返し定義?だそうです。
何でしょう、よく分かりませんが指定の方で指定個数の配列を作るようなものなのでしょうか?

とにかく、代入ではないようです。

       MOVE SPACE TO TBL-ALL

SPACEは予約語で、そのままスペースを意味するようです。

MOVE TOは左の値を右に代入するようです。

つまり、TBL-ALLの中身?をSPEACEで埋めるってことですかね。

       PERFORM WITH TEST BEFORE VARYING IX FROM 3 BY 3 UNTIL IX > 100
         MOVE 'Fizz    ' TO TBL(IX)

どうも、これがループらしいです。

PERFORM WITH TEST [BEFORE, AFTER]

といった形で、前判定と後判定を分けることが出来るようです。
whileとdo whileですね。

VARYING がカウントアップで、UNTILが条件ですか。

IX が3から始まり、3刻みでカウントアップ、100を超えたら終了

for (int i = 3; i <= 100; i + 3)

みたいな感じですかね。

でループの中で、3刻みのTBLに対して「Fizz」を代入しているようです。

次に5刻みでBuzzをやっているようです。

       PERFORM WITH TEST BEFORE VARYING IX FROM 5 BY 5 UNTIL IX > 100
         IF TBL(IX) = 'Fizz    '
           MOVE 'FizzBuzz' TO TBL(IX)
         ELSE
           MOVE 'Buzz    ' TO TBL(IX) 
         END-IF

えーとループの条件は同じで、前と違うのは分岐がありますね。

とはいえ、この分岐の書き方は見慣れた形ですね。

値に「Fizz」が入っていれば、「FizzBuzz」へ変更し、なければ「Buzz」を代入ですね。

比較が「=」なのは、まあ他の言語でもあるので違和感は感じませんね。

で最後に

       PERFORM WITH TEST BEFORE VARYING IX FROM 1 BY 1 UNTIL IX > 100
         IF TBL(IX) = SPACE
           DISPLAY IX
         ELSE
           DISPLAY TBL(IX)
         END-IF
       END-PERFORM

1から100までループして、置換え用テーブルに初期値(スペース)があるものは、ループカウンタを表示して、そうでなければテーブルの値を表示しているということでしょう。

なんか、ソースが読み取れて来ました。

書いてみる

少しづつ、読み解けてきました。
なので、私もFizzBuzzを書いてみようと思います。

サンプルと同じ書き方では面白くありませんので、ここはよくある方法の余りで判定してみましょう。

       IDENTIFICATION  DIVISION.
       PROGRAM-ID.     FizzBuzz.
       DATA            DIVISION.
       WORKING-STORAGE SECTION.
       01 i           PIC 9(03).
       01 cnt         PIC 9(03).
       01 res         PIC 9(03).
       01 rem1        PIC 9(03).
       01 rem2        PIC 9(03).

       PROCEDURE    DIVISION.
       MOVE 100 TO cnt

       PERFORM WITH TEST BEFORE VARYING i FROM 1 BY 1 UNTIL i > cnt
         DIVIDE i BY 3 GIVING res REMAINDER rem1
         DIVIDE i BY 5 GIVING res REMAINDER rem2
         IF rem1 = 0 AND rem2 = 0
           DISPLAY 'FizzBuzz'
         ELSE IF rem1 = 0
           DISPLAY 'Fizz'
         ELSE IF rem2 = 0
           DISPLAY 'Buzz'
         ELSE
           DISPLAY i
         END-IF
       END-PERFORM
       STOP RUN.

出来ました。

余りはDIVIDEで取得できるようです。

DIVIDE i BY 3 GIVING res REMAINDER rem1

iを3で割り、商がresで、剰余がrem1に代入されます。

本当は、余りだけを取りたかったのですが、その方法が見つかりませんでした。

とはいえ、ANDも使え、ELSE IFも使えたので、結構簡単に作ることが出来ました。

まあ、この書き方がCOBOL的に良いのかは分かりませんがw

小数点

忘れていましたが、小数点に強いイメージがあると書きましたので、それを試してみましょう。

まず、前提としてなのですがプログラムは小数点の計算に弱いというのもがあります。

簡単に言ってしまうと、10進数では有限小数の値が、2進数でも有限小数であるとは限らないということです。

0.1 + 0.2 == 0.3

まあ、実際に見てみた方が早いと思うので、簡単なサンプルを用意しました。

さて、左辺と右辺は等しいでしょうか?

常識的に考える限り等しいと思われることでしょう。

では、幾つかの言語で結果を見てます。

  • PHP
<?php
  echo var_dump(0.1 + 0.2 == 0.3);
?>

>> bool(false)
  • JavaScript
console.log(0.1+0.2 == 0.3);

>> false
  • Java
public class Main {
    public static void main(String[] args) throws Exception {
        System.out.println(0.3 + 0.2 == 0.3);

    }
}

>> false
  • ruby
print 0.1+0.2 == 0.3

>> false

全て、等しくないそうです。

別にもっと言語を増やしても良いのですが、まあ結果は変わりません。

  • COBOL
IDENTIFICATION  DIVISION.
program-id.     test.
DATA            DIVISION.
WORKING-STORAGE SECTION.
01 i           PIC 9(03)V9(5).

PROCEDURE DIVISION.

COMPUTE i = 0.1 + 0.2.

IF i = 0.3
  display 'true'
END-IF

STOP RUN.

>> true

COBOLでは等しくなりますね。

これは、固定小数点方式を採用しているためです。

この方式について、詳しく説明するのは難しいので、そういうものだとお考えください。

気になる方はwikiに説明があるのでこちらを御覧ください。

固定小数点方式

  • Excel

余談ですが、Excelも同様の方式を採用している為、等しくなります。

=IF(0.1 + 0.2 = 0.3, TRUE, FALSE)

TRUE

まとめ

インストールにとても時間がかかりましたが、それ以降は割合すんなり出来た気がします。

なんというか、初めて見る言語でしたので、記述がさっぱり分かりませんでしたが読み解いていくと噂に聞くほどではなかった気がします。
まあ、すごく冗長な言語であることは間違いないでしょうが。

さて、次はどうしたものかと考えています。

COBOLでCGI?
どうもフレームワークがあるとこことらしいでのそれを使ってCGIでもやってみましょうか?

それとも、jsでパンチカードでも作って、それをパンチしたものを実行出来るアプリケーションでも作りましょうか?

はたまた、COBOLでGUI作って、アジャストしか見れないブラウザでも作りましょうか?

まあ、来年になったら考えます。

ではまた来年にお会いしましょう。