第4章 ポインタ演算、その他

 ヨーロッパアルプスの三大北壁が 
マッターホルン 
アイガー 
グランド・ジョラス 
であるとすれば、
VZマクロ界の三大北壁は、
ポインタ演算 
ビット演算 
ファンクションコール 
であると言えるでしょう。

4-1 ポインタ演算

文字入力ウィンドウから複数の文字列を取得したいのですが……

======== headfoot.def by I.KAMIMURA ========
【ヘッダ・フッタ】失敗例

* M
80 ^\   "Header and Footer"
        ?.
        &g("Header$(9)........")        ; ヘッダを入力
        (r<0)?.                         ; [ESC]で中断
        h=pw,                           ; ★文字列を h に代入?
        &g("Footer$(9)........")        ; フッタを入力
        (r<0)?.                         ; [ESC]で中断
        f=pw,                           ; ★文字列を f に代入?
        &g("Line/Page")                 ; 1ページあたり本文行数を入力
        (r<1)?.                         ; 正の数値でなければ終了
        pg=r,                           ; 1ページあたり行数を設定
        dn+, dl+,                       ; 表示行モード。行番号表示ON
        mi[, mi-,                       ; 挿入モード
        ei[, ei-,                       ; オートインデントOFF
        ej[, ej-,                       ; オートインデントOFF(全角)
        eu[, eu+,                       ; [TAB]でスペースコードを入力ON
        &s
        #^ #<                           ; ファイルの先頭へ
:A      &m("Line:%d",ld)                ; 現在の行番号をメッセージ
        #n #n &?("[%s]",h)              ; ★ページ先頭に2行挿入「<header>」
        x=we-lx-8, #< &#68(x)           ; 右寄せ
        &#c(pg+2)                       ; 1ページスクロール
        (r)? {                          ; ファイルの最後か?
                (ct)? { #> #m }         ; EOF 行対策
                pg+=4,                  ; 1ページあたり行数を 4 増やす
:B              ((ld+1)%pg)? { #m >B }  ; ページの区切りまで改行
        }
        #m #n &?("==%s==",f)            ; ★ページ最後に2行挿入「<footer>」
        x=we-lx-1, #< &#68(x/2)         ; センタリング
        #> #d (ct)? >A                  ; 次の行へ。EOF なら終了
        eu], ej], ei], mi],             ; オプション復帰
        &d #] &m("Page:%d",ld/pg)       ; 全ページ数をメッセージ
*
======== End of headfoot.def ========

 「Header」「Footer」「Line/Page」の文字列入力ウィンドウが順次あらわれて、入力した文字列と数値をもとに、ヘッダとフッタを作成する……つもりのマクロです。試しに、

  ┌ Header ─────┐      ┌ Footer ─────┐      ┌ Line/Page  ┐
  │VZマクロ道場    │  →  │第4章            │  →  │10          │
  └─────────┘      └─────────┘      └──────┘

 と入力してみると、

マクロ起動の様子

マクロ実行後の結果

 あれれ? ページあたりの行数が本文10行になったのはいいのですが、ヘッダが「[10]」、フッタが「==10==」になりました。

 「&g」の文字列入力ウィンドウに入力された文字列はpw(ワーク用ヒストリーバッファ)に格納されるので、それぞれ「h=pw,」「f=pw,」で変数に保存しておいて、「&?("[%s]",h)」「&?("==%s==",f)」で書き込んでいるつもりなのですが、最後に入力した「10」にすり替わってしまいます。

 これはヒストリーバッファの構造に原因があります。

ヒストリーバッファのイメージ

    pw ............................................................ pw+64
    ┌───────┬─┬─────────────────┬─┬─┐
    │VZマクロ道場│^@│古い文字列                        │^@│^@│
    └───────┴─┴─────────────────┴─┴─┘

    pw ............................................................ pw+64
    ┌───┬─┬───────┬─┬───────────┬─┬─┐
    │第4章│^@│VZマクロ道場│^@│古い文字列            │^@│^@│
    └───┴─┴───────┴─┴───────────┴─┴─┘

    pw ............................................................ pw+64
    ┌─┬─┬───┬─┬───────┬─┬───────┬─┬─┐
    │10│^@│第4章│^@│VZマクロ道場│^@│古い文字列    │^@│^@│
    └─┴─┴───┴─┴───────┴─┴───────┴─┴─┘

 入力するにしたがって、古い文字列から捨てられていきます。それぞれの文字列はヌルコードと呼ばれる制御コード(文字コード $00)で区切られています。pwは常にヒストリーバッファの先頭のアドレス(メモリ上の番地)を指し示します。

 「h=pw,」の時点では、hは「VZマクロ道場」を指し示していますが、3回の文字入力が終わった時点で、最後の「10」を指し示すことになるのです。

ヌルコードの視認

 テキスト上で[CTRL]+[P],[@]とキー入力すると「^@」と書き込まれます。「^」と「@」ではありません。カーソルを合わせると「^」と「@」がくっついているのがわかるはずです。

ヒストリの2番目以降の文字列を指し示す

 ヒストリーバッファの構造がわかれば、2番目以降の文字列を取り出す方法はいろいろあります。

        &g("Footer$(9)........")        ; フッタを入力
        (r<0)?.                         ; [ESC]で中断
        #J #e x=lx, #[                  ; ★文字列の長さを x に代入
        &g("Line/Page")                 ; 1ページあたり本文行数を入力
        (r<1)?.                         ; 正の数値でなければ終了
        pg=r,                           ; 1ページあたり行数を設定
        #J #e y=lx, #[                  ; ★文字列の長さを y に代入
        f=pw+y+1,                       ; ★ pw から y+1 バイト先
        h=f+x+1,                        ; ★ f から x+1 バイト先

 【行番号】の1行ウィンドウは「&g」の文字入力ウィンドウと同じくpwを利用します。文字入力した直後に文字列の長さを調べておき、pwに加算した値をf,hに代入するのです。「+1」は、ヌルコード1バイト分です。

    pw ......f...........h......................................... pw+64
    ┌─┬─┬───┬─┬───────┬─┬───────┬─┬─┐
    │10│^@│第4章│^@│VZマクロ道場│^@│古い文字列    │^@│^@│
    └─┴─┴───┴─┴───────┴─┴───────┴─┴─┘
      2   1 ↑  6     1 ↑
            │          │
            │          └── h=f+6+1,(fから7バイト先)
            │
            └──────── f=pw+2+1,(pwから3バイト先)

「成功した!」の図

 やった! うまくいきました。

 「&?("%s",p)」や「&m("%s",p)」は、アドレスpからヌルコードまでを文字列として取り出す命令です。試しに「h=f+x+1+2,」として、さらに2バイト加算すると、「Zマクロ道場」と書き込まれます。(「V」は2バイト)

 しかし、このマクロにはまだ弱点があります。ヘッダとフッタで同じ文字列を入力した場合、VZは古い方を削除して、新しい方だけを残します。すると、ヒストリーの3番目の文字列は「古い文字列」になってしまうではありませんか。うーん、困りました。ヒストリーの仕組みに左右されないように、どこかに文字列を保存しておく方法はないものでしょうか。

ポインタで文字列の内容を書き換える

p.i バイトポインタ演算子(i=0〜63)

 バイトポインタ演算子を使えば、文字列中の任意の1バイトを取り出すことができます。

======== 2001.def by I.KAMIMURA ========
* M
80 ^\   "2001"
        p="IBM",
        &m("何かキーを押してください")
        !01                             ; メニュー1回目
        a=p.0, a--, p.0=a,              ; 0バイト目の文字コードを1減らす
        b=p.1, b--, p.1=b,              ; 1バイト目の文字コードを1減らす
        c=p.2, c--, p.2=c,              ; 2バイト目の文字コードを1減らす
        !01                             ; メニュー2回目
        &m("")

* P
1       "",8,1,0,-1
        "[$p]"

*
======== End of 2001.def ========

 このマクロを実行すると、pに代入した文字列がメニューに表示されます。

        ┌ 2001 ┐                      ┌ 2001 ┐
        │[IBM] │ 何かキーを押すと ... │[HAL] │ と、pの内容が変わります。
        └───┘                      └───┘

 演算の過程をみてみましょう。

a,b,cの値a,b,cから1引いた値
a=p.0,'I'の文字コード$49'H'の文字コード$48p.0=a,
b=p.1,'B'の文字コード$42'A'の文字コード$41p.1=b,
c=p.2,'M'の文字コード$4D'L'の文字コード$4Dp.2=c,

 これで、「"IBM"」が「"HAL"」に置き換わります。

 ちょっと凝って、文字コードを1ずらす方法を示しましたが、

        p.0='H',                        ; 0バイト目に'H'の文字コードを代入
        p.1='A',                        ; 1バイト目に'A'の文字コードを代入
        p.2='L',                        ; 2バイト目の'L'の文字コードを代入

 と文字コードを直接代入しても同じ結果が得られます。

 「p="IBM",」でpに代入されるのは、文字列の先頭のアドレスであり、「p.i」という記述でpからiバイト目の文字(コード)を取り出すことができるのです。

p..i ワードポインタ演算子(i=0〜63)

 脇道にそれるのを覚悟で、全角文字を書き換える方法も示しておきましょう。

======== 2001.def by I.KAMIMURA ========
* M
80 ^\   "2001"
        p="IBM",
        &m("何かキーを押してください")
        !01                             ; メニュー1回目
        a=p..0, a!!, a--, p..0=a!!,     ; 0ワード目の文字コードを1減らす
        b=p..1, b!!, b--, p..1=b!!,     ; 1ワード目の文字コードを1減らす
        c=p..2, c!!, c--, p..2=c!!,     ; 2ワード目の文字コードを1減らす
        !01                             ; メニュー2回目
        &m("")

* P
1       "",10,1,0,-1
        "[$p]"

*
======== End of 2001.def ========
        ┌ 2001 ┐    ┌ 2001 ┐
        │[IBM]  │ → │[HAL]  │ と書き換わります。
        └─────┘    └─────┘

 バイトポインタは1バイトずつ取り出すのに対して、ワードポインタは2バイトずつ取り出します。

 まず、半角文字は1バイト、全角文字は2バイト(1ワード)と心得ておいてください。

p="IBM",p.1pから1バイト目p='B'
p="IBM",p..1pから1ワード(2バイト)目p='B'

 さて、VZが動作する8086CPU系のパソコンは全角文字コードの上位バイトと下位バイトを逆転してメモリに格納するという特性があります。一方VZは全角文字をそのまま格納するので、「p..i」でワードデータを取り出すと、上位バイトと下位バイトが逆になります。

8086CPUのメモリイメージ

                p  =  "   I    B    M   "  ,
               ----------------------------------
文字コード      70 30 22 68 82 61 82 6C 82 00 2C

VZのメモリイメージ

                p  =  "   I    B    M   "  ,
               ----------------------------------
文字コード      70 30 22 82 68 82 61 82 6C 00 2C

 ※ 文字列の終端の「"」はヌルコード($00)に変換してメモリに格納されます。ヒストリーバッファと同様、ポインタpからヌルコードまでを文字列として取り出す仕組みになっています。

 「$8268」と「$6882」は数列としての意味はありません。単純に上位2桁と下位2桁が逆転します。

 さて、逆転したまま文字コードを1減らすと、思い通りになりません。

        ┌───────────────────┐
        │             1 減らす                 │
        ├───────────────────┤
        │「??」($6882)   →      「??」($6881) │
        │「I」($8268)   →      「H」($8267) │
        └───────────────────┘

 そこで、上位2桁と下位2桁を逆転する演算子が用意されています。

c!! 上位バイトと下位バイトを交換する

a!!,b!!,c!!,a,b,cから1引いた値
a=p..0,'I'の文字コード$8268'H'の文字コード$8267p..0=a!!,
b=p..1,'B'の文字コード$8261'A'の文字コード$8260p..1=b!!,
c=p..2,'M'の文字コード$826C'L'の文字コード$826Bp..2=c!!,

 「a!!,」「b!!,」「c!!,」で逆転して、1引いて、もう一度逆転して代入します。

 この演算子は優先順位が高いので、最後は「p..0=a!!,」と短く済ませていますが、ていねいに書くと、

        a=p..0, a!!, a--, a!!, p..0=a,  ; 0ワード目の文字コードを1減らす

 となります。反対に切り詰めて書くなら、

        a=(p..0)!!, p..0=(a-1)!!,       ; 0ワード目の文字コードを1減らす

 となります。

ポインタで文字列をコピーする

 本題に戻りましょう。ポインタで文字列中の任意の1文字を取り出したり、代入したりできるならば、文字列をどこか固定した場所にコピーして保存することもできるはずです。

======== copystr.def by I.KAMIMURA ========
【文字列のコピー】

* M
80 ^\   "Copy String"
        &g("1st String") (r<0)?.
        &01 &>                          ; 1番目の文字列を a にコピー
        &g("2nd String") (r<0)?.
        &02 &>                          ; 2番目の文字列を b にコピー
        &g("3rd String") (r<0)?.
        &03 &>                          ; 3番目の文字列を c にコピー
        &m("a:%s | b:%s | c:%s",a,b,c)  ; a,b,c の内容をメッセージ

0:      ;--- 1 bytes ずつコピー ---
        i-,                             ; カウンタ初期化
:A      (pw.i)?? >B                     ; ヌル($00)に達したか?
        p.i=pw.i,                       ; pwのiバイト目をpのiバイト目に代入
        i++, >A                         ; カウントアップして繰り返し
:B      p.i=0,                          ; 文字列の終端にヌル($00)を代入

1:      p=a="123456789012345678901234567890123456789012345678901234567890123",
2:      p=b="123456789012345678901234567890123456789012345678901234567890123",
3:      p=c="123456789012345678901234567890123456789012345678901234567890123",

*
======== End of copystr.def ========

 ローカルマクロ「1:」「2:」「3:」で63バイトずつダミーの文字列を用意しておき、入力された文字列を1バイトずつここへコピーします。(文字入力ウィンドウで入力できる最長の文字列は63バイトです)

        ┌ 1st String ┐        ┌ 2nd String ┐        ┌ 3rd String ┐
        │ポルトス    │        │アラミス    │        │アトス      │
        └──────┘        └──────┘        └──────┘

 の順で入力すると、

a:ポルトス | b:アラミス | c:アトス

 とメッセージに出るはずです。

 まだ安心してはいけません。いまのマクロを再カスタマイズをせずに再実行してみてください。再度「ポルトス」「アラミス」「アトス」を入力します。ヒストリー機能で呼び出してもよいでしょう。すると、

a:アトス | b:アトス | c:アトス

 となってしまいます。文字列が似ているので、VZが勘違いしたなんてことはありません。

        ┌ 1st String ┐        ┌ 2nd String ┐        ┌ 3rd String ┐
        │橋本真也    │        │武藤敬司    │        │蝶野正洋    │
        └──────┘        └──────┘        └──────┘

 としても、2度目以降は異常が起きます。

1回目実行のイメージ

1:  p=a="ポルトス$(0)012345678901234567890123456789012345678901234567890123",
2:  p=b="アラミス$(0)012345678901234567890123456789012345678901234567890123",
3:  p=c="アトス$(0)89012345678901234567890123456789012345678901234567890123",

 それぞれの文字列の終端はヌル($00)に置き換わっています。メッセージをだすときにはa,b,cで指し示されるアドレスからヌルまでを取り出すので、問題ありません。

 ところが、2度目の実行では、「&01」でコールされた際、ヌルまでは順調に読み進みますが、ヌルの直後で意味不明の文字列に出会って、エラーになります。「0123...」などというマクロのキーワードはありません。

1:  p=a="ポルトス$(0)012345678901234567890123456789012345678901234567890123",

                    ↑
                    └─ここでコード解析がエラーになる

 こういう場合VZがどう処理するのかわかりませんが、エラーになることは間違いありません。そこで、ヌルの直後をマクロコードに書き換える方法を示します。

0:      ;--- 1 bytes ずつコピー ---
        i-,                             ; カウンタ初期化
:A      (pw.i)?? >B                     ; ヌル($00)に達したか?
        p.i=pw.i,                       ; pwのiバイト目をpのiバイト目に代入
        i++, >A                         ; カウントアップして繰り返し
:B      p.i=0,                          ; 文字列の終端にヌル($00)を代入
        p.(i+1)='.',                    ; ★もう1バイト先に「.」を書き込む

 マクロのキーワード「.」を書き込むという大胆な方法です。

1:  p=a="ポルトス$(0).12345678901234567890123456789012345678901234567890123",
                    ↑
                    └─ここでローカルマクロを終了させる

 こうすれば、「&01」でコールされた際、「.」に出会った時点でコール元に戻ります。実験してみると、2度目以降もうまくいきます。

 こんなことができるのは、VZがマクロコードをテキストの状態のままメモリに格納しているからです(厳密に言うと、文字列の終端の「"」はヌルコードに変換、マクロ番号やキーアサインは数値に変換して格納します)。

 ポインタの演習のために、もう少し面白い方法を紹介しましょう。あらかじめローカルマクロ「1:」「2:」「3:」に「.」を仕込んでおくのです。

1:      p=a=""+6,.
        "123456789012345678901234567890123456789012345678901234567890123"
2:      p=b=""+6,.
        "123456789012345678901234567890123456789012345678901234567890123"
3:      p=c=""+6,.
        "123456789012345678901234567890123456789012345678901234567890123"

 「p=a=""+6」がポインタの面目躍如です。aに代入されるのは、後ろ側の「"」から6バイト目のアドレスです。具体的には文字列の先頭「1」のアドレスです。もちろん改行やインデントは読み飛ばします。

1:      p=a=""+6,.
        "123456789012345678901234567890123456789012345678901234567890123"
        ↑
        │
        └─aは後ろ側の「"」から6バイト目を指し示す

 文字列よりも前にある「.」でローカルマクロは終了しますが、aは「.」より先のアドレスを指し示しているのです。なんともアクロバチックな手法ではありませんか。

 ついでに、目隠しで空中ブランコに挑戦しましょう。

$ 自分自身の直後のアドレスを示す

1:      p=a=$+5,.
        "123456789012345678901234567890123456789012345678901234567890123"

 「$」はVZのマクロでいろいろな意味をもっていますが、極めつけは「自分自身の直後のアドレスを示す」働きでしょう。「文字列を連結する」機能と似ています。

 さらに、「a=5,」は「a5,」と記述できますので、

1:      p=a3+$,.
        "123456789012345678901234567890123456789012345678901234567890123"

 と切り詰めた記述が可能です。

 「$」と「3」を逆に配置すれば、もし数値が2桁になった場合でも「$」から文字列の先頭までの距離は変わらないので、数値を補正する必要がありません。

======== headfoot.def by I.KAMIMURA ========
【ヘッダ・フッタ】成功例

* M
80 ^\   "Header and Footer"
        ?.
        &g("Header$(9)........")        ; ヘッダを入力
        (r<0)?.                         ; [ESC]で中断
        &01 &>                          ; 文字列を h にコピー
        &g("Footer$(9)........")        ; フッタを入力
        (r<0)?.                         ; [ESC]で中断
        &02 &>                          ; 文字列を f にコピー
        &g("Line/Page")                 ; 1ページあたり本文行数を入力
        (r<1)?.                         ; 正の数値でなければ終了
        pg=r,                           ; 1ページあたり行数を設定
        dn+, dl+,                       ; 表示行モード。行番号表示ON
        mi[, mi-,                       ; 挿入モード
        ei[, ei-,                       ; オートインデントOFF
        ej[, ej-,                       ; オートインデントOFF(全角)
        eu[, eu+,                       ; [TAB]でスペースコードを入力ON
        &s
        #^ #<                           ; ファイルの先頭へ
:A      &m("Line:%d",ld)                ; 現在の行番号をメッセージ
        #n #n &?("[%s]",h)              ; ページ先頭に2行挿入「<header>」
        x=we-lx-8, #< &#68(x)           ; 右寄せ
        &#c(pg+2)                       ; 1ページスクロール
        (r)? {                          ; ファイルの最後か?
                (ct)? { #> #m }         ; EOF 行対策
                pg+=4,                  ; 1ページあたり行数を 4 増やす
:B              ((ld+1)%pg)? { #m >B }  ; ページの区切りまで改行
        }
        #m #n &?("==%s==",f)            ; ページ最後に2行挿入「<footer>」
        x=we-lx-1, #< &#68(x/2)         ; センタリング
        #> #d (ct)? >A                  ; 次の行へ。EOF なら終了
        eu], ej], ei], mi],             ; オプション復帰
        &d #] &m("Page:%d",ld/pg)       ; 全ページ数をメッセージ

0:      ;--- 1 bytes ずつコピー ---
        i-,                             ; カウンタ初期化
:A      (p.i=pw.i) i++, ? >A            ; ヌル($00)に達したか?
;:A     (p.i=pw.i)??. i++, >A           ; ヌル($00)に達したか?(同等)

1:      p=h2+$.
        "123456789012345678901234567890123456789012345678901234567890123"

2:      p=f2+$.
        "123456789012345678901234567890123456789012345678901234567890123"

*
======== End of headfoot.def ========

 ひとまず、これを決定版にしておきましょう。

 「1 bytes ずつコピー」のルーチンも、ぐっと短くしてみました。「p.i==pw.i」(関係式)ではなく、「p.i=pw.i」(代入式)であることに注意してください。

 言い忘れましたが、文字入力ウィンドウの横幅を長くしたいときには、タイトルの後ろ側に「$(1)」〜「$(1E)」の制御コードを埋め込んでおけば、それより後ろ側の文字列は枠線に置き換わってくれます。

ポインタで文字列の内容を比較する

 マクロ中で文字列の内容を比較したいことがあります。

======== comp1.def by I.KAMIMURA ========
【文字列の比較】失敗例

* M
80 ^\   "Compare Strings (1)"
        a="Vz Editor (v1.57)",
        b="Vz Editor (v1.57)",
        (a==b)? &m(24) ? &m(25)         ;「一致しました」「相違があります」

*
======== End of comp1.def ========

 このマクロを実行すると、「相違があります」とメッセージが出ます。abの内容は同じなのにどうして? これまでの解説をきちんと理解できた人には簡単でしょう。abには文字列の先頭のアドレスを示す数値がはいっています。アドレスは当然ちがいますから、「a==b」は偽になります。

 文字列の内容を比較するには、1文字(1バイト)ずつ比較しなくてはいけません。

 まず先頭の1文字だけ比較してみましょう。

======== comp2.def by I.KAMIMURA ========
【文字列の先頭の1字を比較】

* M
80 ^\   "Compare Strings (2)"
        a="Vz Editor (v1.57)",
        b="Vz Editor (v1.57)",
        (a.0==b.0)? &m(24) ? &m(25)     ;「一致しました」「相違があります」

*
======== End of comp2.def ========

 結果は「一致しました」です。

 「文字列のコピー」と同様、「a.0」「a.1」「a.2」とバイトポインタを進めて、ヌル(文字列の終端)まで全部一致するかどうか調べます。

======== comp3.def by I.KAMIMURA ========
【文字列の内容を比較】標準型
* M
80 ^\   "Compare Strings (3)"
        a="Vz Editor (v1.57)",
        b="Vz Editor (v1.57)",
        &01
        ? &m(24) ? &m(25)               ;「一致しました」「相違があります」

1:      aa=a, bb=b,
:A      (aa.0==0 && bb.0==0)? { (1). }  ; 同時にヌル($00)に達したら (1) で戻る
        (aa.0!=bb.0)? { (0). }          ; 相違があれば (0) で戻る
        aa++, bb++, >A                  ; 同じならループ

*
======== End of comp3.def ========

 応用がきくように、比較のルーチンをローカルマクロにしました。

 わざわざ「aa=a, bb=b,」と他の変数に代入するのは何故でしょうか? a,bを「a++, b++,」でインクリメントすると、a,bは文字列の先頭を示さなくなり、このあと文字列を利用するときに不都合だからです。

 それなら、起点のa,bを動かさずに、

1:      i-,
:A      (a.i==0 && b.i==0)? { (1). }    ; 同時にヌル($00)に達したら (1) で戻る
        (a.i!=b.i)? { (0). }            ; 相違があれば (0) で戻る
        i++, >A                         ; 同じならループ

 と書けばよいと思った人がいるかもしれません。しかし、iは0〜63の値しか指定できないので、文字列が64バイトをこえると正確に比較できません(部分一致がありえる)。

 ちょっと見通しは悪くなりますが、次のように短くすることもできます。

======== comp4.def by I.KAMIMURA ========
【文字列の内容を比較】短縮型

* M
80 ^\   "Compare Strings (4)"
        a="Vz Editor (v1.57)",
        b="Vz Editor (v1.57)",
        &01
        ? &m(24) ? &m(25)               ;「一致しました」「相違があります」

1:      aa=a, bb=b,
:A      (aa.0 || bb.0)??.               ; 同時にヌル($00)に達したら (1) で戻る
        (aa.0-bb.0)?.                   ; 相違があれば (0) で戻る
        aa++, bb++, >A                  ; 同じならループ

*
======== End of comp4.def ========

 ポインタに限らず、ゼロがらみの条件式では次のような等価表現が成立します。短く記述するテクニックとして憶えてください。

        ┌─────────────────┐
        │        等  価  表  現            │
        ├─────────────────┤
        │(a==0 && b==0)?  →   (a || b)??  │
        ├─────────────────┤
        │(a==0 || b==0)?  →   (a && b)??  │
        └─────────────────┘

 さらに、「?」による内部レジスタのふるまいを見切って、「(1)」「(0)」を省略しました。明示的に数値をわたさなくても、うまい具合に「(1)」「(0)」でコール元に戻ります。

 なお、全角文字が混在する場合も、文字列のコピーや比較は1バイトずつでかまいません。

ヒストリー内の文字列を連結する

 ポインタを駆使すれば、ヒストリー内の文字列を連結するという荒技も可能になります。VZ標準の「24 ^L [F05] "検索文字列の連続取得"」は、連続実行するにしたがって取得範囲を伸ばしていくマクロです。

寝台車と食堂車を連結する。

 この文字列の「寝」にカーソルがあるとすると、マクロキーを連続して押すにしたがって、

連続実行メッセージ
1回目寝台車
2回目寝台車と食堂車
3回目寝台車と食堂車を連結

 という具合に取得する文字列が伸びていきます。

 検索文字列ヒストリー(ps)には次のように不要な文字列がたまっています。

    ps
    ┌──────────┬─┬───────┬─┬───┬─┬──
    │寝台車と食堂車を連結│^@│寝台車と食堂車│^@│寝台車│^@│
    └──────────┴─┴───────┴─┴───┴─┴──

 最後に取得した「寝台車と食堂車を連結」だけ残すようにできないものでしょうか?

======== getstr.def by I.KAMIMURA ========
【検索文字列の連続取得】

* M
24 ^L [F05] "Get Right Words"
        ?.
        (cm-59)? { #G. }                ; 連続実行でなければ検索文字列の取得
        cp[,                            ; カーソル位置のオフセットを退避
        (ct<2)? >C                      ; 行末なら何もしない
        cp+=r, #?                       ; 前回取得した文字列の最後へ移動
        &s #b                           ; ブロックモード
:A      (ct>1)? {                       ; 行末でなければ繰り返し
                a=ct, #d (ct>=a)? >A    ; 下位の文字タイプまで移動
        }
        #G                              ; 検索文字列の取得
        #F #e #e a=ps+lx, #m            ; ヒストリーの順序を入れ替える
:B      a.0=a.1, a++, (a..0)? >B        ; 1番目と2番目を連結
        #b                              ; ブロックモード
:C      cp], &d #G                      ; 元の位置まで戻り、再取得
*
======== End of getstr.def ========

 連続実行かどうかは例によってcm(直前に実行したエディットコマンド番号)で判断します。VZ標準のマクロは、文字列全体をその都度取得していますが、このマクロでは伸びた部分だけ別個に取得します。2度目の実行で、まずヒストリーは次のようになります。

    ps
    ┌────┬─┬───┬─┬──
    │と食堂車│^@│寝台車│^@│
    └────┴─┴───┴─┴──

 この順序を「#F #e #e a=ps+lx, #m」で入れ換えます。

 「a=ps+lx,」で最初のヌルコードのアドレスを算出しておきます。

    ps       a
    ┌───┬─┬────┬─┬──
    │寝台車│^@│と食堂車│^@│
    └───┴─┴────┴─┴──
    ↑      ↑
    └─lx─┘

 そして、文字列のあいだにあるヌルコード1バイト分、ヒストリー全体をずらします。

:B      a.0=a.1, a++, (a..0)? >B        ; 1番目と2番目を連結

 aのアドレスを進めながら、1バイト目を0バイト目にコピーします。

 ヒストリーの終端はヌルコードが2バイト連続しているので、「(a..0)」が0になったら終了します。

    ps      ←全体を1バイトずらした
    ┌───────┬─┬──
    │寝台車と食堂車│^@│
    └───────┴─┴──

 こうして直接ヒストリーを加工すると、たしかに文字列は連結されますが、検索文字列は「寝台車」に設定されたままなので、気をつけてください。ふつうは「#F #e #m」で再設定すればよいのですが、このマクロでは「#G」を最後にもってくるのが狙いなので、文字列全体を範囲指定して、再取得しつつ元の位置へ戻るという一石三鳥の手法をもちいました。ヒストリーにすでにある文字列を取得するので、重複して保存されることはありません。

 次のような書き方は避けるべきです。

        #F #e #e a=lx, #m                       ; ヒストリーの順序を入れ替える
:B      ps.a=ps.(a+1), a++, (ps..a) ? >B        ; 1番目と2番目を連結

 psを起点として、右辺をインクリメントする方法ですが、右辺は0〜63という制限があるので、ヒストリーのサイズが63を越える場合、途中までしか加工できません。(検索文字列ヒストリーバッファのフリーエリアHsは標準で256バイト)

ヒストリーバッファの構造

 ここでヒストリーバッファの構造について触れておきましょう。

        ps
  ┌──┬───┬─┬───┬─┬───┬─┬───┬─┬───┬─┬─┐
  │255 │1番目│^@│2番目│^@│3番目│^@│4番目│^@│5番目│^@│^@│
  └──┴───┴─┴───┴─┴───┴─┴───┴─┴───┴─┴─┘
  ↑    ↑                                                         ↑  ↑
  └  2 ┴────────────  255 ──────────────┘  │
        ↑                                                              │
        └──────────── Hs:256 ───────────────┘

 VZ.DEFの「* O オプション」で指定する初期文字列がないと仮定した図です。

 検索文字列ヒストリーバッファの先頭(ps)の直前には、バッファサイズを示す2バイトの数値($0000〜$FFFF)が書き込まれています。 また、「文字列の終端」とは別に「バッファの終端」はヌルコードで区切られています。検索文字列ヒストリーバッファのフリーエリアHsはバッファの終端までのバイト数を示します。

ヒストリーの先頭の文字列を消去する

 ヒストリーの加工を押し進めて、先頭の文字列を消去する方法を示します。

======== del_his.def by I.KAMIMURA ========
【ヒストリーの先頭の文字列を消去】

* M
80 ^\   "del_his"
        ?.
        #F "この文字列が消えます" #m
        &01
        #F #e                           ; ヒストリーを1回さかのぼると……

1:      ;---ヒストリーの先頭を消去---
        a=b=ps,                         ; ヒストリーの先頭のアドレスを a,b へ
:A      (b.0) b++, ? >A                 ; b を2番目の文字列まで進める
:B      (b..0) a.0=b.0, ? {             ; 2番目を1番目にコピー
                a++, b++, >B            ; a,b を進める
        }
        a.1=0,                          ; 2つ目のヌルを付加する

*
======== End of del_his.def ========

 「a=b=ps,」のあと、bを2番目の文字列まで進めます。

   a  →→→   b
  ┌───┬─┬───┬─┬───┬─┬───┬─┬───┬─┬─┬────
  │1番目│^@│2番目│^@│3番目│^@│4番目│^@│5番目│^@│^@│未使用
  └───┴─┴───┴─┴───┴─┴───┴─┴───┴─┴─┴────

 そして、bがヒストリーの終端(ヌルが2つ連続する)に達するまで1バイトずつコピーします。最後の文字列をコピーし終わったら、さらに「a.1=0,」で2つ目のヌルを付加するのを忘れないでください。

 ローカルマクロの「1:」はそのまま他のマクロで利用できます。

ヒストリーをすりかえる

 ヒストリーバッファに不要な文字列をためないだけなら、もっとシンプルな方法があります。

======== swap_his.def by I.KAMIMURA ========
【ヒストリーすりかえ】

* M
80 ^\   "Swap History"

        ps[, ps=pa,                     ; ps を pa で代理
        #F ".def" #m                    ;「.def」を検索文字列へ
        ps],
        #c                              ; 前方を検索
*
======== End of swap_his.def ========

 「.def」を検索するマクロですが、【検索文字列】の1行ウィンドウをひらいてヒストリーを覗いても、「.def」は残っていません。「ps=pa,」でpsのアドレスをpaに移動して、アプリケーションヒストリーバッファで代用するのです。このバッファはプログラム開発時にdebugなどから利用するコマンドヒストリーで、VZを高速ワープロとして使う人には用のない代物ですから、ここをゴミ捨て場(?)にしましょう。「ps[, ... ps],」でアドレスを元に戻すのを忘れないでください。

 「マクロ中で検索機能を使うが、その痕跡をヒストリーに残したくない」場合に便利な手法です。

疑似ヒストリーに文字列をコピーする

 「ヒストリーの構造」と「ヒストリーのすりかえ」を組み合わせると、なかなか愉快なマクロを書くことができます。

======== copystr4.def by I.KAMIMURA ========
【疑似ヒストリーバッファへ文字列をコピー】(4)

* M
80 ^\   "Copy String (3)"
        &g("1st String") (r<0)?.
        &01 &>                          ; 1番目の文字列を a にコピー
        &g("2nd String") (r<0)?.
        &02 &>                          ; 2番目の文字列を b にコピー
        &g("3rd String") (r<0)?.
        &03 &>                          ; 3番目の文字列を c にコピー
        &m("a:%s | b:%s | c:%s",a,b,c)  ; a,b,c の内容をメッセージ

0:      mp[, ps[, ps=p,                 ; ヒストリーのすりかえ
        #F &?(pw) #m
        ps], mp], #]

1:      p=a=$+7,.
        "$(40,0)$
        123456789012345678901234567890123456789012345678901234567890123"

2:      p=b=$+7,.
        "$(40,0)$
        123456789012345678901234567890123456789012345678901234567890123"

3:      p=c=$+7,.
        "$(40,0)$
        123456789012345678901234567890123456789012345678901234567890123"

*
======== End of copystr4.def ========

 ローカルマクロ「1:」「2:」「3:」の文字列をヒストリーバッファに見せかけて、検索文字列ヒストリーとすりかえれば、文字列をコピーしたりヌルを付加したりする作業は自動的におこなわれます。

 まず、文字列の前にバッファサイズをあらわす数値(2バイト)を埋め込みます。そのぶん「p=a=$+5,」に加算して、「p=a=$+7,」とすれば細工は流々です。a,b,cは文字列「123...」の先頭のアドレスを示し、その2バイト前にはバッファサイズ。これでVZを騙すことができます。

 「$(40,0)」は16進数「$4000」を文字列に埋め込んでいます。VZがこのデータを参照すると、上位バイトと下位バイトが逆転して、「$0040」(10進数の「64」)とみなされます。

他のマクロで破壊されない保存変数

 モード型マクロで、自前のバッファに情報を保存すれば、他のマクロで保存変数が破壊される危険を回避できます。

======== autoback.def  by I.KAMIMURA ========
【簡単オートセーブ】

* M
80 ^\   "auto back"
        ?.
:A      &> r..0-,                       ; カウンタ初期化
:B      &p (s||mb||r>$FF)? { &o(r) >B } ; 文字入力以外はそのまま通す
        &o(r)                           ; 文字入力
        &> r..0+=1,                     ; カウントアップ 「r..0++,は不可」
        (r..0-3)? >B                    ; auto save timing [3]
        &b(3) #S #m >A                  ; セーブ

0:      (3+$)."00"                      ; auto save counter
*
======== End of autoback.def ========

 簡単なオートセーブマクロです。終了させるにはマクロを強制中断してください。(PC-9801なら[STOP]、IBM系マシンなら[Ctrl]+[Break]

 ローカルマクロの「"00"」をカウンタに使います。「(3+$)」で文字列の先頭のアドレスを内部レジスタに渡します。ローカルマクロからコール元に戻ると、内部レジスタがrにセットされるので、「r..0-,」で「"00"」を「"$(0,0)"」に初期化できます。「"00"」と「"$(0,0)"」の違いに気をつけてください。

文字表現コード(数値)埋め込み16進数
"00""$(30,30)"$3030
"^@^@""$(00,00)"または"$(0,0)"$0000または$0

 コンピュータは数値でしか物を考えません。

 ワード(バイト)ポインタ演算子で取り出した数値を「文字コード」として使うか「数値」のままで使うかは、プログラム次第です。

 文字を入力すると、「r..0+=1,」でカウントアップします。「(r..0-3)」の「3」がオートセーブのタイミングです。適当な数値に書き換えてください。

 マクロ内部にカウンタをもっているので、他のマクロと変数が干渉して誤動作する危険がありません。どのみち流動的なrを一時的な変数として利用する方法も参考になるでしょう。

テキストスタックにアクセスする

 行・ブロックを保存するテキストスタックは、コードセグメント(VZ.COMやマクロバッファ)とは別の場所にあります。

czコードセグメント(VZ.COMやマクロバッファ)
dzポインタアクセスに使用するセグメント
gzテキスト管理セグメント
tz現在編集中のテキストセグメント
kzテキストスタックのセグメント
kpテキストスタックのエンドオフセット

 dzはふだんczを指していますが、一時的にほかのセグメントに変更することができます。

======== delstack.def by I.KAMIMURA ========
【カット&クリア】

* M
80 ^\   "Del Stack"
        ?.
        (ks&1)? { #51. }                ; シフト実行なら「スタックの消去」
        #y                              ; 行・ブロックの削除
        (kp)??.                         ; スタックが空なら何もしない
        dz=kz,                          ; dz を kz に変更
        r=kp-2,                         ; 最新の行・ブロックのサイズを r へ
        r-=r..0+1,                      ; サイズ分アドレスを減らす
        &#51(r)                         ; アドレス r 以降を消去
        dz=cz,                          ; dz を cz に戻す

*
======== End of delstack.def ========

 ある行・ブロックを連続して複写しているとき、複写先に邪魔な行・ブロックがあった場合、それを削除するとテキストスタックに積まれて、本来複写したい行・ブロックは2番目に押し込まれてしまいます。このマクロを使えば、削除すると同時にテキストスタックからも消去するので、複写したい行・ブロックは1番目のままです。

 テキストスタックの構造は次のようになっています。

       string          type size
    ┌─┬────────────┬─┬──┬──┐
    │新│.........(行境界)   │01│0216│(kp)│
    │ ├────────────┼─┼──┼──┘
    │↑│...   (1行)    │00│0073│
    │ ├────────────┼─┼──┤
    〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
    │ ├────────────┼─┼──┤
    │ │...   (文字境界)  │02│0072│
    │ ├────────────┼─┼──┤
    │↓│.........(1行)    │00│0256│
    │ ├────────────┼─┼──┤
    │旧│....   (行境界)   │01│0515│
    └─┴────────────┴─┴──┘

 このように古い行・ブロックから順に積み重なっていると考えてください。

 kpは現在のテキストスタックの終端のアドレスを示します。その2バイト前に「行・ブロックのサイズ」(2バイトデータ)、3バイト前に「ブロックのタイプ」(1バイトデータ)が書き込まれています。その情報をポインタで取り出して、最新の行・ブロックの先頭のアドレスを割り出します。そして、非公開コマンド関数「&#51(n)で、そのアドレス以降を消去します。

 このマクロを応用すれば、マクロ中で痕跡を残さずに不要な文字列を消去できます。状況に応じて、「#J #u #[」による文字捨てと使い分けるとよいでしょう。

編集中ファイル名をチェック

 現在編集中のファイルがある特定のファイル名かどうかは、次のようなマクロでチェックできます。

======== chkfname.def by I.KAMIMURA ========
* M
80 ^\   "Check File Name"
        ?.
        (wn)??.                         ; console では実行を禁じる
        a="OPEND1.DEF",
        #J #P                           ;【行番号】ウィンドウにファイル名
:A      #s (cd-'\')? >A                 ; ★「\」まで左へ
        #d #l #x #u                     ; ★ディレトクリ名をクリア
        (dp)? {                         ; ☆ファイル名が小文字なら大文字に変換
                #<                      ; ☆
:B              #23 #f (ct>1)? >B       ; ☆
        }                               ; ☆
        #m
        &01
        ? &m(24) ? &m(25)               ;「一致しました」「相違があります」

1:      b=a, c=pw,
:A      (b.0 || c.0)??.
        (b.0-c.0)?.
        b++, c++, >A

*
======== End of chkfname.def ========

 現在編集中のファイル名を示すシステム変数はないので、【行番号】の1行ウィンドウを介してpwにファイル名を渡します。「c=pw,」で他の変数に代入するのを忘れないでください。pwそのものを「pw++,」とインクリメントすると、そのあとpwに関連する操作をおこなったときハングアップに見舞われるでしょう。主ファイル名のみ比較していますが、★の2行を削除するとフルパス名で比較します。☆の4行はdpオプション(ファイル名の小文字表示)対策です。

オープン済みファイルをチェック

 【入力ファイル】の1行ウィンドウからファイル名を入力すると、すでにオープンされているファイルであっても、ディスクアクセスをともない、イライラすることがあります。できれば改善してほしいところですが、マクロで回避する方法を紹介します。

======== opend1.def by I.KAMIMURA ========
* M
80 ^\   "Check Opend (1)"
        a="A:\DUMMY.TXT",
        &s &01 &d
        ? &m("オープン済み") ? {
                #O &?("%s",a) #m
                (s)? 'Y'
                &m("新規オープン")
        }

1:      i-,
:A      (i++>wc)?.                      ; テキスト数を越えたら終了 (0)
        &#T(i)                          ; i 番のテキストに切り替え
        #J #P #m                        ; pw にファイル名をわたす
        b=a, c=pw,
:B      (b.0 || c.0)??.                 ; 一致したら終了 (1)
        (b.0-c.0)? >A
        b++, c++, >B

*
======== End of opend1.def ========

 このマクロを実行すると、最初は「A:\DUMMY.TXT」というファイルを新規オープンするでしょう。他のテキストに切り替えて、再実行すると、今度は「オープン済み」というメッセージが出るはずです。

テキスト管理ワークの構造

 前掲のopend1.defでは、実際にテキストを切り替えながら、ファイル名を比較しましたが、オープン済みテキスト名はgz(テキスト管理セグメント)にすべて書き込まれています。

テキストワークの構造(抜粋)

オフセット名称サイズ内容
0W_nextword次のワークへのポインタ
2wnumbyteそのファイルのテキスト番号(wn)
3tchlbyte修正フラグ(mr)
4wsplitbyteウィンドウ分割モード(wt)
5blkmbyteブロックモード(mb)
6wybyte画面上でのY位置(ly)
7wnxtbyte次行の画面上でのY位置
8wysbyte保存したカーソルの画面上でのY位置(ky)
9nodnumbbyte表示行フラグ
Alnumbword論理行番号(ln)
Cdnumbword表示行番号(ld)
10lnumb0word論理行番号オフセット
12dnumb0word表示行番号オフセット
16ttopwordテキストトップポインタ
18tendwordテキストエンドポインタ
1Cthomword画面左上のオフセット
1Ftbtmword画面右下のオフセット
40tabrbyteタブサイズ(ht)
41exttypbyte拡張子タイプ(fe)
42ctypebyteカーソル位置の文字タイプ(ct)
43ckanjbyteカーソル位置の文字種(ck)
44ccodewordカーソル位置の文字コード(cd)
46namepwordファイル名へのポインタ
48largebyteラージファイルフラグ
49tempbyteテンポラリファイルフラグ(2:ファイルあり)
4Areadpdwordテキスト読み出しロングポインタ
4FeofpdwordEOFまでのオフセット
52headpdwordヘッドロングポインタ
58tailpdwordテイルロングポインタ
5EtextidwordファイルのID(id)
7Etreptdwordマーク位置オフセット(k0)
82tmarkdwordマーク位置オフセット(k1〜k4)
92pathbyteファイル名(64bytes)

* 本データはjunk.35氏に提供していただきました。

 非公開変数wo(consoleの管理ワーク)がオフセット0のアドレスを示しています。テキスト番号1の管理ワークのアドレスは、このオフセット0に書き込まれています。これをたどって全テキストの管理ワークをたどることができます。最後のテキストの管理ワークにはconsoleの管理ワークのアドレスが書き込まれており、ちょうど円環構造をなしています。

 それぞれの管理ワークの中でオフセット$92がファイル名に該当します。

 「dz=gz,」として1バイトずつファイル名を取り出しますが、尋常なやり方では実際にテキストを切り替えるよりも時間がかかるので、ここでは機械語を駆使したマクロを紹介します。

* M
80 ^\   "Opend"                         ; branch-17 氏作
        ?. &g("file name") (r<0)?.      ; pw にファイル名
        &01
        ? &m("新規オープン")
        ? &m("オープン済み")

1:      ;---オープン済みテキストチェック---
        di=si=pw,
        &i("$(B9,FF,FF,30,C0,F2,AE,F7,D1,C3)")
        dx=gz, bx=wo, ax$92, i-,
:A      di=bx, cx[,
        &i("$(8E,C2,26,8B,1D,01,C7,56,F3,A6,5E,C3)")
        (cx) cx], ?? { &#T(i) (0). }    ; オープン済みなら 0 を返す
        (bx) i++, ? >A
        #O &?(pw) #m (s)? 'Y' (1)       ; 新規オープンなら 1 を返す

*

 branch-17氏に作っていただいたマクロです。機械語部分についての簡単な説明も紹介させていただきましょう。

【機械語部分】

◎文字列の半角文字数を調べる.

technical:
    input  <- DI に文字列の先頭アドレス.
    output -> CX に文字列の半角文字数 + 1.

0000 B9FFFF  MOV     CX,FFFF    ; CX に -1 を代入.
0003 30C0    XOR     AL,AL      ; AL に 0 を代入.
0005 F2      REPNZ              ; 次の命令(SCASB)が真であるかぎり繰り返す.
0006 AE      SCASB              ; ES:DI でポイントするバイトサイズの値が
                                ; AL の値と異なり,且つ CX が 0 でなけれ
                                ; ば真とし,そうでなければ偽とする.真な
                                ; らば,CX の値を 1 引く.
0007 F7D1    NOT     CX         ; CX の論理 NOT をとる.
0009 C3      RET                ; 終了.


◎同一の文字列かどうかを調べる.

technical:
    input  <- SI に文字列の先頭アドレス,CX にその半角文字数 + 1.
              DX と DI にテキスト管理ワークの先頭アドレス(セグメントとオ
              フセット).
    output -> CX が 0 ならば文字列は一致,0 以外ならば不一致.
              BX に次のテキスト管理ワークの先頭アドレス. bx == 0 ならば
              これ以上オープンしたファイルが無い.

0000 8EC2    MOV     ES,DX      ; ES に DX の値を代入する.
0002 268B1D  MOV     BX,ES:[DI] ; BX に ES:DI でポイントするワードサイズ
                                ; の値(次のテキスト管理ワークの先頭アド
                                ; レス)を代入する.
0005 01C7    ADD     DI,AX      ; DI に AX の値を足す(テキスト管理ワーク
                                ; のファイル名が入っている位置までずらす).
0007 56      PUSH    SI         ; SI の値を待避する.
0008 F3      REPZ               ; 次の命令(CMPSB)が真であるかぎり繰り返す.
0009 A6      CMPSB              ; DS:SI でポイントするバイトサイズの値と
                                ; ES:DI でポイントするバイトサイズの値が
                                ; 同じで,且つ CX が 0 でなければ真とし,
                                ; そうでなければ偽とする.真ならば,CX の
                                ; 値を 1 引く.
000A 5E      POP     SI         ; SI に待避した値をもどす.
000B C3      RET                ; 終了.

ビット演算

 前にコンピュータは数値でしか物を考えないと述べましたが、もっと厳密に言うと、電気信号(1ビット)のONとOFFでしか物を考えません。そこで2進数の考え方が出てきます。

ビット数パターン種類
10 12
200 01 10 114
3000 001 010 011 100 101 110 1118
40000 0001 0010 0011 0100 0101 0110 0111
1000 1001 1010 1011 1100 1101 1110 1111
16

 このように、4ビットで16種類のパターンを表現することができます。

 コンピュータでよく使われる16進数はこの4ビットを基本にしています。

4ビット16進数10進数
000000
000111
001022
001133
010044
010155
011066
011177
100088
100199
1010A10
1011B11
1100C12
1101D13
1110E14
1111F15

 しかし、16種類だけではあまり大したことはできません。そこで8ビットにすると、16×16=256種類を表現することができます。この256は馴染みのある数値でしょう。これだけあれば、半角英数字や半角カタカナの文字コードを網羅できます。(巻末文字コード表を参照)

        ┌───────┬───┬───┐
        │8 ビット      │16進数│10進数│
        ├───────┼───┼───┤
        │0000 0000     │0     │0     │
        │0000 0001     │1     │1     │
        〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
        │1111 1110     │FE    │254   │
        │1111 1111     │FF    │255   │
        └───────┴───┴───┘

 この8ビットが1バイトの正体です。半角英数字や半角カタカナを1バイト文字と呼ぶ理由がわかるでしょう。さらに16ビットなら、全角文字(2バイト)を表現できます。

        ┏━━┳━━━━┯━━━━┯━━━┯━━━━━━━━━━┓
        ┃文字┃符号付き│符号なし│16進数│2進数(ビット表示) ┃
        ┣━━╋━━━━┿━━━━┿━━━┿━━━━━━━━━━┫循環
        ┃ ?? ┃-32768  │32768   │8000  │1000 0000 0000 0000 ┃←┐
        ┠──╂────┼────┼───┼──────────┨  │
        ┃' '┃-32448  │33088   │8140  │1000 0001 0100 0000 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃'亜'┃-30561  │34795   │889F  │1000 1000 1001 1111 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃ ?? ┃-2      │65534   │FFFE  │1111 1111 1111 1110 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃ ?? ┃-1      │65535   │FFFF  │1111 1111 1111 1111 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃NUL ┃0       │0       │0000  │0000 0000 0000 0000 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃TAB ┃9       │9       │0009  │0000 0000 0000 1001 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃SPC ┃32      │32      │0020  │0000 0000 0010 0000 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃'@' ┃64      │64      │0040  │0000 0000 0100 0000 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃'A' ┃65      │65      │0041  │0000 0000 0010 0001 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃'a' ┃97      │97      │0061  │0000 0000 0110 0001 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃ ?? ┃128     │128     │0080  │0000 0000 1000 0000 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃ ?? ┃254     │254     │00FE  │0000 0000 1111 1110 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃ ?? ┃255     │255     │00FF  │0000 0000 1111 1111 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃ ?? ┃256     │256     │0100  │0000 0001 0000 0000 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃ ?? ┃512     │512     │0200  │0000 0010 0000 0000 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃ ?? ┃1024    │1024    │0400  │0000 0100 0000 0000 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃ ?? ┃2048    │2048    │0800  │0000 1000 0000 0000 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃ ?? ┃4096    │4096    │1000  │0001 0000 0000 0000 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃ ?? ┃8192    │8192    │2000  │0010 0000 0000 0000 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃ ?? ┃16384   │16384   │4000  │0100 0000 0000 0000 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃ ?? ┃32767   │32767   │7FFF  │0111 1111 1111 1111 ┃  │
        ┠──╂────┼────┼───┼──────────┨  │
        ┃ ?? ┃-32768  │32768   │8000  │1000 0000 0000 0000 ┃→┘
        ┗━━┻━━━━┷━━━━┷━━━┷━━━━━━━━━━┛循環

 最上位ビットが1の場合は負の数として扱います。

 VZで通常扱う数値が-32768〜32767である理由はここから来ています。

 行番号の場合、負の数はありえないので、最大が65535で、その次は0に戻ります。

 16進数の場合、数値の前に「$」を付けるのを忘れないでください。

<< 左シフト演算子

>> 右シフト演算子

 この演算子でビットパターンを左右にずらすことができます。

 具体例を示しましょう。

左シフトビットパターン16進数10進数
a=1,0000 0000 0000 000111
a=1,a=a<<1,0000 0000 0000 001022
a=1,a=a<<2,0000 0000 0000 010044
a=1,a=a<<3,0000 0000 0000 100088
a=1,a=a<<4,0000 0000 0001 00001016
a=1,a=a<<5,0000 0000 0010 00002032
a=1,a=a<<6,0000 0000 0100 00004064
a=1,a=a<<7,0000 0000 1000 000080128
a=1,a=a<<8,0000 0001 0000 0000100256
a=1,a=a<<9,0000 0010 0000 0000200512

 最下位ビットには0が補充されます。

 左シフトするにしたがって、数値が2倍になるのがわかるでしょう。

 最初の数値がたとえば7でも同じです。

左シフトビットパターン16進数10進数
a=7,0000 0000 0000 0111 7 7
a=7, a=a<<1,0000 0000 0000 1110 0E 14
a=7, a=a<<2,0000 0000 0001 1100 1C 28

 負の数でも同じです。

左シフトビットパターン16進数10進数
a=-7,1111 1111 1111 1001 FFF9-7
a=-7,a=a<<1,1111 1111 1111 0010FFF2-14
a=-7,a=a<<2,1111 1111 1110 0100FFE4-28

 右シフトすると、2分の1になっていきます。

右シフトビットパターン16進数10進数
a=56,0000 0000 0011 1000 3856
a=56,a=a>>1,0000 0000 0001 11001C28
a=56,a=a>>2,0000 0000 0000 11100E14

 負の数を右シフトすると、

右シフトビットパターン16進数10進数
a=-7,1111 1111 1111 1001 FFF9-7
a=-7,a=a>>1,1111 1111 1111 1100FFFC-4
a=-7,a=a>>2,1111 1111 1111 1110FFFE-2

 最上位ビットには1が補充されます。

 ビット演算を使う例として次のマクロを見てください。

======== time.def by I.KAMIMURA ========
* M
80 ^\   "Time Stamp"
        (s>2)?.
        (s)?? { #61 #x }
        ax=$2A00, &i($21),
        &s &?("%04d/%02d/%02d",cx,dx>>8,dx&$FF) &d
        p="SunMonTueWedThuFriSat"+(ax&$FF)*3,
        &?("(%3s)",p)
        ax=$2C00, &i($21)
        &s &?(" %02d:%02d:%02d",cx>>8,cx&$FF,dx>>8) &d #]

*
======== End of time.def ========

 MS-DOSのファンクションコールで現在の日付と時刻を取得して、【複写文字列】の1行ウィンドウに書き込みます。そのままリターンでテキストに書き込みます。エスケープでキャンセルできます。

 ビット演算の目白押しです。ax,cx,dxに具体的にどんな数値が返ってくるのか見てみましょう。

======== timedata.def by I.KAMIMURA ========
* M
80 ^\   "Time Data"

        ?.
        #n
        ax=$2A00, &i($21),
        &?("[Date] ax:%04x cx:%04x dx:%04x ",ax,cx,dx)
        ax=$2C00, &i($21)
        &?("[Time] cx:%04x, dx:%04x",cx,dx)
*
======== End of timedata.def ========

 このマクロを実行すると、次のような文字列が書き込まれます。

[Date] ax:2A02 cx:07C8 dx:0B18 [Time] cx:122A, dx:3646
ビットパターン16進数10進数上位下位返り値
ax0010 1010 0000 0010 2A022AL=曜日 (0:日〜6:土)
cx0000 0111 1100 1000 07C81992
dx0000 1011 0001 1000 0B181124DH=月, DL=日
cx0001 0010 0010 1010 122A1842CH=時, CL=分
dx0011 0110 0100 0110 364654DH=秒

 8086CPUではAXは上位(AH)と下位(AL)に分けて使うことができますが、VZには「ax」しか用意されていませんので、上位と下位を別個にとりだすには、ビット演算を使う必要があります。

 「dx&$FF」は、ビットごとの積です。

-ビットパターン上位下位
dx0000 1011 0001 10001124
FF0000 0000 1111 111100FF
0000 0000 0001 100024

 両方とも「1」の場合にのみ、積が「1」になります。

「$FF」は「$00FF」と同じ意味なので、上位バイトをクリアして、下位バイトのみ残すことができるわけです。

        p="SunMonTueWedThuFriSat"+(ax&$FF)*3,

 この部分は分解して書くと、

        p="SunMonTueWedThuFriSat",
        p+=(ax&$FF)*3,

 となります。

 pのアドレスを進めておいて、「&?("(%3s)",p)」で3バイトだけ出力します。

 このタイムスタンプマクロは、「ポインタ演算」と「ビット演算」と「ファンクションコール」のすべてのテクニックを駆使した「いろは歌」のようなマクロです。