Bryan Meyers著 MOVE命令コードをフリー・フォーマットのコードで置き換える RPGについて話をすると、パンチ・カードや仕様定規、固定カラムなどが頭の中で渦を巻き始めるプログラマが多いようです。歴史的に見るとRPGは固定フォーマットの言語で、指定された列の範囲にコードを納めなければなりませんでした。45列という比較的余裕のある表現ができるRPG IVの拡張Factor 2フォーマットでさえも、その制約から抜け出そうとしているようなプログラムに見えます。 バージョン5へようこそ。このリリースで、IBMは従来の列状のC仕様の代わりとなる新しいフリー・フォーマットの仕様を導入しました。フリー・フォーマットのRPGは固定フォーマットのものより間違いなく読みやすく、RPGプログラマにとってはコーディングしやすく、初めてのプログラマにとっても習得しやすいものです。また、プログラムの論理構造もわかりやすいため、保守も容易でバグが入り込みにくくなっています。 ではなぜRPGの世界はこれまで、フリー・フォーマットを積極的に受け入れてこなかったのでしょうか。IBMはこの機会を利用して、もはや使用されていないと判断した多くの命令コードを、フリー・フォーマットではサポートしないようにしました。たとえば、新仕様では結果にインジケータを必要とするような命令コードをサポートしていません。中でもはっきりとサポート対象から外されたのは、MOVE命令コードのグループです。この命令は、フィールドのデータ・タイプや長さが同じであろうとなかろうと、一方のフィールドの値を他方のフィールドに割り当てるという極めて強力な命令です。本稿では、MOVE命令コードをフリー・フォーマットのコードで置き換えるためのテクニックについて解説します。まずその前にフリー・フォーマットのRPGの使い方を簡単に紹介します。 フリー・フォーマット・イン・モーション 図1に、使用中のフリー・フォーマットの仕様を示します。フリー・フォーマットの仕様は、ありふれたRPGシンタックスを使用して/FREEコンパイラ・ディレクティブと/END-FREEコンパイラ・ディレクティブの間にコーディングしますが、すべてが正しい列にあるか否かを心配する必要はありません。コードは8列から80列の間のどこに指定してもよく、6列と7列は空白のままにしておきます。(「フリー・フォーマット」のコードを特定の列に指定しなければならないなどという皮肉なことは、IBMではありえないようです。) フリー・フォーマットのコードの実際の中身は、各行がFactor 1ではなく命令コードで始まっていることを除けば、今までとあまり変わりありません。仕様の正しい順序は以下の通りです。 Opcode(e) Factor1 Factor2 Result; 命令コードがオプションとなる場合は2通りあります。エクステンダを指定する必要がない限り、EVAL(式の評価)とCALLP(プロトタイプ・プロシージャ/プログラムの呼び出し)を省略できます。 Tax = TaxableAmount * TaxRate; Eval(h) Tax = TaxableAmount * TaxRate; UpdCustomer(Company:CustomerNbr); Callp(e) UpdCustomer(Company:CustomerNbr); コードの開始位置をそろえたりインデンテーションをつけたりすることでコードを読みやすくすることが可能で、必要に応じて仕様を複数行に渡って続けることもでき、また先頭に//をつけてインラインの「ダブル・スラッシュ」・コメントを書くこともできます。各フリー・フォーマット文はセミコロンをデリミタとして終わります。 フォース・ダウン、残り数インチ サポートされなくなった命令コードのほとんどにはその代わりになるものがありますが(図2)、既存のコードを変換して使用することに抵抗を感じるプログラマが多いようです(「壊れていなければ、修理するな」)。 前述した通り、フリー・フォーマットのサポートがなくなったものの中でひときわ目立つのは、MOVE命令コードのグループです(MOVE、MOVEL(左へ移動)、MOVEA(配列の移動))。確かに多くのMOVE命令には、代わりになるものが複数存在しますが、まさにそこが問題となるのです。新しいプログラムを書いているときにMOVEを使わないようにするのは簡単ですが、既存のプログラムを書き換えてMOVEを使わないようにするのはそれほど簡単ではありません。どのような状況のときに代わりにどれを使えばよいのかを決めるには、プログラムのロジック、そのプログラムで本来何を実行しようとしていたのか、移動対象のデータの属性、などを慎重に調べなければなりません。 MOVE命令は移動元の変数(Factor 2で指定)の全部または一部を移動先の変数に転送します。このとき移動元の変数の値は変更しません。移動元の変数と移動先の変数はそのタイプや長さが同じ場合も異なる場合もいずれも一般的です。容易に予想されることですが、データのタイプと長さが同じ場合に、変換が最も簡単になります。次の固定フォーマットのコードは C Move Source Result フリー・フォーマットでは次のように変換されます。 Result = Source; しかしこのような単純な代入よりも複雑になると、代入式に組み込み関数をいくつか使用しなければならなくなります。?しかもMOVE命令を使用していたときと同じ結果をフリー・フォーマットで得ようとするなら、代わりを慎重に選ばなければなりません。 データの横パスを通す 移動元の変数と移動先の変数がともに文字型データである場合、EVALはMOVEL(P)命令(左に移動して空白を詰める)と等価になり、EVALRはMOVE(P)と等価になります。次の固定フォーマットのコードは C Movel(p) Source Result C Move(p) Source2 Result2 次のように変換されます。 Result = Source; // MOVEL(P) Evalr Result2 = Source2; // MOVE(P) 移動先のデータに空白を詰めたくない場合は、対応するフィールドの長さを考慮しなければなりません。移動先の変数の長さが移動元の変数の長さよりも「短い」場合は、式の右辺で%SUBST(Substring)関数を使用して移動元フィールドの適切な部分を移動先フィールドに割り当てます。 Result = %Subst(Source :%Size(Source) - %Size(Result) + 1 :%Size(Result)); この汚いコードは、この場合MOVE命令を正確に変換しますが、移動元フィールドと移動先フィールドの実際のバイト長を置き換えることで簡素化するのが普通です。たとえば、移動元が9バイトで移動先が5バイトの場合は、次のようなフリー・フォーマット版でこれを使用します。 Result = %Subst(Source:5:5); 移動先の変数の長さが移動元の変数の長さよりも「長い」場合は、式の左辺で%SUBST関数を使用して移動元フィールドの一部だけを移動先フィールドに割り当てます。 %Subst(Result :%Size(Result) - %Size(Source) + 1) = Source; 移動元が5バイトで移動先が9バイトの場合、次のようにコードを簡素化できます。 %Subst(Result:5) = Source; 空白を詰めないMOVEL命令では位置を調整する必要がありますが、この場合も移動先の変数が移動元の変数よりも長ければ%SUBST関数を使用できます。 %Subst(Result:1:%Size(Source)) = Source; 移動元が5バイトで移動先が9バイトの場合、次のようにコードを簡素化できます。 %Subst(Result:1:5) = Source; もちろん、移動先の変数が移動元の変数よりも短い場合はMOVEL変換に簡単な代入式を使用することで目的を達成できます。 Result = Source; データのインターセプト MOVE命令の使用は「同じタイプの」移動に限られているわけではありません。MOVE命令を使用して数値フィールドを文字列に転送したり、また数値フィールドに戻したりすることができます。またMOVE命令はネイティブの日時データ・タイプも扱うことができ、日付データとの転送がほとんど制限なく、また特別なコーディングを必要とすることなく簡単に行えます。これをフリー・フォーマットで行うには、データ変換関数をいくつか使う必要があります。 %EDITC(コードの編集)関数は数値データを入力すると文字列値を返します。この関数はRPGでおなじみのコード編集を数値に適用するものです。たとえば、 %Editc(01234.56:'2') は編集コード「2」を数値に適用し「1,234.56」という文字列値を(この場合先頭に空白をつけて)プログラムに対して返します。IBMでは正当な各編集コードについてILE RPG Reference(SC09-2508)に記載しています。 %EDITC関数をフリー・フォーマットのコードで使用して、数値を文字列値に割り当てることができます。固定フォーマットのMOVE命令をフリー・フォーマットの仕様に置き換えるには、編集コードXを使用して単純な移動元の数値を文字列値として返します。次に、文字列から文字列への割り当てをする際に使用するテクニックと同じテクニックを使用して、返された文字列値を移動先に割り当てます。移動元フィールドと移動先フィールドの長さが等しければ、次の簡単な割り当てで完了です。 Result = %Editc(Source:'X'); 移動元と移動先のサイズが異なる場合は、%EDITC関数の他に%SUBST関数を使用します。たとえば、移動元のSourceフィールドが7桁の数字で移動先のResultフィールドが9バイト文字の場合、MOVE命令の代わりに次のフリー・フォーマット割り当て式を使用します。 %Subst(Result:3) = %Editc(Source:'X'); 編集コードXはパック数値にもゾーン(符号付き)数値にも、正の値にも負の値にも同様に動作します。 数値フィールドから文字列フィールドに情報を移動させる際に、%CHAR(文字列データへ変換)関数を使用したくなるかもしれませんが、その場合MOVE命令を使用した場合と異なる結果になることがあります。たとえば、%CHAR関数は数値の先頭にあるゼロを取り除きます。また%CHAR関数は、負の数値に対しては符号を数値の16進表現に組み入れるのではなく、符号文字を別につけて返します。図3の例は、移動元(9桁)の値が001234567である場合に、2つの関数の結果の違いを示しています。 しかし%CHAR関数は日時変数の値を文字列変数に移動する際には問題なく動作します。%CHAR関数に対して日時の値を変換するように指定する際は、結果として返される文字列のフォーマットを2番目のパラメータに指定します。この例では、移動元データ変数の値がDecember 31, 2005である場合、計算後の移動先データ変数の値は「2005-12-31」という文字列値になります。 Result = %Char(Source:*ISO); %CHAR関数は分離文字を含んだ値を返すという点に注意してください。分離文字を取り除きたい場合は、結果の値の後ろに0をつけます。 Result = %Char(Source:*ISO0); 前述の例の代わりにこのコードを使用すると、移動先の変数の値は「20051231」という文字列値になります。 数値への攻撃 MOVE命令の機能をフリー・フォーマットで再現して文字列データを数値データに移動するのは厄介です。%DEC(パック10進数に変換)、%INT(整数に変換)、%UNS(符号なし整数に変換)は、式(または変数)を数値の戻り値に変換します。ただしV5R2以前では上記関数に数値式または数値変数を与える必要がありました。 V5R2では上記の関数は文字式や文字変数を以下の規則にしたがって変換します。 式は評価すると、(先頭または後ろに)符号付きまたは符号なしの正当な数値にならなければならない。 式中に空白があっても構わないが、浮動小数点のデータは許されない。 関数に不正な数値データが渡された場合、プログラムは%STATUS=105というエラーを生成する。 %DEC関数にはさらに2つのパラメータを指定する必要があり、この2つのパラメータで移動先の桁数と小数点の位置を指定します。 移動元が「23456.78-」9バイトの文字列で移動先がパック10進数(11桁、小数点以下4桁)の場合、移動先のデータは計算後に負の0023456.7800という値になります。 Result = %Dec(Source:11:4); 移動先データを四捨五入(丸め)するには、上記の変換関数の%DECH、%INTH、%UNSHTなどの変形を使用します。 数値変換関数は日付式をサポートしていませんので、数値変換関数を使用して日付を直接数値に変換することはできません。しかしV5R2では次の例のように%CHAR関数を数値変換関数内に組み込むことができます。 Result = %Int(%Char(Source:*ISO0)); 移動元がDecember 31, 2005というネイティブの日付である場合(データ・タイプD)、計算後の移動先データは20051231という整数値になります。目的とするフォーマットの後に0をつけて分離文字を取り除くのを忘れないでください。 2分間コンバージョン 関数%DATE(日付へ変換)、%TIME(時刻へ変換)、%TIMESTAMP(タイムスタンプへ変換)は、文字列または数値の式または変数に対してネイティブの日付/時刻/タイムスタンプを返します。変換対象の値の他に文字データまたは数値データの日付フォーマットを渡してやらなければなりません。文字データについてはさらに日付フォーマットに分離記号も考慮する必要があります。 この例では、移動元の数値フィールドに051231という値が入っていると、移動先のデータはDecember 31, 2005となります。 Result = %Date(Source:*YMD); 関数%DATE、%TIME、%TIMESTAMPは、常に*ISOフォーマットで値を返しますが、この例では移動先データは正当な日付フォーマットであれば何でも構いません。上の式はネイティブの日付をあるフォーマットから別のフォーマットに問題なく割り当てます。しかし年が2桁のフォーマットでは、日付が19402039のウィンドウの範囲外にはならないという点に注意してください。 複雑な戦略 以上ご説明した通り、既存のMOVE命令をフリー・フォーマットに変換する際には、考慮すべき点が多数あります。しかもMOVEAについてはここでは述べませんでした。必要となるツールについて、移動元フィールドと移動先フィールドのデータ・タイプごとに図4にまとめてあります。 MOVEからフリー・フォーマットに切り替える際は、各変換について十分にテストしてください。すべての場合に対して代替前後でまったく同じように動作しない場合もあります。数値式については、MOVEでは起こらなかったオーバー・フローの問題が発生する可能性があります。 行き詰まった場合は、フリー・フォーマットと固定フォーマットを組み合わせて使えることを思い出してください。そこでは臨時に固定フォーマットのMOVE命令をそのまま使って、プログラムの他の部分ではフリー・フォーマットの利点を生かすこともできます。残念なことに、/END-FREEコンパイラ・ディレクティブでフリー・フォーマットを一旦停止して、MOVEの後で再び/FREEを使用してフリー・フォーマットに戻す必要があります。この例を図5に示します。しかし、許容範囲とはいえ移動先のデータはエレガントとは言えないので、この方法はなるべく避けた方がよいでしょう。 コンパイラを変えるだけで、固定フォーマットとフリー・フォーマットを組み合わせた場合でも美しいコードにすることができます。フリー・フォーマットのコードでは6列目と7列目は空白でなければならないため、IBMは/FREEや/END-FREEディレクティブを使用しなくても、フリー・フォーマットと固定フォーマットの違いを区別できる機能をコンパイラにつけました。そのため6、7列目にフリー・フォーマット仕様のコードを入れようとするとコンパイラがエラーを生成するので、6列目をCとして固定フォーマットの行と認識させればよいのではないでしょうか。 いずれは可能になるでしょうが、今のところRPGが対応していないものもあるので、妥協しなければならないでしょう。 Bryan Meyers氏はこのホームページの提携月刊誌「iSeries NEWS」のテクニカル・エディタで、RPG IV Jump StartやProgramming in RPG IVなどの著作物もあります。同氏はiSeriesに関する教室をオンサイトや「iSeries NEWS」のホームページiSeries Networkのe-Learningプログラムで開いています。