【入門編】INDEXおよびVERIFY関数によるスキャンアルゴリズム – PL/Iの基本構文とデータ制御実践ガイド

こんにちは!メインフレームの世界へようこそ。
JavaやCOBOLといった他のプログラミング言語の経験がある方にとって、突然目の前に現れるPL/I(ピーエルアイ)のソースコードは、少し古い要塞の壁のように硬く、近寄りがたく見えるかもしれませんよね。

「なんだか変数名のルールも独特だし、予約語がないってどういうこと?」
「文字列を探す関数(INDEXやVERIFY)の動きが、他の言語となんだか違う気がする……」

そんな不安を抱えていらっしゃるあなたへ。大丈夫です、怖くありませんよ。一つずつ蓋を開けて中身を覗いていけば、PL/Iがいかに合理的で、かつユニークな思想で作られた言語であるかが分かってきます。

今回は、PL/Iの基礎となる「識別子の面白さ」に少し触れたあと、基幹システムのバッチ処理で毎日のように酷使される文字列スキャン関数 `INDEX` と `VERIFY` の内部挙動と、思わずハマりがちな「戻り値の罠」について、現場のノウハウを交えてたっぷり解説していきますね。

—

1. ちょっと待って、「予約語」がない世界?

JavaやCOBOLを触ってきた方なら、「IF」「MOVE」「DISPLAY」「REMAINS」といった、プログラムの構文として特別に予約されている単語(予約語)の存在をご存知だと思います。これらは変数名として使うことができませんよね。

ところが、PL/Iには原則として「予約語」という概念が存在しません。

どういうことかと言うと、PL/Iのコンパイラは、その単語が書かれている「文脈(コンテキスト)」を見て、それが命令なのか変数名なのかを賢く判断しているんです。極端な話、`IF`という名前の変数を宣言して、その中で条件分岐の `IF` を使うことだって、理屈の上ではできてしまいます(もちろん、ソースコードが読みにくくなるので絶対にやらない方がいいですが!)。

この「文脈依存」という懐の深さが、PL/Iを少し独特な言語に見せている原因の一つですが、基本のルールさえ掴めば怖くありません。変数名(識別子)は、アルファベットや国会文字(#、$など)で始まり、最大31文字まで使えます。COBOLのように「30文字制限でハイフンが〜」と悩む必要もなく、大文字・小文字も区別されません(実務では伝統的に大文字で書くのがメインフレームの作法です)。

—

2. 文字列スキャンの主役:`INDEX` 関数の本当の姿

さて、本題の文字列検索アルゴリズムに入りましょう。
基幹システムのデータ処理では、「この電文の中に特定のコードが含まれているか?」を探す作業が頻繁に発生します。そこで使われるのが `INDEX` 関数です。

Javaの `indexOf()` や、他の言語の文字列検索と同じ感覚で使えますが、メインフレームならではの「文字コード(EBCDIC)の爆速スキャン」が裏で行われています。

まずはコードを見てみましょう。

1
/ ———————————————— /
/ INDEX関数の基本動作サンプルバッチ /
/ ———————————————— /
SAMPLE_INDEX: PROC OPTIONS(MAIN);

DCL TARGET_STR CHAR(30) INIT(‘IBM_MAINFRAME_PL1’);
DCL SEARCH_WORD CHAR(5) INIT(‘MAIN’);
DCL POS FIXED BIN(31);

/ TARGET_STR の中から SEARCH_WORD が最初に現れる位置を探す /
POS = INDEX(TARGET_STR, SEARCH_WORD);

IF POS > 0 THEN
PUT SKIP LIST(‘見つかりました! 位置は: ‘ || EDIT(POS) (F(3)) );
ELSE
PUT SKIP LIST(‘見つかりませんでした。’);

END SAMPLE_INDEX;

ここがポイント!見つからなかった時の「0」

Javaなどのモダン言語の多くは、文字列が見つからない場合の戻り値として `-1` を返しますよね。しかし、PL/Iの `INDEX` 関数は、見つからない場合に `0`(ゼロ)を返します。

ここ、他の言語に慣れている人が最もやらかすバグの温床です。
「あ、見つからないからマイナス判定にしておこう」と書くと、先頭(1文字目)に見つかったときの戻り値 `1` と混同したり、条件分岐を誤ったりします。

  • `INDEX(文字列, 検索文字)` の戻り値の仕様:
  • 見つかった場合:最初に見つかった文字の先頭位置(1始まりの整数)
  • 見つからなかった場合:`0`

「先頭が1」というのも、プログラミング言語としては少し珍しい(0オリジンではない)ポイントですね。メインフレームの帳票レイアウトや固定長ファイルの桁位置(1桁目、2桁目……)と直結しているため、この仕様になっているのです。非常に直感的だと思いませんか?

—

3. ちょっとマニアックで強力な相棒:`VERIFY` 関数

`INDEX` が「特定の文字・文字列を探す」ためのものなら、次に紹介する `VERIFY`(ベリファイ)関数 は、「指定された文字セット以外の、異物(あるいは想定外の文字)が混ざっていないかチェックする」ための強力なスキャンアルゴリズムです。

例えば、「この項目には、数字しか入ってちゃいけない(純粋な数値データかチェックしたい)」という要件があったとします。そんなとき、一文字ずつループを回してチェックしていませんか?
PL/Iなら、`VERIFY` 関数を使えば一撃です。

1
/ ———————————————— /
/ VERIFY関数の基本動作サンプルバッチ /
/ ———————————————— /
SAMPLE_VERIFY: PROC OPTIONS(MAIN);

DCL ACCT_NO CHAR(6) INIT(‘12345A’); うっかりアルファベット混じり
DCL VALID_CHARS CHAR(10) INIT(‘0123456789’);
DCL ERR_POS FIXED BIN(31);

/ ACCT_NO の中に、VALID_CHARS 以外の文字が含まれていないかスキャン /
ERR_POS = VERIFY(ACCT_NO, VALID_CHARS);

IF ERR_POS > 0 THEN
PUT SKIP LIST(‘エラー検知! 不正な文字の位置: ‘ || EDIT(ERR_POS) (F(3)) );
/ この場合、6文字目の ‘A’ がヒットするため、ERR_POS は 6 を返す /
ELSE
PUT SKIP LIST(‘すべて許可された文字だけで構成されています。’);

END SAMPLE_VERIFY;

`VERIFY` のメカニズムを紐解く

`VERIFY(string, verify_string)` は、第1引数の文字列を左から右へスキャンし、「第2引数の文字列に含まれていない文字」が最初に現れた位置を返します。

  • 全部が第2引数の文字だけで出来ていれば、戻り値は `0` になります。
  • 途中にルール違反の文字があれば、その位置(1始まり)を返します。

データ移行やインターフェース電文のバリデーションチェックにおいて、この `VERIFY` 関数はCOBOLの `INSPECT` 文などと比べても非常にシンプルかつ高速に記述できるため、メインフレームのベテランプログラマーたちがこよなく愛する機能の一つです。

—

4. アーキテクトからの実践アドバイス

最後に、実務でPL/Iの文字列関数を扱う際のちょっとしたコツをお伝えします。

PL/Iの文字列変数は、宣言時に `CHAR(30)` のように固定長(固定長文字型)で定義されることがよくあります。もし実データの長さが短い場合、残りの部分は自動的に「空白(スペース)」でパディング(埋め合わせ)されます。

そのため、`INDEX` や `VERIFY` を使う際、意図しない末尾の空白までスキャン対象に含まれてしまい、思わぬ誤作動を起こすことがあります。そんなときは、組み込み関数 `TRIM`(または古い処理系であれば文字列操作の工夫)を組み合わせて、余分な空白をトリミングしてから渡すのが、トラブルを未然に防ぐプロの技です。

レガシーシステムのブラックボックスに見えるコードも、中身の仕様(「見つからなければ0」「1始まりのインデックス」など)を一つずつ理解してしまえば、怖がるどころか、そのコンパクトで無駄のない設計に魅力を感じるはずです。

ぜひ、日々のバッチ改修やマイグレーション調査の現場で、これらの関数を自信を持って使ってみてくださいね。あなたのメインフレームライフを、心から応援しています!

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