【テクニカル・上級編】ON ENDFILEユニットの制御フロー – PL/Iの基本構文とデータ制御実践ガイド

識別子と予約語の呪縛がない世界:PL/I『ON ENDFILE』の深層と制御フローの罠

メインフレームの現場で何十年も稼働し続けているPL/Iバッチプログラムの保守や、Java/C#へのマイグレーション(レガシー移行)を任された時、多くのエンジニアが最初に直面するカルチャーショックの一つが、変数名の自由度の高さと、それに起因する独特の制御構造だ。

C言語やJava、C#といったモダン言語には、`if`、`while`、`return`、さらには`true`や`false`に至るまで、厳格な「予約語(Keywords)」のリストが存在する。これらは識別子(変数名)として使うことは許されない。しかし、PL/Iの言語仕様には「文脈予約語(Contextual Keywords)」という概念はあるものの、厳密な意味での「固定の予約語」が存在しない。
例えば、`IF`や`READ`といったキーワードでさえ、プログラマが変数名として宣言してしまえば、コンパイラは文脈からそれを判別する。この「何でもあり」の柔軟性は、かつてのプログラマには福音だったが、現代の静的解析ツールやマイグレーションプロジェクトにとっては悪夢でしかない。

そして、この言語の特異性を最も色濃く映し出すのが、ファイル入出力における例外処理、すなわち `ON ENDFILE` ユニット である。

今回は、基幹システムの屋台骨を支えるPL/Iの `ON ENDFILE` が引き起こす制御フローの挙動、そして `REVERT` 構文の正しい理解と、モダン移行時に踏み抜く地雷の回避策について、コンパイラの内部挙動や実務のエッジケースを交えて徹底的に解説する。

—

1. `ON ENDFILE` ユニットの正体:割り込みと制御のジャンプ

C言語の `feof()` や Javaの `hasNext()` に慣れた現代のプログラマにとって、PL/Iのファイル終端処理は異質に映る。ファイル終端(ENDFILE)は、通常の逐次制御フローの延長線上ではなく、非同期の割り込み(条件シグナル)として処理されるからだ。

1
DCL INFILE FILE RECORD INPUT;
DCL EOF_FLG BIT(1);

OPEN FILE(INFILE);

/ ENDFILE条件が発生したときのハンドラ(ONユニット)を定義 /
ON ENDFILE(INFILE) BEGIN;
EOF_FLG = ‘1’B;
/ 注意: ここで制御をどう戻すかが極めて重要 /
END;

EOF_FLG = ‘0’B;
READ FILE(INFILE) INTO(REC_AREA);

DO WHILE(EOF_FLG = ‘0’B);
/ レコード処理ロジック /
…
READ FILE(INFILE) INTO(REC_AREA);
END;

CLOSE FILE(INFILE);

このコードの動きをコンパイラの視点で追ってみよう。
`READ` ステートメントを実行した際、物理ファイルの最終レコードを読み終えた状態でさらに読み込みを試みると、OS(BSAM/QSAM)から「End of Volume / File」のステータスが返される。PL/Iのランタイム環境(LE: Language Environment)はこれを検出し、プログラムの実行を中断して、該当する `ON ENDFILE(INFILE)` ユニットへ制御をジャンプ(割り込み)させる。

ここで発生するのが、「どこへ戻るのか」という制御フローの迷子問題である。

—

2. 暗黙の復帰と `REVERT` の役割

`ON ENDFILE` ユニットの処理が完了した時、プログラムはどこへ戻るのだろうか?

基本ルールとして、`ON` ユニットの処理が終わると、制御は 「例外を引き起こした文(この場合は `READ` ステートメント)の直後の文」 へ戻る。上記のコードであれば、`DO WHILE` のループ判定や次の処理へ進むことになる。

しかし、ここに大きな罠がある。`ON` ユニット内で `EOF_FLG = ‘1’B` を立ててループを抜けようとしても、もし `READ` ステートメント自体がループの先頭と末尾の2箇所に散在している場合、あるいは `ON` ユニットの有効範囲(スコープ)を理解していない場合、無限ループや予期せぬアベンド(ABEND)を引き起こす。

さらに重要なのが `REVERT` ステートメント の存在だ。

1
ON ENDFILE(INFILE) GOTO FILE_EOF_ROUTINE;

READ FILE(INFILE) INTO(REC_AREA);
…

FILE_EOF_ROUTINE:
/ 終了処理 /
REVERT ENDFILE(INFILE); / ONユニットの有効範囲を抜ける、または元の状態に戻す /

`ON` ユニットは、それを定義したブロック(BEGINブロックやプロシージャ)の有効範囲内で生き続ける。もしサブルーチンを抜けた後も古い `ON` ユニットが有効なままでいると、意図しないファイル読み込みで予期せぬジャンプが発生し、制御フローが崩壊する。
`REVERT` は、動的に設定した例外処理のフックを解除し、システム標準の動作(あるいは外側のブロックで定義された `ON` ユニット)へと状態を巻き戻すための、極めて重要なステートメントなのだ。これを怠ると、メモリリークならぬ「ハンドラ残留」によるバグの温床となる。

—

3. 現場で直面するエッジケースとトラブルシューティング

基幹システムの現場では、この `ON ENDFILE` と制御フローの特性が、しばしば厄介なトラブルを引き起こす。

① パックデシマルの内部符号反転バグとの複合

読み込んだレコードの数値項目(COMP-3 / パックデシマル)が破損しており、`READ` 後のデータ加工ロジクでコンバージョンエラー(S0C7アベンドなど)が発生するケースがある。
もし開発者が「エラー時も `ON` ユニットでキャッチできるだろう」と誤解して `ON ERROR` や `ON CONVERSION` を雑に実装していると、`ENDFILE` のハンドリングと競合し、ダンプを見てもどこでファイルが切れたのか判別がつかなくなる。
レガシー移行の際、Javaの例外機構(`try-catch`)へ機械的に変換しようとして、この「非同期割り込みと順次制御の混在」を無視し、ファイル終端で無限ループに陥るバグは後を絶たない。

② 埋め込みSQL(DB2)やCICSオンライン処理との差異

バッチ処理のQ選択ファイルであれば `ENDFILE` はお馴染みだが、CICSのセッションやDB2のカーソル処理(`EXEC SQL FETCH`)では、PL/Iの `ENDFILE` ではなく、SQLCODEの `+100` やCICSの `ENDFILE` 条件(RESP値)で制御を行う。
ごちゃまぜになったコードベースにおいて、PL/I本来の `ON ENDFILE` とCICS/DB2の条件分岐が混在すると、トランザクションのロールバック境界を見失い、データベースの不整合やASRA(CICSアベンド)を引き起こす主原因となる。

③ 最適化コンパイラ(OPT(2)以上)による挙動の変化

IBMの Enterprise PL/I コンパイラで最適化オプション(`OPT(2)` や `OPT(3)`)を指定した場合、レジスタ割り当てやコードの並び替え(レジスタ・キャッシング)が行われる。
古い記述の `GOTO` を多用した `ON` ユニットや、変数の明示的な初期化が抜けているコードでは、最適化の度合いによって「テスト環境では動くのに、本番の最高最適化ビルドではループを抜けずに暴走する」という、最もアーキテクトが頭を抱える現象が発生する。コンパイラ最適化を信じ切る前に、制御フローの脱出経路が厳密に保証されているかを確認しなければならない。

—

4. マイグレーション(レガシー近代化)への指針

JavaやC#、あるいは現代的なクラウドネイティブ環境へPL/Iシステムを移行する際、この `ON ENDFILE` のような「暗黙のジャンプと例外処理」をどうモダン化すべきか。

答えは明確である。「割り込みベースの制御フローを、明示的なステートメントベースのフローに書き換えること」だ。

  • 移行前のレガシー思考: `ON ENDFILE(X) GOTO …` でプログラム全体をジャンプさせ、どこからでも大域脱出を試みる。
  • 移行後のモダン設計: ファイル読み込み関数は必ずステータス(成功、データなし/EOF、エラー)を戻り値として返すようにラップし、呼び出し側が `if (status == EOF)` で明示的にループを制御する。

// Java移行後のイメージ:例外ベースではなく戻り値(ステータス)による明確な制御フロー
FileStreamReader reader = new FileStreamReader(“INFILE”);
RecordData record;

while ((record = reader.readNext()) != null) {
// レコード処理
processRecord(record);
}
reader.close();

予約語を持たない自由奔放なPL/Iの文法と、非同期な `ON` ユニットの制御フローは、かつてのリソースが極限まで限られた時代における偉大な発明であった。しかし、システムの保守性、スケーラビリティ、そして何より次世代への継承を考えるとき、その挙動の裏にある「制御の流れ」を完全に理解し、現代の言語仕様へと正しく翻訳・再設計することこそが、我々システムアーキテクトに課された使命である。

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