- 「メインフレーム・コンピューター」で遊ぼう - http://www.arteceed.net -

07.文字(文字列)の操作

S/370アーキテクチャーではバイト(8ビット)が情報の基本単位です。EBCDICコードもそうですが、1文字は1バイトで示されます。事務処理や制御系のプログラムでは四則演算よりも文字(文字列)の取り扱いの方が多いでしょう。S/370はバイト操作に関する命令も豊富に持っています。


バイト転送命令

    ----+----1----+----2----+----3----+----4----+----5----+----6----+----7--
             IC    r1,d2(x2,b2)        レジスター ← メモリー(バイト)
             ICM   r1,m3,d2(b2)        レジスター ← メモリー(バイト)
             STC   r1,d2(x2,b2)        レジスター → メモリー(バイト)
             STCM  r1,m3,d2(b2)        レジスター → メモリー(バイト)
             MVI   d1(b1),i2           即値 → メモリー(バイト)
             MVC   d1(l,b1),d2(b2)     メモリー ← メモリー(バイト)
             MVCL  r1,r2               メモリー ← メモリー(バイト)
    

    IC,ICM,STC,STCM

    IC(Insert Character)命令は第2オペランドで指定された主記憶の1バイトが、第1オペランドで指定されたレジスターの最下位(右端)バイトに格納されます。上位3バイトは変更されません。
    ICM(Insert Characters under Mask)命令は第2オペランドで指定された主記憶の連続したnバイトが、第1オペランドで指定されたレジスターの、m3で示されるマスクビットに対応したバイトに格納されます。マスクビットは4ビットで、それぞれのビットはレジスターの各バイトに対応します。例えばマスクビットがb’1001’であればレジスターの第1および第4バイト(いちばん左端と右端)に、b’0011’であればレジスターの第3および第4バイト(下位2バイト)に格納されます。文字が格納されなかった位置のバイト内容は変更されません。

      ----+----1----+----2----+----3----+----4----+----5----+----6----+
               IC    R0,=C'A'            LOAD C'A' INTO LOW ORDER BYTE
               ICM   R1,B'8000',=X'FF'   LOAD X'FF' INTO HIGH ORDER BYTE
               :
               SLR   R2,R2               CLEAR GR2
               ICM   R2,B'0011',UHWORD   GR2 = 60000
               :
      UHWORD   DC    AL2(60000)
      

    マスクビットは 15,X’0F’,B’1111′ のように10進、16進、2進のいずれの表記も可能です。レジスターのどのバイトに対応させてるかが一目でわかるため、B’0000’の表記がわかりやすいでしょう。
    符号無しハーフワード2進数をレジスターにロードする場合、値が32768以上になるとLH命令では上手く行きません。ICM命令をマスクビットB’0011’で使用します。

      ICM命令は条件コードがセットされます。
      0 … レジスターに挿入されたデータの全てのビットが0、またはマスクビットが全て0
      1 … 挿入されたデータの左端ビットは1
      2 … 挿入されたデータの左端ビットは0
      3 … 使用されない

    STC(Store Character)およびSTCM(Store Characters under Mask)命令はIC,ICMの逆でレジスター→主記憶へのバイト転送動作になります。マスクビットの意味は同じです。


    MVI

    MVI(Move Immediate)命令は第2オペランドで指定した1バイトの即値(固定値)が、第1オペランドで指定された主記憶の1バイトに格納されます。

      ----+----1----+----2----+----3----+----4----+----5----+----6----+
               MVI   CHAR,C'A'           SET C'A' INTO CHAR AREA
               MVI   CHAR,0              SET X'00' INTO CHAR AREA
               MVI   CHAR,X'FF'          SET X'FF' INTO CHAR AREA
      

    MVC

    MVC(Move Character)命令は第2オペランドで指定された主記憶の内容が、第1オペランドで指定された主記憶に転送されます。転送される長さは最大256バイトで、第1オペランド側で指定します。文字の転送は左から右に1バイトずつ順に行われ、指定した長さ分の転送が終了すると命令は完了します。オペランドが重複する場合を除き、第2オペランド側の主記憶内容は変更されません。

      ----+----1----+----2----+----3----+----4----+----5----+----6----+
               MVI   AREA,C' '                SET PADDING CHARACTER
               MVC   AREA+1(L'AREA-1),AREA    CLEAR AREA BY BLANK
               :
      AREA     DC    XL256'00'
      

    1バイトずつ転送されることを利用して、領域を特定の文字・値で初期化する例。


    MVCL

    MVCL(Move Long)命令は256バイトを超える長さのデータを転送します。第2オペランドで指定された主記憶の内容が、第1オペランドで指定された主記憶に転送されます。転送される長さは受け側、送り側それぞれのオペランドに指定します。受け側<送り側の時は受け側の長さ分だけ転送され、受け側>送り側の時は、受け側の余った領域に、指定した充填文字(Padding character)が埋められます。MVCLも文字の転送は左から右に1バイトずつ順に行われます。
    オペランドの指定は少々複雑です。送り側・受け側領域それぞれのアドレスおよび長さをレジスター4つを使って指定します。第1オペランドは受け側です。r1レジスターにアドレス、r1+1レジスターに長さを格納します。第2オペランドは送り側です。r2レジスターにアドレス、r2+1レジスターに長さを格納します。長さの最大値は16MB-1(2^24)です。第2オペランド側r2+1レジスターの先頭バイトには充填文字を格納します。レジスターは乗除算同様、偶数番号レジスターと奇数番号レジスターをペアにして使い、必ず偶数側のレジスター番号を指定します。

      ----+----1----+----2----+----3----+----4----+----5----+----6----+
               LA    R0,AREA1                 MOVE AREA2 TO ARE1 AND
               LA    R1,480                   FILL X'FF' INTO UNMOVED
               LA    R14,AREA2                SPACE.
               LA    R15,320
               ICM   R15,B'1000',=X'FF'       LOAD PAD CHARACTER
               MVCL  R0,R14                   MOVE LONG DATA
      
               LA    0,BUFFER                 LOAD BUFFER ADDRESS
               LA    1,512                    LOAD BUFFER LENGTH
               SLR   15,15                    INDICATE NULL CLEAR
               MVCL  0,14                     CLEAR BUFFER AREA
               :
      AREA1    DC    480C' '
      AREA2    DC    320C' '
      BUFFER   DC    512X'00'
      

    256バイト以上の領域間のデータ転送と領域クリアーの例。領域をクリアーするには送り側の長さを0にすればよい。この場合対応する偶数レジスターにはどんな値が入っていてもかまいません。長さが0の場合は参照されません。わざわざ0クリアーする必要はありません。
    MVCLは複雑なので簡単な使い方だけ紹介しました。命令実行中や実行後にレジスター内容が変更される、条件コードが設定される、誤ったオペランド重複はリジェクトされる(先ほどのMVCのサンプルで256バイトの領域を空白埋めする例を示したが、同様のことをMVCLでやろうとしてもオペランド重複として拒否されてしまう)などです。詳細は命令のリファレンス・マニュアルを読んで下さい。


変換・探索・編集命令

以降の命令は、アセンブラービギナーには扱いが面倒なものです。こんなものもあるのか、と言う程度で名前だけ覚えるぐらいでもいいです。必要に迫られたら、リファレンスマニュアルなどを参考にしてトライして見て下さい。

    ----+----1----+----2----+----3----+----4----+----5----+----6----+----7--
             TR    d1(l,b1),d2(b2)     メモリー(変換)← テーブル
             TRT   d1(l,b1),d2(b2)     メモリー(探索)← テーブル
             ED    d1(l,b1),d2(b2)     メモリー(編集)← パック10進数
             EDMK  d1(l,b1),d2(b2)     メモリー(編集)← パック10進数
    

    TR,TRT

    TR(Translate)命令はその名の通り主記憶上のデータを変換する命令です。第1オペランドで指定された主記憶の内容が、第2オペランドで指定されたテーブル(変換表)に従って変換されます。変換される長さは最大256バイトで、第1オペランド側で指定します。第2オペランド側のテーブル内容は変更されません。

    第2オペランドにTABLEと言う名前のテーブル領域を指定したとします。第1オペランドの主記憶のデータがx00の時、そのx00はTABLEの先頭からx00バイト離れた位置(TABLE+x00)のバイト内容に置き換わります。第1オペランドの主記憶のデータがx28の時、そのx28はTABLEの先頭からx28バイト離れた位置(TABLE+x28)のバイト内容に置き換わります。TR命令は文字コードの変換(例えばEBCDICからASCII)、2進→16進変換、非表示文字の変換(例えばメモリーダンプなどで文字ではないバイナリー値などを画面にそのまま表示すると、意味不明の文字になったりするのでそれを防ぐ。空白やピリオドに置換えたりする)などに使われます。

      ----+----1----+----2----+----3----+----4----+----5----+----6----+
               TR    WORKAREA,TABLE           TRANSLATE EBCDIC A-H TO ASCII
               :
      WORKAREA DC    CL8'ABCDEFGH'            STRING TO BE TRANSLATED
      TABLE    DC    XL256'00'                TRANSLATE TABLE AREA
               ORG   TABLE+C'A'               LOCATE TO TABLE+X'C1'
               DC    AL1(X'41',X'42',X'43',X'44',X'45',X'46',X'47',X'48')
               ORG   ,                        CORRECT LOCATION COUNTER
      

    かなり手を抜いたテーブルですが、EBCDICのAからHまでの文字をASCIIコードに変換する例です。限られた範囲の文字を変換するだけなら256バイトを真面目に用意する必要はありません。OS/390アセンブラーハンドブック [1]にも載ってます。

    TRT(Translate and Test)命令はTRと似ていますが、変換はされません。主に文字列内の特定文字を探索するために使われます。第1オペランドで指定された主記憶の内容が、第2オペランドで指定されたテーブル(変換表)に従って探索されます。探索される長さは最大256バイトで、第1オペランド側で指定します。第1オペランド側の主記憶内容はアーギュメントとも呼ばれます。第1および第2オペランド側のテーブル内容は変更されません。

    第2オペランドにTABLEと言う名前のテーブル領域を指定したとします。テーブルはx00(NULL)でクリアーします。空白文字を探したければTABLE+x40の位置に、Aを探したければTABLE+xC1の位置にそれぞれ0以外の値を設定します。これはファンクションバイトと呼ばれます。複数の文字を探す場合は文字毎に値を変えればどの文字が見つかったかが簡単にわかります。TRT命令は読み込んだデータ内の区切り文字やコマンドバイトなどを探す場合に使われます。

      TRT命令は条件コードがセットされます。
      0 … 全てのファンクションバイトが0(どの文字も見つからない)
      1 … アーギュメントの途中で0でないファンクションバイトが検出された(文字がデータの途中に見つかった)
      2 … アーギュメントの最後で0でないファンクションバイトが検出された(文字がデータの最後に見つかった)
      3 … 使用されない
      ----+----1----+----2----+----3----+----4----+----5----+----6----+
               SLR   R2,R2                    CLEAR FUNCTION REGISTER
               TRT   DOCS,SCANTAB             FIND C' ' AND C'.' IN DOCUMENTS
               BC    B'1000',NOTFOUND         IF NOT FOUND
               B     *+4(R2)                  BRANCH BY FUNCTION CHARACTER   +
                                              GR1 -> FOUND ADDRESS           +
                                              GR2 -> FUNCTION(WHICH CHARACTER
               EX    0,*                       00 ABTER(S0C3)(ITS IMPOSSIBLE)
               B     DOBLANK                   04 FUNCTION C' ' BLANK
               B     DOPERIOD                  08 FUNCTION C'.' PERIOD
               :
      DOCS     DC    C'HELLO! TODAY IS VERY FINE.'
      SCANTAB  DC    XL256'00'                TRANSLATE TABLE AREA
               ORG   SCANTAB+C' '             LOCATE TO TABLE+X'40'
               DC    AL1(04)
               ORG   SCANTAB+C'.'             LOCATE TO TABLE+X'C1'
               DC    AL1(08)
               ORG   ,                        CORRECT LOCATION COUNTER
      

    DOCSと言う名前の領域データを探索して、空白またはピリオドを探します。空白が見つかったら4、ピリオドなら8をファンクションバイトとして設定しています。文字が見つかった場合(CC=1または2)、GR1には見つかったアドレスが、GR2の最下位バイトには対応するファンクションバイト値が設定されます。GR2の上位3バイトは変更されません。文字が見つからなければ(CC=0)GR1とGR2は変更されません。


    ED,EDMK

    パック10進数を人間が見てわかるようにするために、UNPK命令によってゾーン10進数に変換する方法がありました。ここでは編集しながらゾーン10進数に変換してくれるED(Edit)およびEDMK(Edit Mark)命令を紹介します。
    第2オペランドで指定された主記憶のパック10進数が、第2オペランドで指定された主記憶上の編集パターンに従って変換されます。変換される長さは第1オペランド側である編集パターンのバイト数で指定します。変換元のパック10進数の内容は変更されません。

      編集パターン

      埋め文字   : 任意の文字(普通は空白)
      数字選択文字: x20 ここに数字桁がゾーン変換されて入る。
      有意開始文字: x21 この次のx20パターンから先行ゼロを出す。普通は10の位をx21にすればよい。
      その他     : そのまま出力される。3桁ごとにカンマで区切ったり、小数点記号を置いたりする。
                 最終桁よりも右側に置かれた場合、元のパック10進数が正の場合、埋め文字に置き換わってしまう。
                 これを防ぐには元の数値を一旦マイナス値に変えるか、第1オペランドの長さを短くしてED命令を実行するなどの工夫をする。

      数字選択文字(有意開始文字も含む)は変換しようとするパック10進数の桁数分は必要である。ただし第2オペランドはバイト単位での指定なので、有効桁数が偶数の場合は符号ビットが加わると、最上位桁の左側に0がもう1桁追加される。なので偶数桁なら+1して次の奇数桁分の数字選択文字を用意する。数値が6桁なら最低7個のx20が必要である。数値が7桁ならそのまま7個のx20でよい。これに埋め文字とその他の文字を含めて必要な第1オペランドのバイト数を計算する。

    ED命令は慣れるまでは期待した結果に編集されず、何度もトライ&エラーを繰り返す命令でもあります。命令リファレンスを見ても最初はよくわからないかも知れません。地道に試して慣れていって下さい。面倒な命令ですが、理解できるとアウトプットの編集が楽になります。
    「000012345」 と言う表示は間違いではありませんが、テストや自分だけで使うツールならともかく、正式品のプログラムなら「 12,345」と言う編集を面倒がらずに、「やろうと思えばいつでもできる」ぐらいでいられた方がいいでしょう。暗記する必要などありません、リファレンス見ながらで十分です。

    EDMK命令はED命令と同じですが、命令実行後にGR1に先頭の有効数字桁のアドレスが格納されます。(ただし格納されない例外条件もあるのでリファレンスマニュアルを参照)この機能によって先頭桁の前にマイナス記号や通貨記号などを付けることが容易になります。

      ----+----1----+----2----+----3----+----4----+----5----+----6----+
      ①3桁毎のカンマ編集
               L     0,=F'2147483647'         LOAD INTEGER
               CVD   0,DECIMAL                CONVERT TO DECIMAL
               MVC   FORMAT1,EDITMASK         SET EDIT PATTERN MASK
               ED    FORMAT1,DECIMAL+2        EDIT Z,ZZZ,ZZZ,ZZ9
               :
      DECIMAL  DC    D'0'
      FORMAT1  DC    CL15' '
      EDITMASK DC    XL15'4020206B2020206B2020206B202120'
      
      ②3桁毎のカンマ編集(負数の時有効桁の先頭に?記号を付ける)
               L     0,=F'-123456789'         LOAD INTEGER
               CVD   0,DECIMAL                CONVERT TO DECIMAL
               MVC   FORMAT2,EDITMASK         SET EDIT PATTERN MASK
               LA    1,FORMAT2+L'FORMAT2-1    PRESET SIGNIFICANT DIGIT
               EDMK  FORMAT2,DECIMAL+2        EDIT Z,ZZZ,ZZZ,ZZ9
               BNM   *+4+2+4                  IF NOT MINUS,
               BCTR  1,0                      LOCATE TO SIGN MARK FIELD
               MVI   0(1),C'-'                SET MINUS SIGN MARK
               :
      DECIMAL  DC    D'0'
      FORMAT2  DC    CL15' '
      EDITMASK DC    XL15'4020206B2020206B2020206B202120'
      
      ③百分率編集(ZZ9.9%)
               L     0,=F'324'                LOAD INTEGER 32.4%
               CVD   0,DECIMAL                CONVERT TO DECIMAL
               MVC   FORMAT3,EDITMASK         SET EDIT PATTERN MASK
               ED    FORMAT3(7),DECIMAL+5     EDIT ZZ9.9%
               :
      FORMAT3  DC    CL8' '
      EDITMASK DC    XL8'40202021204B206C'