はじめに
@Nemesisさんの「Javaで湯婆婆を実装してみる」が話題なので、流行りに乗っかろうと思います。
まだ書かれていなそうな言語、ということで昔使っていたCOBOLでやってみることにしました。
必要な処理と対応するCOBOL構文
湯婆婆の処理の流れは、以下の4工程です。
①コンソールに規定のメッセージを出力
②コンソールに入力された名前を受け取る
③ランダムで何文字目かを決める
④決めた文字を規定のメッセージに含めて出力
各処理に対応するCOBOL構文は以下の通りです。
- コンソール出力
DISPLAY 変数.
- コンソール入力
ACCEPT 変数 FROM CONSOLE.
- ランダム
FUNCTION RANDOM().
- 値の代入
MOVE 1 TO 変数.
- 部分文字列の取得
文字型変数(開始Index:取得バイト数)
- 変数宣言
01 変数 PIC X(40).
- コメントアウト
7バイト目に*を付けた行
コード(その1)
長いので畳んでます
IDENTIFICATION DIVISION.
PROGRAM-ID. coba-ba.
AUTHOR. Keita INAGAKI.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
DATA DIVISION.
WORKING-STORAGE SECTION.
*
01 IN-NAME PIC X(40).
01 RAND-VAL PIC V9(10).
01 INPUT-SIZE PIC 9(5).
01 TMP-IDX PIC 9(5).
01 RESULT-INDEX PIC 9(5).
01 SEED-VAL PIC 9(5).
* 乱数のシードに渡せるのは0~32767のため、分秒4桁を渡す
01 DATE-NOW.
03 DATE-NOW-YY PIC X(04).
03 DATE-NOW-MM PIC X(02).
03 DATE-NOW-DD PIC X(02).
03 DATE-NOW-HH PIC X(02).
03 DATE-NOW-FFSS PIC X(04).
*
******************************************************************
* PROCEDURE DIVISION
******************************************************************
PROCEDURE DIVISION.
CNTL SECTION.
* メッセージ出力
DISPLAY "契約書だよ。そこに名前を書きな。".
* コンソール入力を受け取る
ACCEPT IN-NAME FROM CONSOLE.
* 入力文字長の取得
PERFORM GET-INPUT-SIZE.
DISPLAY "フン。"
IN-NAME(1:INPUT-SIZE)
"というのかい。贅沢な名"
"だねぇ。".
* 文字を奪う
PERFORM TAKE-AWAY-STRING.
* DISPLAY RESULT-INDEX.
DISPLAY "今からお前の名前は" IN-NAME(RESULT-INDEX:1)
"だ。いいかい、" IN-NAME(RESULT-INDEX:1)
"だよ。分かったら返事をするんだ、" IN-NAME(RESULT-INDEX:1)
"!!".
STOP RUN.
*
**********************************************************
* N文字目を決める(in:IN-NAME out:RESULT-INDEX)
**********************************************************
TAKE-AWAY-STRING SECTION.
TAKE-AWAY-STRING-RTN.
* 乱数シードのために現在時刻を取る
MOVE FUNCTION CURRENT-DATE TO DATE-NOW.
MOVE FUNCTION NUMVAL(DATE-NOW-FFSS) TO SEED-VAL.
* 乱数は0以上1未満で作成される
MOVE FUNCTION RANDOM(SEED-VAL) TO RAND-VAL.
* DISPLAY RAND-VAL.
* 乱数と文字長を掛けて何文字目かを決定する
MULTIPLY RAND-VAL BY INPUT-SIZE GIVING RESULT-INDEX.
IF RESULT-INDEX = 0 THEN
MOVE 1 TO RESULT-INDEX
END-IF.
TAKE-AWAY-STRING-EXT.
EXIT.
**********************************************************
* 入力文字数カウント(in:IN-NAME out:INPUT-SIZE)
**********************************************************
GET-INPUT-SIZE SECTION.
GET-INPUT-SIZE-RTN.
INITIALIZE INPUT-SIZE.
PERFORM VARYING TMP-IDX FROM 1 BY 1
UNTIL TMP-IDX > 40
* DISPLAY "char: " IN-NAME(TMP-IDX:1)
IF IN-NAME(TMP-IDX:1) = SPACE THEN
EXIT PERFORM
END-IF
ADD 1 TO INPUT-SIZE
END-PERFORM.
GET-INPUT-SIZE-EXT.
EXIT.
苦戦はしましたが、一通りの処理が書けました。早速実行してみます。
良い感じに文字を奪って出力できています。が、
\ 2バイト文字だー!! /
COBOLの部分参照はバイト単位なので、2バイト文字の考慮が必要でした。
ググれば何とかなる、と思ったら同じような書き込み多数で、どうやら便利な関数は無いようです。
覚悟を決めてsjisの文字コード表を確認します。
1バイト目を確認すれば、それが1バイトの文字か2バイトの文字かが判断できるようなので、
COBOLの制御に組み込んでみます。半角カナの範囲も忘れずに判定します。
* 全角半角判定
IF (IN-NAME(TMP-IDX:1) >= X"00" AND
IN-NAME(TMP-IDX:1) <= X"7F") OR -- ascii
(IN-NAME(TMP-IDX:1) >= X"A1" AND
IN-NAME(TMP-IDX:1) <= X"DF") THEN -- 半角カナ
* DISPLAY "半角:" IN-NAME(TMP-IDX:1)
ELSE
* DISPLAY "全角:" IN-NAME(TMP-IDX:2)
ADD 1 TO TMP-IDX -- 次の文字までIndexをずらす
END-IF
1バイト目の文字範囲をチェックし、2バイト文字であれば2バイトの塊として扱う形で記述しています。
(定義外のエリアをどう扱うべきか、ちょっとわからなかったので、とりあえず2バイト扱いしています。。)
コード(最終版)
長いので畳んでます
IDENTIFICATION DIVISION.
PROGRAM-ID. coba-ba.
AUTHOR. Keita INAGAKI.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
DATA DIVISION.
WORKING-STORAGE SECTION.
*
01 IN-NAME PIC X(40).
01 RAND-VAL PIC V9(10).
01 INPUT-BYTES PIC 9(5).
01 INPUT-SIZE PIC 9(5).
01 TMP-IDX PIC 9(5).
01 TMP-SIZE PIC 9(5).
01 RESULT-INDEX PIC 9(5).
01 RESULT-STR PIC X(10).
01 RESULT-BYTES PIC 9(1).
01 SEED-VAL PIC 9(5).
* 乱数のシードに渡すのは0~32767のため、分秒4桁を渡す
01 DATE-NOW.
03 DATE-NOW-YY PIC X(04).
03 DATE-NOW-MM PIC X(02).
03 DATE-NOW-DD PIC X(02).
03 DATE-NOW-HH PIC X(02).
03 DATE-NOW-FFSS PIC X(04).
*
******************************************************************
* PROCEDURE DIVISION
******************************************************************
PROCEDURE DIVISION.
CNTL SECTION.
* メッセージ出力
DISPLAY "契約書だよ。そこに名前を書きな。".
* コンソール入力を受け取る
ACCEPT IN-NAME FROM CONSOLE.
* 入力文字長の取得
PERFORM GET-INPUT-BYTES.
* 受け取った名前を出力
DISPLAY "フン。"
IN-NAME(1:INPUT-BYTES)
"というのかい。贅沢な名"
"だねぇ。".
* 文字を奪う
PERFORM TAKE-AWAY-STRING.
* DISPLAY RESULT-INDEX.
* 全角半角で取得バイト数を変えるため、処理変更
* DISPLAY "今からお前の名前は" IN-NAME(RESULT-INDEX:1)
* "だ。いいかい、" IN-NAME(RESULT-INDEX:1)
* "だよ。分かったら返事をするんだ、" IN-NAME(RESULT-INDEX:1)
* "!!".
PERFORM GET-STR-BY-INDEX.
DISPLAY "今からお前の名前は"
RESULT-STR(1:RESULT-BYTES)
"だ。いいかい、"
RESULT-STR(1:RESULT-BYTES)
"だよ。分かったら返事をするんだ、"
RESULT-STR(1:RESULT-BYTES)
"!!".
STOP RUN.
*
**********************************************************
* N文字目を決める(in:IN-NAME out:RESULT-INDEX)
**********************************************************
TAKE-AWAY-STRING SECTION.
TAKE-AWAY-STRING-RTN.
* 乱数シードのために現在時刻を取る
MOVE FUNCTION CURRENT-DATE TO DATE-NOW.
MOVE FUNCTION NUMVAL(DATE-NOW-FFSS) TO SEED-VAL.
* 乱数は0以上1未満で作成される
MOVE FUNCTION RANDOM(SEED-VAL) TO RAND-VAL.
* DISPLAY RAND-VAL.
* 入力文字長を取得する
PERFORM GET-INPUT-SIZE.
* DISPLAY INPUT-SIZE.
* 乱数と文字長を掛けて何文字目かを決定する
MULTIPLY RAND-VAL BY INPUT-SIZE GIVING RESULT-INDEX.
IF RESULT-INDEX = 0 THEN
MOVE 1 TO RESULT-INDEX
END-IF.
TAKE-AWAY-STRING-EXT.
EXIT.
**********************************************************
* 入力バイト数カウント
* in : IN-NAME
* out: INPUT-BYTES
**********************************************************
GET-INPUT-BYTES SECTION.
GET-INPUT-BYTES-RTN.
INITIALIZE INPUT-BYTES.
PERFORM VARYING TMP-IDX FROM 1 BY 1
UNTIL TMP-IDX > 40
IF IN-NAME(TMP-IDX:1) = SPACE THEN
EXIT PERFORM
END-IF
ADD 1 TO INPUT-BYTES
END-PERFORM.
GET-INPUT-BYTES-EXT.
EXIT.
**********************************************************
* 入力文字数カウント
* in : IN-NAME
* out: INPUT-SIZE
**********************************************************
GET-INPUT-SIZE SECTION.
GET-INPUT-SIZE-RTN.
INITIALIZE INPUT-SIZE.
PERFORM VARYING TMP-IDX FROM 1 BY 1
UNTIL TMP-IDX > 40
* DISPLAY "char: " IN-NAME(TMP-IDX:1)
IF IN-NAME(TMP-IDX:1) = SPACE THEN
EXIT PERFORM
END-IF
ADD 1 TO INPUT-SIZE
* 全角半角判定
IF (IN-NAME(TMP-IDX:1) >= X"00" AND
IN-NAME(TMP-IDX:1) <= X"7F") OR
(IN-NAME(TMP-IDX:1) >= X"A1" AND
IN-NAME(TMP-IDX:1) <= X"DF") THEN
* DISPLAY "半角:" IN-NAME(TMP-IDX:1)
ELSE
* DISPLAY "全角:" IN-NAME(TMP-IDX:2)
ADD 1 TO TMP-IDX
END-IF
END-PERFORM.
GET-INPUT-SIZE-EXT.
EXIT.
**********************************************************
* 入力文字数カウント
* in : RESULT-INDEX
* out: RESULT-STR
**********************************************************
GET-STR-BY-INDEX SECTION.
GET-STR-BY-INDEX-RTN.
INITIALIZE TMP-SIZE.
PERFORM VARYING TMP-IDX FROM 1 BY 1
UNTIL TMP-IDX > 40
IF IN-NAME(TMP-IDX:1) = SPACE THEN
EXIT PERFORM
END-IF
* 全角半角判定
ADD 1 TO TMP-SIZE
* DISPLAY "char: " IN-NAME(TMP-IDX:1)
IF TMP-SIZE = RESULT-INDEX THEN
IF (IN-NAME(TMP-IDX:1) >= X"00" AND
IN-NAME(TMP-IDX:1) <= X"7F") OR
(IN-NAME(TMP-IDX:1) >= X"A1" AND
IN-NAME(TMP-IDX:1) <= X"DF") THEN
MOVE IN-NAME(TMP-IDX:1) TO RESULT-STR
MOVE 1 TO RESULT-BYTES
ELSE
MOVE IN-NAME(TMP-IDX:2) TO RESULT-STR
MOVE 2 TO RESULT-BYTES
END-IF
* DISPLAY "RESULT BECOME " RESULT-STR
EXIT PERFORM
END-IF
IF (IN-NAME(TMP-IDX:1) >= X"00" AND
IN-NAME(TMP-IDX:1) <= X"7F") OR
(IN-NAME(TMP-IDX:1) >= X"A1" AND
IN-NAME(TMP-IDX:1) <= X"DF") THEN
*
ELSE
ADD 1 TO TMP-IDX
END-IF
END-PERFORM.
GET-STR-BY-INDEX-EXT.
EXIT.
実行結果はこちら!
無事に全角/半角/半角カナで文字を奪うことに成功しました!
もちろん、入力文字が空の際はちゃんとエラーになります。
これをもって、COBOL版湯婆婆とさせていただきます。
おわりに
高級言語のありがたみを実感しました。
ただ今回のおかげで、今まで敬遠していた文字コードと向かい合うことができ、理解が進んだので、
湯婆婆に取り組んで良かったな、と思います。ありがとうございました。