こんにちは!メインフレームの世界へようこそ。
JavaやCOBOLといった他のモダンな言語や伝統的なビジネス言語を経験されてきた方にとって、IBMメインフレームの「PL/I(ピーエルワン)」は、最初少しだけとっつきにくく感じられるかもしれません。
「なんだか記号だらけで難しそう…」
「ポインタやメモリ管理の話が出てきたらどうしよう…」
そんな不安を抱えていませんか? 大丈夫ですよ。怖がる必要は全くありません。一つひとつの仕様を紐解いていけば、PL/Iがいかに合理的で、パワフルな言語であるかが分かってきます。
今回は、そんなPL/Iの数あるユニークな機能の中から、「`STRING`関数による構造体から文字列へのキャスト」という、レガシー移行の現場でもよろしい頻度で遭遇する重要テーマを、優しく噛み砕いて解説していきますね。
—
そもそもPL/Iの変数名や予約語ってどうなっているの?
本題に入る前に、JavaやCOBOLから来た人が驚く、PL/Iのちょっと面白い特徴に少しだけ触れておきましょう。
実は、PL/Iには「厳密な意味での予約語(Keyword)」というものが存在しません。
「えっ、じゃあ `IF` とか `DCL`(宣言)とかは何なの?」って思いますよね。これらは予約語ではなく、「文脈語(Contextual Keyword)」と呼ばれています。
どういうことかと言うと、コンパイラは「その位置がどういう文脈(コンテキスト)にあるか」で言葉の意味を判断しているんです。
例えば、極端な話ですが、変数の名前に `IF` や `DO` を使っても、コンパイラは「あ、これは変数名だな」と前後の文脈から賢く察してくれます。(※もちろん、ソースコードが読みにくくなるので絶対にやめましょうね!)
この「柔軟だけど懐が深い」設計思想こそが、PL/Iの最大の特徴であり、今回のテーマである構造体の扱いにも深く関係してきます。
—
構造体(GROUP / 01レベル)を丸ごと扱いたい!
他の言語、例えばCOBOLで考えてみてください。
01レベルの親項目の下に、いくつかの細かいデータ項目(子項目)がぶら下がっている構造体(グループ項目)がありますよね。
01 CUSTOMER-REC.
05 CUST-ID PIC X(5).
05 CUST-NAME PIC X(20).
05 CUST-AMT PIC 9(7)V99.
COBOLでこのレコード全体をファイルに出力したり、ログにそのままドカンと出力したりしたい時、`CUSTOMER-REC` という名前をそのままバッファとして使ったりしますよね。
では、PL/Iではどう書くでしょうか?
PL/Iでは、構造体は以下のように宣言します。
1
DCL 1 CUSTOMER_REC,
5 CUST_ID CHAR(5),
5 CUST_NAME CHAR(20),
5 CUST_AMT FIXED DEC(7,2);
さて、ここからが本題です。
「この `CUSTOMER_REC` という構造体の中身を、個別に処理するのではなく、ひとつの長い『文字列(CHAR型)』として扱いたい!」と思った時、あなたならどうしますか?
Javaなら `toString()` メソッドをオーバーライドしたり、StringBuilderで連結したりするでしょうか。
しかし、PL/Iには、もっとエレガントで、そしてメインフレームのメモリ構造に直結した素晴らしい解決策が用意されています。それが今回主役の `STRING`関数 です。
—
`STRING`関数とは何か? メモリ上の秘密
PL/Iの `STRING`関数を使うと、バラバラのデータ型が集まった構造体全体を、あたかも「一枚の連続した文字列(CHAR)」であるかのように一発でキャスト(変換・みなすこと)できます。
百聞は一見にしかず。実際のコードを見てみましょう。
1
/ ————————————————– /
/ 構造体から文字列へのキャストサンプル /
/ ————————————————– /
DCL 1 CUSTOMER_REC,
5 CUST_ID CHAR(5) INIT(‘12345’),
5 CUST_NAME CHAR(20) INIT(‘YAMADA TARO’),
5 CUST_AMT FIXED DEC(5) INIT(1000);
/ まるごと格納するための大きな文字列変数 /
DCL WORK_STR CHAR(30);
/ 【ここがポイント!】構造体を丸ごと文字列変数に代入 /
WORK_STR = STRING(CUSTOMER_REC);
このコードを実行すると、`WORK_STR` には何が格納されるでしょうか?
JavaやC言語の感覚だと、「型が違うからコンパイルエラーになるのでは?」あるいは「文字化けするのでは?」と心配になりますよね。
ですが、安心してください。綺麗に、意図した通りのバイト列が結合されて格納されます。
なぜこれが可能なのか?(コンパイラの内部挙動)
メインフレームのメモリを思い浮かべてみてください。メモリというのは、突き詰めると「0と1のバイトの連続した箱(ストレージ)」にすぎません。
PL/Iのコンパイラは、構造体が宣言された時、その配下にある項目をメモリ上に隙間なく(あるいは規則的なパディングを挟んで)連続して配置します。
`STRING(CUSTOMER_REC)` と書いた瞬間、コンパイラはこう翻訳します。
> 「あ、この構造体全体の先頭アドレスから、最後の項目の終わりまでの長さを、ただの『連続した文字の塊(CHAR)』として扱いたいんだな。よし、中身のデータ型の違いは無視して、メモリの生データをそのままCHAR型として扱わせてあげよう」
つまり、`STRING`関数は、型変換の関数というよりも、「コンパイラに対してメモリの解釈のメガネをかけ替える呪文」のようなものなんです。
—
現場で役立つ!応用テクニックと注意点
この `STRING`関数、マイグレーションやレガシーシステムのバッチ改修の現場では、以下のようなシーンで猛威を振るいます。
1. 電文(メッセージ)の丸ごとログ出力・デバッグ
複雑な構造体の中身を、一文字ずつループで回して確認するのは大変ですよね。`STRING` で一撃でワークエリアに落とし、それをそのままSYSOUT(ログ)に出力すれば、一発で全フィールドのバイトイメージが確認できます。
2. ファイルへの一括入出力(I/O)の簡略化
レコード構造体をそのまま外部ファイルに書き出す際、文字コードや詰め物の調整に重宝します。
ただし、ここに注意!
「便利だから何でも `STRING` でキャストしちゃえ!」とするのは少し待ってください。注意すべき点が一つあります。
それは、構造体の中に異なるデータ型(例えば `FIXED BIN` や `FLOAT`、ポインタなど)が含まれている場合、コンパイラが自動的に行う「アライメント(境界調整)」によって、予期せぬパディング(空白バイト)がメモリ上に挟まることがあるという点です。
そのため、文字として綺麗に連結したい場合は、宣言時に `UNALIGNED` 属性を明示して、メモリ上の無駄な隙間をなくしてあげるのが、ベテランPL/Iアーキテクトのスマートな作法です。
1
/ UNALIGNEDを指定して、隙間なくメモリに詰め込む /
DCL 1 CUSTOMER_REC UNALIGNED,
5 CUST_ID CHAR(5),
5 CUST_NAME CHAR(20),
5 CUST_AMT CHAR(5); / キャストを多用するなら文字型で揃えるのも手 /
—
おわりに
いかがでしたでしょうか?
「PL/Iの構造体と `STRING`関数」のコンビネーション、イメージが湧いてきたでしょうか。
一見すると難解に見えるレガシー言語の仕様も、「メモリの連続性をどう解釈させるか」という根本的な視点に立てば、非常にシンプルで理にかなっていることが分かりますよね。
JavaやCOBOLで培ったあなたのプログラミングの基礎があれば、PL/Iの習得なんてあっという間です。怖がらなくて大丈夫、一歩ずつ自分のペースで進めていきましょう。
次回のブログでも、現場で思わず「なるほど!」とうなる実用的なPL/Iの知見をお届けします。どうぞお楽しみに!
