数多くのBIFの中から - %SCANと%CHECK
数多くの組み込み関数(BIF)の使用法や動作に関して混乱が生じているRPGプログラマーは多いようです。特に、%XLATE、%REPLACE、%SCAN、%CHECKといったBIFは、多くの混乱の元になっているように思われます。この記事では、%CHECKと%SCANに焦点を当てています。最近、%SCANのコンパニオンBIFである%SCANRが導入され、%SCAN自体に対しても関連する機能強化が行われたことから、この記事を再度取り上げることとしました。機能強化の詳細については後述します。
%SCAN BIFは、文字列処理の改善のために、%EDITCおよび%EDITWとともにV3R7で導入されました。一方、%CHECKは比較的新顔であり、/Freeフォーム コーディングで従来のCHECK命令コードの機能をサポートするためにV5R1で導入されました。あらゆるBIFと同様に、従来の命令コード バージョンと比較して大きな利点の1つは、BIFは条件式で直接使用できる、つまり一時作業変数を作成して、それをテストする必要がない点です。
以下に、それら2つのBIFの構文定義を示します。
%CHECK( comparator : base {: startpos})
%SCAN( search argument : source {: startpos} {: length}})
一見して、パラメーターが非常によく似ているため、特に私もそうですが、頭の中で「コンパレーター」などの用語を「検索文字列」に変換していまいがちです。おそらく、これが混乱の一因となっているのかもしれません。けれども、これらの2つのBIFの間には、非常に大きな違いが2つあります。
この記事で使用されているコードは、ここからダウンロードできます。
1つ目は、%CHECKでは、比較文字列が個々の文字のリストとして扱われるのに対して、%SCANでは単一の文字列として比較文字列を処理する点です。2つ目は、%SCANが検索引数内での文字のオカレンスを見つけようとするのに対して、%CHECKは比較文字列の中に存在しない任意の文字を識別しようとする点です。
%CHECK
まずは、%CHECKの動作を見てみましょう。このBIFは、入力文字列の文字を1文字ずつ、比較文字列のリストと比較します。比較セットに存在しない文字が見つかった場合、入力文字列の中でのその文字の位置を返します。言い換えれば、比較文字列は、入力フィールドで有効な文字のリストと考えることができます。
たとえば、電話番号フィールド内の個々の文字を検証する比較文字列は、次のようなものになるかもしれません。
'0123456789-( )'
%CHECKは関数であるため、次のようにして、検証テストで直接使用できます。
If %Check( ' 0123456789-()': character) <> 0;
// Bad character in phone #
EndIf;
これは、文字列内の「不正な」文字の実際の位置が重要でない場合に有用です。実際のコード例では、有効な文字の文字列を定数としてコーディングしたのですが、ここでは説明を容易にするためにリテラルをコーディングしていることに注意してください。
%CHECKのコンパニオンBIFである%CHECKRは、%CHECKと同じように動作しますが、チェックを行う方向は逆になります。つまり、%CHECKRは入力フィールドの右端からスキャンを開始します。以前は、スペース以外の文字が入れられたフィールドを処理するときに、この機能は非常に有用でした。けれども、BIFの%TRIMxファミリーがアップデートされ、指定されたどのような文字もトリミングできるようになったため、%CHECKRの有用性は以前に比べて低くなっています。
%SCAN
多くのRPGプログラマーと同じように私も、%SCANの1つ目のパラメーターが、検索場所となる文字列だったか、検索対象の文字列だったかを思い出せなくて困ることがよくありました。私の場合は、前RPGコンパイラー チームのメンバー、Hans Boldt氏が示してくれた例のおかげでこの問題を解決することができました。彼は、(「look for a needle in a haystack(干し草の山の中で針を捜す)」という成句をもとに)次の構文を示してくれました。
%SCAN( needle : haystack ... )
それ以降は、正しい順番を思い出せないということはなくなりました。
前述したように、%SCANは、特定の文字の文字列を探します。その文字列は、単一の文字である場合もあれば、文字のグループである場合もあります。%SCANは、入力フィールド(「haystack(干し草の山)」と言ったほうがよいでしょうか)の中で、指定された文字列を見つけた場合、最初の一致する文字の位置を返します。一致が見つからなかった場合は、0を返します。
%SCANを使用する際は、スペースについて考慮することが必要な場合があることを忘れないようにしてください。たとえば、「is」という単語を検索する場合には、先頭および後続スペースの両方について指定が必要になるかもしれません。そうしないと、「This」や「issue」などの単語も一致となってしまい、これでは望んでいた結果にならないかもしれません。
記事の冒頭で記したように、7.3のリリースで、%SCAN BIFの仲間として%SCANRが加わりました。ご推察の通り、%SCANRは文字列の最後の文字の位置からスキャンを開始します。この機能の便利な用途としては、ファイル名とパス名の残りの部分との区切りとなる「/」を後方からスキャンすることによって、完全IFSパス名からファイル名を抽出できるということがあります。また、IBMは、%SCANRを追加しただけでなく、%SCANxの両方のバージョンに、検索の長さを制限するのに使用できる4つ目のパラメーターも追加しています。RPGマニュアル(https://www.ibm.com/support/knowledgecenter/ja/ssw_ibm_i_73/rzasd/bbscanr.htm)に、実践的なBIFの優れた例がいくつか掲載されています。重複になるためここでは示しませんので、そちらを参照してください。
%SCANxに関する最後の重要な点です。スキャン コードをできるだけ汎用的にしたい場合は、比較文字列に可変長フィールド(すなわち、VarCharとして定義されているもの、またはキーワードVARYINGを持つもの)を使用することをお勧めします。固定長の文字列を使用した場合、比較で問題を引き起こしがちな後続スペースが含まれている場合があります。%TRIMRで比較文字列を囲めば問題を回避できますが、可変長フィールドを使用したほうがより効率的です。
以下に、この点に関する短い例を示します。
Dcl-s fixedLengthTarget Char(10);
Dcl-s varyingLengthTarget VarChar(10);
Dcl-s source Char(30);
Dcl-s position Int(5);
source = 'this is the test input string';
Dsply ('Input string: ' + source );
// Scan for fixed length target
fixedLengthTarget = 'is';
position = %Scan( fixedLengthTarget: source);
// position value is zero due to trailing spaces in target
Dsply ('Fixed target ' + fixedLengthTarget
+ ' at position ' + %Char(position));
position = %Scan( %TrimR(fixedLengthTarget): source);
// position value is 3 as trailing spaces stripped off
Dsply ('Trimmed target ' + fixedLengthTarget
+ ' at posiiton ' + %Char(position));
// Scan for varying length target
varyingLengthTarget = 'is'; // Length of target set to 2 (length of value)
position = %Scan( varyingLengthTarget: source);
// position value is 3
Dsply ('Varying target '
+ varyingLengthTarget + ' at position ' + %Char(position));
*InLr = *On;
最後に、ここまで言及してこなかったパラメーターについて触れておきます。そのパラメーターとは開始位置のことで、これらの両方のBIFに共通で、同じように機能します。このパラメーターは、比較が開始される位置を指定します。デフォルトでは、開始位置は1です。ただし、%CHECKRは除きます。%CHECKRではもちろん、開始位置は入力フィールドの最後の文字になります。このパラメーターは、ループでBIFを呼び出すときに最もよく使用されます。すでに処理済みの検索文字列のインスタンスをスキップする必要があるからです。私の場合、%CHECKでこのパラメーターを使用した記憶はありませんが、%SCANではよく使用しています。
この記事が、それらのユーティリティについての理解を深め、よくある誤解を解消するのに役立ってくれるとすれば幸いです。