Vz Editor
2009-07-25T10:05:44+09:00

VZトークン関連覚書

VZエディタ搭載マクロ言語のトークンに関する覚書である。VZではあまりトークンという言い方をせず,マニュアルでもキーワードと称されている。VZマクロはC言語型のフリーフォーマット言語であるが,そのままマクロバッファに読み込まれて順次実行されるインタプリタであるため,逐次解釈の高速化のため徹底したトークンの短縮化が図られている。このため自然言語により近いトークンの高級言語に比べると読みにくさはあるものの,記述の際の打鍵数が少なくてすむという利点がある。また,短縮化されていてもそれなりに機能を想像しやすいトークンであるという特徴をもつ。

この覚書の構造
見出し内容
シンボル一覧1文字のシンボルからトークン索引へリンクする
トークン索引上記の1文字を含むトークンの簡易説明から機能分類別の詳細解説へリンクする
トークン詳説機能分類別に詳細解説を行うが簡易説明で十分な場合もある

シンボル一覧

! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
空白類TAB SPC CR/LF 制御コード文字

トークン索引

!

トークン種別書式機能
!単行演算子a!(a=a!) 否定
!!単行演算子a!!上位バイトと下位バイトの交換
!=論理演算子a!=b不等号 (a!=b)?{ブロック}
>!フロー制御(式)>!{ … }直前の(式)の値に対応した文字に続くブロックを実行 対応文字がなければ【|】の次に示されたelseを実行

"

トークン種別書式機能
"特殊文字"abc""文字列"を括る二重引用符 打鍵されたように出力

#

トークン種別書式機能
#識別子#if DOSV
#endif
行頭において続く条件文とシンボルを用いてマクロバッファへの読込みを制御 #if/#else/#elseif/#endifが使用可能
#nnコマンド#09 #10コマンド関連覚書を参照
#?コマンド#a #fコマンド関連覚書を参照
&#nnコマンド関数&#51(0)関数関連覚書を参照
&#?コマンド関数&#V(41)関数関連覚書を参照

$

トークン種別書式機能
$定数$ff $825f十六進数の明示 値の範囲は$0〜$ffff
$特殊文字a3+$,."ABCD"実行位置のポインタを示す キーワード自身のマクロバッファ内のオフセットを取得して自前バッファの確保やメニューのポインタ取得等で使用される
$特殊文字"$[8]"
"$"str$""
文字列中において次の文字/文字列との組合せで特別な意味を持つ特殊文字として使用される
$"【"】文字 文字列終端を示す【"】の意味を打ち消して文字列中に含ませる
$$【$】文字 特殊文字の意味を打ち消して【$】そのものを文字列中に含ませる
$0〜$7指定した表示属性への変更
$!表示属性を反転(リバース)
$p?変数p?の示す文字列を参照出力
$a?オプションa?の示す表示属性に変更
$??変数??の値を十進表示で出力
$(hh,hh,..)十六進コードを指定してマルチバイト出力
$[n]nバイトのヌル(0)を展開出力 自前バッファの簡易設定(v1.6)
$[n,c]nバイトの文字cを展開出力 自前バッファの簡易設定(v1.6)

%

トークン種別書式機能
%二項演算子a%b剰余
%=代入演算子a%=b剰余の代入(一定範囲の数値に丸める)
%?書式制御&m("%d %u",a,a) &m() &?() &sp() の引数文字列内にパラメタの値を指定書式で展開
%d符号付き十進数
%u符号なし十進数
%x十六進数
%c文字
%s文字列
%l?書式制御(v1.6)&?("%lu",p,q) &m() &?() &sp() の引数文字列内にパラメタの値を指定書式で展開
2個のパラメタを合わせてロング整数出力
%ld符号付きロング整数
%lu符号なしロング整数
%lx十六進のロング整数
%?ファイルメニュー "CopyBack$(9)%!%^RCP32 %[/s+] /b %@1 %2%G" ファイルメニューのアイテムに動作を制御する追加機能を提供
%1ソースディレクトリで選択したファイル名(カレントディレクトリ以外はパス名を付加)
%2ターゲットディレクトリのディレクトリ名(選択されたファイル名があれば付加)
%*ソースディレクトリの全選択ファイル名
%!DOSコマンドウィンドウを表示せずに実行
%^コマンド終了後ファイラーに戻る
%@nnファイル超の選択時にレスポンスファイルを使用
%:ソースディレクトリのディレクトリ名
%G入力窓で文字列を取得(スキップ条件あり)
%F入力窓で文字列を取得(スキップ条件なし)
%\【\】を補完 直前がスペースの場合はスキップ
%[opt]ディレクトリ選択時に[opt]を挿入

&

トークン種別書式機能
&二項演算子a&bビット毎の論理積(AND)
&特殊文字\x a&b正規表現検索モードでのAND検索の区切り文字
&&論理演算子a&&b論理積
&=代入演算子a&=bビット毎の積(AND)を代入
&+nフロー制御&+2実行中のマクロの+n番のマクロをコール
&-nフロー制御&-3実行中のマクロの-n番のマクロをコール
&?システム関数&d &q関数関連覚書を参照
&#nnコマンド関数&#68(41)関数関連覚書を参照
&#?コマンド関数&#V(41)関数関連覚書を参照
&S?文字列関数&Su(p)関数関連覚書を参照
&L? &I?ロング演算関数&Le(p,q) &Im(p,n)関数関連覚書を参照
&F?ファイラー関数&Fl("*.*")関数関連覚書を参照
&Z?特殊関数&Zs(p)関数関連覚書を参照
&J?特殊関数&Ja関数関連覚書を参照
&E?特殊関数&Ea(2)関数関連覚書を参照
&Bm &Cr特殊関数&Bm &Cr関数関連覚書を参照

'

トークン種別書式機能
'特殊文字'c' '字'文字を打鍵したように出力 'ab'と'a''b'は同じ

(

トークン種別書式機能
(特殊文字(r)? … 演算式を評価して内部レジスタに格納 ※開き括弧はなくても動作する
(特殊文字&a()関数の引数をくくる
(特殊文字(a-b)+c式の中で演算の優先順位を明示 ※開き括弧が余分に必要な場合もある

)

トークン種別書式機能
)特殊文字(r)? … 演算式を評価して内部レジスタに格納
)特殊文字&m(21)関数の引数をくくる
)特殊文字(a-b)+c式の中で演算の優先順位を明示

*

トークン種別書式機能
*識別子*Macro行頭にあり続く1文字のシンボルによってセクションの開始(シンボルがなければセクションの終了)を示す 2文字目以降は無視
*二項演算子a*b
*特殊文字foo*検索文字列中の検索時のワイルドカード文字 オプションEwの指定の逆に作用する
*特殊文字*comment*(マクロ文中の)次の*まで読み飛ばす
*=代入演算子a*=b積を代入
&*フロー制御&*最後に組み込んだマクロをコール
>*フロー制御>*最後に組み込んだマクロにジャンプ

+

トークン種別書式機能
+二項演算子a+b加算
+単項演算子a+aに1を代入 a=+ の略だが = を省いた代入よりも優先順位は低い a+++ a3+ はともに1になる
+代入演算子a=+代入式において値1を指定
++単項演算子a++インクリメント
+=代入演算子a+=b和を代入
&+nフロー制御&+2実行中のマクロの+n番のマクロをコール
>+nフロー制御>+2実行中のマクロの+n番のマクロにジャンプ

,

トークン種別書式機能
,特殊文字式,演算式の区切り文字 式を式文として閉じる

-

トークン種別書式機能
-二項演算子a-b減算
-単項演算子a-aに0を代入 a=-の略だが=を省いた代入よりも優先順位は低い a--- a3- はともに0になる
-代入演算子a=-代入式において値0を指定
-代入演算子-a負の符号 正負を逆転させる
-書式制御%-6d書式制御オプション 出力桁幅指定時に左詰めで出力
--単項演算子a--デクリメント
-=代入演算子a-=b,差を代入
&-nフロー制御&-2実行中のマクロの-n番のマクロをコール
>-nフロー制御>-3実行中のマクロの-n番のマクロにジャンプ

.

トークン種別書式機能
.フロー制御(r<0)?.実行中のマクロを終了して呼出元に戻る
.二項演算子a.0バイトポインタ(byte ptr)演算子(セグメントは dz)
..二項演算子a..0ワードポインタ(word ptr)演算子(セグメントは dz)

/

トークン種別書式機能
/フロー制御(r)??/マクロの実行を終了(呼出元に戻らない)
/二項演算子a/b除算(余りは切り捨て)
/=代入演算子a/=b商を代入(余りは切り捨て)

:

トークン種別書式機能
:識別子05 ^E [UP] :Upコマンドセクションのヘルプ表示用コメント
:フロー制御:A次の1文字をラベルにする

;

トークン種別書式機能
;識別子;Comment以降行末までを読み飛ばす

<

トークン種別書式機能
<論理演算子a<b小なり
<=論理演算子a<=b以下
<<二項演算子a<<1ビット左シフト(上位にシフト)
#<コマンドコマンド関連覚書を参照

=

トークン種別書式機能
=識別子マクロヘッダのタイトル行であることを示す識別子
=代入演算子a=b代入(省略すると前の括弧も省略されているのと同じ扱い)
!=論理演算子a!=b等しくない
%=代入演算子a%=b剰余の代入(一定範囲の数値に丸める)
&=代入演算子a&=bビット毎の論理積(AND)を代入
*=代入演算子a*=b積を代入
+=代入演算子a+=b和を代入
-=代入演算子a-=b差を代入
/=代入演算子a/=b商を代入(余りは切り捨て)
<=論理演算子a<=b以下
==論理演算子a==b等しい
>=論理演算子a>=b以上
[=代入演算子a[=bプッシュ後代入(a[,a=b)
^=代入演算子a^=bビット毎の排他的論理和(XOR)を代入
|=代入演算子a|=bビット毎の論理和(OR)を代入

>

トークン種別書式機能
>フロー制御>A続く1文字の示すラベルにジャンプ
>論理演算子a>b大なり
>=論理演算子a>=b以上
>>二項演算子a>>1ビット右シフト(下位にシフト)
>?フロー制御(式)>?{ … }直前の(式)の数値に応じたブロックにジャンプ
>!フロー制御(式)>!{ … }(Case goto) 直前の(式)の値に対応した文字に続くブロックを実行し,対応文字がなければ「|」の次に示されたelseを実行
>+nフロー制御>+3実行中のマクロの+n番のマクロにジャンプ
>-nフロー制御>-2実行中のマクロの-n番のマクロにジャンプ
>^フロー制御>^実行中のマクロの先頭にジャンプ

?

トークン種別書式機能
?フロー制御(式)?.内部レジスタが0なら次の命令文/ブロックをスキップ
#?コマンドコマンド関連覚書を参照
&?()システム関数関数関連覚書を参照

@

トークン種別書式機能
@[Key]識別子@[BS][ALT]+特殊キー/[Grph]+特殊キー(NEC-98)
#@コマンドコマンド関連覚書を参照
%@nファイルメニュー%@2nファイル超の選択時にレスポンスファイルを使用

[

トークン種別書式機能
[単項演算子a[プッシュ(push)
[=代入演算子a[=bプッシュ後代入(a[,a=b)
[+代入演算子a[+プッシュ後に1を代入(a[,a+)([=+の略)
[-代入演算子a[-プッシュ後に0を代入(a[,a-)([=-の略)
[Key]識別子[F01] [INS]特殊キー
\[Key]識別子\[END] \[CR][Shift]+特殊キー
^[Key]識別子^[HOME] ^[-->][Ctrl]+特殊キー
@[Key]識別子@[DEL] @[PGUP][Alt]+特殊キー/[Grph]+特殊キー(NEC-98)
#[コマンドコマンド関連覚書を参照
$[n,c]特殊文字n個の文字cを展開 自前バッファの簡易設定(v1.6)
$[n]特殊文字n個のヌル(0)を展開 自前バッファの簡易設定(v1.6)
%[??]ファイルメニュー%[/s]ディレクトリ選択時に有効

\

トークン種別書式機能
\エスケープシーケンス\* \\前置して特殊文字を機能させない
\\ \を機能させない
\* *を機能させない
\[nlr]エスケープシーケンス\n \l \r改行文字を文字列中に表現する
\n:[CR/LF](0D0Ah)
\l:[LF](0Ah)
\r:[CR](0Dh)
\[wczxi]検索オプション\x ^\*検索文字列の先頭で指定
(\wcのように複数指定可)
オプション頭文字機能
\wWord searchワードサーチ 強制的にew+の動作
\cignore Case大文字/小文字の同一視 強制的にec+の動作
\zignore Zen/han全角/半角文字の同一視(VWX常駐時ZH+の動作)
\xregular eXpr.正規表現モード(VWX常駐時RX+の動作)
\iIgnore [*\]特殊文字【*】【\】の処理をさせない

]

トークン種別書式機能
]単項演算子a]ポップ(pop)
[Key]識別子[F01] [HOME]特殊キー
\[Key]識別子\[END] \[CR][Shift]+特殊キー
^[Key]識別子^[HOME] ^[TAB][Ctrl]+特殊キー
@[Key]識別子@[DEL] @[PGUP0][Alt]+特殊キー/[Grph]+特殊キー(NEC-98)
$[n,c]特殊文字(3+$)."$[8,'0']"n個の文字cを展開 自前バッファの簡易設定(v1.6)
$[n]特殊文字a3+$,."$[80]"n個のヌル(0)を展開 自前バッファの簡易設定(v1.6)
%[??]ファイルメニュー%[/s]ディレクトリ選択時に有効

^

トークン種別書式機能
^^論理演算子a^^b排他的論理和
^[Key]識別子^A ^Q ^[CR][Ctrl]+英字 [Ctrl]+特殊キー Prefix-Key
&^システム関数&^関数関連覚書を参照
>^フロー制御>^実行中のマクロの先頭にジャンプ

_

トークン種別書式機能
[Prefix]_識別子[Prefix]+[Nfer] 6番目の[Prefix]+_は未定義(絶対使用禁止)
&#_()コマンド関数関数関連覚書を参照

`

トークン種別書式機能
`未定義未定義

{

トークン種別書式機能
{フロー制御(r)?{ … }ブロック開始 ループの先頭ポインタにあたる(mg..5に実行のたびに記憶)

|

トークン種別書式機能
|フロー制御(r)>!{ … | . }>!におけるelse
|特殊文字\x a|b正規表現検索モードでのOR検索の区切り文字
|二項演算子a|bビットごとの論理和(OR)
|=代入演算子a|=bビット毎の論理和(OR)を代入

}

トークン種別書式機能
}フロー制御(r){ … }ブロック終了 ループカウンタ(mg..4)をデクリメントしてループ先頭にジャンプ

~

トークン種別書式機能
~単項演算子a~(a=~a) (1の補数、ビット反転)

空白類

トークン種別書式機能
<SPC>空白類20h 1バイトスペース(\s) &o($20)またはFEP無効時に<SPC>キーで出力 空白類の代表で桁幅指定出力のパディング 文字列中にあるときを除きマクロバッファには読み込まれない yes/noの応答にも使用(解釈は設定に依存)
<CR/LF>空白類0D0Ah改行コード(\n) MS-DOS標準の改行コードでマクロバッファには読み込まれない空白類として扱われる
<TAB>空白類09hタブコード(\t) &o(9)またはEu-時に#21で出力され空白類として扱われる メニューアイテム内では表示部とスイッチ部の区切りに使用される
00h...1Fh空白類文字タイプ(ct)が1か2を示すコードで[TAB]と[CR/LF]を除いてキーボードから直接入力できない制御文字 コマンド#22を使用して入力可能
0Ah(\l)空白類表示上は改行と扱われるがVWXで正規表現上の行末とは扱われないことに注意
0Dh(\r)空白類表示上は改行と扱われないが文字タイプ(ct)は2を示す 見掛け上は「^M」のように見えるが1バイト ステータス情報では[0D0Ah]と表示されることに注意 0Dh直後0Ahを出力すると0D0Ahになる
' '文字2バイトスペース(8120h) 「漢字」だが文字タイプ(ct)が2を示す マクロ中ではもちろん空白類として扱われず生で存在すればエラーとなる !Openイベントで (ne=='DE')?{ ts"*=", td62, qk="{}", }のように疑似表示することが推奨される

文字

トークン種別書式機能
'!'...'~'文字'a' 'z'キー通りの文字を入力 文字タイプ(ct)が記号(ct==3)と英数字(ct==6)に大別される '$'と'_'はct==6であることに注意

トークン詳説

識別子

識別子には =()*#^[];:@\_- がある。

識別子用例
モジュール化マクロファイルの基本書式
==== macro_title =====モジュールヘッダ・タイトル
(Removable Sleep)( )モジュールオプション
* Macro*セクションの開始
#if 98#環境条件文
1 ^[Key1] "タイトル"\ ^ @ [ ] " "マクロ番号起動キータイトル
#else
1 "タイトル"起動キーは必須ではない
#endif環境条件文の終了
;マクロ本体;コメント
*セクションの終了
==== end of macro ====必須ではない

=】 モジュールヘッダ・タイトル(v1.6)

マクロファイル冒頭にモジュールヘッダのタイトルが必須である。マクロヘッダのタイトルは行頭から始まる1個以上の識別子「=」とタイトル文字列から構成される。v1.57までは必須ではなかったが,このスタイルで記述することが通例となっていた。

マクロバッファに読み込まれる際,モジュールヘッダのタイトルの先頭から8文字以内が「モジュールID」となる。

モジュールヘッダの構造
相対アドレスデータ長データ要素
+0Byteモジュールヘッダ番号(=0FFh)
+1Wordモジュールヘッダのサイズ(=m)
+3Byteモジュールフラグ include/Zは0
+1 Sleep,+2 Removable,+4 External,+80h keyboard macro
+4Word次のモジュールヘッダのリンクポインタ
+6WordモジュールIDの長さ(=n)
+8nモジュールID(8文字以内のファイル名,大文字に変換される)
+n+9m-(9+n)モジュールのタイトル文字列
(文字列末尾にヌル1バイトがあり,次にマクロ番号のmg..2..2)

#】 環境条件文(v1.6)

行頭の識別子【#】に続く条件文のある場合,引数の条件を満たす場合のみマクロバッファに読み込まれる。条件文は if , ifn , else , elseif , elseifn , endif が使用できる。なお,製品版では条件文のネストは認識しない。

引数は 98 , DOSV , IBM , IBMJ , J31 , AX , PS55 , US といったハードウェア依存の部分を選択するためのステイタスが予約語として採用されている。

高橋版で拡張が行われ,環境変数 VZSYM の定義によってユーザ設定の自由な切替が行えるようになった。空白区切りで複数指定可能となっている。大文字小文字を区別する。

ハードウェア切替の例
#if 98
1 [HELP] "Help" &z("helpe") >*
#elseif J31
1 [F10] "Help" &z("helpe") >*
#else
1 [F12] "Help" &z("helpe") >*
#endif

#if 98
        (ks&16)
#else
        (ks&4)
#endif
VZSYMによる切替の例

高橋版で採用された環境変数 VZSYM は主にautoexec.batや起動バッチに記述する。ステータスを複数指定する場合は空白で区切る。大文字小文字を区別し,いくつでも指定が可能になっている。#if構文がネスト可能となったこととあわせて,非常に強力な機能となっている。

set VZSYM=s16 LX

* Command keys
#if LX
00      ^Q ^K [ESC] ^O ^I       ;Prefix Keys (max6)     ;Don't prefix6+_
#else
00      ^Q ^K [ESC] ^O [NFER] ^I;Prefix Keys (max6)     ;Don't prefix6+_
#endif
高橋版に初期設定済みのステイタス

98 , DOSV , IBM , IBMJ , J31 , AX , PS55 , US , VIVI , tVZ , t{number} , HIRO

tVZで高橋版の判定ができるので,高橋版で追加されたオプションは tVZ を指定するとよい。

#if tVZ
Is+     ;ファイラーでのインクリメンタルサーチ
#endif

(】 モジュールオプション

モジュールヘッダに続く行頭の【(】とシンボル1文字はモジュールオプションと解釈される。通常は(Removable)のように機能を明示するが,頭文字1文字だけで認識する。大文字小文字を区別しない。複数のオプションを設定したい場合は,空白で区切って「(Removable Sleep)」のように記述する。

製品版は (R) と (S) の2種類のオプションが有り,高橋版で (Z) が追加された。それぞれの機能は次の通り。

モジュールオプション一覧
シンボル正式名機能モジュールメニューの表示
(R(Removable)自動削除可能R モジュール名
(S(Sleep)モジュール先頭マクロが実行中でない場合は2番目以降のマクロは機能させないE.のようにピリオドが付与される
(Z(Z自動削除されない無印(インクルードマクロと同じ)
用例
==== mheader ====
(Removable)

==== mheader ====
(Removable Sleep)


モジュールメニューの表示例

図

R  test1 Removable
E. test2 Sleep
   test3 Z
R. test4 Removable + Sleep

*】 セクションの開始・終了

行頭の【*】に続く1文字のシンボル(大文字小文字を区別しない)によってセクションの開始(シンボルがなければセクションの終了)を示す。2文字目以降は無視される。 シンボルの種類は以下のものがあり,ほとんどは基本設定ファイルで記述されているものであって,通常のマクロで使用するのは「M マクロ」と「P メニュー」だけと考えて差し支えない。他のセクションを記述する必要があるのは,イベントマクロなどの基本動作に強く影響するマクロなどである。

用例
==== test ====
* m ←「Macro」である必要はない。「Music」でも「madness」でも可。
1 ^[F01] "テスト"
        ?.
        ;以下略
* popup menu ← 別のセクションの開始
1 "メニュー",16,1,,-1
        "アイテム"
*
↑ セクションの終了
セクションの種類
セクション種別
シンボルセクションの内容
* C [Command keys]コマンドキー
* F [Function key labels]ファンクションキー表示
* T [Text windows]テキストウィンドウ
* S [System menus]システムメニュー
* M [Macro keys]マクロ
* P [Popup menus]メニュー
* O [Options]オプション
* A [Alias]エイリアス
* H [History]ヒストリー
* E [Else]その他

マクロ番号

マクロには番号が必要であり,グローバルマクロ,ローカルマクロとも割当て可能な番号の範囲が決まっている。番号による呼出先でないのであれば,あまりマクロ番号を気にする必要はない。マクロ番号は別個のマクロで重複しても構わないし,同じモジュールの中に同じ番号のマクロがあっても差し支えない。ただし,一旦マクロ番号による呼出ルーチンを記述したら,厳密に管理しなければならない。

グローバルマクロ番号,ローカルマクロ番号,メニュー番号はいずれも1番から番号を振る体裁になっているが,これらの番号は内部的には共通の番号帯のうちにあり,使用上の制限がある。

マクロ番号割当て規則

公式には以下のようになっている。

グローバルマクロ番号1〜89
キーボードマクロ番号(自動付与)90〜99
ローカルマクロ番号1〜99
メニュー番号1〜30

実際には以下のようになっている。

グローバルマクロ番号(キーボードマクロ番号帯を含む)1〜127
ローカルマクロ番号(メニュー番号帯を含む)0〜126

キーボードマクロの自動付与番号は,90番以降でグローバルマクロの最も大きな番号の次の番号が割り振られる。つまり,グローバルマクロで90番を使っていれば,キーボードマクロは次の91番から振られることになる。仕様はいずれにしても特別な理由のない限り,グローバルマクロの番号は89番までとし,マクロローダ等で使われることの多い87〜89も空けておいた方が無難だ。

また,ローカルマクロ番号も通常は95番までにとどめておくべきである。通常の使い方をする以上,メニュー用の番号は空けておかなければならないからだ。キーボードマクロの場合は自動的に使える番号を振ってくれるが,メニューはそうはいかない。

マクロ&メニュー番号詳細
区分番号内部設定値(十六進数)割り当てられる要素
マクロ1〜891〜89(01h〜59h)グローバルマクロ(127まで使用可能)
マクロ90〜12790〜127(5Ah〜7Fh)キーボードマクロ(グローバルマクロが使用可能)
マクロ0:〜96:128〜223(80h〜DFh)ローカルマクロ(126:まで使用可能)
メニュー0224(E0h)ヘルプメニュー(ローカルマクロが使用可能)
メニュー1〜30225〜254(E1h〜FEh)メニュー(ローカルマクロが使用可能)

このように想定された範囲を越えてマクロ番号は割当て可能である。しかし,>100 &100 のように3桁の番号を指定してジャンプ/コールできるわけでもなく,ただ >> &> が使えるのみでは,わざわざ使うメリットはない。(マクロ,メニューの番号指定の解釈は数字2文字によって行われ,3桁以上に限らず >1 &1 のような1桁も正常に解釈されない。1桁の場合は2桁になるようゼロをパディングして >01 &01 のように記述する。)

ただし,メニューに相当する番号を付与したマクロが実行できるということで,擬似メニューのような技術が開発されるに至ったことを思えば,マクロ達人の探求心はおそるべしと敬愛の念を禁じえないのである。

= test
* m
1 ^\ "test" ?.
        r="test",
        &>
        !30

125:    ; 125までは問題ないが,126はメニューの30と重なるので問題あり
        &m("Result: %d %u $%x '%c' $"%16s$"",r,r,r,r,r)
* p
30 "menu",10,1,,-1 "item"
*

^ @ \ [ ]】 起動キー

^】【@】はCtrlキーとAlt(Grph)キーといった機能キーの表記である。【[】と【]】で括って特殊キーを表記する。【\】はシフトキーを示す表記である。

起動キーの割当て規則
定義の方法

マクロ番号とタイトルの間に0個〜2個の起動キーを記述することで定義する。常用でないマクロであれば起動キーを与えず,外部マクロ実行([ESC]^)機能で組込み・実行したり,組み込んだあとはモジュールメニュー([ESC]M)から実行するという選択肢もある。

==== macro ====
* m
1 [第1キー] [第2キー] "タイトル"
ひとつのコマンドまたはマクロについての定義規則
用例1 2個のキーのうち1個が特殊キー
24 ^L [F05] "Get Right Words"

× ^[-->] [F05] "Get Right Words"
用例2 3番目以降のプリフィクスキーを先に記述
8  [ESC]I @I "Insert File"

× @I [ESC]I "Insert File"	;起動できない/同じキーの他のマクロが起動される

この理由は,2個目にアサインされるプリフィクスキーの値は7Fhまでしか入れず,80h以上の場合は2番目のキーの値で1番目のメモリを上書きしてしまうためである。

…といった制限によるものだ。

特殊キーは3番目以降のプリフィクスキーと値が重複しており,次の表のようにメモリに格納される。

キー値1番目のキー2番目のキー
00h-7Fh^A, @A, Pfx1, Pfx2Pfx1, Pfx2, ^A, @A,
80h-FEhPfx3, Pfx4, Pfx5, Pfx6[Key], \[Key], ^[Key], @[Key]

例えば,[F01] [ESC]A のように2個のキーを記述すると,メモリ上では記述順ではなく [ESC]A [F01] の順に格納される。もっとも [ESC]A[F01] はキーの値が同じなのでメモリダンプしても見分けがつかないであろう。:-)

なお,プリフィクスキーと特殊キーの重複する値の対応表は「コマンド拡張で学ぶVZマクロ入門 #00」に掲載してあるものを参照されたい。

キーストローク

コマンド,マクロの起動キーとして機能キー(CtrlやAlt)とともに押したり,2ストローク(プリフィクス)キーの2打目に押せるキーは,キーボードから直接入力でき,次の文字コードの範囲(40h〜5Fh)に含まれる。

+$0+$1+$2+$3+$4+$5+$6+$7+$8+$9+$A+$B+$C+$D+$E+$F
40h@ABCDEFGHIJKLMNO
50hPQRSTUVWXYZ[\]^_

プリフィクスキーの直後に押して戻り値を返すキーは,次の文字コードの範囲(30h〜3Fh)に含まれる。

+$0+$1+$2+$3+$4+$5+$6+$7+$8+$9+$A+$B+$C+$D+$E+$F
30h0123456789:;<=>?
参考:^ @ \ [ ] の別の解釈
^コマンド#^
関数&^
フロー制御>^
二項演算子^
論理演算子^^
代入演算子^=
ファイルメニューオプション%^
@起動時オプション@, @@
\DOS:パスの区切り文字\fg.33の部品
エスケープシーケンス(制御文字)\n, \l, \r, \*, \\
特殊文字(検索オプション)\x, \0, \w, \c, \z, \i
ファイルメニューオプション%\
[単項演算子[
代入演算子[=
コマンド#[
文字列内の展開出力$[n], $[n,c]
]単項演算子]
コマンド#]
文字列内の展開出力$[n], $[n,c]

"macttlstr"】 マクロタイトル文字列

マクロタイトル文字列は,特にこれといった機能はないので,指定しなくても差し支えないが二重引用符の省略はできないので,省略する場合も「""」のように記述する必要がある。大きなマクロバッファを確保できなかった v1.5 以前には,積極的なタイトルの省略が推奨されていた。[1]

まったく意味がないかというとそうでもなく,メニューのアイテムとしてマクロの呼出命令を記述した場合は,メニューに表示させる文字列として参照されるので,マクロメニューを利用するのであれば大いに活用したいところだ。例えば,実行環境(マクロのシフト実行や実行可能モードなどを記述してもよいし,後述する展開出力機能を利用して変数・オプションの現時点の値を表示するのもよいだろう。

v1.6 以降は比較的大きなマクロバッファを確保できるようになったので,こういったインターフェイス部分にメモリ資源を投入することも意義のあることだ。

なお,マクロローダによっては,マクロタイトルに実行モード別情報を記述して,実行キーを縮減する機能を持つものもある。[2]

記述例
* m

;;---- 略 ----;;

; ---- System Menu #2 ▼ ---- 番号変更不可

58 "Disp L/P ($dn)"     ;H-Oka 頁+行番号の即効トグル表示
        ?. dn!, dl!, &d #]      ;Sub menu

59 "Rmargin($we)"       ;H_ear
        ?.
        (we!=wd)
        ?we=wd,
        ?{      (ks&1 && wt>3)
                ?{ (2){ #W we=wx, } (0) }
                ?we255,
        }
        &d

; ---- System Menu #2 ▲ ---- 番号変更不可


* p
3       "",16,12
        &58 &59 #45 #52 #54 #67 #68 #79 #83 &21
;       L/P MGN ^QJ ^QB ^QL ^KR ^KH eX  eW  Tab
        "auto Save mode",!06
        "$4EMS p/P: $ef/$em"

参照:"の別の解釈
文字列の出力
関数の引数内文字列

;】 コメント(以降行末までを読み飛ばす)

その名の通り,注釈を付すために用いられ,「;」以降行末までバッファに読み込まれない。プログラムコードのメンテナンスのために分かりやすいコメントを付与することは,多くのプログラム言語で推奨されている。記号に近く可読性の低いVZマクロにおいては,コメントを付与することを習慣とすることは半ば常識とも言える反面,簡単に書けて用途の限定されやすいマクロの性格上,習慣化しにくいことも確かであるようだ。

マクロバッファには読み込まれないが,マクロバッファに転送される前に一時バッファに読み込まれるため,表示数行にも及ぶような極端に長いコメントは書かない方が無難である。

「;」は高級言語の「//」に当たる。「/*〜*/」に当たるものは「*〜*」とも言えるが,非公式な用途であり,かつマクロバッファを占有するため,コメントには極力「;」を使うべきである。

なお,一時的に実行させない行の行頭などにコメント記号をつけることを,「コメントアウト」と言う。簡便で有効なデバッグ手法なので,ぜひ活用したい。

用例:
コメントアウトを利用して更新履歴を残す
;41 [CR] ""     ;indelcr        H_ear
;       #m &s kx[, #< #s ' ' #16 kx], #x &d
41 [CR] ""      ;YASAT
        ?. el[+, #28 #m el],
注意:
「* C」のコマンドセクションにおいては,コメント機能を使っているのは #00 だけであり,他のコマンドはヘルプメニュー表示用の「:」(コロン)になっている。「:」から行末までマクロバッファに読み込まれるので,文字数を減らすことでマクロバッファの余裕が増加する。

:】 コマンドセクションのヘルプ表示用コメント

「* C」のコマンドセクションにおいては,ヘルプメニュー表示用の「:」(コロン)になっている。「:」から行末までマクロバッファに読み込まれるので,文字数を減らすことでマクロバッファの余裕が増加する。

一方で #00 だけは通常のコメント機能を使っている。混同しないよう注意したい。

参照::の別の解釈
:ラベルの識別子:A
ローカルマクロ番号の識別子01:

特殊文字

特殊文字には 「$」「\」「"」「&」「'」「(」「)」「*」「,」「|」「{」「}」 がある。

マクロコード中の特殊文字,文字列中の特殊文字,検索オプション特殊文字

$

特殊文字「$」は,十六進数の明示のほか,特殊文字に前置して特殊文字の意味を打ち消したり,実行位置のポインタを示したり,展開出力する直値や変数を示したり,表示属性を制御したり,…といった幾多の機能を持つ。

$】 実行位置のポインタ

$自身のマクロバッファ内のオフセットアドレスを示す。主な用途は以下のとおり。

自前バッファの確保
        &01 &02
1:      a3+$,."$[80]"           ;通常バッファ
2:      (5+$)."$(50,0)$[80]"    ;履歴バッファ
メニューヘッダのアドレス取得
10:     (9+$).  ;$の次の)から9バイト先がメニュータイトル
* popup menu
1       "0000/00/00(・)",16,7
        "O ?日前後"
        "B   一昨日"
自己書換位置アドレスの取得
1 ^\ "" ?.
        a-,
 :a     b=5+$, &m("・・・・")
        (a%2)?c="おはよう", ?c="おやすみ",
        &Sp(b,"%s",c) &w(60)
        (a++<6)?>a
        .
$】 特殊文字の意味を打ち消す

文字列中にある特殊文字の意味を打ち消すときに前置する。対象となる特殊文字は である。

$$】 「$」自身

文字列中に「$」文字そのものを出力する。 特殊文字の意味を打ち消して「$」そのものを文字列中に含ませるための機能である。

正規表現で行末にマッチさせようとするとき,指定文字列の末尾に「$」があることになる。これをこのままにしてしまうと,文字列末尾を示す「"」の特殊文字としての機能が無効になってしまい,後続のマクロコードの部分も延々と文字列が続いているという扱いになってしまう。

用例
×      #F &?("\x ^<h%d[^>]*>.+</h%d>$",h,h) #m
                                     ↓
○      #F &?("\x ^<h%d[^>]*>.+</h%d>$$",h,h) #m
参考
末尾に「$」のある文字列を検索文字列にセットするには
$"】 「"」そのもの

文字列中に「"」文字そのものを出力する。文字列終端を示す「"」の意味を打ち消して文字列中に含ませるための機能である。

用例
        &m("Press the $"ENTER$" key after verifying that ...")

        → 「Press the "ENTER" key ...」と出力される
参考
「"」を含む文字列を検索文字列にセットするには
$0〜$7】 表示属性

文字列中で表示色を変更する

0:黒 1:青 2:緑 3:水 4:赤 5:紫 6:黄 7:白
(IBM-PCは低輝度)

用例
        &g("$00:黒 $11:青 $22:緑 $33:水 $44:赤 $55:紫 $66:黄 $77:白")
図 のように表示する
$!】 表示属性

文字列中で表示色を反転する(リバース)

用例
        &g("表示を $! 反転 $! する")
図 のように表示する
$p?】 展開出力

文字列中で変数p?の示す文字列を展開表示する

用例
テキストウィンドウのタイトルでの実例を示す
* Text windows

"Open File: <$pd> [$wc/$tc]",50
"Search: <OPT:$po> \[wczxif]",50
"Replace: [$ps] to ?",50
pd(カレントディレクトリ名)を表示する例
図1
po(検索オプション文字列)を表示する例
図2
ps(検索文字列)を表示する例
図3
$a?】 表示属性

文字列中でオプションa?の示す表示色に変更する

用例
&g("abc$andef$aoghi")
入力窓のタイトルに 図 のように表示される(色はAnとAoに依存)
使用可能な表示属性のオプション
An通常のテキスト
Acコントロール文字1
Aeコントロール文字2(v1.6)
Al行番号
Ahブロック領域
Asステータスライン
Aoコンソールファイルのステータスライン
Arリードオンリファイルのステータスライン
Am最下行のメッセージ
Abウィンドウわく
Atウィンドウタイトル
Awウィンドウメニュー
Afウィンドウメニューのダイレクトキー
Aiウィンドウメニューのカーソル
Ajヘルプウィンドウ
Ak2ストローク先頭キー
Agファイラーの背景
Auファイラーのカーソル
Apファンクションキー(IBM-PC版)
AdDOSコマンドライン
Ayステータスラインのファイル名(v1.6)
Avアンダーラインのテキスト属性
$??】 展開出力

文字列中で変数??の十進表示を展開

用例
        &g("タブ幅は$htカラム")
入力窓の標題に 図 のように編集中テキストのタブ幅が表示される。
$(hh,hh,..)】 展開出力

文字列中で十六進コードを指定して展開出力

用例
        &g("$(83,5e,83,43,83,67,83,8B)")
入力窓の標題に タイトル と表示される
注意
&?("$(0d,0a)")のように記述すること自体は間違いではなく,マクロバッファにも読み込まれるが,改行コードの出力はできない。
$[n]】 展開出力

文字列中にnバイトのヌル(0)を展開出力する。 自前バッファの簡易設定に利用できる(v1.6)。

用例
自前バッファの設定例を示す
        &>                      ;[a] 変数aにアドレスを取得して戻る
        &01 b=r,                ;[b] 戻ってから変数bにアドレスを取得

        ; ---- buffers ----
0:      a3+$,."$[80]"           ;通常バッファ
1:      (5+$)."$(50,0)$[80]"    ;履歴バッファとしても使える
アドレス指定位置は以下のように数える
0:      a3+$,."$[80]"           ;サイズ情報がない
;           0123
1:      (5+$)."$(50,0)$[80]"    ;サイズ情報は2バイト
;           012  3  4 5
$[n,c]】 展開出力

文字列中にnバイトの文字cを展開出力する 自前バッファの簡易設定に利用できる(v1.6)

用例
        &m("123$[8,'0']456")
        ;メッセージラインに「12300000000456」と表示される

        &?("{$[2,9]}")
        ;カーソル位置に「{<- TAB -><- TAB ->}」と出力される


0:      (3+$)."$[16,'@']"
        ;自前バッファに「(3+$)."@@@@@@@@@@@@@@@@‥」のように展開される
        ;「‥」はヌル(00h)2個を示すものとする

'】【"】 文字,文字列の出力

'c' '字' 文字を打鍵したように出力 'ab'と'a''b'は同じ

"abc" "文字列"を括る二重引用符 打鍵されたように出力

(】【)】 括弧

(r)? …  演算式を評価して内部レジスタに格納 ※開き括弧はなくても動作する

&a() 関数の引数をくくる

(a-b)+c 式の中で演算の優先順位を明示 ※開き括弧が余分に必要な場合もある

,】 式を式文として区切る

*】 強制スキップ

検索オプション等

\】 エスケープシーケンス

\】 検索文字列中で検索オプションを指定

\】 検索文字列中で特殊文字を機能させない

&】 正規表現モードにおけるAND

|】 正規表現モードにおけるOR

演算式

演算式を構成するトークンは,定数変数演算子で,式の終わりには「,」か「)」が必要となっている。「,」は式を評価せず,「)」は式を評価して値を内部レジスタに格納する。(評価する場合は「(」と「)」で括るのが通例だが,閉じ括弧だけでも動作する。)

定数

定数には,十進数,十六進数,文字,文字列,コマンドコードがある。

種別記述例解釈
十進数0, 100, -234正規表現[0-9]+にマッチする数字は十進数と解される。前置できる符号は「-」(マイナス=負数)のみ。行頭ではマクロ番号と解される。
十六進数$0A, $100正規表現[0-9A-Fa-f]+にマッチする文字(集合)に「$」を前置すると十六進数と解される。$の前に符号を付けることもできなくはないが,混乱の元となるので推奨できない。
文字'A', 'あ', '亜'単一引用符で括った文字は,その文字が示す文字コードの値として解される。コード体系はシフトJIS。
文字列"string"二重引用符で括った文字の集合は「文字列」と解され,先頭文字のアドレスを示す。
コマンドコード#nn「#」を前置した2桁の十進数(10に満たない場合は10の桁に「0」を埋め込む)はコマンドコードと解され,コマンド番号+256($100)の値を示す。「#」に特定のアルファベットあるいは記号の続くとき,変換テーブルを参照してコマンドコードに変換される。

変数

式を構成するトークンのひとつである変数には,汎用変数,特殊変数,汎用フラグ,オプション,システム変数がある。このうちフラグ変数以外は1バイト,それ以外は2バイトの値を格納できる変数である。個々の変数についての詳細は,「変数・オプション・スイッチ関連覚書」を参照してほしい。変数名から機能・内容の詳細を知るには,「変数・オプション・スイッチ一覧表」からリンク先となっている。なお,変数はすべて広域変数であり,どこで使用しても構わないが値の保存には向いていないことに注意が必要だ。

種別記述例解釈
汎用変数a〜q,t〜y,aa〜qq,tt〜yyユーザ変数として自由に使用できる広域変数(システム変数のr,rr,s,ss,z,zzを除く,v1.5x以前はw,wwも除く)
レジスタ変数ax,bx,cx,dx,di,siシステム機能呼出時の機能番号や値の受け渡しや戻り値の格納に使用される
汎用フラグfa,fb,fc,fd,fi,fj,fx,fy0か1の値をとるフラグとして使用する。1バイトだが0〜255の値が格納できるわけではない。無理に代入しても偶数は0,奇数は1に丸められる。
オプション起動後代入不可能なものがあることに注意
オプション類は大別して以下のものがある.
システム変数動作中代入が無意味なものがあることに注意
システム変数は大別して以下のものがある.

演算子

式を構成するトークンのひとつである演算子には,単項演算子二項演算子論理演算子代入演算子がある。演算の優先順位はおおむねこの並び順になるが,個々の優先順位はソースファイルexpr.asmのOperater tableセクションの出現順に優先度が高い。

演算子の種類演算子一覧
単項演算子[, ], ++, --, ~, !, !!
二項演算子.., ., <<, >>, &, ^, |, *, /, %, +, -
論理演算子<, <=, >, >=, ==, !=, &&, ^^, ||
代入演算子=, [=, &=, ^=, |=, *=, /=, %=, +=, -=
単項演算子
演算子用例機能
[a[プッシュ:aの値をマクロスタックに格納してスタックポインタを更新 [変更]
]a]ポップ:マクロスタックの値をaに格納してスタックポインタを更新 [変更]
++a++aの値を加算(インクリメント)「a=a+1」と等価
--a--aの値を減算(デクリメント)「a=a-1」と等価
~a~aにaの補数を格納(1の補数、ビット反転)「a=~a」と等価
!a!aにaの論理否定値を格納(0以外は1,0は1)「a=!a」と等価
!!a!!上位バイトと下位バイトの交換
※以下のものは優先順位が二項演算子と同様に扱われる
演算子用例機能
+a+a=1と同じ (もともと代入との組合せで「=+」の省略形だが,代入演算子の省略により演算の優先順位が変化している。)
-a-a=0と同じ (もともと代入との組合せで「=-」の省略形だが,代入演算子の省略により演算の優先順位が変化している。)
--a符号の反転 a*=-1と同じ
※注意事項
  • ポインタで示されるアドレスの値をプッシュする場合のように,より優先順位の低い演算を優先させる場合,開き括弧を前置することでエラーを回避する。あるいはプッシュ後代入を使用する。
    ×(p..0)[,
    ((p..0)[,
    p..0[=0,
  • 逆にポインタで示されるアドレスに値をポップする場合,ダミーにポップして代入するという方法がある。
    ×((p..0)],
    p..0=0],
  • 一見ダメに見えそうな「a..0-」は,「a..0=-」の省略形であるため式として通る。単項演算子として使える「+」と「-」は省略された代入演算子との組合せであるため,優先順位が低くなると考えても大嘘ではないかもしれない。
混合式におけるプッシュ・ポップ演算子の振る舞いは,v1.6で変更になっている。
= test by NaruTo (PK14#09098_1994-12-11)
*m
1 ""    ?.
        6[, 4[, 6[, 4[,
        &m("%d %d",0]+0],r]+r])
*
 このマクロを実行すると,v1.57では「10 10」,v1.60では「10 12」を示す。
二項演算子
演算子用例機能
..a..0ワードポインタ(使用されるセグメントは dz)[変更]
.a.0バイトポインタ(使用されるセグメントは dz)[変更]
<<a<<1左シフト(ビット単位でずらす)
>>a>>1右シフト(ビット単位でずらす)
&a&bビットごとの論理積(AND)
^a^bビットごとの排他的論理和(XOR)
|a|bビットごとの論理和(OR)
*a*b乗算
/a/b除算(余りは切り捨てられる。戻り値に余りが入ったりしない。)
%a%b剰余(a%b は a÷b の余り)
+a+b加算
-a-b減算
※注意事項
  • '..'と'.'はv1.5x以前とv1.6で優先順位が逆転している
  • 符号付きのポインタにヌルを埋める時は注意
     例:pn..(-38).0-, または pn..37~.0-, もしくは pq..137.0-,
  • 加算と減算の混合式は特に注意
     例:1-1+1 は 1-(1+1) で -1 となるので,ループに使うと無限ループや暴走の可能性もある。この例だけなら 1--+1 とも書ける。
論理演算子
演算子用例機能
<a<b小なり
<=a<=b以下
>a>b大なり
>=a>=b以上
==a==b等しい
!=a!=b等しくない
&&a&&b論理積
^^a^^b排他的論理和
||a||b論理和
代入演算子
演算子用例機能
=a=b代入(省略すると前の括弧も省略されているのと同じ扱い)
[=a[=bプッシュ後代入(a[,a=b)
&=a&=bビット毎の論理積(AND)を代入
^=a^=bビット毎の排他的論理和(XOR)を代入
|=a|=bビット毎の論理和(OR)を代入
*=a*=b積を代入
/=a/=b商を代入(余りは切り捨て)
%=a%=b剰余の代入(一定範囲の数値に丸める)
+=a+=b和を代入
-=a-=b差を代入

フロー制御

ラベルジャンプ

:】 次の1文字をラベルにする

識別子【:】に続く1文字をラベルにする。

特に注意すべきことは,メッセージ等の文字列に含まれるコロンの次にラベルになりうる文字があったとき,その位置がラベルと誤認される可能性があることだ。テスト中に変数の値を表示する時などに,「&m("a:b = %d:%d",a,b)」のように書いたとしよう。この文字列中の「:b」はラベルになりうる。本来のラベルの前にこの命令があれば,確実にこの位置にジャンプしてくる。ちなみに「:%」もラベルとなりうる。

文字列中にこのようなラベル様の文字があったとしても,大抵はコードとして意味のない次の文字列に遭遇して停止してしまうし,万一コードとして実行可能な文字列が続いていたとしても文字列終端のヌルで確実に強制終了される。文字列中にコロンを使った場合は,コロンの後に空白を1文字入れておくとよいかもしれない。

また,マクロのどの位置にも配置できるラベルだが,マクロ全体の見通しをよくするためにも,できるだけ行頭付近に記述したほうがよいだろう。

>】 続く1文字の示すラベルにジャンプ

前項で示したように,ラベルジャンプは単純にマクロバッファ内をスキャンしてゆくので注意が必要だ。あまり神経質になる必要はないがマクロの見通しを良くしておくことが大切だ。

また,マクロ内に飛び先が見つからない場合,同じモジュール内の別のマクロにある同じ名称のラベルに飛んでしまうことがある。ローカルマクロだけでなく,グローバルマクロの垣根を越えて飛んでゆき,戻ってくることはない。この動作を見切った上で敢えて書くという手もあるが,あまり多用しない方がよいだろう。

マクロの終了

.】 実行中のマクロを終了して呼出元に戻る

高級言語で言うところの return である。通常は単純にマクロの停止命令と考えてよい。

&nnでコールされたマクロの実行を終了して呼出元に戻るとき,その時点の内部レジスタの値をシステム変数rに格納する。サブルーチンを (0). として終了すると,あたかも return 0; のように戻り値を抱えて戻ってくるわけだ。つまりマクロであってもサブルーチンできちんと評価を行えば,呼出元できちんと分岐を行える。プログラムの基礎として身に付けたい習慣だ。

なお,.で戻ってきてから呼出元の実行が再開されるのは,コールするときにリターン後に実行を開始する次の命令位置をmg..2..1に記憶させているからである。

/】 マクロの実行を強制的に中断(呼出元に戻らない)

高級言語で言うところの break …と言いたいところであるが,実際にはもっと邪悪な命令である。何の保存も復帰もしてくれない強制終了命令なのだ。さすがにマクロの実行中断後にエディタとしての機能に大きな問題を生じるようなオプション(mx,ep,ux)や文字列バッファのアドレスなどはリセットされるが,プッシュ・ポップの辻褄が合わなくなったりするので極力使用は避けるべきであろう。

分岐・ブロック・ループ

?】 条件分岐

内部レジスタが偽(0)なら次の命令文/ブロックをスキップする簡易な条件分岐である。

(式)のように括弧で式をくくると,その式を評価して結果を内部レジスタに格納する。条件分岐を成立させている秘密の鍵こそ内部レジスタである。内部レジスタは,市販の解説書ですらコマンド等の戻り値rと混同されているものがあるほど正体の見えにくい存在だが,マクロ関係構造体の mg..0 に格納されているrとは別の値である。条件分岐の際はこの内部レジスタを参照して次の命令文/ブロックの実行を制御している。

機能の裏返しである「真(0以外)なら次の命令文/ブロックを実行」というイメージがあって,通常は次のように記憶されている。

(式1)?{ 式1の評価が真のとき実行 }

例外のない裏返しであるから間違いとは言えないが,注意しなければならないのは,「条件分岐」の説明であたかも次のようなフロー制御を想像してしまうことだ。

(式1)?{ 式1の評価が真のとき実行 }?{ 式1の評価が偽のとき実行 }

この構造を用いて説明することは,あまり正確ではない。むしろ不正確な説明である。通常は,あたかもこのような条件分岐のようにふるまうが,【?】を実行した直後に内部レジスタを論理NOTすることで巧妙に分岐させているのである。

(式1)式1を評価すると0以外(真)か0(偽)が内部レジスタ(mg..0)に格納される。
?この時点の内部レジスタの値を参照して,次のブロックの実行の有無を決定し,内部レジスタを論理NOTする。
{ 式1の評価が偽のときスキップ }式の評価が偽のとき,このブロックはスキップされる。つまり式1の評価が真なら実行される。実行に先立ち内部レジスタの値は0(偽)になっているが,ブロック内で内部レジスタがどのように変化しようとも関知されない。
?「この時点の」内部レジスタを参照して,次のブロックの実行の有無を決定し,内部レジスタを論理NOTする。直前のブロックで内部レジスタの値に変化がなければ,0(偽)のために次のブロックはスキップされる。そして内部レジスタは論理NOTされて1(真)となっている。
{ 式1の評価にかかわらず直前の内部レジスタが偽のときスキップ }もしも前のブロックの中で何かしら値が評価されて内部レジスタに0以外の値が格納されていれば,式1の評価が真だった場合でもスキップされずに実行される。

所期の動作(「条件分岐」というイメージから想像されるプログラム言語らしい動作)をさせたい場合は,2番目のブロックの最後で強制的に内部レジスタに0を格納すればよい。具体的には,次のように(0)を加えてやればよい。

(式1)?{
        式1が真の時に実行
        (式2)?{
                式2が真の時に実行
        }
        (0)
}?{
        式1が偽のときに実行
}

内部レジスタの値の変化は次のようになる。

(式1)式1の評価が真式1の評価が偽
?次のブロックを実行
 内部レジスタは0に変化
次のブロックをスキップ
 内部レジスタは1に変化
{ ブロック1実行スキップ
(式2)式2の評価が真式2の評価が偽
?次のブロックを実行
 内部レジスタは0に変化
次のブロックをスキップ
 内部レジスタは1に変化
{ ブロック1(2) }実行スキップ
(0) }内部レジスタはダメ押しで0
?次のブロックをスキップ次のブロックを実行
{ ブロック2 }スキップ実行

【?】を実行する段階での内部レジスタの値によって次のブロックを実行するかどうかが決定されるということが理解できれば,条件分岐の動きを見切った書き方が可能になる。

例えば,(式)と?は連続していなくても構わない。

(式1) 命令[群] ?{ 式の評価が真のとき実行 }

命令[群]において内部レジスタの値が絶対に変化しないのであれば,次のブロックの実行について動作が変わることはない。命令[群]において式1の値が変化したとしても,内部レジスタさえ変化しなければ問題はない。

例えば,式1の評価を左右した値が変わっても構わない。

(r) 戻り値のあるコマンドを実行 ?{ コマンド実行の成否による分岐の変動はない }

(偽)? でスキップされる命令こそトークンである。その解釈について高橋版VZの製作者である高橋洋光氏は次のように解説する。

? に続く文字が「!#(&>{'"」以外だと、'A' より大きければアルファベットで始まる変数名で式,小さければ1文字スキップになります。(dc13#06056,Hiro)

つまり,1文字スキップするのは「$%)*+,-0123456789:;<=?@」である。何?【;】があるのは何故か?自己書換で試してみてほしい。確かに認識されている。

1 ^\ "test" &t
        c13+$, c.0=';',
        (0)??
        &m("%d/%d",a,b)
*

なお,引数のある関数の直前でスキップが発生すると,括弧の手前までがスキップされて括弧部分が独立して実行される。書式制御が含まれる場合は分断されるが,複数の式として解釈されるため,通常は何事もなかったように実行される。ただし,内部レジスタは不定となる。

        (0)?&m("%d/%d",a,b)

このコードがどのように実行されるか確かめてみると,次のようになる。

        (0) → ? → &m → ("%d/%d", → a, → b)

引数部分は意図してか偶然か,すべて式として成立するためエラーにはならない。しかし最終的に b) によって b が評価され,内部レジスタの値が変動する。

{】 ブロック開始・ループのポインタとカウンタのセット

ブロックの開始を宣言するキーワードである。前項の?でスキップされるケースでは,次の}が出現するまでを命令の塊(ブロック)とする。同時にループカウンタとループ先頭ポインタをセットする。ループカウンタは{を実行した直後の内部レジスタの値がmg..4に,ループ先頭ポインタは{の次のアドレスがmg..5に格納される。

条件分岐でブロックを実行対象とするとき,真(0以外)であった内部レジスタの値は論理NOTされて偽(0)となっている。ブロック開始キーワードを実行したとき,ループカウンタには内部レジスタの0がセットされる。0回ループでも1回実行されるが,そのよう仕様になっている理由は,もしかするとここに起因するものなのかもしれない。ともかく,条件分岐を経て実行されているブロックでは,ループカウンタに必ず0が格納されるため,ループがらみでとんでもなく処理が重くなったりすることは避けられているわけだ。

さて,ループカウンタはブロック開始キーワード{の実行のたびにセットされるわけだが,セットされる値はその時点の内部レジスタの値である。{を実行すれば無条件でセットされる。したがって次のようなことも起こりうる。

(式1){ 式1の値の回数繰り返し実行される }
     { 内部レジスタの値の回数繰り返し実行される }

前のブロックの中で内部レジスタの値が変化しなければ,次のブロックも式1の回数分繰り返し実行される。もちろん前のブロックの中で値が評価されて内部レジスタの値が変更されれば,次のブロックまでに内部レジスタが変化しなければ,繰り返し回数はそのままカウンタにセットされる。

        (3){ &m("%d / %d",mg..0,mg..4) (5) }    ;3回繰り返し
        #d#d                                    ;内部レジスタの変化なし
        { &m("%d / %d",mg..0,mg..4) }           ;5回繰り返し

もう気付かれたと思うが,不用意にブロック開始キーワードを書くことは厳に謹まなければならないということになる。内部レジスタの値によっては,それこそ無限にブロック内が実行される危険もあるということだ。

}】 ブロック終了・ループカウンタのデクリメント

ブロック開始キーワード{と対となるブロック終了キーワードである。実際の動きとしては,}が出現するとループカウンタ(mg..4)を参照して,カウンタの値が0または-1以外ならデクリメントし,その結果のカウンタの値が真(0以外)ならループ先頭ポインタ(mg..5)にジャンプする。

ループでなく条件分岐によって実行されているブロックであっても,実際は0回ループとして実行されている。次のマクロのように内部でループカウンタをセットし直すと,きっちりループすることが確認できるだろう。

        (1)?{ &m("%d / %d / %x",mg..0,mg..4,mg..5 ) (mg..0)??mg..4+=2, }

マクロのコール/ジャンプ

>^】 実行中のマクロの先頭にジャンプ

実行中のマクロの先頭にジャンプするキーワードである。直接起動可能なグローバルマクロではオプションやユーザ変数をプッシュしたりする関係もあってあまり使用されず,もっぱらサブルーチン内でのループ様の処理に使用される。

&+n】 実行中のマクロの+n番のマクロをコール
&-n】 実行中のマクロの-n番のマクロをコール
>+n】 実行中のマクロの+n番のマクロにジャンプ
>-n】 実行中のマクロの-n番のマクロにジャンプ

実行中のマクロの番号にnを加算または減算した番号を付与されているマクロをコールあるいはジャンプするキーワード群である。コール/ジャンプ先マクロはグローバルマクロに限らず,ローカルマクロを含めて最初に見つかったものになる。nの範囲は1桁に納まる数値なので,0〜9。2桁以上の数値を指定しても1文字目だけ認識されるため,分断されて残った数字がコール先から戻った後に単独で実行されることになり,不正なコードになるのでマクロの実行はそこで停止する。

マクロバッファ内を探す順序は,次のようになる。

通常は,モジュール内に見つからなければ,vz.def等のインクルードマクロの番号を探すということになるだろうが,nの範囲を考えると同じモジュールのマクロを指定するための命令と考えておいたほうがよいだろう。

用例:game.def
10:     e-, &01 (t)>?{ >+1 >+2 >+3 >+4 >+5 >+6 >+7 }

1桁制限もあるせいか,使用されている例をあまり目にする機会はない。製品版添付のgame.defは多分にサンプルマクロの様相を示すもので,「こんな命令もあります」的な使い方を示すものであろう。通常はマクロ番号を記述すれば済むことだ。相対番号指定は人間に「数える」必要を生じさせるので,直感的でなく,使い勝手はあまりよいと思えない。

&*】 最後に組み込んだマクロをコール
>*】 最後に組み込んだマクロにジャンプ

マクロの組込み関数で最後(直前)に組み込んだマクロをコールあるいはジャンプするキーワードである。もっぱらマクロローダ等の特殊なマクロで使用される。

Case文

>?】 直前の(式)の数値に応じた処理を実行
書式
(式)>?{ 処理0 処理1 処理2 ... }

直前の(式)の数値に対応した処理を実行させる多重分岐である。数値は0から連続するいくつかの範囲に納まることが必要だ。このため式の要素となりうるのは,システムモード(s),コマンドやメニューの戻り値(r),日付に対する曜日など整数の剰余(m%n)といったものがほとんどである。

式を評価して内部レジスタに格納された値が0なら処理0を,1なら処理1を,2なら処理2を実行する。0から数えることに注意すること。

処理の内容は,ジャンプ命令や値の受け渡しなど簡易なものでなければならず,複雑な処理をブロック内で行わせることは極力避けるべきである。

用例
        !01                             ;メニューの戻り値を分岐に利用する
        (r<0)?.                         ;キャンセル(r==-1)なら終了
        (r)>?{ >A >B >C >D >E }         ;メニューのアイテム数に応じた処理を用意
 :A     c="リンゴ",                     ;rが0のときの処理
 :B     c="バナナ",                     ;rが1のときの処理
 :C     c="ニンジン",                   ;rが2のときの処理
 :D     c="ドリアン",                   ;rが3のときの処理
 :E     c="エラー:選び直してください", ;rが4のときの処理
用例
        (s)>?{ >A >B >C . . }           ;システムモードで処理を分岐
 :A     p=px, >D
 :B     p=pw, >D
 :C     p=ps, >D
 :D     &?("%s",p)
用例
        (s)>?{ (p=px) (p=pw) (p=ps) . . }       ;システムモードで処理を分岐
        &?("%s",p)

用意した処理の数を超えた値を受け取った場合は,当然のことながらブロックの次の[else]以降を実行することになる。用意した処理をスルーして実行を継続すると不都合を生じることが多々考えられるため,[else]の位置に停止命令やジャンプ命令,例外処理命令などを書いておいた方がよいだろう。

用例
        !01 (r<0)?.
        (r)>?{  }
>!】 

(式)>!{ c0 op0 c1 op1 | else }

直前の(式)の値に対応した文字に続くブロックを実行し,対応文字がなければ「|」の次に示されたelseを実行する。文字の代わりに(値)を設定することも可能で,コマンドコードの値を渡して分岐することができる。Case文の注意事項も参照のこと。

用例1:vzfl.def
受け取った文字による分岐
        (r)>!{
        @       #Q      ;vzpath
        R       #R      ;rename
        X       >X      ;eXec
        }.
:X      pm="%1 %2", #!
用例2:stkhelp.def by saki...
受け取ったコマンドキーの値による分岐
        ;※rはメニューの戻り値
        (r&$FF+$100)>!{
                (#d)  >S        ; メニュー選択
                ;略
用例3:stkhelp.def by saki...
ポインタで取得した文字の値による分岐
        ;HTML.SHD専用切出しルーチン
        (g..0)>!{
                ('l&') (c="<")
                ('g&') (c=">")
                ('a&') (c="&")
                ;略
用例4:Niflogv.def(delor2.def) by Emmett
「Else」を用いてループ状にしている例
:A      &m("↑: 親発言  ↓: 子発言  ←→: 兄弟発言  [ESC]: Quit")
        &p (r)>!{ (#e) &01 (#s) &02 (#d) &02 (#x) &03 (#[) {&m("")>Z} | >A}
Case文の注意事項

Case文のブロック内では,問題なく使える命令と問題を生じる命令がある。

問題のない命令
問題を生じる命令

トークン関連覚書
The VZ resources,  valid XHTML 1.0 Strict.  ©2009  by mezala.