【実務・中級編】TRANSLATE関数による文字変換の内部テーブル – PL/Iの基本構文とデータ制御実践ガイド

おい、最近配属された若手が「PL/Iって予約語がないから、変数名に `IF` とか書けちゃうんですね、便利ですね!」なんて呑気に笑っているのを聞いてね、私は思わず冷や汗をかいた口だよ。

おいおい、ちょっと待てと。確かにPL/Iの言語仕様上、`IF` や `READ` といったキーワードは「予約語(Reserved Words)」ではなく「文脈依存語(Keywords)」だ。だから変数名に使うこと自体はシンタックスエラーにならない。だがね、そんなコードを書いたら、後から保守する人間(あるいは数年後の自分自身)が呪いの言葉を吐きながらデバッグする羽目になる。メインフレームの基幹バッチでそんな爆弾を埋め込むのは勘弁してほしい。

今回は、PL/Iのそうした自由度の高い(そして時に牙を向く)文脈を踏まえつつ、「TRANSLATE関数による文字変換の内部テーブル」に焦点を当ててみよう。EBCDICコード体系の特性を理解し、夜間バッチのCPU時間を1秒でも削るための実践的なテクニックを、私の現場経験を交えて叩き込んでやる。

—

1. なぜ今、TRANSLATE関数とEBCDICの効率性が問われるのか

金融や流通の基幹システムを支えるIBMメインフレームにおいて、文字データの変換処理は日常茶飯事だ。小文字を大文字に強制変換したり、特定の特殊文字をスペースに潰したり、あるいは外字領域のパージを行ったりね。

ここで初心者がやりがちなのが、文字列の長さに応じたループを回し、`IF` 文で一文字ずつ判定するような愚行だ。数十万件、数百万件のレコードを処理するVSAMや順次ファイル(QSAM)の入出力ループ内でそんなことをやったら、夜間バッチのウィンドウ(実行時間制限)に確実に収まらなくなる。

そこで登場するのが、PL/Iの組み込み関数(BUILTIN)である `TRANSLATE` だ。

EBCDICコード体系におけるTRANSLATEの底力

`TRANSLATE` 関数は、第1引数の文字列に対し、第2引数(変換先)と第3引数(変換元)のテーブルを使ってバイト単位の置換を行う。
特筆すべきは、これがコンパイラによって最適化され、内部的に高速なマシン語命令(MVCLやTR命令など)に展開されやすい点だ。

特にEBCDIC環境(IBM ZのCPUIDが処理する文字コード)では、文字の並びがASCIIとは異なる(例えば、A~I、J~R、S~Zが連続していない等)。このEBCDICの特性を理解した上で変換テーブルを設計しないと、予期せぬ文字化けやパフォーマンス劣化を引き起こす。現場のシニアとして、この「テーブルの作り方」の極意を伝授しよう。

—

2. 実践:TRANSLATE関数を活用したデータクレンジング・バッチ

百聞は一見に如かずだ。実際のメインフレーム開発現場を想定した、実用的なPL/Iのソースコードを見てほしい。
このプログラムは、VSAM(KSDS)からレコードを読み込み、顧客名や住所に含まれる「全角・半角の揺れ」や「制御文字」を `TRANSLATE` 関数で一網打尽にクレンジングし、別の出力ファイルへ書き出すバッチジョブの骨子だ。

1
TRAN_SAMPLE: PROC OPTIONS(MAIN);

/————————————————————–/
/ 宣言部:ファイル定義と変数宣言 /
/————————————————————–/
DCL IN_FILE FILE RECORD INPUT; / 入力VSAM/QSAMファイル /
DCL OUT_FILE FILE RECORD OUTPUT; / 出力順次ファイル /

DCL IO_STATUS FIXED BIN(31) INIT(0); / 入出力ステータス /
DCL EOD_FLG BIT(1) INIT(‘0’B); / 終了フラグ /

/ 入出力レコードの構造体定義 /
DCL 1 IN_REC,
5 CUST_ID CHAR(8), / 顧客ID /
5 CUST_NAME CHAR(40), / 顧客名(変換対象) /
5 CUST_ADDR CHAR(60); / 住所(変換対象) /

DCL 1 OUT_REC,
5 CUST_ID CHAR(8),
5 CUST_NAME CHAR(40),
5 CUST_ADDR CHAR(60);

/ TRANSLATE用テーブルおよびワーク変数の定義 /
/ ※EBCDIC環境における制御文字(X’00’〜X’1F’)をスペース(X’40’)に置換 /
DCL CTRL_TO_SPACES CHAR(32) STATIC INIT(
/ X’00’ – X’0F’ をすべてスペース(X’40’)に置き換えるテーブル /
‘40404040404040404040404040404040’X ||
/ X’10’ – X’1F’ をすべてスペース(X’40’)に置き換えるテーブル /
‘40404040404040404040404040404040’X
);

/ 変換元の制御文字群(X’00’ から X’1F’ まで) /
DCL CTRL_CHARS CHAR(32) STATIC INIT(
‘000102030405060708090A0B0C0D0E0F’X ||
‘101112131415161718191A1B1C1D1E1F’X
);

/ ONユニットによるファイル終了(ENDFILE)の制御 /
ON ENDFILE(IN_FILE) EOD_FLG = ‘1’B;

/————————————————————–/
/ 処理部:メインロジック /
/————————————————————–/
OPEN FILE(IN_FILE), FILE(OUT_FILE);

/ 初期リード /
READ FILE(IN_FILE) INTO(IN_REC);

DO WHILE (^EOD_FLG);

/ 1. 顧客名の制御文字除去と大文字化の複合処理 /
/ まず制御文字をスペースに変換し、続けて小文字を大文字に変換する /
OUT_REC.CUST_NAME = TRANSLATE(IN_REC.CUST_NAME, CTRL_TO_SPACES, CTRL_CHARS);

/ 小文字(a-z)を大文字(A-Z)に変換する標準的なテーブル適用例 /
/ ※EBCDICにおける小文字領域を指定して大文字領域へマップする /
OUT_REC.CUST_NAME = XLATE_LOWER_TO_UPPER(OUT_REC.CUST_NAME);

/ 2. 住所情報のクレンジング(タブや改行コードの排除) /
OUT_REC.CUST_ADDR = TRANSLATE(IN_REC.CUST_ADDR, CTRL_TO_SPACES, CTRL_CHARS);
OUT_REC.CUST_ID = IN_REC.CUST_ID;

/ クレンジング済みレコードの書き出し /
WRITE FILE(OUT_FILE) FROM(OUT_REC);

/ 次レコードの読み込み /
READ FILE(IN_FILE) INTO(IN_REC);
END;

CLOSE FILE(IN_FILE), FILE(OUT_FILE);
RETURN;

/————————————————————–/
/ 内部プロシージャ:EBCDIC小文字→大文字変換テーブル関数 /
/————————————————————–/
XLATE_LOWER_TO_UPPER: PROC(P_STR) RETURNS(CHAR(40)) BUILTIN;
DCL P_STR CHAR(40) ;

/ EBCDICの小文字エリア(a-i, j-r, s-z等)を大文字へ変換する定義 /
/ 実務ではCOPY句などで共通化することが多い /
DCL LOWER_TBL CHAR(9) STATIC INIT(‘abcdefghi’);
DCL UPPER_TBL CHAR(9) STATIC INIT(‘ABCDEFGHI’);

/ ※実際には j-r, s-z も含めた完全なEBCDIC小文字テーブルを定義する /

RETURN( TRANSLATE(P_STR, ‘ABCDEFGHI’, ‘abcdefghi’) );
END XLATE_LOWER_TO_UPPER;

END TRAN_SAMPLE;

—

3. アーキテクトが教えるデバッグとコーディングの勘所

上記のコードを見て、「なぜわざわざ `STATIC` 属性でテーブルを定義しているのか?」と疑問に思ったなら、君はいい勘をしている。ここにメインフレーム特有のパフォーマンスチューニングの極意がある。

① `STATIC` 属性の徹底とリワインドコストの削減

PL/Iでテーブルや定数を定義する際、`STATIC` を付け忘れると、プロシージャが呼び出されるたびに(あるいはループのスコープ内で)ワークエリアへのメモリ割り当てと初期化走査が発生する。
数百万件のレコードを処理する中で、毎回のループで変換テーブルを作り直すようなコードを書いたら、それだけでCPU時間を無駄に消費する。テーブルは必ず `STATIC`(あるいは `INIT` を伴う静的領域)で定義し、メモリ上に固定配置させろ。

② 文脈依存語の罠とコーディング規約

冒頭でも触れたが、PL/Iには予約語がない。例えば、うっかり変数名に `TRANSLATE` や `READ` と名付けてしまっても、コンパイラは文脈から判断しようとするが、時として予期せぬシンタックスエラーや、人間には読めないスパゲッティコードを生み出す原因になる。
現場のコーディング標準としては以下のルールを厳守してほしい:

  • 組み込み関数名やキーワードを変数の一部、あるいは変数名そのものに絶対に使わない。
  • 内部テーブルや定数には必ずプレフィックス(例:`TBL_` や `WK_`)を付与する。

③ ONユニットの制御フローと例外処理

ファイル入出力における `ENDFILE` などの例外条件は、`ON` ユニットによって制御フローが非同期的にトラップされる。
初心者によくあるミスが、`ON ENDFILE` のスコープを勘違いして、ループの外側で正しくフラグが落ちずに無限ループに陥るパターンだ。今回のサンプルコードのように、ファイル読み込みの直前・直後と `ON` ユニットのスコープを明確にペアリングさせ、バッチの安全性を担保すること。

—

最後に先輩から一言

メインフレームのPL/I開発は、古臭く見えるかもしれない。だが、ハードウェアの特性(EBCDICコード体系やキャッシュ効率)を極限まで引き出せるこの言語の仕組みを理解していれば、現代のオープン系言語では真似できない圧倒的なスループット叩き出すバッチプログラムを書くことができる。

「動けばいいや」の精神で書かれた場当たり的なコードは、いつか必ず深夜の障害コールという形で君に跳ね返ってくる。
変数名ひとつ、テーブルの持ち方ひとつにこだわりを持ち、「誰が見ても美しく、マシンにとっても効率的なコード」を追求してほしい。期待しているぞ。

タイトルとURLをコピーしました