混合リストを使用してCLコマンドに「データ構造」を追加する
データ構造がないRPGプログラムで最後に作業したのがいつだったか思い出せませんが、たぶん、System/34上だったような気がします。データ構造は誰もが使用しています。それにはもっともな理由があります。ポケットと同じくらい、手近にあってすぐに使えるからです。CLコマンドを書くときに、データ構造としてフォーマットされたパラメーターを組み込むことができます。IBMでは、それらを混合リストと呼んでいます。この記事では、そのやり方について説明します。
お気付きでないかもしれませんが、混合リスト パラメーターを持つIBM提供のコマンドは、皆さんも使用しています。たとえば、 ファイル・コピー(CPYF) コマンドには、そのようなパラメーターがいくつかあります(FROMKEY、TOKEY、INCCHAR、INCREL、およびSRCSEQ)。以下は、INCCHARパラメーターのプロンプトです。
シンプルな混合リスト
混合リスト パラメーターを定義するには、以下のように、ELEM(要素)キーワードが必要です。
CMD PROMPT('Do It')
PARM KWD(INCCHAR) TYPE(INCCHAR_T) +
PROMPT('Include records by char test')
INCCHAR_T: ELEM TYPE(*CHAR) LEN(10) RSTD(*NO) DFT(*NONE) +
SPCVAL((*RCD) (*FLD)) EXPR(*YES) +
PROMPT('Field')
ELEM TYPE(*INT4) RSTD(*NO) EXPR(*YES) +
PROMPT('Character position')
ELEM TYPE(*CHAR) LEN(3) RSTD(*YES) DFT(*EQ) +
VALUES(*EQ *NE *GT *GE *LT *LE *NG *NL *CT) +
PROMPT('Relational operator')
ELEM TYPE(*CHAR) LEN(256) EXPR(*YES) +
PROMPT('Value')
要素が4つあり、それぞれが独自のデータ タイプ、プロンプト ストリングなどで定義されています。IBMのソース コードは手元にないので、これは、IBMがこのようにしてそのパラメーターを定義したのかもしれない、という1つの例に過ぎません。
この記事で使用されているコードは、 こちらからダウンロードできます。
コマンド処理プログラム(CPP)は、5つの値が入ったメモリーのブロックを受け取ります。1つ目の値は、混合リスト内に要素がいくつあるかを知らせる2バイトの整数です。この例では、その値は4となります。このフィールドは、私にとっては全く不要なので、いつも無視しています。その後に、4つの入力フィールド値が続きます。
INCCHARパラメーターは、CL CPPで以下のように処理されます。
pgm parm(&IncChar)
dcl var(&IncChar) type(*char) len(275)
dcl var(&Field) type(*char) stg(*defined) len( 10) DefVar(&IncChar 3)
dcl var(&Position) type(*int) stg(*defined) len( 4) DefVar(&IncChar 13)
dcl var(&RelOper) type(*char) stg(*defined) len( 3) DefVar(&IncChar 17)
dcl var(&Value) type(*char) stg(*defined) len(256) DefVar(&IncChar 20)
プログラムは、そのパラメーターを単一の文字ストリングとして受け取ります。ここでは、定義済み記憶域を使用して、パラメーターを4つの要素の記述でオーバーレイしました。定義済み記憶域は、RPGのデータ構造とまったく同じというわけではありませんが、同じ目的を果たします。
混合リストの配列
お望みなら、混合リストの配列をCPPに渡すこともできます。それがどのようなものなのかを、以下に示します。以下は、CPYFのINCRELパラメーターのプロンプトです。
混合リストの配列を定義した場合、コマンド プロセッサーは異なるパラメーター構造を使用します。このプロセスを説明するには、例を示すのが1番だと思われるので、私が先日関わったプロジェクトについてお話しすることとします。
私は、適切に書かれていない40年前のRPGプログラムの「ロジック」を理解しようとしていました(GOTOや標識を思い浮かべてみてください)。プログラムにプリンター ファイルを追加して、変数の値を記録しました。以下のようのものです。
1 A Y 12.50 Y RA Y 12345
2 A Y 12.50 Y RA Y 12345
3 A Y 8.00 Y RA Y 12345
4 C Y 6.00 Y DF Y 12345
5 A Y N DF Y 44444
6 A Y 12.50 Y DF N 44444
7 A Y 12.50 Y DF N 44444
8 A Y 12.50 Y DF N 44444
9 B Y N RA N 44444
10 C N 8.00 Y RD N 12345
11 B Y 8.00 Y RD N 44444
12 A Y 12.50 Y RD N 44444
13 A N 12.50 Y DF N 12345
14 A N 6.00 N TS Y 12345
15 A N 6.00 Y DF Y 44444
16 A N 12.50 Y RD Y 44444
17 B N 8.00 Y TS Y 44444
18 C Y 12.50 Y TS Y 12345
19 C Y 8.00 Y TS N 44444
20 B Y N RA N 12345
21 C Y 6.00 Y RD Y 44444
22 C Y Y RA N 77777
23 A Y 12.50 N TS Y 12345
24 A Y 6.00 Y RA N 33333
スプール ファイルは、様々な条件と、それらがつながる出力について教えてくれました(おかげで、スパゲッティ コードをすべて解きほどく必要はなくなりました)。
スプール・ファイル表示(DSPSPLF)コマンドによって、レポートは表示されましたが、関連のある情報に絞る方法は示されませんでした。本当に必要だったのは、一定の基準を満たした行のみを表示することでした。これを実現するために、1回使うだけのユーティリティを書きました。この例では、SSF(Search Spooled File: スプール ファイル検索)ユーティリティと呼びます。SSFは、2つのオブジェクト(コマンドとCLプログラム)から成ります。
私のコマンドでは、1つから3つの検索基準を入力することができます。たとえば、
SSF FILE(QSYSPRT) JOB(*) SPLNBR(*LAST) +
POS1(6) VAL1(N) +
POS2(16) VAL2(TS) +
POS3(19) VAL3(Y)
これは、「6桁目にN、16および17桁目にTS、19桁目にYがあるレポート行のみを表示する」ということを意味します。
*...+....1....+....2....+
14 A N 6.00 N TS Y 12345
17 B N 8.00 Y TS Y 44444
決して洗練されたものではありませんが、私の目的を満たすのには十分であり、急ごしらえで、掛かった時間と労力はわずかでした。このツール作成のための短期投資は十分に成果を上げ、私の多くの時間とクライアントの多くの金銭の節約になりました。
その後、時間があったときに、スプール ファイルの行を選択的に表示するためのきちんとしたユーティリティを書くこととしました。SSFの新バージョンでは、以下の点を改善しました。
- 比較演算子一式をサポート(*EQ *NE *GT *GE *LT *LE *NG *NL *CT)。
- and/or/not論理が可能になりました。
- 条件を括弧でグループ化できるようになりました。
- 混合リストの配列を使用して検索基準を指定。
新バージョンのSSFでは、前述の例は以下のようになります。
SSF FILE(QSYSPRT) JOB(*) SPLNBR(*LAST) +
SELECT(( *N *N *N 6 *EQ N ) +
( *AND *N *N 16 *EQ TS ) +
( *AND *N *N 19 *EQ Y ))
*Nトークンは、混合リストのその要素に対する値を提供しないことを意味します。確かに、この構文は少し暗号のようですが、それがCLであり、コマンドをプロンプトするときに、これらのパラメーターを適切にキー化することは難しくありません。
これで、ありとあらゆる検索を行えるようになりました。もうひとつ、例を示します。
SSF FILE(QSYSPRT) JOB(*) SPLNBR(*LAST) +
SELECT((*N *N *N 4 *EQ A) +
(*AND *N '(' 16 *EQ DF) (*OR *N *N 16 *EQ TS ')'))
これは、「カラム4にA、16および17にDFまたはTSのいずれかがあるレポート行を表示する」ということを意味します。
では、この魔法を起こしているコードを見てみましょう。まず、混合リストがどのように定義されるかを以下に示します。
CMD PROMPT('Search Spoole File')
PARM SELECT TYPE(SELECTION) MAX(12) PROMPT('Selection criteria')
SELECTION: +
elem TYPE(*CHAR) LEN(4) RSTD(*YES) VALUES(' ' *AND *OR) +
EXPR(*YES) PROMPT('And/Or')
elem TYPE(*CHAR) LEN(4) RSTD(*YES) VALUES(' ' *NOT) +
EXPR(*YES) PROMPT('Not')
elem TYPE(*CHAR) LEN(1) RSTD(*YES) VALUES(' ' '(') +
EXPR(*YES) PROMPT('Open parenthesis')
elem TYPE(*INT4) RSTD(*NO) EXPR(*YES) PROMPT('Position')
elem TYPE(*CHAR) LEN(3) RSTD(*YES) DFT(*EQ) +
VALUES(*EQ *NE *GT *GE *LT *LE *NG *NL *CT) +
PROMPT('Test')
elem TYPE(*CHAR) LEN(40) EXPR(*YES) PROMPT('Search value')
elem TYPE(*CHAR) LEN(1) RSTD(*YES) VALUES(' ' ')') +
EXPR(*YES) PROMPT('Close parenthesis')
混合リストには、要素が7つあります。これらでは、適切な場所に*AND、*OR、*NOT、および括弧を入力することができます。SELECTパラメーターは、MAX(12)を指定しています。これにより、混合リストの配列が作成されます。
私のCLコマンド処理プログラムは、データをレコード選択式に変換します。これは、「QUERYファイルのオープン(OPNQRYF)」に渡します。
%SST(SPLFDATA 4 1) *EQ 'A' *AND (%SST(SPLFDATA 16 2) *EQ 'DF' *OR
%SST(SPLFDATA 16 2) *EQ 'TS')
このCLプログラムは、スプール ファイルをスクラッチ プログラム記述データベース ファイルにコピーし、「OPNQRYF」および「QUERYファイルからのコピー(CPYFRMQRYF)」を実行して、選択されたレポート行を2つ目のスクラッチ ファイルにコピーしてから、その2つ目のスクラッチ ファイルで「物理ファイル・メンバー表示(DSPPFM)」を使用して、私が表示したい行のみを表示してくれます。
私のCLプログラムは、SELECTパラメーターを1つの長いストリングとして受け取ります。そのストリングから適切にデータ値を抽出できるかどうかは私次第です。そのやり方を以下に示します。
最初の2バイトは、配列内の混合リストの数を知らせてくれる整数です。ここでは、%BIN関数を使用して、この値を取得しています。
pgm ( . . . &inSlt)
dcl &inSlt *char 1024
dcl &ListSize *uint 2
ChgVar &ListSize %bin(&inSlt 1 2)
この後に続くのは、それぞれのデータ構造がパラメーター ストリング内のどこから始まるかを知らせる一連の2バイトの整数です。渡された混合リストごとに2バイトの整数が1つずつあります。以下は、そのデータを16進数で表したものです。
007E00430008
以下は、その情報を表にしたものです。
これらの値は、パラメーター ストリングの先頭からのオフセットであるので、たとえば、67という値はパラメーター ストリングの先頭から67バイト目を意味します。つまり、そのストリングの68桁目です。
それぞれの混合リストはそれらのオフセットの後に続きます(最後から最初へ)。それぞれの混合リストには、8つの値が格納されています。リスト内の値の数(これは常に7であり、無視することができる)と、その後に、コマンド定義でELEMによって定義された7つのフィールドが続きます。
混合リストの値をストリングから抽出するのは、煩雑ではありますが、難しいことではありません。
dcl &inSlt *char 1024
dcl &ListSize *uint 2
dcl &Lx *uint 4
dcl &Ox *uint 4
dcl &Quote *char 1 value('''')
dcl &ValueLen *uint 4
dcl &SplfData *char 10 value(SPLFDATA)
dcl &Offset *uint 4
dcl &Element *char 59
dcl &AndOr *char 4 stg(*defined) defvar(&Element 3)
dcl &Not *char 4 stg(*defined) defvar(&Element 7)
dcl &OpenParen *char 1 stg(*defined) defvar(&Element 11)
dcl &Position *int 4 stg(*defined) defvar(&Element 12)
dcl &Test *char 3 stg(*defined) defvar(&Element 16)
dcl &Value *char 40 stg(*defined) defvar(&Element 19)
dcl &CloseParen *char 1 stg(*defined) defvar(&Element 59)
dcl &QrySlt *char 1024
/* Build the query select expression */
chgvar &ListSize %bin(&inSlt 1 2)
dofor &Lx from(1) to(&ListSize)
chgvar &Ox (&Lx * 2 + 1)
chgvar &Offset (%bin(&inSlt &Ox 2) + 1)
chgvar &Element %sst(&inSlt &Offset 59)
chgvar &ValueLen (%checkr(' ' &Value))
if (&Position *ne 0) do
ChgVar &QrySlt +
( &QrySlt *bcat +
&AndOr *bcat &Not *bcat &OpenParen *cat +
'%SST(' *cat &SplfData *bcat +
%char(&Position) *bcat +
%char(&ValueLen) *tcat ')' *bcat +
&Test *bcat +
&Quote *cat %trimr(&Value) *cat &Quote *cat +
&CloseParen)
enddo
照会選択ストリングが構築されたら、後は、スクラッチ ファイルを作成して、それを表示するだけです。
OpnQryF file((&TempLib/&SplfData)) +
QrySlt(&QrySlt) OpnID(&SplfData)
CpyFrmQryF FromOpnID(&SplfData) +
ToFile(&TempLib/&FileToShow) +
MbrOpt(*REPLACE) CrtFile(*YES)
DspPFM &tempLib/&FileToShow
以下のコマンドを実行すると、
SSF FILE(QSYSPRT) JOB(*) SPLNBR(*LAST) +
SELECT((*N *N *N 4 *EQ A) +
(*AND *N '(' 16 *EQ DF) (*OR *N *N 16 *EQ TS ')'))
以下の結果が得られます。
*...+....1....+....2....+
5 A Y N DF Y 44444
6 A Y 12.50 Y DF N 44444
7 A Y 12.50 Y DF N 44444
8 A Y 12.50 Y DF N 44444
13 A N 12.50 Y DF N 12345
14 A N 6.00 N TS Y 12345
15 A N 6.00 Y DF Y 44444
23 A Y 12.50 N TS Y 12345
このコードをダウンロードして、いろいろ試してみてください。
RPGのデータ構造が大好きという方なら(そうでない人はいるのでしょうか)、混合リストをCLコマンドに組み込みたいと思うのではないでしょうか。私はそれで構いません。