LoginSignup
10
4

More than 3 years have passed since last update.

COBOLで湯婆婆を実装してみる~2バイト文字判定の仕方~

Last updated at Posted at 2020-11-16

はじめに

@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.


苦戦はしましたが、一通りの処理が書けました。早速実行してみます。
hankaku.png
良い感じに文字を奪って出力できています。が、
2baito.png
\ 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.


実行結果はこちら!
perfect.png
無事に全角/半角/半角カナで文字を奪うことに成功しました!
もちろん、入力文字が空の際はちゃんとエラーになります。
error2.png
これをもって、COBOL版湯婆婆とさせていただきます。

おわりに

高級言語のありがたみを実感しました。
ただ今回のおかげで、今まで敬遠していた文字コードと向かい合うことができ、理解が進んだので、
湯婆婆に取り組んで良かったな、と思います。ありがとうございました。

10
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
4