第1章 マクロのキーワード

1-1 文字列を書き込む

"<文字列>"

 <文字列>をキーボードからの入力文字列とする

 序章の冒頭で実例を示しました。

1-2 条件分岐と内部レジスタ

(式)

 ()の評価は真(1)か偽(0)のどちらかになり、その値は「内部レジスタ」に格納されます。

(ct>1)内部レジスタ
カーソル位置は改行ではない1
カーソル位置は改行である0

?

 「?」に出会うと、内部レジスタを参照します。1(真)なら直後の1命令または1ブロックを実行。0(偽)なら直後の1命令または1ブロックをスキップします。そして、いずれの場合も、内部レジスタの値を反転(論理NOT)します。

?直後の1ブロック内部レジスタを反転
内部レジスタ 1実行内部レジスタ 0
内部レジスタ 0スキップ内部レジスタ 1

 序章で示した^Dのマクロを例にとってみましょう。

80 ^D   "→"
        ?.
        (ct>1)? #d ? &x(lx+1)

カーソル位置が改行でない場合

    (ct>1)? #d ? &x(lx+1)
     ↑ ↑ ↑ ↑ ↑
     │ │ │ │ │
     │ │ │ │ └─ スキップする
     │ │ │ └─ 内部レジスタを参照
     │ │ └─ 内部レジスタを論理NOT(0 になる)して、実行
     │ └─ 内部レジスタを参照
     └─ 内部レジスタは 1 になる

カーソル位置が改行である場合

    (ct>1)? #d ? &x(lx+1)
     ↑ ↑ ↑ ↑ ↑
     │ │ │ │ │
     │ │ │ │ └─ 実行する
     │ │ │ └─ 内部レジスタを参照
     │ │ └─ スキップして、内部レジスタを論理NOT(1 になる)
     │ └─ 内部レジスタを参照
     └─ 内部レジスタは 0 になる

 つまり、

(式)? 真なら実行する命令 ? 偽なら実行する命令

 は、正式に規定された条件分岐ではなく、「内部レジスタ」と「?」の巧妙な仕組みによって実現する疑似的な二重分岐なのです。

二重分岐の誤ったイメージ

         ┌─ ? <真> ─┐
         │        │
    (式) ─┤        ├─→
         │        │
         └─ ? <偽> ─┘

二重分岐の正しいイメージ

          スキップ    スキップ
        ┌──────┐┏━━━━━━┓
        │      │┃      ┃
    (式)━?━ <真> ━┷?─ <偽> ─┸━━→

1-3 条件分岐のネスト

 「内部レジスタ」と「?」の仕組みが理解できれば、条件分岐の中でさらに条件分岐する、いわゆるネストが危険であることも容易に理解できるでしょう。

(式)? { 真 (式)? 真 ? 偽 } ? { 偽 }

 { 真 ... } のブロックの中で、さらに(式)を使っています。ここで真に分岐した場合、もともとスキップするはずだった { 偽 } を実行してしまいます。

    (式)? { 真 (式) ? 真 ? 偽 } ? { 偽 }

          ↑ ↑ ↑ ↑ ↑ ↑  ↑
          │ │ │ │ │ │  │
          │ │ │ │ │ │  └─ 実行してしまう
          │ │ │ │ │ └─ 内部レジスタを参照
          │ │ │ │ └─ スキップして、内部レジスタを論理NOT(1)
          │ │ │ └─ 内部レジスタを参照
          │ │ └─ 内部レジスタを論理NOT(0)して、実行
          │ └─ 内部レジスタを参照
          └─ 内部レジスタ 1 と仮定

1-4 内部レジスタを強制変更

 条件分岐をネストする際の危険を力ずくで回避する方法があります。

(式)? { 真 (式)? 真 ? 偽 (0) } ? { 偽 }

 「(0)」に注目してください。内部レジスタに明示的に 0 をいれることによって、最後の「?」に出会ったとき、{ 偽 } を確実にスキップさせることができます。

1-5 条件の論理反転

(式)?? <偽>

 「??」によって、真偽を反転させることができます。

基本型

80 ^X   "↓"
        ?.
        #x (r==-1)? { #> #m }           ; 返り値が -1 なら #> #m を実行

応用1

80 ^X   "↓"
        ?.
        #x (r==-1)??. #> #m             ; 返り値が -1 でなければ終了

.

 現在実行中のマクロを終了する

 まず「.」について説明しておきます。これは、現在実行中のマクロを終了する命令です。「ピリオド」で「終了」する……。これを憶えられない人はいないでしょう。

 「?」自体も1命令であることに注意してください。最初の「?」で内部レジスタを参照して、0(偽)だったら次の「?」をスキップして、「.」でマクロを終了します。逆に、1(真)だったら、次の「?」を実行します。最初の「?」で内部レジスタは 0 に反転していますから、「.」をスキップして、「#> #m」を実行します。

 ちなみに、#x の返り値は 0 か -1 のどちらかしかないので、次のように短く書くことができます。

(基本型2)

80 ^X   "↓"
        ?.
        #x (r)? { #> #m }               ; 返り値が 0 以外なら #> #m を実行

(応用2)

80 ^X   "↓"
        ?.
        #x (r)??. #> #m                 ; 返り値が 0 なら終了

 「(r)」は条件式の体裁をそなえていないので、奇妙に思われるかもしれませんが、前項の「内部レジスタを強制変更」で述べたように、内部レジスタにそのまま数値を渡しています。この場合には 0 か -1 が渡されます。「(r==-1)」のように条件式を書いた場合には、1(真)か 0(偽)が渡されます。「?」は内部レジスタが「0」か「0以外(負数を含む)」かだけを参照します。

 「(r==-1)」を「(r=-1)」と書きまちがえないよう気をつけてください。「(r=-1)」だと、rに-1を代入して、その値を内部レジスタに渡すという意味になります。すると、「#> #m」が必ず実行されてしまいます。

1-6 多重分岐とメニュー

(式)>? { <0の場合> <1の場合> <2の場合> 〜 }

 連続した数値が条件になっている場合、「多重分岐」を使って短く記述することができます。たとえば、次のようなマクロがあったとします。

80 ^\   "カーソル位置の文字タイプ"
        (ct==0)? >A
        (ct==1)? >B
        (ct==2)? >C
        (ct==3)? >D
        (ct==4)? >E
        (ct==5)? >F
        (ct==6)? >G

:A      &m("EOF(ファイルの最後)").
:B      &m("改行マーク").
:C      &m("半角/全角スペース、タブ、制御コード").
:D      &m("記号(句読点、括弧、半角カタカナなど)").
:E      &m("ひらがな").
:F      &m("全角文字(漢字、全角英数字、全角カタカナなど)").
:G      &m("半角英数字(ドル記号'$'とアンダーバー'_'を含む)").

 ct(カーソル位置の文字タイプ)は 0〜6 で連続しています。多重分岐を使ってみましょう。ちなみに、「>A」はラベル「:A」へのジャンプ命令です。ラベルで分岐した場合、放っておくと、最後の「:G」まで全部実行してしまい、分岐した意味がなくなるので、それぞれ「.」で終了させます。

(多重分岐を利用)

80 ^\   "カーソル位置の文字タイプ"
        (ct)>? { >A >B >C >D >E >F >G }
        &m("無効").

:A      &m("EOF(ファイルの最後)").
:B      &m("改行マーク").
:C      &m("半角/全角スペース、タブ、制御コード").
:D      &m("記号(句読点、括弧、半角カタカナなど)").
:E      &m("ひらがな").
:F      &m("全角文字(漢字、全角英数字、全角カタカナなど)").
:G      &m("半角英数字(ドル記号'$'とアンダーバー'_'を含む)").

 実際にはありえませんが、もしctが7だったらどうなるでしょう。対応するジャンプ命令がないので、ブロックをスキップして、「&m("無効")」を実行します。

 多重分岐は主にメニューと組み合わせて使います。

* M
80 ^\   "ファイル一覧"
        ?.
        !01                             ; メニューの 1 番をポップアップ
        (r<0)?.                         ; [ESC]でキャンセル (r==-1)?. と同じ
        #O                              ;【入力ファイル】の1行ウィンドウ
        (r)>? { >A >B >C }              ; 多重分岐
:A      "c:\vz\vz.def" >Z
:B      "c:\vz\macro.doc" >Z
:C      "c:\autoexec.bat" >Z
:Z      #m                              ; リターンで確定(共通処理)

* P
1       "ファイル選択",16,3
        "A VZ.DEF"
        "B MACRO.DOC"
        "C AUTOEXEC.BAT"

 頻繁に参照するファイルをメニューで選択してオープンするマクロです。

 「!01」で次のようなメニューがポップアップします。

    ┌【ファイル選択】┐
    │A VZ.DEF    │  r…0
    │B MACRO.DOC   │  r…1
    │C AUTOEXEC.BAT │  r…2
    └────────┘

 何番目が選択されたか、返り値rに入ります。「A VZ.DEF」なら0、「C AUTOEXEC.BAT」なら2です。[ESC]でキャンセルすると-1になるので、「(r<0)?.」という条件式で終了します。「:A」〜「:C」に分岐したあと、「>Z」で共通処理「#m」へジャンプするのがうまい書き方です。メニューの書式については、後で詳しく述べます。

 ところで、多重分岐のブロックの中で、そのまま命令やブロックを書いたりすると、おかしな動作をすることがあります。

80 ^\   "カーソル位置の文字タイプ"
        (ct)>? {
        &m("EOF")
        &m("改行マーク")
        &m("スペース、タブ、制御コード")
        &m("記号")
        &m("ひらがな")
        &m("全角文字")
        &m("半角英数字")
        }
        &b(3)                           ; ビープ音 3/60 秒

 本来はctに応じたメッセージを出したあと、ビープ音が鳴るはずですが、メッセージが出ないことがあります。

 多重分岐の内部処理はかなり簡略化されているようなので、慣れないうちはラベルへのジャンプを利用するほうがよいでしょう。

1-7 ループの仕組み……内部レジスタとブロック

 序章の「区切り線」マクロで示した「ループ」は、「?」がない点は条件分岐と異なりますが、内部レジスタ、およびブロックと密接な関係がある点は共通しています。ふだんは意識する必要はありませんが、理解を深めるためにブロックの仕様を確認しましょう。

{ ブロック開始キーワード

 内部レジスタの値をループカウンタにセットし、カレントポインタをループ開始ポインタにセットします。

} ブロック終了キーワード

 ループカウンタを 1 減らし、0 になったら、「}」の次の命令へ進みます。0 でなければ、「{」の次の命令(ループ開始ポインタ)へ戻ります。

    (79){ "*" }          ; 「"*"」を79回実行する
     ↑ ↑ ↑ ↑
     │ │ │ │
     │ │ │ └─ ループカウンタを 1 減らし、0 になったら次へ進む
     │ │ └─ 繰り返したい命令
     │ └─ ループ開始ポインタがこの位置にセットされる
     └─ 内部レジスタ 79 がループカウンタにセットされる

1-8 ループのネストは不可

 以下のように、ループの中でさらにループするような書き方はできません。

(10){ (2){ "*" } }

 20個の「*」を書き込むつもりでも、結果としては2個しか書き込まれません。ループカウンタとループ開始ポインタは1組しかないということです。どうしてもループをネストする必要がある場合は、自前でループをもたなくてはなりません。

        i=10,                           ; カウンタを10に設定
:A      (2){ "*" }                      ; 2 個の「*」を書き込む
        i=i-1,                          ; i を 1 減らす(i-=1,とも書ける)
        (i)? >A                         ; i が 0 でなければループ

 これなら20個の「*」を書き込むことができます。

 「i=i-1,」を見て、「左辺と右辺が等しくないじゃないか」と思った人がいるかもしれません。「演算子」の項であらためて説明しますが、「=」は「右辺を左辺に代入する」という意味です。数学の等式と混同しないように気をつけてください。「i=i-1,」のように、演算の結果を同じ変数に代入する場合は、「i-=1,」という特別な書き方ができます。減らす数値が1の場合には、「i--,」という書き方も用意されています。

        i=10,                           ; カウンタを10に設定
:A      (2){ "*" }                      ; 2 個の「*」を書き込む
        i--,                            ; i を 1 減らす
        (i)? >A                         ; i が 0 でなければループ

 これはさらに、

        i=10,                           ; カウンタを10に設定
:A      (2){ "*" }                      ; 2 個の「*」を書き込む
        (i--)? >A                       ; i を 1 減らして 0 でなければループ

 と短縮して記述できます。

ループの中で「{」「}」を使ってはいけない

 ループのネストに限らず、ループの中で使ってはいけないキーワードがあります。

        i+,                             ; i=1, と同じ意味
        (10){ (i)? "=" ? "-" i!, }      ; i!, で 1 と 0 を反転

 このマクロを実行すると、「=-=-=-=-=-」と書き込みます。

 ところが、条件分岐先を「{」「}」で囲って、

        i+,
        (10){ (i)? { "=" } ? { "-" } i!, }

 とすると、「=」1個が書き込まれるだけです。

 「{」があらわれると、ループカウンタとループ開始ポインタが再設定されて、うまくいかないのです。ループのネストが不可なのも、これが原因です。

1-9 0回ループ?

        (0){ 繰り返したい命令 }

 のように、ループ回数に0を与えた場合には、ブロックを1回だけ実行します。

1-10 無限ループ

        (-1){  "*"  }           ; 「"*"」を無限ループする

 のように、ループ回数に-1を与えた場合には、ブロックを無限に繰り返して実行します。mi.defvzj31.defの「高速スクロール」で利用されています。

vzj31.defより)

30 \[UP] "高速↑"
        (-1){ #e (ks&3)??. }

31 \[DOWN] "高速↓"
        (-1){ #x (ks&3)??. }

 シフトキーを押し下げているあいだスクロールを繰り返す仕組みです。ksはシフトキーの押し下げ状態を示すシステム変数です。シフトキーを離すと、「.」でマクロを終了します。

 無限ループはどちらかというと特殊な機能なので、あまり使うことはないでしょう。

1-11 同一マクロ内でのジャンプ

 すでに述べたように、ブロックとは無関係に、特定の位置へジャンプすることができます。

>A ラベル「:A」へジャンプ

 「>A」〜「>Z」の26通りが有効です。

 「:A」〜「:Z」は、マクロ番号とはちがって、論理行頭に記述しなくてもエラーにはなりませんが、マクロの構造をよく見渡せるように、論理行頭に記述したほうがよいでしょう。

>^ マクロの先頭へジャンプ

 「無限ループ」の項で例に示した高速スクロールのマクロは、「>^」を使えば短く記述できます。

30 \[UP] "高速↑"
        #e (ks&3)? >^

31 \[DOWN] "高速↓"
        #x (ks&3)? >^

 あえてラベルジャンプを使うなら、次のようになります。

30 \[UP] "高速↑"
:A      #e (ks&3)? >A

31 \[DOWN] "高速↓"
:A      #x (ks&3)? >A

1-12 他のマクロへのジャンプ

>nn nn番のマクロの先頭へジャンプ

* M
80 ^\   "ファイル一覧"
        ?.
        !01                             ; メニューの 1 番をポップアップ
        (r<0)?.                         ; [ESC]でキャンセル (r==-1)?. と同じ
        #O                              ;【入力ファイル】の1行ウィンドウ
        (r)>? { >01 >02 >03 }           ; 多重分岐

1:      "c:\vz\vz.def" #m               ; ローカルマクロ 1 番
2:      "c:\vz\macro.doc" #m            ; ローカルマクロ 2 番
3:      "c:\autoexec.bat" #m            ; ローカルマクロ 3 番

* P
1       "ファイル選択",16,3
        "A VZ.DEF"
        "B MACRO.DOC"
        "C AUTOEXEC.BAT"

 「1:」〜「3:」はローカルマクロと呼ばれ、キーアサインとマクロタイトルがないとはいえ、独立したマクロです。「80 ^\ "ファイル一覧"」のようなマクロはグローバルマクロと呼ばれます。

 「多重分岐とメニュー」で示したマクロと見比べてください。

:A      "c:\vz\vz.def" >Z
:B      "c:\vz\macro.doc" >Z
:C      "c:\autoexec.bat" >Z
:Z      #m                              ; リターンで確定(共通処理)

 「:」の位置に注意してください。

 ラベルでは「:A」と前側に。ローカルマクロでは「1:」と後側に置きます。

>01」を「>1」と書くことはできません。

 マクロ番号が1桁のときには必ず2桁目を0で補完してください。ひきかえジャンプ先のマクロ自体は「1:」でも「01:」でも結構です。

>> 次に記述されたマクロへジャンプ

 マクロ番号とは無関係に、現在実行中のマクロの次に記述されたマクロへジャンプします。

>+n >-n 現在実行中のマクロ番号±n番のマクロへジャンプ

 ジャンプ元とジャンプ先の相対的な番号に依存するため、不用意にマクロ番号を変更すると、エラーの原因となります。一般に公開するマクロでは、ユーザーの環境に合わせてマクロ番号を変更する可能性が高いので、あまりおすすめできません。

>* 最後に組み込んだマクロファイルの先頭のマクロへジャンプ

 少々特殊な機能で、いわゆる「外部マクロローダ」で使用されます。

 VZ標準の「外部マクロの実行」がよい例です。

10 [ESC]^ @^ "外部マクロの実行"
        ?.
        #O                              ;【入力ファイル】の1行ウィンドウ
        &?(pi)                          ; &?(pi)… vz.def のディレクトリ
        "*.def" #m                      ; ワイルドカードでファイラーを起動
:A      (s)?{ &c >A }                   ; 編集モード(s=0)に戻るまでループ
        (r<0)?.                         ; [ESC]でキャンセルなら終了
        #80 (r)?.                       ; #80…^K^再カスタマイズ(組み込む)
        #C                              ; マクロファイルをクローズ
        >*                              ; 最後に組み込んだマクロへジャンプ

1-13 他のマクロをコール

&nn &> &+n &-n &*

 「>」と「&」がちがうだけで、「>nn」「>>」「>+n」「>-n」「>*」とよく似ています。ジャンプコールのちがいは何でしょうか? 「ファイル一覧」のマクロをコールで書き換えてみましょう。

* M
80 ^\   "ファイル一覧"
        ?.
        !01                             ; メニューの 1 番をポップアップ
        (r<0)?.                         ; [ESC]でキャンセル (r==-1)?. と同じ
        #O                              ;【入力ファイル】の1行ウィンドウ
        (r)>? { &01 &02 &03 }           ; 多重分岐
        #m                              ; リターンで確定(共通処理)

1:      "c:\vz\vz.def"                  ; ローカルマクロ 1 番
2:      "c:\vz\macro.doc"               ; ローカルマクロ 2 番
3:      "c:\autoexec.bat"               ; ローカルマクロ 3 番

* P
1       "ファイル選択",16,3
        "A VZ.DEF"

        "B MACRO.DOC"
        "C AUTOEXEC.BAT"

 共通処理「#m」の位置に注意してください。ジャンプは行ったきり戻ってこないので、「1:」〜「3:」のそれぞれで「#m」を記述しました。コールは呼び出し元に戻ってくるので、「#m」を1回記述すれば済むのです。「ラベルジャンプ」「マクロジャンプ」「マクロコール」の3通りを状況によって使い分けてください。

サブルーチン化

 マクロコールが本領を発揮するのは、共通処理をサブルーチン化する場合です。

* M
80 ^\   "怪獣退治"
        ?.
        &01                             ;「1:」をコール
        &m("ウルトラセブン") &w(60)
        &01                             ;「1:」をコール
        &m("平和が戻った")

1:      &m("諸星ダン") &w(60)
*

 ※ &w(60)で、約1秒間待ちます。(wait)

 ちょっと単純すぎる例ですが、「&m("諸星ダン") &w(60)」を2度実行するのに、同じマクロコードを2度記述せずに済みます。共通処理が長く、コールされる回数が多くなるほどマクロのサイズを小さくする効果があります。また、ひとまとまりの処理をサブルーチン化して、「&01」の1命令に置き換えることによって、メインルーチン(グローバルマクロ)の構造を見渡しやすくなります。

ラインバッファのオーバーフロー対策

 VZはマクロを読み込む際、マクロコードをいったんラインバッファ(Bl:標準値1024バイト)に転送します。ひとつのマクロがラインバッファにおさまらないと、転送を中断して、「ラインバッファがいっぱいです」とエラーメッセージを出します。ひとつのマクロが過大になりそうなときは、ローカルマクロコールを利用した構造に書き直したほうがよいでしょう。

1-14 マクロのネストは最大で8レベルまで

 サブルーチンからさらにサブルーチンをコールすることもできます。8レベルを越えると、ウンともスンとも言わなくなるので気をつけてください。「モード型マクロ」と呼ばれるタイプのマクロの中からコールされることもあり得るので、通常は半分の4レベル程度にとどめておいたほうがよいでしょう。

1-15 グローバルマクロとローカルマクロの優先順位

 ローカルマクロに限らず、グローバルマクロもサブルーチンとしてコールすることができます。「>26」や「&26」といった命令があると、マクロバッファ内で現在実行中のマクロ以降でローカルマクロ「26:」を探し、見つからない場合には、マクロバッファの先頭からグローバルマクロの「26」を探します。どちらも見つからなかった場合、エラーとなり、画面左下隅に「>26」と表示されます。

                         ┌───────┐
         ┏━━━━━━━━━━━━━━━│━┓┌────┴───┐
         ┃* M              │ ┃│グローバルマクロ│
         ┃               │ ┃│「26」を探す  │
         ┃2 [F01] "【ファイルメニュー】│ ┃└────────┘
         ┃    ?. !01        │ ┃     │
         ┃               │ ┃     │
         ┃3 \[F01] "【モードメニュー】" │ ┃     │
         ┃    ?. !02        │ ┃     │
         ┃               │ ┃     │
         ┃4 \[F02] "【サブメニュー】"  │ ┃     │
         ┃    ?. !03        │ ┃     │
         ┃               │ ┃     │
         〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜│〜〜     │
┌───────┐┃80 ^\  "test"        │ ┃     │
│ローカルマクロ│┃    &26          │ ┃     │
│「26:」を探す │┃               │ ┃     │
└───┬───┘┃               │ ┃     │
    │    〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜│〜〜     │
    ↓    ┗━━━━━━━━━━━━━━━│━┛     ↓
┌──────────┐             │   ┌────────┐
│不発見なら先頭に戻る├─────────────┘   │不発見ならエラー│
└──────────┘                 └────────┘

 通常はサブルーチンはローカルマクロにして、メインルーチンより後ろ側に書くようにしてください。この意味で、VZに添付されているmi.defには不都合があります。

21 ^QF "文字列の↓検索"
        ?. mp[, #F &l mp], (r)?. >26

22 ^QB "文字列の↑検索"
        ?. mp[, #F &l mp], (r)?. >28

23 ^^ "カーソル文字列の↓検索"
        ?. mp[, #G mp], >26

24 ^\ "カーソル文字列の↑検索"
        ?. mp[, #G mp], >28

26 ^L [F05] "↓検索"
        ?. mp[, mp=2, #c mp],

28 ^O \[F05] "↑検索"
        ?. mp[, mp=2, #r mp],

 「>26」「>28」で、グローバルマクロへジャンプしています。もし、これらのマクロより後ろ側のマクロ(外部マクロとして追加組み込みされたマクロも含む)で、ローカルマクロ「26:」「28:」が使われていたら、そちらへジャンプしてしまいます。

 それを防止するには、「26」「28」の前に、中継用のローカルマクロ「26:」「28:」を記述してやるとよいでしょう。

26:     >>
26 ^L [F05] "↓検索"
        ?. mp[, mp=2, #c mp],

28:     >>
28 ^O \[F05] "↑検索"
        ?. mp[, mp=2, #r mp],

 ※ 「>>」は次に記述されたマクロを実行する命令

 これで確実にジャンプできます。

 また、マクロをながめると、同じような記述が多いことに気がつきます。ちょうどいい機会ですから、サブルーチン化を試みてみましょう。

 まず、オリジナルのマクロについて解説します。

21 ^QF "文字列の↓検索"
        ?.
        mp[,                            ; ページングモード保存
        #F                              ;【検索文字列】の1行ウィンドウ
        &l                              ; 入力してリターンを待つ
        mp],                            ; ページングモード復帰
        (r)?.                           ; [ESC]でキャンセルしたら終了
        >26                             ; "↓検索"マクロへジャンプ

 【検索文字列】を入力してリターンを押すと、ただちに前方に検索をおこないます。VZ標準の検索では、検索文字列を設定したあと、さらに ^R(↑検索)か ^C(↓検索)を押さなくてはなりません。気の短い人はこのマクロを使うとよいでしょう。

 mpはシステム変数で、現在のページングモードを示します。通常の編集画面ではステータスライン(画面最上行)の左端が[P]となっているはずですが、このときmpは0です。

ページングモードステータスラインmp
表示ページングモード[P]0
論理ページングモード[C]1
文字列検索モード[S]2

 検索関連の操作(#F,#G)をおこなうと、[S]に変わってしまうので、元に戻すためにmpの保存・復帰をおこないます。

[ マクロスタックへのプッシュ(push)

] マクロスタックへのポップ(pop)

                                                        ┌───────┐
                                                        │マクロスタック│
                                                        ├───────┤
        mp[,(マクロスタックにmpの値をプッシュ)1 ──→│  1  2  0  4  │
        ……                                            〜〜〜〜〜〜〜〜〜
        mp],(マクロスタックの値をmpにポップ) ←── 1│     2  0  4  │
                                                        └───────┘

 マクロスタックと呼ばれる倉庫に一時的に数値を押し込みます。取り出すときは、いちいち中身を確認しなくても、いちばん表にあるものを引っつかんでくればよいわけです。変数に保存するなら「p=mp, ... mp=p,」という書き方になります。一時的に保存するだけならマクロスタックがよいでしょう。マクロスタックの大きさは標準で64バイト。一回のプッシュあるいはポップで2バイトが出入りするので、32個まで格納できます。複数のプッシュとポップをおこなう場合には、逆順で取り出してください。

                                ┌───────┐
                                │マクロスタック│
                                ├───────┤
                                │           5  │
        mi[,   0 ──────→ │        0  5  │
        mp[,   1 ──────→ │     1  0  5  │
        ……                    〜〜〜〜〜〜〜〜〜
        mp],   ←────── 1 │        0  5  │
        mi],   ←────── 0 │           5  │
                                └───────┘

&l 1行文字列の入力(Line in)

 「&l」はシステム関数です。1行ウィンドウやコマンドラインで、リターンキーかエスケープキーが押されるまで、キー操作を受け付けます。【検索文字列】の1行ウィンドウ内で文字を入力したり、カーソルを移動したり、文字を削除したり、といった操作はすべて「&l」1個で受けとめることができます。

 「(r)?.」は「(r==-1)?.」と同じ意味です。1行ウィンドウをエスケープでキャンセルすると、返り値は-1になるので、何もせず終了させています。

23 ^^ "カーソル文字列の↓検索"
        ?. mp[, #G mp], >26

 「#G」はカーソル位置の1語を検索文字列として取得するコマンドです(Get Word)。【検索文字列】の1行ウィンドウで検索文字列を設定した場合と同様、検索モード([S])になり、画面左下に検索文字列がメッセージされます。

26 ^L [F05] "↓検索"
        ?. mp[, mp=2, #c mp],

 「mp=2,」で強制的に検索モードにして、「#c」で前方検索します。「mp[, ... mp],」でページングモードの保存・復帰をおこなうので、そのあいだmpを好き勝手にいじることができるわけです。前回検索した文字列を再度検索するマクロですから、「"↓再検索"」というタイトルのほうが似合う気がします。

 さて、共通処理をサブルーチン化すると、次のようになります。

21 ^QF "文字列の↓検索"
        ?. &01 ?. >26

22 ^QB "文字列の↑検索"
        ?. &01 ?. >28

1:      mp[, #F &l mp], (r)

23 ^^ "カーソル文字列の↓検索"
        ?. &01 >26

24 ^\ "カーソル文字列の↑検索"
        ?. &01 >28

1:      mp[, #G mp],

26:     >>
26 ^L [F05] "↓検索"
        ?. &01 #c mp],

28:     >>
28 ^O \[F05] "↑検索"
        ?. &01 #r mp],

1:      mp[, mp=2,

 ローカルマクロ「1:」が3つ出てきますが、実行中のマクロ以降で最初に見つかった「1:」をコールするので、番号が重複していても支障ありません。残念ながら、共通処理が短く、コールされるのも2回ですから、あまり短縮効果は上がっていませんが、利用の仕方はおわかりいただけたと思います。

1-16 ローカルマクロの先頭で「?.」は不可

 グローバルマクロの先頭にある「?.」は「(s)?.」と同じである、と前に書きました。たいていのマクロは編集モードで使うので、いちいち「(s)?.」を記述すると、マクロバッファを無駄に消費する。そこで「省メモリ」を旨とするVZの作者c.mos氏は、グローバルマクロの先頭であらかじめシステムモード(変数s)を内部レジスタにいれておき、「?.」だけで済む仕様にされたのでしょう。

 では、ローカルマクロの先頭で「?.」と書いてもよいのでしょうか?

21 ^QF "文字列の↓検索"
        &01 ?. >26

22 ^QB "文字列の↑検索"
        &01 ?. >28

1:      ?. mp[, #F &l mp], (r)

 結論を言うと、基本的に不可です。(ただし、このマクロに関しては、「&01」がグローバルマクロの先頭にあるので、可です)

 あえて、ローカルマクロでシステムモードをチェックするなら、

1:      (s)?. mp[, #F &l mp], (r)

 と、きちんと記述しなくてはなりません。実を言うと、これでも不都合があります。「(s)?.」でローカルマクロの「1:」を終了すると、呼び出し元の「21」「22」に戻り、その時点で内部レジスタは0になっているはずですから、「?.」にひっかからずに、「>26」「>28」を実行してしまいます。それを防止するには、2つの方法があります。

/ マクロを強制中断(使用禁止?)

1:      (s)?/ mp[, #F &l mp], (r)

 「.」(ピリオド)が現在実行中のマクロを終了する命令だとしたら、「/」(スラッシュ)はマクロのネストなどをいっさい無視して、マクロを強制的に中断する命令です。このマクロでは問題ありませんが、「(s)?/」より前で「mp[, mp=2,」などとシステム変数をいじっていた場合、「mp],」を待たずに中断してしまいます。また、「モード型マクロ」と呼ばれるタイプのマクロからこの種のマクロを使うと、モード型マクロもろとも中断してしまい、なにかと弊害があります。

 筆者の独断で、マクロの強制中断は使うべからずと言明しておきます。

&q  マクロのネストを1レベルスキップ(Quit)

1:      (s)? { &q. } mp[, #F &l mp], (r)

 こちらをおすすめします。

 システム関数「&q」で、呼び出し元のマクロを1レベルスキップできます。この場合、1レベルしかネストしていないので、「&q」の次の「.」で、終了します。

89 ^\   "カメの家族"
        ?.
        &01
        &m("親ガメ")
1:      &02 &m("子ガメ") &w(60)
2:      &03 &m("孫ガメ") &w(60)
3:      &m("曾孫ガメ") &w(60)

;3:     &q &m("曾孫ガメ") &w(60)        ; こちらを使うと「孫ガメ」をスキップ

 このマクロを実行すると、「曾孫ガメ」「孫ガメ」「子ガメ」「親ガメ」と順次メッセージがでます。ローカルマクロ「3:」を「&q」付きのものと差し替えると、「孫ガメ」をスキップします。

1-17 サブルーチンの返り値=内部レジスタ

 微妙なちがいですが、次のように書くとどうなるでしょうか。

21 ^QF "文字列の↓検索"
        ?. &01 (r)?. >26

22 ^QB "文字列の↑検索"
        ?. &01 (r)?. >28

1:      mp[, #F &l mp],

 エスケープによるキャンセルを、グローバルマクロに戻ってから「(r)?.」でチェックしようという魂胆です。結論から言うと、不可です。マクロに関する特記事項(マニュアル11-37)により、サブルーチンから戻った直後には、返り値(r)には内部レジスタがセットされます。この場合、グローバルマクロ先頭の「?.」で反転した内部レジスタの値1がセットされるので、「(r)?.」で必ず止まってしまいます。したがって、コール元に戻る前に、

1:      mp[, #F &l mp], (r)

 と返り値を評価しておく必要があります。

1-18 グローバルマクロ番号は1〜89、ローカルマクロ番号は1:〜99:

グローバルマクロ番号は実際は1〜127

 マニュアルによれば、1〜89がグローバルマクロで、90〜99はキーボードマクロですが、グローバルマクロで90〜99を使用してもかまいません。キーボードマクロの番号は「90番以降」かつ「グローバルマクロの最大番号より大きい番号」が自動的にふられます。たとえば、グローバルマクロですでに94を使用している場合、キーボードマクロの番号は95以降になります。

 実を言うと、グローバルマクロの番号は100〜127も使用できます。ただし、「>100」「&100」といったジャンプやコールはできません。また、127を使用すると、キーボードマクロを登録できなくなるので、119あたりまでにとどめておいたほうがよいでしょう。

ローカルマクロ番号は実際は0:〜99:

 ローカルマクロ番号も100以降を使用できますが、コールできないのでは仕方がありません。「0:」を使用できるのがミソで、小物マクロで1つだけサブルーチンを使うような場合には、「0:」をふって「&>」でコールするのが見栄マクロプログラマの極意となっています。

25 ^KJ "Char Code"
        ?.
        &b(3) &>
        (r.0)? { r.0-, &q &m("") #?. }
        r.0+,
:A      (s)?? &m("code:%02xh type:%d",cd,ct) &c >A

0:      (3+$).""

 おっと……。いきなり難易度Cになってしまいましたが、「文字コード表示」を^KJで実行/終了するマクロです(多重実行を防止)。「&>」だと「&00」より1バイト短く記述できますし、ローカルマクロ「0:」は相対位置を動かしてはいけないことがわかります。

1-19 文字定数(文字コード)

'A' 'あ' '亜'

 文字列を書き込む方法として、二重引用符「"」でくくる方法はすでに紹介しましたが、1文字だけでよいなら、「'A'」「'あ'」「'亜'」と記述できます。前に示した「句読点変換」のマクロを「'」で書き換えてみましょう。

80 ^\   "句読点置換"
        ?.
        #56                             ; 文字列の置換
        '。' #m                         ;【検索文字列】に「。」を入力リターン
        '.' #m                         ;【置換文字列】に「.」を入力リターン
        'a''y'                          ;「A テキスト全体」を全部一度に置換
        #56                             ; 文字列の置換
        '、' #m                         ;【検索文字列】に「、」を入力リターン
        ',' #m                         ;【置換文字列】に「,」を入力リターン
        'a''y'                          ;「A テキスト全体」を全部一度に置換

 「"ay"」が「'a'」と「'y'」に分かれていることに注意してください。「'ay'」と記述すると誤動作します。こういう単純な文字入力では、どちらを使ってもかまいませんが、内部処理的には、まったく異なる意味をもちます。(「ポインタによる文字列の比較」の項で詳述)

1-20 演算式の終端を明示

, (カンマ)

 「n=ln,」「i=10,」「i--,」「mp[, mp=2, ... mp],」に見られるように、演算式ごとに終端を「,」で明示するのが普通です。

        i=10,                           ; カウンタを10に設定
:A      (2){ "*" }                      ; 2 個の「*」を書き込む
        (i--)? >A                       ; i を 1 減らして 0 でなければループ

 のように、iを1減らした値をその場で評価する場合には、「)」で演算式の終端を判別できるので、「,」は必要はありません。むしろ付加するとエラーになります。

        i=10,                           ; カウンタを10に設定
:A      (2){ "*" }                      ; 2 個の「*」を書き込む
        &01                             ;「1:」をコール
        (i)? >A                         ; i が 0 でなければループ

1:      i--                             ; i を 1 減らす

 のように、「i--」がマクロの最後にある場合は、「マクロの終端」が「演算式の終端」を兼ねるので、これまた「,」は必要ありません。しかし、1バイトをあらそう切羽詰まった事情でもないかぎり、付加した方がよいでしょう。

1-21 メニュー

!nn nn番のポップアップメニューをコール

 「多重分岐とメニュー」の項で示したように、「!nn」でメニューをコールして、何番目の項目が選択されたか、返り値をえることができます。

返り値
r>=0選択したメニューアイテムの番号
r==-1[ESC]でキャンセル

 メニューの書式はマニュアルでは次のように書かれています。

  --------------------------------------------------------------------------
  <n> "[<title]",<width>,<items>[,<value width>[,-1][;<comment>]
      { ["<item>"] [,#nn|&nn|!nn|<option>] }
  --------------------------------------------------------------------------
          <n>             : メニュー番号(システム:1〜9 マクロ:1〜90)
          <title>         : メニューのタイトルメッセージ
          <width>         : メニューの横サイズ
          <items>         : メニューアイテムの個数
          <value width>   : パラメータのカラム数
          <comment>       : コメント
          <item>          : アイテムメッセージ
          #nn             : コマンド番号(nnは2桁の10進数)
          &nn             : マクロ番号
          !nn             : メニュー番号
          <option>        : オプション
  --------------------------------------------------------------------------

 意味がわかりますか? 筆者にはよくわかりません……。なんて言ってないで、実例で検討しましょう。

メニュー基本形

80 ^\   "巨人の星"
        ?.
        !01
        (r<0)?.
        (r)>? { >01 >02 >03 }

1:      ;---花形満---
        !02 (r<0)?.
        (r)>? { >A >B >C }
:A      &m("鉄バットと鉄ボールで鍛えたパワー").
:B      &m("一本足打法、帽子、ホームスチール").
:C      &m("ノックアウト打法")

2:      ;---左門豊作---
        !02 (r<0)?.
        (r)>? { >A >B >C }
:A      &m("玉砕するわけにはいかんとですたい!").
:B      &m("波切りスイング").
:C      &m("京子さんに一目惚れ")

3:      ;---オズマ---
        !02 (r<0)?.
        (r)>? { >A >B >C }
:A      &m("見えないスイングで引きつける").
:B      &m("見えないスイングで砂煙をはらう").
:C      &m("アメリカに帰った")

* P
1       "打者",12,3
        "T 花形満"
        "W 左門豊作"
        "D オズマ"

2       "魔球",18,3
        "1 バットを狙え"
        "2 消える魔球"
        "3 超スローボール"

 マクロそのものに意味はありません。メニューは次のように表示されるはずです。

    ┌ 打者 ──┐             ┌─ 魔球 ─────┐
    │T 花形満 │ メニューアイテムの個数は │1 バットを狙え  │
    │W 左門豊作│ どちらも3個       │2 消える魔球   │
    │D オズマ │             │3 超スローボール │
    └─────┘             └─────────┘
    ↑     ↑             ↑         ↑
    └─────┴─ メニューの横サイズ ─┴─────────┘
       12                     18

 「1 "打者",12,3」「2 "魔球",18,3」のパラメータ(引き数)でメニューアイテムの個数とメニューの横サイズを指定しています。

 この例のように、メニューの返り値で処理を分岐するのが最も基本的な使い方です。メニューの記述を開始する前に、「* P」(Pop Up Menu)と宣言するのを忘れないでください。「* M」と「* P」はマクロバッファに混在して格納されます。「* P」のあとに再びマクロコードを記述する場合には、あらためて「* M」と宣言してください。

ショートカットキー

 カーソルを移動してリターンを押さなくても、アイテムメッセージの先頭にあるショートカットキー(英数字)を押して選択することもできます。英字の場合、メッセージの先頭からいちばん最初にあらわれた英大文字が有効になるので、「microEMACS」と書いて「E」をショートカットキーに指定できます。

メニューアイテムの番号(メニューの返り値)

 メニュー2では「1」〜「3」をショートカットキーに指定していますが、これはメニューアイテムの番号(メニューの返り値)とは関係ありません。

選択したアイテム返り値
「1 バットを狙え」0
「2 消える魔球」1
「3 超スローボール」2

メニュー応用型(1)

 VZ.DEFのメニューには、メニューアイテムをメッセージではなく、コマンド番号やマクロ番号で記述しているものがあります。

* M マクロ
2  [F01] "【ファイルメニュー】"
        ?. !01
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
* P メニュー
1       "",24,14 #70 #73 #75 #72 #71 #74 #76 #82 &06 &07 &08 &09 #78 #77

 次のようなメニューが表示されます。

    ┌─【ファイルメニュー】─┐
  ┌→│O ファイルのオープン  │←#70
  │ │C ファイルのクローズ  │←#73
  │ │S ファイルのセーブ   │←#75
  │ │N 新規ファイルオープン │←#72
  │ │R Read onlyオープン   │←#71
  │ │L クローズ・オープン  │←#74
 14│ │A 既存ファイルへの追加 │←#76
 個│ │P ファイル名の変更   │←#82
  │ │D テキストの二重化   │←&06
  │ │U 編集のやり直し    │←&07
  │ │I カーソル位置へ挿入  │←&08
  │ │X 全ファイルのクローズ │←&09
  │ │E DOSコマンド実行    │←#78
  └→│Q エディタを終了    │←#77
    └────────────┘
    ↑            ↑
    └────────────┴─メニューの横サイズ(24)

 #nnで、VZの基本コマンドのコメント(「* C コマンド」で定義)をメニューに表示し、選択するとその機能を実行します。&nnも同様で、マクロのタイトル(「* M マクロ」で定義)を表示・実行します。help.defのように、!nnでメニューを多重コールするものもあります。

メニュー応用型(2)

* M マクロ
3 \[F01] "【モードメニュー】"
        ?. !02
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

* P
2       "",30,15,6
        "C CR の表示",Dc
        "T TABの表示",Dt
        "E EOFの表示",De
        "L 行番号の表示",Dl
        "N 行番号モード 論理 表示",Dn
        "S システム情報の表示",Ds
        "\ バックスラッシュ     \ \",Db
        "U アンダーライン",Du
        "A オートインデント",Ei
        "I 検索時の英大/小文字  区別 同一視",Ec
        "W ワードサーチ",Ew
        "H ページング単位       全画面 半画面",Eh
        "B バックアップ",Eb
        "R 画面の横幅(2〜250)",WE
        "P ページ長",PG

 次のようなメニューが表示されます。

    ┌─【モードメニュー】─────┐
  ┌→│C CR の表示      ON   │←Dc(スイッチ型オプション)
  │ │T TABの表示      OFF  │←Dt(    〃     )
  │ │E EOFの表示      ON   │←De(    〃     )
  │ │L 行番号の表示     OFF  │←Dl(    〃     )
  │ │N 行番号モード     論理  │←Dn(    〃     )
  │ │S システム情報の表示  OFF  │←Ds(    〃     )
 15│ │\ バックスラッシュ   \   │←Db(    〃     )
 個│ │U アンダーライン    OFF  │←Du(    〃     )
  │ │A オートインデント   ON   │←Ei(    〃     )
  │ │I 検索時の英大/小文字 区別  │←Ec(    〃     )
  │ │W ワードサーチ     ON   │←Ew(    〃     )
  │ │H ページング単位    全画面 │←Eh(    〃     )
  │ │B バックアップ     ON   │←Eb(    〃     )
  │ │R 画面の横幅(2〜250)  80   │←WE(数値型オプション)
  └→│P ページ長       0   │←PG(    〃    )
    └───────────────┘
    ↑           ↑   ↑
    │           └───┴─パラメータのカラム数(6)
    │               ↑
    └───────────────┴─メニューの横サイズ(15)

 「アイテムメッセージ」の後ろに「オプション」を記述すると、オプションの現在値をいっしょに表示できるのです。オプションには、「+」か「-」を指定するものと、数値を指定するものがあります。VZ.DEFの「* O オプション」を参照してください。

タイプオプションメニュー表示
スイッチ型オプションDc+/Dc-ON/OFF
数値型オプションPG00

スイッチ型オプション

 たとえば、「C CR の表示」にカーソルを移動してリターンで選択すると、「ON」と「OFF」が切り替わります。画面に編集テキストがあれば、改行マーク「↓」が消えたり現れたりするのが確認できるでしょう。「C」を押してショートカット選択すると、切り替わると同時にメニューが終了して、すぐに編集を再開できます。

 「N 行番号モード」ように「論理」と「表示」を切り替えて表示したいなら、

------------------------------------------------------------------------------
        "N 行番号モード*論理 表示",Dn
                       ↑   ↑
                      タブ  スペース
------------------------------------------------------------------------------

 のように、まずタブで区切って、「ON」「OFF」に相当する語を半角スペースで区切って記述してください。この種のメニューを使ったマクロを、タブ/スペース変換したり、制御コードを読み飛ばすパソコン通信ネットで送受信したりすると、タブコードが消えて誤動作します。安全を期すには、次のようにタブコードを文字列中に埋め込んでください。

------------------------------------------------------------------------------
        "N 行番号モード$(9)論理 表示",Dn
------------------------------------------------------------------------------

 ※ タブの文字コードは 09h

 タブにかぎらず、制御文字(00h〜20h)を文字列中にそのまま書き込むと、なにかと支障があるので、「$(16進数)」という書式で埋め込んだほうがよいでしょう。(このほかにも「$」は特別な意味をもっています)

数値型オプション

 「R 画面の横幅(2〜250) 80」を選択すると、

    ┌ R 画面の横幅(2〜250) ┐
    │           │
    └───────────┘

 という1行ウィンドウがあらわれます。ここにたとえば「40」を入力してリターンを押すと、画面の横幅が変わります。この場合も、リターン選択かショートカット選択かで、メニューに戻るか編集画面に戻るかを使い分けることができます。

混在型

* M マクロ
4 \[F02] "【サブメニュー】"
        ?. !03
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

* P
3       "",26,9  #45 #51 #52 #54 #67 #68 &15 #79
        " $4EMS free/total: $ef/$em"

 次のようなメニューが表示されます。

    ┌─【サブメニュー】───┐
  ┌→│J 指定行番号ジャンプ  │←#45
  │ │C スタック消去     │←#51
  │ │B ブロックの先頭/最後 │←#52
 9 │ │U 行の回復       │←#54
 個│ │R Read onlyモード    │←#67
  │ │I インデントの変更   │←#68
  │ │T タブの切替え  (8)  │←&15「15 ^KI "T タブの切替え  ($ht)"」
  │ │P DOS画面出力の参照   │←#79
  └→│ EMS free/total: 0/1  │←" $4EMS free/total: $ef/$em"
    └────────────┘
    ↑            ↑
    └────────────┴─メニューの横サイズ(26)

 最後のアイテムだけ、メッセージをそのまま記述してあります。「$ef/$em」はメニュー上では「0/1」というぐあいに表示されるでしょう。「$」の直後に変数を記述すると、その値が表示されるのです(ただし、「$p」や「$pp」など「$p?」の変数は文字列を表示します。詳しくは「文字列内の制御文字」)。7番目のアイテムのように、コールするマクロのタイトルに「$オプション」の書式がある場合も有効です。

 「$4EMS」の「$4」はメニュー上に対応する表示がありませんが、これはメッセージの表示色を指定しているのです。

非実行型

 help.def には、

4       "ファイラー",42,10,24,-1
        #05 #06 #07 #08 #88 #89 #90 #91 #92 #93

 のように、パラメータに「,-1」を付加したものがあります。このメニューにはカーソルがあらわれないので、なにも選択(実行)できません。メッセージを表示するだけのメニューです。

メニューのタイトルを省略

 メニューのタイトルを省略すると、コール元のマクロのタイトルが代わりに表示されます。【ファイルメニュー】【モードメニュー】【サブメニュー】のマクロを読み返してください。いずれも「""」となっています。

 ローカルマクロからコールした場合、タイトルはなくなり、メニューの枠線だけになります。

メニュー番号とタイトルのあいだには最低1個のタブ・スペース

 メニュー番号とタイトルのあいだには最低1個のタブ・スペースをおかないと、タイトル部分が読み飛ばされて、おかしな表示になります。

* P
3"",26,9  #45 #51 #52 #54 #67 #68 &15 #79
        " $4EMS free/total: $ef/$em"

 こうすると表示が乱れます。メニュータイトルがある場合には、コール元のマクロタイトルが表示されてしまいます。VZのメニュー解析に手抜きがあるのでしょうか。アイテムメッセージの右側に「;」で補足説明(コメント)を書くこともできないので、気をつけてください。

1-22 メニューがらみのサーチ優先順位

 メニューがらみのサーチを整理しておきます。

メニューをコールする

 メニューをコールすると、現在実行中のマクロ以降をサーチします。見つからなかった場合、マクロのコールとはちがって、マクロバッファ先頭からの再検索はしません。したがって、メニューはコール元のマクロより後ろ側に配置する必要があります。

メニューからマクロをコールする

* P
3       "",26,9  #45 #51 #52 #54 #67 #68 &15 #79
        " $4EMS free/total: $ef/$em"

 のように、「&15」でコールした場合、マクロバッファの先頭からグローバルマクロをサーチします。グローバルマクロ「15」がメニューの前と後に2つあった場合、前側のマクロがコールされます。ローカルマクロの「15:」はいっさい関係ありません。

; 行末までコメントとして読み飛ばす

 いまさら言うまでもないでしょうが、VZは「;」(セミコロン)から行末まではコメント(補足説明)として読み飛ばすので、マクロコードの説明など自由に書くことができます。マクロファイル中のよぶんな改行やタブ・スペースも読み飛ばすので、見やすいように適当にインデント(字下げ)してください。もちろん「"」で囲った文字列内のタブ・スペースはちゃんと読み込みます。

$ 文字列内の制御文字

 「"」で囲った文字列の中に「"」という文字そのものを書きたいときはどうするの? 「$」そのものを書きたいときは? ……こう思ったあなたは鋭いです。

記述表示&m &?
$"「"」そのもの
$$「$」そのもの
$0〜$7表示色×
$!リバース(反転)×
$a?「オプションA?」の表示色(?は英字)×
$p?「変数P?」の示す文字列×
$pcコマンド名(vz,vzj31,vzibmなど)×
$pnカレントドライブ名×
$pdカレントディレクトリ×
$??「変数??」の10進表示×
$lc表示行数×
$ht編集テキストのタブカラム数×
$(hh,ee,xx,..)16進コード指定(hh,ee,xxは16進数)

※ 「$pg」はうまくいきません

 これらが有効なのは、

 などです。通常の文字列入力や「&m("文字列")」「&?("文字列")」では一部しか使えません。(表の○×を参照)

 「$p?」のように変数の1文字目が「p」の変数は、ポインタ変数と呼ばれ、文字列を表示します。汎用変数の「p」「pp」も転用できます。ほかにも「pa,pf,pi,pq,ps,pw,px,pz」があります。「pg,pu,pv」はうまくいきません。

 「$??」は一般のシステム変数、汎用変数「a,b,c,...」「aa,bb,cc,...」、汎用フラグ「fa,fb,fc,fd,fx,fy」の現在値を10進数で表示します。

 「$(hh,ee,xx,..)」は、制御コードのような要注意の文字を「文字そのもの」ではなく「文字コード」(数値)で埋め込みたいときに使うほか、「&i("$(hh,ee,xx)")」と機械語を記述するときにも使います。

【制御コードを文字そのもので記述】

80 ^\   "改ページ"
        "^L"                            ; [CTRL]+[P],[L]で入力した文字

【制御コードを文字コードで埋め込む】

80 ^\   "改ページ"
        "$(0C)"

文字列の連結

 「$」はさらに、文字列を連結する働きもします。

 「"」で囲った文字列が長く、80桁をこえて見づらい場合には、途中を「$」で区切って改行してください。すると、次の文字があらわれるまで、改行・タブ・スペースを読み飛ばします。

        "亜会証験県暁灯秋売尭晃朗暁叡瞭顕耀聡悪欠挙称浅糾芦鯵戯讐価与予辺$
        当圧羮址嵌発湧剰弥余礼険怪奇疎鉱争弁砺庵塩囲医為癒気粋権育粥戦犠"

1-23 変数

 変数は1文字か2文字の英字であらわし、大/小文字の区別はしません。

 「汎用変数」「汎用フラグ」「オプション変数」「システム変数」に大別できます。

汎用変数

表記推奨する使用法など注意
a〜y(r,s,wを除く)使いっぱなしzも使用禁止
aa〜yy(rr,ss,wwを除く)プッシュ・ポップzzも使用禁止
ax,bx,cx,dx,si,di割り込み処理関数&iでの受け渡し変数

 VZに添付されている次のマクロがよい例です。

マクロ名変数名目的
block.defcc矩形ブロックとして取得した行数
keisen.defmm作表モードのON/OFF

 block.defの場合、^Y [F08]「矩形ブロック削除」で取得したブロックの行数をccに保存しておき、^J [F09]「矩形ブロック挿入」でcc行分のブロックを復活させます。「削除」と「挿入」のあいだで他のマクロを実行できるので、もしそのマクロでccが破壊されると、「挿入」が誤動作します。ccが-1など負数になっていようものなら、無限ループにおちいるでしょう。

 したがって、他のマクロでcc,mmを使いたいときには、

80 ^\   "保存変数"
        cc=10,
        cc[,                            ; マクロスタックにプッシュ
        cc=-1,
        &m("このあいだccをいじってもよい") &w(120)
        cc],                            ; マクロスタックからポップ
        &m("ccは%dです",cc)             ;「ccは10です」と表示されるはず

 のように、マクロの最初でプッシュ、最後でポップしてやるべきです。

 とはいえ、こういった保存変数(と呼ぶことにします)をすべてチェックして重複を避けるのは至難の技です。すべてのマクロですべての変数をプッシュ・ポップするなんて羽目になりかねません。

 そこでおすすめしたいのが、1文字変数と2文字変数の使い分けです。

 と決めておけば、気が楽でしょう。よほど大きなマクロでないかぎり、1文字変数だけで用が足りるはずです。フリーソフトとして公開されているマクロでは、この使い分けが主流になっています。

 あなた自身が、保存変数を使ったマクロを作る場合、当然ながらその変数をプッシュ・ポップしてはいけません。それでは保存変数になりません。マクロファイルの先頭で「保存変数……hh,ll」とコメントしておけばユーザーに親切です。

 なお、z,zz使用禁止であることを強調しておきます。「a〜y」「aa〜yy」をよーく見つめてください。VZをハングアップさせたい人は、

        &m("zz=%d",zz)                  ; zz の値を知りたいのですが……

 を試してみてください。wwでも同じ結果が得られます。(そのほかptなど未使用の2文字変数)

 zのほうはVZの常駐/非常駐を示すシステム変数なので、参照するだけならかまいません。r,rr,ssについては参照するだけでなく、数値を代入する裏技的な使い方ができます。

汎用フラグ

表記推奨する使用法など注意
fa,fb,fc,fd,fx,fyスイッチ(ON/OFF)0(-)か1(+)のみ

 汎用フラグはメニューのON/OFF表示に向いています。

 VZ添付のマクロでは、mi.defがfb,fc,fdを使用しています。mi.defユーザーはかなり多いので、プッシュ・ポップで保護したほうがよいでしょう。

(mi.defより)

* Pop up menu
4       "", 36, 13, 7
        "M メインメニュー",             !03
        "R ROLL UP/DOWNの動作   ジャンプ スムース",fc
        "E EOFの表示",                  De
        "B バックアップの作成",         Eb
        "2 20/25行モード            $lc"
        "N 行番号モード 論理行 表示行", Dn
        "F フルパス名で表示",           Df
        "H タブのカラム数",             HT
        "L 1ページの行数",             PG
        "T TAB→SPACE変換",             Eu
        "\*バックスラッシュ     \ \",   Db
        "Q*編集終了時の動作     Cont Quit",Sq
        "S [SHIFT]+[↓][↑]     高速 超高速",fb

 汎用変数も0か1を代入してスイッチとして使用できますが、メニューのパラメータにすると、数値がそのまま表示されてしまいます。

メニューの記述

        "R ROLL UP/DOWNの動作   ジャンプ スムース",aa

メニューの表示

        〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
        │R ROLL UP/DOWNの動作       0       │
        〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
                                     (aaの値)

オプション変数/システム変数

 オプション変数とシステム変数については、マクロで数値を代入して動作条件などを変更できるものと変更できないものがあります。巻末の一覧表を参照してください。

1-24 演算子

演算子一覧 (優先順位は掲載順と同じ)

【単項演算子】

a+a=1aに1を代入する
a-a=0aに0を代入する
-aa=-aaの符号を変える
a[aをマクロスタックにプッシュする
a]aをマクロスタックからポップする
a++a=a+1aに1を足す(インクリメント)
a--a=a-1aから1を引く(デクリメント)
a~(a=~a)aを補数にする
a!a=a!aを否定にする(論理NOT)
a!!a=a!!aの上位バイトと下位バイトを交換

 「単項演算子」は「変数は1つ」ぐらいの意味なので、深く考える必要はありません。おなじみのマクロスタックへのプッシュ・ポップはあまり数学的なイメージはありませんが、演算子に分類されます。

a[ a] マクロスタックへのプッシュ・ポップ

 主にオプション変数・システム変数の標準設定を保護する目的に使用します。

======== ketaori.def by I.KAMIMURA ========
* M
80 ^\   "超簡単桁折り"
        ?.
        mi[, mi-,                       ; 挿入モード
        ei[, ei+,                       ; オートインデント(タブ・半角スペース)
        ej[, ej-,                       ; オートインデント(全角スペース)
        #<                              ; 論理行頭へ
:A      &k (r==$1B)? >C                 ; [ESC]が押されたら中断して :C へ
        #< &x(72)                       ; カーソルを72+1桁目に移動
        (ct<2)? >B                      ; 行末ならラベル :B へ
        #m >A                           ; 改行してラベル :A へ
:B      #d                              ; 次の行へ
        (ct)? >A                        ;★ EOF(ct:0)でなければラベル :A へ
:C      ej],                            ; 以下、変数を元に戻す
        ei],
        mi],
*
======== End of ketaori.def ========

 1行が長すぎる文書をパソコン通信ネットにアップロードすると、行が尻切れになることがあります。適当な桁位置で改行しておくのがふつうですが、このマクロを使えば一気に桁折りできます。カーソル位置からファイルの最後まで処理するので、中断したかったら[ESC]を押してください。(の行を削除すると、カーソルのある行だけ処理します)

 mi,ei,ejをプッシュ・ポップしている理由を説明します。もしユーザーがVZを上書きモード(mi+)で使っていたら、「#m」は改行ではなく、カーソルを次行の行頭に移動する命令になります。画面が流れ去っていくばかりで、桁折りはおこなわれません。そこで、「mi[,」でユーザーの使用状況(環境)をマクロスタックに保存しておき、「mi-,」でマクロに都合のよいモードに変更します。処理が終了したら、「mi],」で元に戻してやります。ei,ejも同様です。

 「変数」の項で述べたように、汎用変数の中でも保存すべきものがあれば、「aa[, ... aa],」とプッシュ・ポップします。

プッシュとポップは逆順で

 マクロスタックは、数値を一時的に保管する場所です。

 最後に入れたものが一番手前にあるので、複数のプッシュをおこなったら、逆順でポップしてください。

                ┌───────┐
                │マクロスタック│
                ├───────┤
    (プッシュ)       │      5 │  (設定変更)
    mi[,  0 ──────→ │    0 5 │   mi-,
    ei[,  1 ──────→ │   1 0 5 │   ei+,
    ej[,  0 ──────→ │ 0 1 0 5 │   ej-,
    ……          〜〜〜〜〜〜〜〜〜
    (ポップして代入)    │       │
    ej],  ←────── 0 │   1 0 5 │
    ei],  ←────── 1 │    0 5 │
    mi],  ←────── 0 │      5 │
                └───────┘

 ここで注意すべきは、マクロスタックへプッシュしても、mi,ei,ejの値自体は変化しないことです。mi,ei,ejが0(ゼロ)になるわけではないので、「mi-, ei+, ej-,」と意識的に変更する必要があります。(もっとも、この例では結果的に同じ設定になっています)

 「単項演算子」の一覧表で、「aをマクロスタックからポップする」という記述がありますが、正確には「マクロスタックからポップした値をaに代入する」です。

 「a~」「a!」「a!!」については、別に章をもうけて解説します。

【二項演算子】

ポインタ演算

.p.ipからiバイト先バイトポインタ
..p..ipからiワード先ワードポインタ

※ 左辺は変数のみ。iの値は0〜63

ビット演算

<<a<<baを左へbビットシフト左シフト
>>a>>baを右へbビットシフト右シフト
&a&baとbのビットごとのANDビット積
^a^baとbのビットごとのEXORビット差
|a|baとbのビットごとのORビット和

算術演算

*a*baとbを掛ける積算
/a/baをbで割る除算
%a%baをbで割った余り剰余
+a+baとbを足す加算
-a-baからbを引く減算

関係式

<a<baはbより小さい左不等
<=a<=baはb以下等価左不等
>a>baはbより大きい右不等
>=a>=baはb以上等価右不等
==a==baとbは等しい等価
!=a!=baとbは等しくない非等価
&&a&&baかつb(論理AND)積結合
^^a^^baとbはちがう(論理EXOR)差結合
||a||baまたはb(論理OR)和結合

※ 成立なら1、不成立なら0の値をもつ

【代入演算子】

代入演算

=a=bb(右辺)をa(左辺)に代入
&=a&=ba=a&baとbのビット積をaに代入
^=a^=ba=a^baとbのビット差をaに代入
|=a|=ba=a|baとbのビット和をaに代入
*=a*=ba=a*baとbを掛けた結果をaに代入
/=a/=ba=a/baをbで割った結果をaに代入
%=a%=ba=a%baをbで割った余りをaに代入
+=a+=ba=a+baとbを足した結果をaに代入
-=a-=ba=a-baからbを引いた結果をaに代入

 「ポインタ演算」「ビット演算」については、別に章をもうけて解説します。ここでは、関係式の「&&」「^^」「||」による複合条件について解説します。

80 ^\   "西暦→和歴"
        ?.
        &g("西暦")
        (r>=1868 && r<=1999)?? { &m("無効"). }
        (r==1868)? { &m("明治元年"). }
        (r>=1869 && r<=1911)? { &m("明治%d年",r-1867). }
        (r==1912)? { &m("大正元年"). }
        (r>=1913 && r<=1925)? { &m("大正%d年",r-1911). }
        (r==1926)? { &m("昭和元年"). }
        (r>=1927 && r<=1988)? { &m("昭和%d年",r-1925). }
        (r==1989)? { &m("平成元年"). }
        (r>=1990)? { &m("平成%d年",r-1988). }
    ┌ 西暦 ┐
 最初に│   │の1行ウィンドウがあらわれます。
    └───┘

 西暦の数値(1868〜1999)を入力すると、「明治」〜「平成」の和歴に換算してメッセージに出します。1868〜1999でない数値だと、「無効」になります。

 システム関数「&g」は1行ウィンドウに入力された数値をrに返すので、その値によって分岐するわけですが、範囲をチェックするには「&&」で2つの関係式を連結します。

               (r>=1868 && r<=1999)??.

「rは1868以上」かつ「rは1999以下」でなければ終了

 次のような書き方はできません。

               (1868<= r <=1999)??.

 こんな場合VZはどう処理するのかわかりませんが、内部レジスタに0以外の値が渡されるのは間違いないので、思い通りに分岐しません。

 「&&」ではなく、「||」を使うこともできます。

               (r<1868 || r>1999)?.

「rは1868より小さい」または「rは1999より大きい」なら終了

 「??.」が「?.」に変わっていることに注意してください。

代入演算で値を丸める

 rを生のままでなく、都合のいいように加工して使うこともできます。

80 ^\   "オリンピック"
        ?.
        &g("西暦")
        (r>=1896 && r%4==0)??.          ; 1896以上かつ4の倍数を評価
        r-=1892,                        ; rから1892引いた結果をrに代入
        r/=4,                           ; rを4で割った結果をrに代入
        &m("第%d回オリンピック",r)      ;「第?回オリンピック」

 「1992」と入力すると、「第25回オリンピック」とメッセージが出ます。

 第1回オリンピックは1896年で、4年ごとの開催ですから、まず「1896以上かつ4の倍数」であるかどうかチェックします。「r%4」は「rを4で割った余り」という意味です。余りが0なら4の倍数だとわかります。

 「||」で書き換えると、

        (r<1896 || r%4)?.               ; 1826より小さいまたは4の倍数でない

 となります。関係式「r%4==0」が演算式「r%4」に変わったことに注目してください。「r%4!=0」より短く記述できます。このように、「&&」「||」で連結する式は別タイプでもかまいません。のみならず、別の変数を使った式を連結することもできます。

 あとは「r-=1892,」「r/=4,」で西暦を開催回数に加工する手法をご賞味ください。

 複数の演算を一括すると、次のように短く記述することができます。

80 ^\   "オリンピック"
        ?.
        &g("西暦")
        (r>=1896 && r%4==0)??.
        &m("第%d回オリンピック",(r-1892)/4)

 「-」は「/」より優先順位が低いので、(...)で囲みます。おまけに「&m」の括弧の中に演算を配置すれば、気分はもう激辛マクロ師です。