はじめに:PL/Iの美しさと、モダン移行における「文字検証」の罠
こんにちは。基幹システムの現場を長年支え続けてきたメインフレーム・アーキテクトの私にとって、PL/Iという言語は、C++やJavaの足元にも及ばない圧倒的な機械語への直結性と、COBOLにはない数理的で美しい構造化を併せ持った、まさに「孤高の言語」です。
しかし、オープン系へのマイグレーションや、Java/C#へのリライトを控えた現代の現場において、このPL/I特有の「甘美で泥臭い仕様」が、しばしば若手エンジニアや移行ツールの自動変換ロジックを盛大に混乱させます。
今回は、PL/Iのデータ制御において最も頻繁に、そして最もクリティカルに使われる`VERIFY`関数を取り上げます。
「文字列内に許可されていない文字が含まれていないか」を判定するだけの、一見地味なこの組み込み関数が、IBM ENTERPRISE PL/Iコンパイラの最適化や、メインフレームのハードウェア命令(SS指令など)のレベルでどのように展開されているのか。そして、Javaなどへのマイグレーション時にどのような牙を剥くのか。現場の知見を総動員して、その内部ロジックの深淵へと迫ります。
—
1. `VERIFY`関数の基本仕様と「予約語を持たない言語」の背景
まず、PL/Iの根本思想に触れておきましょう。C言語やJavaとは異なり、PL/Iには厳密な「予約語(Reserved Words)」が存在しません。`IF`や`DO`といったキーワードであっても、文脈次第では変数名として定義できてしまうという、コンパイラ泣かせの自由度を持っています。
この柔軟な構文規則の中で、`VERIFY(string, table)` 関数は次のような挙動を示します。
- `string` の左から1文字ずつ読み込み、その文字が `table`(比較文字セット)の中に存在するかどうかをスキャンします。
- もし `string` の文字が `table` の中に見つからない場合、その文字が現れた最初の位置(1始まりの整数)を返します。
- `string` のすべての文字が `table` に含まれている場合は、`0` を返します。
基本的なPL/Iコード例
//
/ 顧客コードが半角英数字(A-Z, 0-9)のみで構成されているか検証する /
//
DCL WK-CUST-ID CHAR(10) INIT(‘A1234B5678’);
DCL VALID-CHARS CHAR(36) INIT(‘ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789’);
DCL ERROR-POS FIXED BIN(31,0);
/ VERIFY関数の実行 /
ERROR-POS = VERIFY(WK-CUST-ID, VALID-CHARS);
IF ERROR-POS > 0 THEN
DO;
/ エラー処理:許可されていない文字が検出された位置を特定 /
DISPLAY(‘INVALID CHARACTER AT POSITION: ‘ || TRIM(ERROR-POS));
END;
ELSE
DO;
DISPLAY(‘CUST-ID IS VALID.’);
END;
一見すると何の変哲もないバリデーションですが、コンパイラはこの短い1行をどのように機械語に翻訳しているのでしょうか。
—
2. コンパイル最適化とハードウェア命令の内部ロジック
IBM ENTERPRISE PL/Iコンパイラ(OPTIMIZE(2)以上)は、`VERIFY`関数に遭遇すると、文字列の長さ(LENGTH)とテーブルのサイズに応じて、高度なコード生成を行います。
内部展開のメカニズム
1. テーブル生成のインライン化:
`VALID-CHARS` のような静的定数文字列は、コンパイル時に256バイトの真偽値(またはビットマップ)テーブルとしてワークエリアに展開されるか、あるいはCPUキャッシュに載りやすい形式に変換されます。
2. S/390・z/Architectureのハードウェア命令へのマッピング:
近代の z/Architecture では、文字探索やスキャンにおいて `TRT`(Translate and Test)命令や、ベクトル機能(Vector Facility)を利用した高速な並列スキャンが内部的に選択されます。
ここでアーキテクトとして注意すべきなのは、可変長文字列(VARYING属性)や、ポインタを介した動的メモリ上の領域を `VERIFY` に渡した場合の挙動です。
—
3. ポインタとベース変数による動的メモリ操作時のエッジケース
基幹システムのオンライン(CICS)や、巨大なレコードを扱うバッチ処理では、ストレージの効率化のために `BASED` 変数とポインタを用いた動的領域のキャストが日常茶飯事に行われます。
ここに落とし穴があります。以下のようなコードを考えてみてください。
DCL RAW-BUFFER-PTR PTR;
DCL 1 RAW-RECORD BASED(RAW-BUFFER-PTR),
3 REC-ID CHAR(4),
3 REC-BODY CHAR(4096);
DCL ALLOWED-SET CHAR(64) STATIC INIT(…);
DCL IDX FIXED BIN(15,0);
/ CICSのGETMAIN等で取得したアドレスをベース変数に割り当てていると仮定 /
/ ここでREC-BODY内にパディングや未初期化領域(LOW-VALUES / X’00’)が混入 /
IDX = VERIFY(REC-BODY, ALLOWED-SET);
アベンド(S0C4 / S0C7)の恐怖と内部符号の罠
もし `RAW-BUFFER-PTR` が不正なアドレスを指していたり、`REC-BODY` の実効長を超えるメモリ領域を指し示すようにストレージ・オフェンスが発生した場合、`VERIFY` 関数の内部スキャンが領域外(Protected Storage)に踏み込み、容赦なく S0C4(Protection Exception) アベンドを引き起こします。
また、メインフレーム特有の「パックデシマル(COMP-3)」の領域をうっかり `CHAR` 型としてキャストし、`VERIFY` にかけた場合、内部の符号ビット(`C`, `D`, `F` など)が不正文字とみなされ、想定外の位置でエラー判定されるバグが頻発します。特に、ホストの符号反転(正負の逆転バグ)が起きたデータ群を移行前にスクリーニングする際、`VERIFY` の戻り値が狂う現象は、レガシー移行プロジェクト初期の「あるある」です。
—
4. 埋め込みSQL(DB2)およびCICS環境における実務的注意点
データベース(DB2 for z/OS)から取得した `VARCHAR` カラムをPL/Iのホスト変数に受ける際、インジケータ変数を見落とした状態で `VERIFY` を実行すると致命傷になります。
- NULL値のハンドリング:
DB2の列が `NULL` である場合、PL/I側のホスト変数の内容は不定(直前のゴミデータ)のままです。この状態で `VERIFY` を実行すると、意図しないバリデーションエラーや、最悪の場合CICSトランザクション全体のアベンド(ASRA等)に繋がります。必ずインジケータ変数(`IND-VARIABLE < 0`)のチェックを先行させなければなりません。 ---
5. マイグレーション(Java/C#等への移行)における設計指針
私たちシステムアーキテクトが、PL/IからJava(Spring Framework等)やC#へ基幹システムを移行する際、この `VERIFY` 関数の挙動をどのようにモダン言語へ移植すべきでしょうか。
Javaにおける同等実装の罠
Javaの `String.indexOf()` や正規表現(`java.util.regex`)は非常に強力ですが、PL/Iの `VERIFY` とは「許可されていない文字を探す」か「含まれている文字を探すか」のセマンティクスが逆である点に注意が必要です。
- PL/I `VERIFY(str, table)`: `table` に含まれない最初の文字の位置を返す(エラー検出に向く)。
- Javaの正規表現アプローチ: むしろ否定文字クラス(`[^…]`)を用いたマッチングが直訳となります。
移行設計のベストプラクティス(Javaの例)
// PL/Iの VERIFY(WK-CUST-ID, VALID-CHARS) と同等のロジックを担保するユーティリティ
public static int pl1Verify(String target, String validChars) {
if (target == null || target.isEmpty()) {
return 0;
}
// 許可された文字セット以外の文字が最初に出現する位置(1始まり)を返す
for (int i = 0; i < target.length(); i++) {
char c = target.charAt(i);
if (validChars.indexOf(c) == -1) {
return i + 1; // PL/I仕様に合わせるため1オリジンで返す
}
}
return 0; // すべて許可文字内
}
自動翻訳ツール(トランスレータ)は、このような微妙な「1オリジンと0オリジンの差」や「文字コードの差異(EBCDICからASCII/UTF-8への変換時のパディング文字の扱い)」を見落とします。移行後の単体テスト(UT)や結合テスト(IT)において、文字化けやバリデーションすり抜けによるデータ汚染を防ぐためには、上記のような厳密なラッパー関数を用意することが、アーキテクトとしての必須の布石となります。
---
おや、そろそろ次のジョブネットの監視アラートが鳴る頃ですね。
レガシーシステムの深部で何十年も動き続けてきたコードの息吹を感じつつ、次世代への安全な橋渡しを成功させることこそ、我々メインフレーム・アーキテクトの腕の見せ所です。次回のコラムでも、さらにディープなPL/Iの裏側に迫ります。
