一歩進んだ Rexx

Classic REXX (REstructured eXtended eXecutor)
File #06

FileSpec のよーなもの -- '99.7.28

インターフェースは違うけれど, FileSpec()みたいなもの。

何が違うかっつーと, 書式を与える所が違う。 組み込み関数でない分 面倒だけれど, でも FileSpec()のよーな変な動作は 今のところないし, 中々に使いやすいと σ(^^)思うよ。

使い方は・・

  fname = 'd:\Home\Rexx\SilkWeb.cmd'
  say FmtFName(fname, '"%x" のパス名は "%p"で, ドライブは "%d"です。')

で, これがそのプログラムなのさっ。
もう三世代目くらいなんだけど(謎), 中々気に入った形にならないだよ (T-T)

FmtFName: procedure   /* % = %d%p%f ; %f = %n.%e */
parse arg base =1 x, fmt
p = pos(':', base); d = left(base, p); base = substr(base, p +1);
options 'EXMODE'; cnv = translate(base, '/', '\'); options 'NOEXMODE';
lp = lastpos('/', cnv); p = left(base, lp); f = substr(base, lp +1);
lp = lastpos('.', f); parse var f n =(lp)'.' e; if lp == 1 then n = '';
res = ''
tmp1 = 1
do forever
   tmp2 = pos('%', fmt, tmp1)
   if tmp2 == 0 then leave
   val = substr(fmt, tmp2 +1, 1)
   if symbol(val) == 'VAR' then val = value(val)
   else /* 'LIT', 'BAD' */ if val \== '%' then val = x''val
   res = res''substr(fmt, tmp1, tmp2 -tmp1)val
   tmp1 = tmp2 +2
end
res = res''substr(fmt, tmp1)
return res

先頭部分のコメントは, その書式の指定方法を表してんだべっ。EPM と同じさぁ。


verifyを使った, INIファイルの表示 -- '99.7.4

今回は, verify を効果的に(?)使ったプログラムを紹介してみよう。
verify とゆー関数は, 文字集合を指定して, ソレが現れる最初の場所, あるいはソレが現れない最初の場所を探し出してくる。

試しにプログラムを作ってみよう。あるデータを, 文字列の場合はその文字列を, バイナリの場合 16進表示で ソレらを表示したい。 扱うサンプルは os2.ini と os2sys.ini にしてみよう。がっはっは〜。

元ネタは Rexxのオンライン解説書にある iniファイルのダンププログラム。これをちょビット改造してみる。

IniDump: procedure expose ChgBin ChgTxt
parse arg Inif, MLen, fDmp +1
if \datatype(fDmp, 'B') then fDmp = 0

say center(' Ini file dump V1.0 ', 26, '=') '('Inif')'
say
Bin = xrange('fd'x, '1f'x)

Rc = SysIni(Inif, 'All:', 'apps.')
if Rc \= 'ERROR:' then do i = 1 to apps.0
   Pre = '['apps.i
   Rc = SysIni(Inif, apps.i, 'All:', 'keys.')
   if keys.0 > 1 then do; say Pre; Pre = '   '; end;
   if Rc \= 'ERROR:' then do k = 1 to keys.0
      Val = SysIni(Inif, apps.i, keys.k)

      if length(Val) < 1024 then do
         Txt = '='EscEncStr(Val, ChgBin, ChgTxt)
         if verify(Txt, Bin, 'Match') == 0 then nop
         else if length(Val) <= MLen then Txt = '=0x'c2x(Val)
         else Txt = ''
      end; else Txt = '';

      ak = Pre EscEncStr(keys.k, ChgBin, ChgTxt)
      say left(ak, max(24, length(ak))) 'Val('length(Val)')'Txt
      if fDmp & Txt == '' then Dump(val)
   end; else say 'Keys Error';
   say
end; else say 'Apps Error';
return
どだ。(↑)では, 次のことをアレしてる。すなわち,
  1. バイナリ(0x00〜0x1f, 0xfd〜0xff) が含まれてないなら nop,
  2. そうでなく(含まれていて) 長さがある程度短いなら 16進数で,
  3. それでもないのなら内容は出さない (けど Dumpで出すかもしれない)
って感じだね。

EscEncStr ってぇのは, 0x0a を '\n' とかに変換するもの。 じつは, コレ, 白龍シリーズでも使っていたりする。

EscEncStr: procedure
parse arg val, bin, str
do p = verify(val, bin, 'M') by 0 while p > 0
   txt = word(str, pos(substr(val, p, 1), bin))
   val = left(val, p -1)txt''substr(val, p +1)
   p = verify(val, bin, 'M', p +length(txt))
end
return val

で, 次は先頭部分。

/* Ini File Dump */
options 'ETmode'
True = 1; False = 0;

call RxFuncAdd 'SysIni', 'rexxutil', 'SysIni'

ChgBin = '01 09 0d 0a 1b 00'x
ChgTxt = '\a \t \r \n \e ^@'   /* 00= ^@ あるいは \0でもよいかも */

parse arg fname .
if fname = '' then fname = 'Both'
call IniDump fname, 32, False
exit

この方法を利用すれば, 今まで見えなかったものが見えてくるかも。
プログラムとゆーのは魔法のようなものかもしれない。って何訳分かんない事ゆってんだか (^^;


もっと CSV形式分解 -- '99.5.25

CSV形式の項目の切り出し (新版)。
だいたいの所は, 前回のを参考にしてもらうとゆー事でぇ。 大きな違いは, 項目中の「"」は「""」として表現するとゆうルール。 だからもちろん, この関数の中では, 逆の置き換えをする って事だね。

CSVparse: parse arg CSVparse; return CSVparseCore();
CSVparseCore: procedure expose (CSVparse)
val = value(CSVparse)
if left(strip(val, 'L'), 1) == '"' then
   do p = pos('"', val) by 0
      p = pos('"', val, p +1)
      if p == 0 then leave
      if substr(val, p +1, 1) == '"' then do; p = p +1; iterate; end;
      parse var val '"'+0 res =(p)+1 n',' val =(p)+1 str
      if n \= '' then val = str
      res = ChangeStr('""', substr(res, 2), '"')
      leave
   end
if symbol('res') == 'LIT' then parse var val res',' val
call value CSVparse, val
return res

最初に見つかった文字が「"」なら, 閉じの「"」を見つけて, 中の文字列に先の変換をかける。 関数の中の「do〜end」がそーゆー所。 閉じ「"」を見つける時には, それが「""」のよーに並んでいるのなら, その次の位置から再度「"」を探している。

さて, parse の部分の解説。 ここの時点では, 閉じ「"」は, 見つかってるんで, ホントは, parse var val '"' res =(p) で取り出せる。 項目は resに入る訳だねっ。(p は終わりの「"」の位置を指している)。
なんで, 複雑になってるかっつーと, 直後が「,」なら後ろを valにセット, そうでないなら一旦 strに入れといて, 後で valにセット っつー訳。
「+1」は, 「"」の部分を一つ進めるため。「+0」は, この項目が空項目であった時のためなのさっ。

で, 内容を簡単に (←ホントに簡単だな) 説明した所で, ちょいとサンプル。

/* CSV形式なソレをアレ */
options 'ETmode'

parse arg fname
if fname = '' then exit
txt = charin(fname,, chars(fname))
do i = 1 while length(txt) > 0
   say i':'CSVparse('txt')']'
   if left(txt, 2) == '0d0a'x then do
      say '9'x'改行'
      txt = substr(txt, 3)
   end
end
exit

ChangeStr:procedure   /* from ORexx */
   parse arg needle,haystack,newneedle
   result=''
   tempx=1
   do forever
      tempy=pos(needle,haystack,tempx)
      if tempy=0 then leave
      result=result||substr(haystack,tempx,tempy-tempx)||newneedle
      tempx=tempy+length(needle)
   end
   result=result||substr(haystack,tempx)
return result

引数としてファイル名を与えると, 一項目ずつ取り出す。ってやつ。

あや, 必須の関数も入ってるねコレ (^^;;ChangeStr()


ちょっと Parse -- '99.5.17

CSV形式を分割するのを例に(笑), parse の簡単な使い方を取り上げよう。

まず, 単純な CSV形式の場合を見てみると・・

  text = '"山田","太郎","100"'
  parse var text '"' vvv '",' text
とか思い付く。これを parse の部分を(項目が無くなるまで)ループさせると出来上がり。 もちろん, 区切る部分はこうでもよい。→ parse var text vvv '","' text
textの最後の項目(この場合 "100"の項目) には「",」とか「","」とかは付いていないんで, 正式には, 元データの最後の「"」を削っておくか, ソレを余分につけるかをするはずであろう。
  text = substr(strip(text), 2)',"'   /* 前処理 */

  parse var text vvv '","' text       /* ループ */

たけっちょ, これで終わりではない。世の中そんなに甘くない。

  text = '"山田"□,□"太郎"□,□"100"' /* □はスペースのことだと思いねぇ */
  text = substr(strip(text), 2)',"'    /* 前処理 (上のと同じ) */

  parse var text vvv '"' ',' '"' text  /* ループ (ちょっち変更) */
これ (↑)は, 余分な所に空白がある場合の対処。もちろん空白がない場合もオッケー。 でもこれ, スペースに限定できない所がつらい (T-T)
つまり, text = '"山田" 余白は, 木を切る "太郎" ヘイヘイホー, "100"'
でも同じ結果になる。まぁ, そーゆー仕様とゆー事にしてしまえばよいけど (^^)

さて, 問題が幾つか残っている。もー, これでもか これでもかっつーくらい (T-T)

こーなってくると, 単純に parseで分解するには無理がある。
そして, さらには文字列中に改行が含まれる場合 と ゆうのがある。 実はコレ, Palm Desktop を試しに動かしてみた時に気が付いた(笑)

CSV形式を分解には, 次のバージョンが用意してある。にょほほほほ。 なぜココで公開しないのかとゆーと, 実戦投入後, 問題がなかったらココで公開と, ゆーことで・・


CSV形式を分解 -- '99.4.30

CSV形式 ・・・ カンマで分離する事ができる値? の形式?
よく目にするようなものは, こんなん。

"山田","太郎","100"

カンマで区切るっつっといて, 「"」とかもよく使われる。 多分, 文字列の中にカンマが入っても大丈夫なようにだろー, 知んないけどね (^^;

んで今回, 一項目ずつ, ソレを取り出す関数。 使い方は, 例えば,

  str = '"山田","太郎","100"'
  do 3; say CSVparse('str'); end;
CSVparse: parse arg CSVparse; return CSVparseCore();
CSVparseCore: procedure expose (CSVparse)
val = value(CSVparse)
if left(strip(val, 'L'), 1) == '"' then
   do p = pos('"', val) by 0
      p = pos('"', val, p +1)
      if p == 0 then leave
      do i = p -1 to 1 by -1 while substr(val, i, 1) == '\'; end;
      if (p -i) //2 == 0 then iterate
      parse var val '"'+0 res =(p)',' val
      res = substr(res, 2)
      leave
   end
if symbol('res') == 'LIT' then parse var val res',' val
call value CSVparse, val
return res

この形式でやりとりできるソフトでも, 細かい点では色々な違いがある。 と, ゆーのも文字列に中に「"」が含まれた場合, 今度はコレが問題になる (CSV形式の生成の時の話だよ)。

それを解決するには, 「"」を 2個並べる方法がある。二倍にするとゆった方が正確かも (^^
他の手段としては, 「\」付きの「"」は文字列のソレだとゆーことで回避するっつーのもある。 (↑)の関数がソレ。つまり, その方法で生成された文字列を復元できるって事だ。
ちなみに, ループしてんのは, 『'\\'かもしれないし調べてみよ』とゆー事なのだ。

でも, さらにソレが問題になることもある (T-T)
「,」も「"」も, 2バイトコードの一部になることはないのだけれど, 「\」は, その二バイト目になることが有り得る。 だから, この関数を呼び出す前に options 'EXMODE' にする必要があるかもしんない。


モノクロビットマップ -- '99.5.6

ビットマップの生成。と, ゆーより, そのヘッダーの生成を行う (モノクロだけだけど)もの。 引数には, 画像サイズ (cx, cy) を指定する。

でもね, ソレはよいとして J_Pocketに その構造が記されてんだけど, おかしい所があった。 (どこがどーおかしかったかは覚えていない (^^;; )

BMPheadMono: procedure
parse arg cx, cy
/* J_POCKET の pm3.inf より (ただし, ソレの bitmapfileheaderに記述ミスあり) */
bitmapfileheader = 'BM----'d2li(0, 2)d2li(0, 2)'----'
bitmapinfoheader = '----'d2li(cx, 2)d2li(cy, 2)d2li(1, 2)d2li(1, 2)
RGB = '000000'x'ffffff'x
bmph = bitmapfileheader''overlay(d2li(length(bitmapinfoheader), 4), bitmapinfoheader, 1)
bmph = overlay(d2li(length(bmph), 4), bmph,  3)RGB
bmph = overlay(d2li(length(bmph), 4), bmph, 11)
return bmph

BMPファイルは, ヘッダーがあって, その後にイメージが付く。 イメージ部分は上下が逆になってるんで, 行(ラスター?)単位に切り分けて逆さにしていかなきゃなんない。 そーして出来たイメージ部分の前に (↑)コレで出来たものを付けてれば完成 ってな訳。
まぁ, 白龍シリーズの PiloNE-Picで, こーゆー事してるんで, 参考にでもしてくらはい。
早いとこ, 正式公開したいにゃ〜 (^^)∫ <PiloNE-Pic

あっと, d2li() は, 以前紹介したもの

んで, ついでにビット反転するための, not を行う関数(↓)。 でも関数にしなくっても, 直接でもよいかも。

BitNot: return bitxor(arg(1),, 'ff'x);

なんで標準にないんだろ, これ。


FileSpec -- '99.4.19

OS/2 版の Rexxには FileSpec() ちゅーもんがある。(他の環境は知らないけど (^^;)
修飾された(?)ファイル名を指定して, その「ドライブ」部分とか, 「パス」, 「ファイル名」なんかを取り出すもの。

これ, まぁまぁ便利なんだけど, DBCSが含まれるファイル名の場合ちょいと問題あり。 だからって options 'EXMODE' を指定してても, 今度は別の問題が出てくる。 まさに, あちらを立てればゴジラが立たず。(←?)

そゆことで, 今んとこ 〆(^^) としては, 取りあえずこんな風に・・

FileSpec: procedure
parse arg opt, fname
options translate('EXmode')
if translate(left(opt, 1)) == 'N'
   then return substr(fname, lastpos('\', fname) +1)
   else return 'FILESPEC'(opt, fname)

普通に, say FileSpec('P', 'c:\予約\一覧') とか指定すっとなかなかな値を返す。 内部で, そーゆーモードにしてるんで, 呼び出す時に options 'EXMODE' の指定はいらない。 一時しのぎとしては, いいんでないかい って感じ。

さて, それでも問題が全くない訳ではない。かなピー事に,

うーむ, やはり一時しのぎって感じ。 あ, もう一つ。・・使うソースそれぞれに, この記述が必要なこと。ちょいと面倒かも (^^;

蛇足かも・・ のコーナー。(←勝手に作るなっ)
ソース見ると, options 'EXMODE' が指定してあるけど, その解除はない。 なぜかとゆーと, procedure になっているから。 それはつまり, return する時に, 勝手に解除されるとゆー訳なのさっ。はっはっはっ。


行で分割 -- '99.4.5

改行位置で文字列を分割とゆーのは, それなりに必要性があろーとゆーもの。
でもねー, それが復帰・改行だけじゃなくって, ものによって改行(0x0a)だけとか, 復帰(0x0d)だけとかゆーのもある。 たとえば Palm/Pilot なんかでは 0x0a だけになってるし, UNIXでは・・アレ? 何だっけ。

標準の関数 linein() なら, どのタイプの行区切りであっても正しく切り取ってくれる。 が, (当然だけど)これはファイル入力として使う時にしかアレできない。ひぇー (T_T
と, ゆーことで, 今回は変数の内容を改行位置で分割するもの。

VarLines: parse arg VarLines; return VarLinesCore();
VarLinesCore: procedure expose (VarLines)
   val = value(VarLines)
   p = verify(val, '0d0a'x, 'M')
   if p == length(val) & right(val, 1) == '0d'x then return 0   /* 途中 */
return p > 0   /* 本来は行数を返す. 行区切りなしは(途中の意味で)0行を返す */
こりは (↑), 分割が可能かどーか調べるもの。
VarLineIn: parse arg VarLineIn; return VarLineInCore();
VarLineInCore: procedure expose (VarLineIn)
   val = value(VarLineIn)
   p = verify(val, '0d0a'x, 'M')
   if p == 0 then p = length(val) +1
   res = left(val, p -1)
   p = p +abbrev(substr(val, p), '0d0a'x)
   call value VarLineIn, substr(val, p +1)
return res
こちらは(↑), 実際に分割するもの。

使い方は簡単。こう。

  nl = '0d0a'x
  data = 'aaa'nl'bbb'nl'ccc'nl

  do i = 1 while VarLines('data')
     say i')'VarLineIn('data')
  end
  if length(data) > 0 then say data

ここまでやっといて なんだけど, 他にもっとよい方法があるかもしんない。うひゃひゃひゃひゃ〜。
例えばパイプを使うとか (うーむ, 駄洒落が思い浮かばない)。それとかキューとか (9じゃないよ)。

まぁ, (↑)に上げた方法でもそんなアレでも無いはずなんで (←って何が何だか?)。
・・まっ, いいか。


DBCS関数 -- '99.3.21

DBCS ・・ DataBase Management System (←そりは DBMS)
・・んじゃ, DataBase Control System (←意味ないっ)

文字列を扱う関数では, options 'EXMODE' から options 'NOEXMODE' ではさまれてる間, ダブル・バイト・コードをソレ相応に使ったりできるものもある。 ・・とりあえず, どーゆーふーに? とかゆーのは置いといて。なはは。

標準の関数以外にも, 準標準関数 (←何だソレ) として, DBCSを取り扱うためのものがいろいろ取り揃えてR。 んで, これ DOS版でも OS/2版でも使える。Classic Rexx でのオンラインマニュアルには載っていないけど, それでもちゃんと使えるのだ。

※ (↑)Sift-JISにとっては必要がないオプションは, 省いてるんで注意 って注意するよーなもんでもないね (^^;;

この中で 〆(^^) が特によく使うのが DBRight()DBValidate()。 前者は, パス指定の最後が '\'かどうかを調べるのによい。知っての通り '\'は Sift-JISでのダブル・バイト・コードの 2バイト目のコードとして現れる事があんだね。 だから単に right(path,1)とかするのは間違うこともある訳で, そんなこんなで (↓)こんなふーに使ってる訳なのだ (^^)

if DBRight(path, 1) \= '\' then
   path = path'\'

もう一つの DBValidate() ・・こりは例えば, どこかから 切り出してきた文字列が, ちゃんと文字列として大丈夫なのかどーか, つまりダブル・バイト・コードがあんじゅとずしおうのごとく別れ別れになってしまわないよーに調べる時に使う。
って, ちょっち漢字 分からなかったんでひらがなだったりする (^^;; 安樹と厨子王? うきゃ〜 (←分らないなら書くなよ〜)

プログラム用意する時に気を付けておきたいのは, やはりパス名のところ。
OS/2 では FileSpec()が用意されているんで何かと便利。・・でもこれに頼るとちょっちアレ。 options 'EXMODE' の中では, ダブル・バイト・コードを識別してくれて"うれピー"と, なるところが変な現象が起きたりもする (T_T
たとえば say FileSpec('N', '\Home\J') とやってみると値が変。'\J'を何かと勘違いしているよーで, このほかにもあったよーな気がする。 このばやい, options 'NOEXMODE'に戻すと正常。
まぁ 分った上で使えばそれなりに便利なんだけどね。


Copyright (C) 1998-2004 Rexx使いの織華
email: ori@drive.co.jp