●Vzマクロでwhileを書くには
VzのマクロにはC言語のwhile文に相当する制御構造がありません。ラベルを利用して、whileやcontinueやbreakに相当する処理を記述します。
:A (式)? {
処理
:B (式)? {
処理
>B
}
>A
}
一般にプログラミングの世界ではgoto(ラベルへのジャンプ)は処理の流れ、処理の主従関係をわかりにくくするものとして悪名が高いようです。ましてやVzのラベルはアルファベット一文字のため連想が働きにくく、ラベルの付与ミス、ジャンプ先のミスなど蟲の棲家となっています。もちろんマクロローダはラベルの相関チェックはしてくれません。
●continueサブルーチン
そこでcontinueに相当するサブルーチンを作成してみました。
======== while1.def ======== by ikuboh
#if 0
【動作】
i: 5 4 3 2 1 end
j: 54321
#endif
* M
89 ^\ "while1"
ax=1, bx=2, cx=3, dx=4, si=5, di=6,
i=5, j=5,
(i)? {
&m(" hit any key i: %d",i) &p
(j)? {
&m(" hit any key j: %d",j) &p
j--,
&01
}
i--,
&01
}
&m("end ax: %d bx: %d cx: %d dx: %d si: %d di: %d"
,ax,bx,cx,dx,si,di)
1: ;--- continue ---
;現在ブロック直前の(式)へ戻る
(内容省略)
*
======== End of while1.def ========
|
このマクロにはラベルがないにもかかわらず、iとjそれぞれが正しくループします。
ローカルマクロ1:がC言語でいうcontinueに相当します。ブロックの途中で使っても、閉じ括弧'}'の直前で使ってもかまいません。ブロックをネストさせて、何度使ってもかまいません。予想される動作を#if 0〜#endifに記述しましたので、ループの流れを確認してください。
このサブルーチンは、現在いるブロックの直前にある(式)へ実行ポインタをジャンプさせます。(式)とブロックの間に?や他の処理があってもかまいません。((式)&&(式))のようにネストした式でもかまいません。
こうして { ブロック } だけで記述すれば、括弧の対応とネストだけ意識すればよく、自然とメンテナンスしやすい(あちこちにジャンプしない)コードになります。
無限ループを書きたい場合には、C言語のwhile(1)にならって、
(1){
(式)?{ 処理 }
&01
}
と記述しましょう。Vz本来の無限ループ(−1)と区別できて好都合です。
【C言語のcontinueとの違い】
このサブルーチンは「ループの先頭」に戻るのではなく、単純に「ブロック」の直前の(式)に戻ります。
●breakサブルーチン
さて、continueを作るなら、breakもないと片手落ちです。
======== while2.def ======== by ikuboh
#if 0
【動作】
i: 5 4 3 2 1 end
j: 543 3 3 3 3
#endif
* M
89 ^\ "while2"
ax=1, bx=2, cx=3, dx=4, si=5, di=6,
i=5, j=5,
(i)? {
&m(" hit any key i: %d",i) &p
(j)? {
&m(" hit any key j: %d",j) &p
(j==3)? &02 ;★1回ブレーク
j--,
&01
}
i--,
&01
}
&m("end ax: %d bx: %d cx: %d dx: %d si: %d di: %d"
,ax,bx,cx,dx,si,di)
1: ;--- continue ---
;現在ブロック直前の(式)へ戻る
(内容省略)
2: ;--- break ---
;1回ブレーク
(内容省略)
*
======== End of while2.def ========
|
jのカウントダウンは3でストップしたまま、iのループとjのループが交互に実行されます。
【C言語のbreakとの違い】
このサブルーチンは「ループの外側」に脱出するのではなく、単純に「ブロックの外側」に脱出します。
●2回breakサブルーチン
さて、ちょっと欲張って2回breakするサブルーチンも作ってみました。
======== while3.def ======== by ikuboh
#if 0
【動作】
i: 5
j: 543 end
#endif
* M
89 ^\ "while3"
ax=1, bx=2, cx=3, dx=4, si=5, di=6,
i=5, j=5,
(i)? {
&m(" hit any key i: %d",i) &p
(j)? {
&m(" hit any key j: %d",j) &p
(j==3)? &03 ;★2回ブレーク
j--,
&01
}
i--,
&01
}
&m("end ax: %d bx: %d cx: %d dx: %d si: %d di: %d"
,ax,bx,cx,dx,si,di)
1: ;--- continue ---
;現在ブロック直前の(式)へ戻る
(内容省略)
3: ;--- Wbreak ---
;2回ブレーク
(内容省略)
*
======== End of while3.def ========
|
jのカウントダウンが3に達すると、いきなり終了します。
ダブルブレークが欲しくなるのは、CASE文から確実に脱出したい場合です。
VzのCASE文は堅牢ではなく、処理の内容によってはとんでもない場所へジャンプして暴走する可能性があるため、
(式)>! {
CASE1 >ラベルA
CASE2 >ラベルB
CASE3 >ラベルC
| >ラベルD
}
というぐあいに、ただちに別の場所へジャンプさせるのが慣習ですが、Wbreakを使えば安心して複雑な処理を書くことができます。
(式)>! {
CASE1 { 処理 &03 }
CASE2 { 処理 &03 }
CASE3 { 処理 &03 }
| { 処理 &03 }
}
CASE1から脱出する途中で、CASE2やCASE3のブロックと出会いますが、開き括弧と閉じ括弧とがペアとなっているため読み飛ばし、CASE文全体のブロックの外側へ正しくジャンプします。
同じ原理でif〜elseif〜elseも整然と記述できます。ダミーの(0){...}を記述するのがポイントです。
(0){
(式)?{ if処理 &03 }
(式)?{ elseif処理 &03 }
else処理
}
(0)は、(1)でも他の数字でもかまいませんが、ダミーであることを暗示するのに好都合です。
●X回breakサブルーチン
まてよ、continueとbreakとWbreakと、そんなにたくさんのサブルーチンを取り込むとマクロが大きくなっちゃうよ、と心配する方のために、ブレーク回数を指定できるサブルーチンを作りました。
======== while4.def ======== by ikuboh
#if 0
【動作】
i: 5 4 3 2 1 end
j: 543 3 3 3 3
#endif
* M
89 ^\ "while4"
ax=1, bx=2, cx=3, dx=4, si=5, di=6,
i=5, j=5,
(i)? {
&m(" hit any key i: %d",i) &p
(j)? {
&m(" hit any key j: %d",j) &p
(j==3)? &04 ;★1回ブレーク相当
j--,
&01
}
i--,
&01
}
&m("end ax: %d bx: %d cx: %d dx: %d si: %d di: %d"
,ax,bx,cx,dx,si,di)
1: ;--- continue ---
;現在ブロック直前の(式)へ戻る
(内容省略)
4: ;--- Xbreak ---
;X回ブレーク
;(X=内部レジスタ X=0または1なら1回スキップ)
;使用法: (x) &Xbreak
(内容省略)
*
======== End of while4.def ========
|
コールする直前に(回数)という記述で内部レジスタに回数を渡します。上の例では(回数)を明示していませんが、内部レジスタが0か1の場合には、1回ブレークする動作となります。したがって、このサブルーチンを使う場合には、内部レジスタのふるまいを見切った繊細な取り扱いが必要となります。よほどのことがない限り、continueとXbreakの2つで事足りるでしょう。
参考までに(回数)を明示する例を以下に示します。2回ブレークですが、{ ブロック }のネストが1個増えるため、1回多めに(3)と指定する必要があります。
======== while5.def ======== by ikuboh
#if 0
【動作】
i: 5
j: 543 end
#endif
* M
89 ^\ "while5"
ax=1, bx=2, cx=3, dx=4, si=5, di=6,
i=5, j=5,
(i)? {
&m(" hit any key i: %d",i) &p
(j)? {
&m(" hit any key j: %d",j) &p
(j==3)? { (3) &04 } ;★2回ブレーク相当
j--,
&01
}
i--,
&01
}
&m("end ax: %d bx: %d cx: %d dx: %d si: %d di: %d"
,ax,bx,cx,dx,si,di)
1: ;--- continue ---
;現在ブロック直前の(式)へ戻る
(内容省略)
4: ;--- Xbreak ---
;X回ブレーク
;(X=内部レジスタ X=0または1なら1回スキップ)
;使用法: (x) &Xbreak
r[, si[=mg,
(内容省略)
*
======== End of while5.def ========
|
まちがえて(回数)にマイナス値や過大な値を与えた結果、脱出先が見つからなかった場合、Xbreakは何もしません。
●サービス機能:ループカウンタのクリア
Vzの作者であるc.mos氏の実に巧妙な手抜きによって実現した制御構造は、まれにマクロ作者が思いも寄らぬ挙動をもたらすことがあります。その最たるものはループカウンタとループ開始ポインタの挙動です。ループカウンタとループ開始ポインタはそれぞれ1個しか存在せず、かつグローバルです。よって、
(回数){
(回数){ 処理 }
}
のようなループのネストはできません。
それだけなら機能がないと割り切ればいい話ですが、実はループカウンタとループ開始ポインタはマクロ作者が意識しないときでも頻繁に働いています。Vzはマクロコードを順に実行していって、閉じ括弧'}'に出会うとループカウンタを参照し、0か1以外だったら、問答無用でループ開始ポインタへジャンプします(このあたりの仕様はマニュアル256ページで確認してください)。
- 処理のどこか別の場所でループを中断(ラベルで脱出など)し、ループカウンタが0になっていない。
- 開き括弧'{'に出会う前に閉じ括弧'}'に出会う
という条件を満たした場合、予想外の場所へジャンプする可能性があるのです。
たいていは開き括弧に先に出会い、その時点でループカウンタとループ開始ポインタがリセットされるため、大事にいたらないのです
continue,break,Wbreak,Xbreakは必ずループカウンタを初期化(0にする)します。
●思想:苦痛を伴う構造改革
Vzのマクロの場合、比較的大きな外部マクロがcontinue,breakによる構造改革の対象となります。最初はスタイルに縛られて窮屈な思いをするかもしれません。しかし、スタイルにのっとって記述することで得られるメリットは大きいはずです。昨今のオブジェクト指向言語は、ユーザ変数さえも隠蔽し、直接アクセスできないスタイルを強要するのが当たり前になっているではありませんか。
●転載、配布、改変について
この作品は転載、配布、改変とも自由です。不具合などありましたらぜひ作者ikubohへフィードバックしてください。
アーカイブには以下のファイルを同梱しました。
while.html | いま読んでいるドキュメント |
while1.def | サンプル1 |
while2.def | サンプル2 |
while3.def | サンプル3 |
while4.def | サンプル4 |
while5.def | サンプル5 |
continue.asm | continueサブルーチンのアセンブラソース |
break.asm | breakサブルーチンのアセンブラソース |
wbreak.asm | wbreakサブルーチンのアセンブラソース |
xbreak.asm | xbreakサブルーチンのアセンブラソース |
このドキュメントで紹介したサブルーチンは特に単独のファイルにはなっていません。while?.defから適当に切り貼りして利用してください。
- アセンブラソースから機械語サブルーチンへの変換にあたっては、エル・エス・アイ ジャパン(株)のLSI C-86 v3.30c 試食版に付属のr86.exeを利用させていただきました。
- Vzマクロ形式に整えるために、拙作robot.defを使用しました。
|