一歩進んだ Rexx

Classic REXX (REstructured eXtended eXecutor)
File #08

Base64のエンコード -- '99.11.23

ずいぶん前に, Base64のデコードっつーのを紹介した事もあった。 そーいえば, そんなこともあったっけ (~-~) (遠い目)

そう, こりはメールの添付ファイルなんかでよく使われる MIMEのアレ。 例えば画像ファイルなんかを, メールの後ろにくっつけたり, サイドカーみたいに横にくっつけてみたり, カンガルーみたいに前にくっつけたりする。って事はない。

Base64EncoStr: procedure
BaseChars = xrange('A', 'Z')xrange('a', 'z')xrange('0', '9')'+/='
parse arg txt
val = x2b(c2x(txt))
res = ''
do i = 1 to length(val) by 6
   res = res||x2c(b2x(substr(val, i, 6, 0)))
end
n = length(res)//4
return translate(res, BaseChars, xrange(, '3f'x))copies('=', (n > 0)*(4 -n))

コレ (↑) がソレ。バイナリデータをエンコードするもの。引数はバイナリデータ。そんだけ。 で, 結果はエンコードした巨大な文字列。そしてソレを 70文字くらいに分割するのは呼び出し側の責任なのだ。

この Base64ってのは, 3バイト 計24bit を, 6bitずつの4文字にしていく感じ。 6bit なので 64種類の文字とゆーことなにょだ。さしずめ, 元のデータは Base256 って感じだね。

ところで, うーむ。プログラムの最後の行ってのがみっともないね, コレ。(^^;


メールアドレスの分解 -- '99.11.12

どことなくメーラーっぽいのを, 少し前に発表した。 ・・のだけれど, 送信部分にしろ, 受信部分にしろ, とてもじゃないけど納得できない。 これじゃ, みっともなくって人さまにお見せする訳にはいかない。 (←って, 出してんじゃん, もう)

うーん, 少なくとも送信だけは自前でアレしたかったんだけっちょ, コードを継足し継足ししていってたら, もう何が何だかって感じになって来てて, はぅぅー (T-T)

でも, なんてゆーか, 特異点・・じゃなかった, なんだっけ。コレを作ったことで, ひとつ賢くなったこともあった (^o^)
ソレは, RFC822でのメールアドレスの構造の理解度が, 少しばかり深まったこと。うひひ。

てことで, メールアドレスを解析して分解するヤツのサブセット (↓) なぜサブセットかっつーと, To: とかにアドレスを複数指定する時の, 「,」に対応していないから。 (^^;

FromAddrParse: procedure
parse arg str
ctext = ''
a_bra = '<'
a_bpos = ''
do p = 1 by 0
   p = verify(str, '"[('a_bra, 'M', p)
   if p == 0 then leave
   v = pos(substr(str, p, 1), '"[(')
   if v > 0 then do
      quote = word('" ] ()', v)
      nest = 1
      do n = p by 0
         n = verify(str, quote, 'M', n +1)
         if n == 0 then leave p
         do i = n -1 to 1 by -1 while substr(str, i, 1) == '\'; end;
         if (n -i) //2 == 0 then iterate
         v = pos(substr(str, n, 1), '()')
         if v == 0 then leave
         nest = nest +word('1 -1', v)
         if nest > 0 then iterate
         ctext = ctext''substr(str, p, n -p +1)
         str = left(str, p -1)substr(str, n +1)
         iterate p
      end
      p = n +1
   end
   else do
      a_bra = '>'
      a_bpos = a_bpos p
      p = p +1
   end
end

if words(a_bpos) < 2 then return '00 00'x''word(str, 1)'0'x''ctext
parse var a_bpos p n .
return '0'x''strip(left(str, p -1))'0'x''strip(substr(str, p, n -p +1))'0'x''ctext

呼び出し方は簡単。FromAddrParse() の引数に, その文字列を与えるだけ。 そーすると, '00'x を区切りに, 3つのパーツに別れて戻ってくる。前の部分のコメント, 実際のアドレス, 括弧のコメント って感じ。

つまり, 次のように指定すると・・

  text = '"でへへ" <ori@drive.co.jp> (うはは)'
  parse value FromAddrParse(text) with ch +1 phrase(ch)addr(ch)ctext
が入るってわーけ。分かったかな? (←?)

でも, あんまし使いみちなさそう。(^^; (←ソレは言わない約束よ)


do ループの続き -- '99.12.14

あ, さてー, do ループの続きの話であーる。

前回ので, repetitor(反復句ともゆー), conditional(条件句ともゆー) の二つがあるってことを書いたけど, コレは, どちらか片方だけを指定してもよいし, 前回の最後の方に載せたよーに, 両方指定する事だってできるのだ。

どう? (シャレ?), 結構強力だよね。

それから, leaveiterate。 これは, C言語でゆーところの breakと continue。 コレがまた強力なことに, こんなことまでできてしまう。

do MainLoop = 1 while Func1()
   do while Func2()
      if Condition then leave MainLoop

ま, 見てみりゃ意味は分かるんではないかと思うけど。あ, それから iterate でも 同じように後ろにそーゆーのを指定できるんで, 調べてみてちょ。
でも, 同じ目的のために signalを使ってしまうと, ループが終わってしまうことになるんで, そこんとこ注意すべしってとこだね。

次に, ループではないよーな時に, 途中の処理を省きたい時にはどーすんのか。 たとえば, コレ (↓)はまずい。

do
   ...
   if Cond() then leave
   ...
end

ループじゃないんで, leaveは使えない訳なんだねっ。
このばやい, 手っとり早い方法は, dodo 1 にすると OK。 ま, 物にもよるけどね。こーすることでジャンプラベルっぽくなるって訳なのら。

で, 今回のまとめに, 幾つか doについて注意する点をあげておこー。
まず, 一行にいくつもの記述をする時。こんな時, つい間違えてしまいそーなのが
コレ → if a == 1 then do n = n +1; end コレは, do の後に ; がついていないのが間違い。けっこーありがち。
それから, ほかに間違いやすいものといえば
コレ → do lines(fname)。 これは, whileが抜けてる。
どちらも, 文法上は問題ないんで, 間違いを発見しにくいところがアレにゃのだ。

あうー, ちと基礎過ぎたかも。そんなとこで。うじゃ (^o^)/~


do ループ -- '99.12.7

今回は do です。最近調子どぅ なーんちゃって (^^)\(バキッ☆

Rexxでは, 命令をくくる時とかループする時とかに do 〜 end が使われる訳で, そーゆーとこは, C言語での { } と似てると言えなくもない。
でもって, ループの条件なんかも do の後ろに色々と記述する訳なんだな, コレが。 そこいらへんは, NetRexxでは, また微妙に違ってて, ループの目的には do じゃなくって loop っつーソレ専用のがあるのだ。

さて, このループの使い方, 結構強力に使えるんで, 覚えておきたいところのこと。 実際, ものによっちゃ C言語よりすぐれてたりするのもあるし, 逆に劣ってたりするのもある。

do の後ろには, さまざまなのを指定する訳だけっちょ, 大きく分けて repetitor(反復句ともゆー), conditional(条件句ともゆー) の二つがあるじょ。

まずは, repetitor(反復句)。例えば次のよーなものだっっ。

コレって, 結局は同じことで, 最終値が分かってるなら上のを, 回数が分かってんなら下のを使う訳なのだ。 ここでゆーところの i = 1 を指定した場合, to, by, for を指定することができて, その出現の順番は適当で構わない。構わないったら構わない。 ソレは指定した順番に Rexxの内部に蓄えられるのさっ。
つまり do i = a() for b() by c() to d() ってな場合, この 4つの関数は, ループの始まりで一回だけ呼び出されて, あとはその値が保持されちゃう。 その呼び出す順番ってのが, このばやいは a() → b() → c() → d() ってこと。

制御変数に代入すんのをやめて, ループ回数だけ指定ってのもあり。ゆってみたら do for n って感じ (ホントにそー指定したらエラーになると思うけど)。 それから, forever って指定するのもある。

次に, conditional(条件句)。これってこんな感じ(↓) なのだ。

違いは, 条件の意味合いと, そして条件を判断する場所。 untilのばーいは, 実際に条件判断すんのはループの最後にゃのだ。記述すんのは先頭なんだけどね。
んで, この conditional(条件句) と, 前の repetitor(反復句) との違いをアレしてみよう。 それは, conditional(条件句) とゆーのは, ココに関数なんかを指定すると, ループする度に毎回呼び出されるとゆーこと。 つまり, (↓)こんな感じで呼び出して, たとえループする回数が同じだったとしても, func() が呼び出される回数は大きく異なるってことに注意すべし。

あ, 何だか大きくなったんで, 続きは今度。えへへ。(^^)/


外部関数パッケージの登録 -- '99.10.25

外部関数パッケージを登録する際に, 例えば次のよーに指定するだろう。

  call RxFuncAdd 'SysLoadFuncs', 'rexxutil', 'SysLoadFuncs'
  call SysLoadFuncs

さて, 普通は大丈夫なコレが, ある時 にっちもさっちもど〜にも・・・ ってな場合がある。 どんな時だろう? ・・それは, 外部関数パッケージのモジュール名を間違えていた時 (二番目の引数のヤツね)。 この時, いったい何が起きているのだろーか。

実は, RxFuncAdd は, 『登録』されたら0を返すよーになっちょるのだ。
けど, これは .DLL が見つかったかどーかとは関係がない。な, なんてこと〜 (ここで絶叫)。
どーあがこうとも, 『登録』したかどうかだけしか返さないのだ。

モジュール名を間違えてしまったら, 『登録』はできても, 実行はもちろんできない。 んで, 開放しようにも, この場合の SysDropFuncs は存在しないんで, 実行できない。 その上, 正しいものにプログラムを書き直して実行しても, 既に『登録』されているんでうまく動かない。
うきゃ〜, どしたらよかっぺ〜。ってソレは案外簡単に解決できるのじゃ。(^o^)

その方法とは, RxFuncDrop で, この場合 SysLoadFuncs を開放するだけ。 もちろん, ソケットライブラリーの場合なら call RxFuncDrop SockLoadFuncs だよ。 そのあとは, 使いたいものを正確に『登録』すること。

そりから, ついでに起動メッセージを表示させない方法についてのこと。

次のように指定することで, 起動メッセージを最初の一回だけにする事ができ, 時々見かける (ソレだけが目的じゃないだろうけど)。

if RxFuncQuery('SockLoadFuncs') then do
   call RxFuncAdd 'SockLoadFuncs', 'rxsock', 'SockLoadFuncs'
   call SockLoadFuncs
end

でも, 全く表示しないってことも出来る事もある。 たとえば, このソケットライブラリーの場合だと・・

call RxFuncAdd 'SockLoadFuncs', 'rxsock', 'SockLoadFuncs'
call SockLoadFuncs 'N'

って感じだね。後ろにつけてる 'N' は, なんでもよいのかもしれない。 それから, すべての外部関数パッケージが, そーなってるかどーかは知らない。あしからず。わっはっはー。


ファイル操作2 -- '99.10.4

さて, お待たせの 前回の続きなんだにょー。・・って, 待ってたかどうかはアレだけど。

関数 stream() では, さらにいろんな指定ができるにょだ。

ってとこ。無理やり分けてるね, コレ。だはは (^^;

open には, 'open', 'open read', 'open write' の3種類(classic rexxの場合)がある訳なんだけど, 最初のは特に指定の必要もないし, 「オープンしてるんだぞっ」て表現するのに(?) 使うくらいしか思い付かない。
'open read' は, 他のプロセスが同時に使うかもしれない時とか, すでに使っていて読み込み専用でしかアレできない時なんかに使うんだ。きっと。たぶん。 あ, ソレ以外にも, 間違って書き込んだりしないように防御のためかな。
'open write' は何のためだろう。間違って読み込まないため? (←何じゃそりゃ)

次は, query系の呪文 ・・・ ファイル作成日時とか, ファイルサイズとか, ファイルが存在するかどうかとかを調べてくれるものなのだ。
'query datetime' は日時を返すヤツだね。でも可能なら 'query timestamp' を使ったがよい。なんつってもY2K対応だよ, Y2K (って 年が4桁になってるだけ)。 ただ, FX00505 以降でないと使えないみたいだけど・・ (T-T
だから, ソレ以前のものとか, DOS版の Rexxでは我慢して 'query datetime' を使うしかない。 あ, もちろん ORexx ではどちらも可。
ファイルサイズを返す 'query size' は, 何も操作を行っていない時の chars() と同じ値が返る。 だったら chars() の方が簡単でいいじゃん, とか思うかもしれないけど, この時にオープンされてしまうんで 後でクローズしなくちゃなんない。 それ考えたら, ソレと同時に読み書きする以外の場合は 'query size' を使った方がよいかも。
'query exists' は, ファイルが存在するかどうか調べるヤツ。そんだけ。

さて, seek 系。OS/2 では, ファイルの読み位置も書き位置も同じなんで, コレ一つで全てをアレできる。 ・・・んだけど, σ(^^) は 位置を知る時以外 使っていない。 んじゃ, どーやってるかっつーと, 読み書き位置を指定する時には, charin(), charout() の最後の方の引き数で指定してるにょだ。
でも, とりあえず解説すると, 'seek' と offset を指定してその位置に移動する。 んで, その二つの間に '+', '-' のどちらかを入れると, 今の位置からの相対って感じで移動にするし, '=' だと, 指定していないのと同じ。絶対移動って感じかな。何だかかっこいい (←バカ?) で, '<' だと, ファイルの終わりの位置から数えた位置って事になる訳だ。

うーん, INFのアレも, もうそろそろ仕上げたいんだげっちょ, なんだかほったらかし。まっ いいか。 (←こら)
では (^o^)/~


ファイル操作1 -- '99.9.29

今回ファイル操作を紹介してみよう。とくに難しい訳でもなくって, 関数もこの程度。

* 行 単位* 文字単位
入力lineincharin
出力lineoutcharout
remainlineschars
その他stream

試しに, rootディレクトリにある 'README' を配列(?)に取り込んでみよう。

fname = '\README'
do i = 1 while lines(fname) > 0
   data.i = linein(fname)
end
data.0 = i -1

他の言語, 例えば C言語でなら fopen() とかでオープンの必要があるんだども, これには必要ない。 いや, 付けたってよいんだけど, 読み込み専用にオープンする時くらいしかアレなのじゃ。 そん時は, call stream fname, 'c', 'open read' て感じ。

さて, ひとつ変なことがある。lines() がファイルの残りの行数を返すとゆーのなら, ループの部分は do i = 1 to lines(fname) でよいのでは?
ところがどっこい, そうは問屋が下ろさないのだ。DOSとか OS/2とかその他では, 全部読み込んでしまわないと, 行数ってのがわかんない。 ぐわ〜何て事だ。
でも chars()では, 残りのバイト数がバッチリ分かる。まぁ, OSがそーできてるんで仕方がないって事なんだね。
で, 'COM1:' なんかのデバイスでは, chars() でも lines() でも, 結局『ある=1/なし=0』しか返す事ができない。 まぁ仕方がないやね。

今度は, さっき出てきた chars() を使ってファイル読み込み。

fname = '\README'
data = charin(fname,, chars(fname))
call stream fname, 'c', 'close'

あ, そーいや忘れてたけど, 行単位のプログラムでも, クローズは付けた方がよいね。でないとプログラムが終わるまでオープンしたままになっちゃうし。(^^;

で, 予定より大きくなってきたので, この辺で・・ うじゃ (^o^)/~


DLL の見つけ方 -- '99.9.8

今回は, OS/2 に限ったものを紹介しよう。 とゆっても, そうでない場合って少ないかも。てへへ (^^;
何をするかとゆーと, DLLがそのパスに入っているかどうかを確認するもの。

これは, 白龍シリーズのインストーラーでも使った手段でもあ〜る。
OS/2版の Rexxには SysSearchPath() とゆー関数がある。 これは, PATH か, それとも似た形式になっているリストから, 指定のファイルを見つけるもの。 なんだ簡単じゃん, これ使うだけじゃんじゃん。
そーではない。DLLのありかを指定するLIBPATH は, 環境変数ではないのだっっ。

だもんで, 手順とすれば,

  1. config.sys から LIBPATH の指定を取り出す。
  2. そこからひとつずつパスを切り出す。
  3. そこにブツがあるかどうか調べる。

とゆー事になろー。でもホントはもっと簡単。

(↑)の config.sysからの「取りだし」は幸いにも SysFileSearch とゆー, これまた OS/2版独自の関数がある。 で, 環境変数さえ用意できれば, ソレ以降の手順も先にあげた関数で何とかできよう。
そう, 環境変数を設定してしまえばよいのだ。わははははー。 (←バカ?)

/* DLL を探す */
call RxFuncAdd 'SysLoadFuncs', 'rexxutil', 'SysLoadFuncs'
call SysLoadFuncs

call SetLocal
call SysFileSearch 'LibPath', '\Config.sys', 'lpath.'
if lpath.0 > 0 then do
   parse var lpath.1 '=' txt
   call value 'LPATH', txt, 'OS2ENVIRONMENT'
end

say SysSearchPath('LPATH', 'pisock.dll')
say SysSearchPath('LPATH', 'emx.DlL')
call EndLocal

SetLocal, EndLocal ってのは, 環境変数の状態を元の状態に戻すってヤツ (それ以外にも戻すものあるけど)。


マクロ機能 -- '99.8.17

例えば何かのプログラムにマクロ機能を付けたいとしよう。 そのマクロ機能によって柔軟に色々できるよーにしようとゆー訳だ。
色々とは何か・・・。色々っつったら色々だよォ〜。 (←なにゆってんだか)

もともと この Rexxは, そーゆう機能拡張を行う手段として用意されてるみたいな気もする。 とゆーのも, 原産地でのページ IBM Hursley Laboratories WWW2 server でも, PMGlobe だとか あるからだ。 中には GoServe のよーに, Rexxで出来ているんじゃないかと見まごう物さえある。

で, いろんなプログラムから Rexxをマクロ機能として呼び出せることはアレだけど, 実はRexx自身からRexxを呼び出すのも可能なのであ〜る。 まぁ, Rexxで色々プログラムを作っている人にとっては, 当たり前だと言われるかもしれないけどね, コレ。
そして, この呼び出し方にも幾つかある。で, 今回は interpret での方法を紹介しよう。

interpret で文(プログラム, マクロ)を呼び出す場合, 欲しいけれどソレがない機能に

てーのがある。 コレができないとゆう事だと, 何となく不便であろう。
そこでっ・・・

DoMacro: signal on syntax name MacroErr; interpret strmacro;
MacroErr: return '';

PrepareMacro: procedure expose strmacro nl
strmacro = translate(arg(1),, nl)
return

コレはマクロの呼び出しの部分と, その準備の部分。

呼び出し方は call DoMacro で, 当然 引数も指定できる。 もちろんマクロ側も, parse arg などでソレをアレする必要があるんだけれどね。にゃはは。
そして, 戻り値も指定でき, マクロ側の return で指定する。もし指定していなければ null文字列が呼び出し元に戻るって訳だい。

準備を行う関数である PrepareMacro には, 引数としてマクロのコードを指定する。 そんだけ。 で, グローバル変数 strmacro に登録されて, それが call DoMacro で呼び出される事になるのさっ。
ついでに説明すると, translate() で変換しているのは, interpret が改行文字列をエラーにしてしまうので, 取り除いているのだ。 プログラム先頭にでも nl = '0d0a'x を入れといてちょ。

コレの問題点は, マクロの一つ一つの行末に ;を明示的に指定しなけりゃならないって事。 これってば PrepareMacro ででもやっても(勝手に付けても)よいかもしんない。

さて, こうやってみると, (行数も少ないし)馬鹿馬鹿しいもんだけれど, 案外思い付かない物なのかも。 ・・そんなこと無い? やっぱ無いですか? (T-T)


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