RPGでの文字列操作
学生時代のことを覚えていますか。午後や夕方にコンピュータの実験室で過ごした長い時間、学生会館で踊った金曜日の夜、図書館で本以外のあらゆるものを片端から借り出して過ごした時間。私にもそうした思い出がありますが、RPGの文字列操作だけは勉強した覚えがありません。特殊なタスク用に合わせたコンピュータ言語のとある講義を受講しました。文字列操作に関する講義の時間ではPascalに重点が置かれ、RPGについては全く触れられませんでした。今日ではPascalの跡を継いで支配的になっているのがJavaですが、RPGに新しい組み込み関数 (BIF: Built-In Functions) がいくつか加わったことで今や文字列操作に関してはRPGの方が優れた言語となっています。
今日の多くのプログラマにとってはJavaを勉強するよりは既存のRPGのスキルに若干の知識を加える方が容易でしょう。RPGの文字列操作はそれほど難しくありません。必要なのは少しばかりの知識とかなりの忍耐、そして根気です。XMLが最初に登場したときに、私はXMLの構文解析機能をRPGで記述していて、半分ほど出来上がったときにIBMがXML構文解析用の独自のAPIをリリースしたのです。RPGでXML構文解析機能を作るというプロジェクトは断念しましたが、RPG文字列操作に関して重要な教訓を学ぶことができたのです。
BIFの方が優れている
今世紀に入る前、RPGの文字列操作は文字列を配列に移したりMOVE、SCAN、CHECK、CHECKR、CAT、XLATEなどのオペコードを使用するのが一般的でした。今や/FREEをつけることでこうしたオペコードや数多くの新しい文字列操作関数がほんの一握りのBIFや演算子で行うことができます。文字列操作文は複雑で長くなりやすいので/FREEを使用することを強くお勧めします。
%SCANはその名前から想像がつく通り、文字列をスキャンして副文字列が存在するかどうかを調べ、存在すればその位置を返します。%SCANのBIFはたとえば次のようになります。
Pos = %scan(string1 : string2 {: start});
1番目と2番目にある文字列はフィールドでもリテラルでも構いません。startはオプションで、スキャンを開始する位置を指定します。%SCANが副文字列を検出した場合、その開始位置が返されPosにその値が格納されます。
代入先変数 (この例ではPos) が配列の場合であっても、%SCANは副文字列が見つかったすべての位置を返すことはできません。%SCANはSCANオペコードとは異なり、配列を処理することはできません。
また%SCANは条件文中で使用することもできます。次に示す例では、リテラル「A」がmystringの3文字目以降に存在する場合に条件がtrueとなります。
If %SCAN('A' : mystring :3) > 0;
%CHECK(comparator : base {:start}) は文字列baseの一番左の文字 (もしくは{:start}で指定された位置) から始めてcomparator中にあるすべての文字を検索します。comparator中にない文字が文字列中にあった場合は、そのない文字が最初に見つかった位置を返します。TESTN (テスト数値) の代わりに%CHECKを使用することができます。
%CHECK('1234567890':arSSrcPyId)
%CHECKR(comparator : base {:start})はCHECKと同様に動作しますが、右端の文字から左に向ってチェックします。このBIFは空白文字でない最後の文字を探すことで文字列の長さを調べたいときに便利です。
Length = %CHECKR(' ':mystring);
%CHARは数字、日付などの非文字フィールド・タイプを文字タイプに変換します。数値の先頭のゼロは無視します。
%EDITCは編集コードを使用して数値を文字に変換します。この際、編集コードはプリンタやディスプレイ出力で使用される場合と同様に動作します。
%EDITWは編集ワードを使用して数値を文字に変換します。
%SIZEはフィールドが占有しているバイト数を返します。フィールドの長さが固定の場合、そのフィールドに非空白文字がどれだけ格納されていても、返された値は常にフィールドのサイズとなります。フィールドの長さが可変の場合、返された値は常に最大長+2となります。%SIZEを使用すると便利な場合があることは確かですが、文字列処理においてはあまり便利ではないということが苦労の末にわかりました。ですから%SIZEを使用して混乱しないように注意してください。文字列のデータ長を調べるには%CHECKRや%LENの方が便利なオプションです。たとえば、MYSTRINGという10バイトの長さのフィールドにAPPLEという文字列が入っている場合に%CHECKR(' ':MYSTRING) を使用すると5という値が返ってきます。
%LENは文字列の長さを返します。固定長のフィールドに対しては、常にフィールドのサイズが長さとして返されます。可変長のフィールドについては、返されてくる長さは後ろに付いている空白文字を含んだデータの長さとなります。ただし%LENを%TRIMや%TRIMRや%TRIMLと組み合わせて使用すると、先頭や後ろについている空白文字を除いた文字列データの長さが返されてきます。
%XLATE(from:to:string:{start}) はフィールド中のデータをある文字集合から別の文字集合に変換します。%XLATEの一般的な使用方法は、大文字と小文字の間の変換です。例として、upというフィールドまたは定数にアルファベットの大文字がすべて含まれていて、lowというフィールドまたは定数にアルファベットの小文字がすべて含まれているとします。下に示すコードを実行するとLastNameというフィールドの2文字目からを小文字に変換し、姓の最初の文字だけが大文字のまま残ります。残念ながら、この簡単なテクニックではVan PeltやMacDonaldといった名前を正しく処理することはできません。
ResultStr = %XLATE(up : low : LastName : 2);
「from」や「to」の文字列も次の例のようにリテラルで指定できます。次の例では、文字列中の空白文字をすべてゼロに変換します。
Acct# = %XLATE(' ':'0':Acct#);
%SUBST(string:start{:length}) は文字列中の副文字列の位置を返します。lengthを指定しない場合、BIFはstartの位置から始めて副文字列全体を返します。また%SUBSTは条件文中でも使用できます。
When %subst(mystring:3:4) = 'XML'
; %TRIM(string) は文字列の始めと終わりの空白文字を取り除き、その結果の文字列を返します。%TRIMR(string) を使用して末尾の空白文字だけを取り除くか、あるいは%TRIML(string) を使用して先頭の空白文字を取り除いてください。
%REPLACE(replacement string : source string {:start position {: source length to replace}}) は、指定されたstart位置から指定された文字数だけ置換文字列を元の文字列に挿入した結果得られる文字列を返します。開始位置の指定を省略したときは、1文字目を指定したものと仮定します。元の文字列の長さの指定を省略したときは、置換文字列の長さが使用されます。MaidというフィールドにMary Smithという値が入っているとします。次のコードでSmithをSilvaで置換します。
MarriedName = %replace('Silva':Maid:8:7);
%STRはnull文字で終了する文字列を取得したり格納したりするのに使用します。このBIFは、null文字で終了する文字列の便利さを良く知っているCおよびC++の愛好家にとっては魅力のあるものです。null文字で終了する文字列に対して%STRを使用すると、文字列からnull文字を取り除いた文字列を返します。%STRを使用して文字列を格納すると、フィールドの最後にnull文字を付け加えます。 RPGのBIFの全リストについてはpublib.boulder.ibm.com/iseries/v5r1/ic2924/books/x091315219.htmを参照してください。
その他の便利なツール
BIFではありませんがEVALRは便利なツールで、操作の結果を受け継いで入力先のフィールドに合わせて右寄せで文字列を格納してくれます。BIFではありませんが便利なツールとして「+」があります。これは二つの文字列を結合するものです。ただし、間に空白文字を挟まずに結合する場合には%TRIMと一緒に使用しなければなりません。たとえば、FirstNameが10バイトのフィールドでTerryという値が入っており、LastNameという10バイトのフィールドにはSilvaという値が入っている場合、FirstName+LastNameとすると「Terry Silva」という結果になります。このように「Terry Silva」という結果を得るためのコードとしては以下のようなものをお勧めします。
%trim(FirstName) + ' ' + %Trim(lastName)
可変長フィールドと文字列
現代のRPGでは、変数は必ずしも固定長のサイズでなくても構いません。データ定義仕様中でVARYINGというキーワードを使用すると、変数の長さがその変数中に格納されている値に応じて決められるようになります。変数には意味のある空白文字を含めることもできます。%LEN BIFは、先頭や末尾の空白文字を含んだデータの長さを正しく教えてくれます。可変長の文字フィールドを結合するときは、必ずしも%TRIM BIFを使用しなければならないわけではありません。%LENがフィールドをスキャンする際、変数中に意図的に配置されているデータだけをスキャンします。たとえば、8バイトの固定長のフィールドがある場合、「A dog」という文字列中で空白文字をスキャンすると空白文字の位置として2、6、7、8が返ってきます。一方可変長フィールドに同じデータが格納されている場合に空白文字をスキャンするとその位置として2だけが返ってきます。
BIFのパワー
BIFの本当のパワーは複数のBIFを組み合わせて使用したときに発揮されます。前述のMaidの例では%REPLACE BIFを使用しましたが、最後の2つの名前は長さも開始位置も異なっています。単に%REPLACEを使用するだけではなく、次の文のように%REPLACE、%CHECKR、%SCANを組み合わせて使用する必要があります。
MarriedName = %replace(Surname: Maid :
%scan(Maid: ' ') + 1:%checkr(' ':Surname));
%SCANはMaid中に格納されている文字列で最初の空白文字の位置を探し、それに1を足して開始位置とします。%CHECKRは新しい姓の長さをlengthパラメータとして返します。この方法は旧姓にミドルネームが含まれている場合はうまく動作しません。その場合は下記 のようにする必要があります。
MarriedName = %replace(Surname: Maid :
%checkr(' ':Maid:%checkr(' ':Maid) - 1)
+ 1:%checkr(' ':Surname));LastNamePos =M
%checkr(' ':Maid) - 1;
文字列の結合は通常%TRIM BIFと組み合わせて使用して行います。たとえば、市と州と郵便番号を結合して住所を印刷できるようにするには次のようにBIFを使います。
Address = %trim(city) + ',' + %trim(state)
+ ' ' + %editc(zip:'X');
ここで、%CHARではなく%EDITC BIFを使用した点に注意してください。%CHARでは先頭のゼロが取り除かれてしまい、それでは米国の郵便番号として意味をなさないからです。
1つの文字列の中に同じ副文字列が数回使用されている場合があるかもしれません。つい最近私もXML転送ファイルで同じ経験をしました。ベンダーが提供しているソフトウェアではXML文書中にアンパーサンド記号 (&)、不等号 (<、>) を使用することができません。私は各レコードに対してこうした文字が使われていないかどうかをスキャンし、見つけたときは置き換えました (図1)。
言語の壁を越える
RPGでできることには限界がありますが、文字列BIFを使用すると他のプログラミング言語で今まで予約されていたたくさんのことができます。RPG ILE環境ではRPGプログラムからJavaやCのモジュールを利用することができます。BIFのすばらしい世界にようこそ。楽しい週末となりますように
【 あわせて読みたい記事 】