バッファのゴミは,主にヒストリーバッファを上書きバッファとして使用してしまったときに発生します。文字列複写関数&Sc()
やメモリ複写関数&Sm()
は上書きであるため,既存の文字列の区切りどころか多バイト文字すら割ってしまうためです。このように断片となった文字列は再利用すら困難になってしまいます。そればかりか,偶然ヌルが既存のヌルの直前に複写された場合,それ以前の履歴は一切利用できなくなってしまいます。
ヒストリーバッファに文字列を複写する場合は,履歴を生かして複写できるヒストリ格納関数&Sh()
を使いましょう。
マクロで#G
(検索文字列の取得)を使用したときに,その文字列を履歴に反映させないようにするためには,次項を参照してください。
ブロック範囲を#G
(検索文字列の取得)で取得すると,取得した文字列は検索文字列ヒストリーバッファpsに格納されます。実際に検索文字列として利用するならともかく,テキストスタックと異なり容易に複写・出力可能な文字列として取得した場合は,ある意味ゴミと同じです。
変数psの指すアドレスを一時的に別のバッファにすりかえることで,psにゴミを残すことなく文字列の取得を実行できるようになります。
ps[=pa, #G ps], ;psをpaにすりかえる
&m("%s",pa) ;paに格納された文字列を表示確認
なお,ヒストリーバッファの代用ができるのは,ヒストリーバッファです。ヒストリーバッファの構造を持たないバッファでヒストリーバッファを代用すると,文字列を格納すればするほどバッファの後ろ側を壊していき,最終的にはハングアップにつながりますので注意してください。
モジュールの内部にバッファを用意すると,モジュールが削除されない限り外部から壊されない堅牢な記憶領域を保持することができます。この領域はバイト/ワード単位でそのマクロしか使わない局所変数とすることもできます。
&> x=r,
x..0=a, x..1=b, x..2=c, x..3=d,
&> y=r, ;他の変数でも使い回せる
0: (3+$)."$[8]"
&> ;xに固定
x..0=a, x..1=b, x..2=c, x..3=d,
0: x3+$,."$[8]"
1 ^^ "" ?.
(mb-2)?. ;文字ブロックでなければ終了
&> ps[=r, ;バッファをすりかえる
mp-, #G ;取得成功ならmpが2になることを利用
ps], ;バッファを元に戻す
(mb||mp-2)?{ &m(40) . } ;取得失敗なら終了
&> &m("%s",r) ;確認する
0: (5+$)."$(50,0)$[80]"
一番簡単なのはpa(アプリケーションヒストリーバッファ)を使うことです。VZ自体がこのバッファを使うことはありませんので,他のマクロで使用していなければ自由に使うことができます。ただ,この「他のマクロで使用していなければ」というのが曲者で,どのマクロがpaを使うかということを意識しながら使うということは相当むずかしいでしょう。したがって,使用目的によって上手に使い分けることが必要です。
意識して入力していない文字列を検索文字列の履歴に残さないことが目的ならpaで十分です。再利用するのであれば,積極的にpsを使ってもよいでしょう。保存変数を確保したいのであれば,マクロ内部に自前バッファを用意します。そして必要ならそのマクロがマクロバッファから削除されて保存変数が初期化されたりすることのないように,インクルードマクロとします。それ以外のケース,例えば転送用の一時バッファであれば完璧に保護する必然性は低いので,一般的な作業エリアを借りるという手があります。
VZはcz(スタックセグメント)のマクロバッファでない部分に,結構たくさんの作業領域を持っています。その中でも一時的作業エリアとしての名称を持つバッファがいくつかあります。
tmpbuf | コマンドラインのラインバッファ | pq..134(pn..-41) | 256バイト(total 672バイト) |
---|---|---|---|
tmpbuf2 | エイリアスの文字列展開用バッファ | pq..135(pn..-40) | 256バイト |
tmpbuf3 | DTA,smooth,&?()のワークエリア | pq..136(pn..-39) | wd*2バイト |
tmpbuf4 | ラインバッファ直後緩衝 | pq..149(pn..-26) | 256バイト |
これらのうち最も安全に使用できるのが,tmpbuf4であろうと思われます。このバッファはラインバッファあふれのチェック用領域で,&z
等のマクロ組込時以外は比較的自由に使用できるというのが定説です。サイズも256バイトありますのでHs(検索文字列ヒストリーバッファのサイズ)と同程度です。
ただし,このバッファはヒストリーバッファの構造を持っていないため,注意が必要です。どうしてもヒストリーバッファとして使用したい場合は,サイズ情報を埋め込まなければなりません。
1 ^\ "" ?.
pq..149..0=$fe, ;サイズ情報を先頭に埋め込む $fe==254==256-2
mp[, ps[=pq..149+2, ;3バイト目からバッファとして使用
i-, mb-,
:l ; ---- main loop ----
(i++>10)?>z ;10回繰り返し
#b #> #G mb-, #d ;行を取り込み mb-:空行対策
>l
:z ; ---- exit ----
ps], mp],
&m("done.") &d #] .
2 ^^ ""
?.
ps[=pq..149+2, #F ps], ;バッファ内容の確認
ラインバッファの末尾2バイトにサイズ情報を埋め込めば,256バイトまるまる使える計算ですが,そこまでする必要はないでしょう。
バッファの先頭にある文字列は,次のマクロで確認できます。
1 ^^ "" &m("%s",pa)
先頭だけでなく,履歴をすべて確認したい場合は,#F
(検索文字列の設定)などの履歴を遡ることのできる機能を使用するのが簡単です。実は前項で必要があって書いたものと同じものです。
1 ^^ ""
?.
ps[=pa, #F ps], ;バッファ内容の確認
もしも,履歴を遡って見つけた文字列をカーソル位置に出力したければ,#F
ではなく#61
(文字列の選択複写)を使用すると便利でしょう。
1 ^^ "" ?.
ps[=pr, #61 &Cr ps],
ラインバッファは初期設定でも2048バイト,最大で8192バイトの大きさを持つバッファで,行の編集途中でなければ比較的自由に使える大きなバッファです。編集モード以外ではVZ本体からのアクセスがないのと,通常は編集画面よりも表示サイズが小さいという理由からか,動的メニューを構築するのによく使用されます。
ここでは使用する際の注意点だけ述べますので,具体的な使用例は個々のマクロを参照してください。
まず,使用する前にラインバッファを解放しておく必要があります。解放と言っても特別なことをするわけではなく,行の編集中であれば編集状態を反映させるかキャンセルするかして,ラインバッファから出るだけのことです。通常は編集状態を反映させるわけですから,コマンド#]
(#85
:ステータスラインの再描画)を実行します。
#] p=wm+230, ;ラインバッファを作業領域として使うという宣言
ラインバッファの先頭アドレスは wm+228
で,他にアクセス可能なポインタとして pq..148
(pn..-27
)が使えます。ただし常駐直後のコマンドラインでは,wmの値は0なので使用することはできません。
なお,ラインバッファは環境によって大きさが変化することに注意しましょう。大きな領域を必要とする作業の場合は,bl(ラインバッファのサイズ)をチェックして安全に使用できるか確かめる必要があるかもしれません。
テキストスタックは64KBの大きさを持つバッファです。メモリ1GBのマシンが出まわる昨今,蟻と象の違い以上に差がありますが,16ビットのアプリケーションにとっては別のセグメントに存在するバッファというだけでも意義があります。前項のラインバッファがスタックセグメントのごく一部の領域であったのに比べると,少なくとも数倍のサイズです。
ただし,テキストスタックを使用するとなると,それまでカット・コピーされた文字列を捨てるわけですから,初期化する前に確認する必要があります。
&Bm ;安全策らしい
&a(17) ;テキストスタックを消去しますか?
(r-1)?. ;[YES]以外は中止
3(0) ;初期化(エンドポインタを先頭に移動するだけ)
テキストスタックを作業領域とするマクロの代表は,高橋洋光先生のjistoms.defです。ですが,このマクロ,徹頭徹尾機械語なので難解です。その代わりと言っては月とスッポンに過ぎますが,サンプルを上げておきます。こちらの機械語はわずかです。
= revcomp.def v0.21 = by H_ear
#if 0
テキストの逆順比較(スタックに取り込んで比較)
#endif
* mad for vz!
1 ^\ "" ?.
(wa.5+wb.5)?{ &m("ブロックモードでは実行できません") . }
&a(17) (r-1)?.
&s &Bm
3(0) t=20000, ;取り込む単位(32000以下)
:l ; ---- main loop ----
&> dz[=kz, di=kp-4, aa=kp..-1, dz], #W ;kp..-1:ブロックサイズ
&> dz[=kz, si=kp-4, bb=kp..-1, dz], #W
(aa<=bb)?i=aa, ?i=bb, ;比較回数は小さな方に合わせる
bx=i, ax=kz, di-=i, si-=i, cx-,
&01 ;機械語サブルーチンを実行
(cx==i && aa==bb)?{
3(0)
:b #s i=r, #W #s i+=r, #W
(i+2)>?{ >q >d >l }
&m("!Error") >z
}
&Ia(wa+126,aa-cx) #U #M #W
&Ia(wa+126,bb-cx) #U #M #W
:d &m(25) >z
:q &m(24)
:z ; ---- exit ----
3(0)
&d #] .
00: #b #M
(wa..64)?{
&Is(wa+126,t)
}?{
(wa..63>t || wa..63<0)
?{ &Is(wa+126,t) }?{ &Im(wa+126,0) }
}
#U mb2, #k #M
01: &i(3+$).
"$(8E,D8,8A,00,3A,01,75,0A,4B,41,83,FB,00,74,03,E9,F0,FF,C3)"
*
[Variables]
di,si address
aa,bb block size
i,bx,cx counter
t text stack size
ax kz segment address
[Buffers]
wa+126/wb+126 [4]Mark0
[Mnemonic]
mov ds,ax
A: mov al,[si+bx]
cmp byte ptr al,[di+bx]
jne Z
dec bx
inc cx
cmp byte ptr bx,0
je Z
jmp A
Z: ret
[History]
2005/04/21 0.10 初版
2005/04/22 0.11 一方の行頭まで一致する場合に終了時の位置合せを失敗していた
2005/04/25 0.20 ロングポインタ使用版
2005/04/25 0.21 比較ループを機械語に置き換え
[Notes]
Mark0はジャンプ時にVZによってセットされるので再セットが必要
[Todo]
実行後のカーソル位置が#69の完全な裏返しになっていない。
バッファを初期化する方法は,とても簡単です。文字列格納用バッファとして使用している場合は、先頭にヌル(0x0h)を埋め込むだけです。ただ,2バイトでデータの読み出しができるので,先頭2バイトをヌルにしておいた方がよいでしょう。
ヒストリーバッファの場合も,連続したヌルがバッファの終端になります。
ps..0-, ;psの初期化
なお,そのバッファをロング整数用に確保したり,構造体として用意した場合は,次項を参照してください。
バッファを構造体として使用する場合などは、バッファのどこにでもアクセスする可能性があるので,初期化するにはバッファ全体にヌルを書き込む必要があります。これは「ヌル潰し」と呼ばれています。(ほんとか?)
基本的には,次のようになります。
1 ^^ "" ?.
&> b=r, ;バッファの先頭アドレスを取得
a=30, ;バッファのサイズ
(a){ a--, b.a-, } ;1バイトずつヌル潰し
; ----- '@'の埋めてあるバッファ -----
0: (3+$)."$[30,$40]"
バッファのサイズが偶数であれば,少しスピードアップできます。
a=30/2, ;バッファのサイズ/2
(a){ a--, b..a-, } ;2バイトずつヌル潰し
あるいは,こんな書き方もできます。
a=30/2, ;バッファのサイズ/2
(a){ b..a---, } ;2バイトずつヌル潰し
バッファのサイズが4の倍数であれば,ロング演算関数を利用してステップ数を減らすことが可能ですが,速度面は微妙というところでしょう。
&> b=r, ;バッファの先頭アドレスを取得
a=b-4, ;変動アドレス
(10){ &Im(a+=4,0) } ;4バイトずつヌル潰し
; ----- '@'の埋めてあるバッファ -----
0: (3+$)."$[40,$40]"
複数の自前バッファをひとまとまりで確保するには,バッファの先頭アドレスから何バイト先であるかを指定します。次の例は,先頭アドレスをaとして4個のバッファを確保する指定です。
&01
a=r,
b=a+10,
c=a+20,
d=a+30,
; ==== 中略 ====
1: ;ローカルマクロ
(3+$)."$[40]"
これで問題はないのですが,いかにも冗長な感を否めません。そこで相対アドレスを指定する部分を,少しまとめてみましょう。代入式は右辺から順に代入されていきます。
&01
a=r,
d=10+(c=10+(b=10+a)),
さらに「=」を省略しても代入と扱われ,そのうえ演算の優先順位が変更されることを利用すると,次のように短縮して記述することが可能です。
&01
d10+c10+b10+a0=r,
やや奇異な式ですが,短縮せずに書くと次のようになります。
d=10+(c=10+(b=10+(a=0+r))),