こんにちは!メインフレームの世界へようこそ。
JavaやCOBOLといった他の言語での経験をしっかりとお持ちのあなたなら、PL/I(ピーエルアイ)のコードを見たとき、「おっ、なんだか色々な機能がごちゃっと詰まっていて面白そうだな」と感じる一方で、「なんだこの独特な世界観は……!」と少し身構えてしまったかもしれません。
大丈夫です、安心してください。レガシーシステムの荒波にもまれながら生き抜いてきた私たちシニアアーキテクトが、一歩ずつ優しく手を取りながら紐解いていきますので、肩の力を抜いて読んでくださいね。
今回は、PL/Iの数あるユニークな仕様の中でも、ファイル処理の要である「ON ENDFILEユニットのスタック構造と制御フロー」について深掘りしていきましょう。Javaの例外処理やCOBOLの `AT END` 句とは一味も二味も違う、PL/Iならではの奥深い世界にご案内します。
—
そもそもPL/Iの「ON条件」ってなに?(Javaの例外処理との違い)
JavaやC#を使っている方なら、エラーやファイルの終端(EOF)に出くわしたときは `try-catch` ブロックを思い浮かべるはずです。「ここでファイルを読むぞ、もし終端エラー(EOF)が起きたらキャッチしてループを抜ける」という流れですね。
しかし、PL/Iの考え方はちょっと違います。PL/Iには「ONユニット」という仕組みがあり、これは「エラーや特定のイベント(ファイル終了など)が発生したときにお金を払ってでも実行してほしい割り込み処理をあらかじめ登録しておく」というスタイルをとります。
例えるなら、工場の生産ラインに「センサー(ON条件)」を仕掛けておくようなものです。「もし部品がなくなったら(ENDFILE)、自動的にこの緊急停止ボタン(ONユニット)を押すように配線しておこう」というイメージですね。
—
今回の主役:ON ENDFILEの「スタック構造」の秘密
ファイルからデータを順番に読み込んでいくバッチ処理で、よくこんなコードを見かけませんか?
DCL INFILE FILE RECORD INPUT;
/ ファイル終了時の割り込み動作を定義 /
ON ENDFILE(INFILE) BEGIN;
/ ファイルが終わったときの処理 /
PUT SKIP LIST(‘ファイルの終わりを検知しました’);
EOF_FLAG = ‘1’B;
END;
ここでJavaやCOBOL出身のエンジニアが一番ハマりやすいポイント、そしてPL/Iの最も奇妙で強力な特徴が「ON条件のスタック(積み重ね)構造」です。
なぜスタックが必要なの?
例えば、メインの処理ルーチン(A)から、とある汎用的なサブルーチン(B)を呼び出すとします。
ルーチンAでもルーチンBでも、同じファイル `INFILE` を読み込む設計になっていたとき、「ファイルが終わったときの振る舞いを、ルーチンごとに入れ替えたい!」という要望が生まれることがあります。
PL/Iでは、新しい `ON` 文を実行すると、古いON条件の定義が消えてなくなるわけではなく、その「上に新しい定義が積み上げ(プッシュ)される」のです。まるでオセロの駒や、パンケーキを上へ上へと重ねていくように。
そして、その役割を終えたときや、元の振る舞いに戻したいときに登場するのが、今回のキーマン`REVERT` 文です。
—
実践!コードで見るON条件のプッシュとポップ(REVERT)
百聞は一見にしかず。実際のバッチプログラムを想定したコードを見てみましょう。大文字で書かれた重厚な佇まいですが、コメントを読めば怖くありませんよ。
/ ========================================================== /
/ ON ENDFILE スタック構造とREVERTのサンプルプログラム /
/ ========================================================== /
TESTPROG: PROC OPTIONS(MAIN);
DCL INFILE FILE RECORD INPUT;
DCL EOF_FLAG BIT(1) INIT(‘0’B);
/ 1. まず、ファイルを開きます /
OPEN FILE(INFILE);
/ 2. 【大元のONユニット設定】(スタックの底) /
ON ENDFILE(INFILE) BEGIN;
PUT SKIP LIST(‘【大元】標準のENDFILE処理が呼ばれました。’);
EOF_FLAG = ‘1’B;
END;
/ メインの読み込み処理を実行するサブルーチンを呼ぶ /
CALL PROCESS_SUB_ROUTINE(INFILE);
CLOSE FILE(INFILE);
RETURN;
/ ———————————————————- /
/ サブルーチン:ここで局所的なONユニットのオーバーライドを行う /
/ ———————————————————- /
PROCESS_SUB_ROUTINE: PROC(P_FILE);
DCL P_FILE FILE RECORD INPUT;
PUT SKIP LIST(‘— サブルーチン開始 —‘);
/ 3. 【局所的なONユニット設定】(大元の上にプッシュされる!) /
ON ENDFILE(P_FILE) BEGIN;
PUT SKIP LIST(‘【サブルーチン内】独自のENDFILE処理が呼ばれました!’);
/ ここであえて特別なフラグを立てたりする /
END;
/ ここでファイル読み込み(実際にはREAD文などが入ります) /
/ … 処理の途中でENDFILEが発生したと仮定 … /
SIGNAL ENDFILE(P_FILE); / シミュレーション用の割り込み発生命令 /
/ 4. サブルーチンを抜ける前に、スタックを元に戻す(ポップする) /
/ REVERTを書かないと、呼び出し元に戻った後もこの局所的ONが暴発する恐れがあります! /
REVERT ENDFILE(P_FILE);
PUT SKIP LIST(‘— サブルーチン終了 —‘);
END PROCESS_SUB_ROUTINE;
END TESTPROG;
コードのここがポイント!
1. `ON ENDFILE …` の二重定義
メイン側とサブルーチン側の両方で `ON ENDFILE` が宣言されています。PL/Iの世界では、サブルーチン側で宣言した瞬間、大元の設定が消えるのではなく、「サブルーチン用の設定が上に重ねて積まれる(プッシュ)」状態になります。
2. `SIGNAL` による擬似発生
PL/Iでは `SIGNAL` 文を使うことで、実際にファイル終端まで読まなくても、意図的にテスト用のON条件を発生させることができます。テストやデバッグで非常に重宝する機能です。
3. 命綱となる `REVERT` 文
ここが今回の最も大切な学びです。サブルーチンが終わるとき、`REVERT ENDFILE(P_FILE);` を実行していますよね。
これを実行することで、スタックに積み上げられていたサブルーチン用のON条件がスッと取り除かれ(ポップされ)、一階層下(大元)のON条件が再び有効な状態に戻ります。
もしこの `REVERT` を書き忘れてサブルーチンを抜けてしまうとどうなるでしょう? 呼び出し元に戻った後も、サブルーチン側の古いスコープのつもりで割り込みが動き続けてしまい、バッチ全体が予期せぬ挙動を引き起こすという、レガシー現場の「あるあるトラブル」に直結してしまいます。
—
まとめ:PL/Iのスタック構造は怖くない!
ここまで、ON ENDFILEのスタック構造と `REVERT` 文の仕組みを駆け足で見てきましたがいかがでしたでしょうか?
- ONユニットは「割り込みの予約登録」である
- 同じファイルに対して何度もONを書くと、設定がどんどん上に積み重なる(スタック構造)
- 積み上げた設定をキレイに片付けて元の状態に戻すのが `REVERT` の役目
他のモダンな言語に慣れていると、最初は「なんでこんなに動的なスコープ管理をするんだろう?」と驚くかもしれませんが、大規模なメインフレームのバッチプログラムにおいて、各モジュールが独立して安全にファイルを制御するためには、このスタックと復帰の仕組みがなくてはならないものでした。
仕組みさえ分かってしまえば、PL/Iは非常に直感的で、プログラマの意図を忠実に実行してくれる素直な言語です。
もし実際のマイグレーションや保守の現場で複雑なON条件の連鎖に出くわしたときは、この記事の「パンケーキのスタックとREVERT(お片付け)」を思い出してみてくださいね。
あなたのメインフレーム開発ライフが、少しでも快適で楽しいものになりますように!それではまた次回の技術解説でお会いしましょう。
