MSXで拡張BASICを作る

### 拡張BASICとはなにか

MSXではBASICの命令を自作して追加することができます。
文法的には
<B>CALL xxxx</B>
とか
<B>CALL xxxx(n,n ....)</B>
という形式になります。

マシン語を使うときはUSR(n)で呼び出すことはできますが、引数が１つしかないという制約があります。
CALL文は複数の引数を持てるというメリットがありますが、デメリットとしては
記述が難しいことと、戻り値がないことです。
どうしても戻り値を取得したい場合は、変数のアドレスを引数として渡してそのアドレスに結果を代入するという方法もあります。

### CALL文の作り方

以下の文献を参考にしました。

<A HREF="http://ngs.no.coocan.jp/doc/wiki.cgi/TechHan?page=4%BE%CF+%A5%DE%A5%B7%A5%F3%B8%EC%A4%C8%A4%CE%A5%EA%A5%F3%A5%AF">テクハンCMD命令の拡張</A>

<A href="http://ngs.no.coocan.jp/doc/wiki.cgi/datapack?page=5%BE%CF+%A5%DE%A5%B7%A5%F3%B8%EC%A4%C8%A4%CE%A5%EA%A5%F3%A5%AF">BASICの内部ルーチン</A>

<a href="http://ngs.no.coocan.jp/doc/wiki.cgi/datapack?page=3%BE%CF+%A5%AB%A1%BC%A5%C8%A5%EA%A5%C3%A5%B8%A5%BD%A5%D5%A5%C8%A4%CE%BA%EE%C0%AE%CB%A1">カートリッジヘッダ</a>

<A HREF="https://www.msx.org/wiki/CALL">実装サンプル</A>

実装サンプルはありがたいのですが、一部間違っているのでそのままでは動かないと思われます。

### 実装方法

CALL文の本体はPAGE1に置くのが標準のようですが、PAGE2や3に置くことも可能のようです。

今回はWEBMSX上で開発するにあたって次のようにしました。

① マシン語はページ３の0c000h～に配置する。
②ページ１のメモリマッパーをページ3と同じにする。
③CALL拡張したフラグを&hfcc9+48+1のbit5に設定する。

こうすればRAMをROMのように振る舞うことができます。
注意する点は実際の実行はページ１(4000h~)なので絶対番地でアクセスするには-8000Hしないといけないことです。
なるべくJPではなくJRを使っていれば-8000Hをしなくてもいいのですが・・。


### サンプルコード
アセンブラ
```
; External variables & routines
CHPUT   EQU     0A2h
CALBAS	EQU	0159h
ERRHAND EQU     0406Fh
FRMEVL  EQU     04C64h
FRMQNT  EQU     0542Fh ;2byte DE
GETBYT  EQU     0521Ch ;1byte A or E

FRESTR	EQU	067D0h
CHRGTR  EQU     04666h
VALTYP  EQU     0F663h
USR     EQU     0F7F8h
PROCNM	EQU	0FD89h

org 0c000h
BEEP EQU 0c0h
init:
db 041h,042h
db 0,0
dw main - 8000h
db 0,0,0,0

org 0c010h
main:
PUSH IX
PUSH BC
PUSH DE
PUSH HL

LD HL,MYCMD - 8000h
CALL CHKCMD - 8000h
JR NC,END2S
LD HL,MYCMD1 - 8000h
CALL CHKCMD - 8000h
JR NC,END3S
LD HL,MYCMD2 - 8000h
CALL CHKCMD - 8000h
JR NC,END4S
LD HL,MYCMD3 - 8000h
CALL CHKCMD - 8000h
JR NC,END2S
JR END1E

END2S: ;SUCCESS
POP HL

CALL	CHKCHAR -8000h
DEFB	"("

LD IX,GETBYT
CALL	CALBAS
LD (0d002h),A

CALL	CHKCHAR -8000h
DEFB	")" 

POP DE
POP BC
POP IX
XOR A
RET

END3S: ;SUCCESS
POP HL

CALL	CHKCHAR -8000h
DEFB	"("

LD IX,FRMQNT
CALL	CALBAS
LD (0d000h),DE

CALL	CHKCHAR -8000h
DEFB	")" 

POP DE
POP BC
POP IX
XOR A
RET

END4S: ;SUCCESS
POP HL

CALL	CHKCHAR -8000h
DEFB	"("

LD IX,FRMQNT
CALL	CALBAS

PUSH DE

CALL	CHKCHAR -8000h
DEFB	","

LD IX,FRMQNT
CALL	CALBAS

POP IX
ADD IX,DE

LD (0d000h),IX

CALL	CHKCHAR -8000h
DEFB	")" 

POP DE
POP BC
POP IX
XOR A
RET

END1E: ;NG
LD A,'N'
CALL 0a2h

POP HL
POP DE
POP BC
POP IX
SCF
RET

MYCMD:DB "A",0
MYCMD1:DB "B",0
MYCMD2:DB "C",0
MYCMD3:DB "D",0

CHKCMD:
LD IX,PROCNM
LD BC,16
LOOP:
LD A,(IX+0)
CP 0
JR Z,NEXT
CPI
JR NZ,NEXT
JP PO,NEXT - 8000h
INC IX
JR LOOP
NEXT:
LD A,(IX)
CP 0
JR NZ,ERROR1
LD A,(HL)
CP 0
JR NZ,ERROR1
HIT:
XOR A
RET
ERROR1:
SCF
RET

CHKCHAR:
CALL	GETPREVCHAR	-8000h
EX	(SP),HL
CP	(HL) 	        
JR	NZ,SYNTAX_ERROR	
INC	HL
EX	(SP),HL
INC	HL		

GETPREVCHAR:
DEC	HL
LD	IX,CHRGTR
JP      CALBAS


TYPE_MISMATCH:
LD      E,13
LD	IX,ERRHAND
JP	CALBAS

SYNTAX_ERROR:
LD      E,2
LD	IX,ERRHAND	
JP	CALBAS
```
BASIC
```
10 clear 200,&hbfff
20 bload"program.bin"
30 out&hfd,&hc0
35 poke&hfcc9+48+1,32 'ROM ENABLED
36 goto 300
37 END
300 ' BENCH
310 time=0:fora=0to999
320 _A(1)
330 NEXT
340 print TIME
350 time=0:fora=0to999
360 _B(1)
370 NEXT
380 print TIME
390 time=0:fora=0to999
400 _B(1+2)
410 NEXT
420 print TIME
430 time=0:fora=0to999
440 _C(1,2)
450 NEXT
460 print TIME
```

### 実装した命令
以下の命令が使えます。

|命令書式|内容|
|:--|:--|
|CALL A(n)|nの値をd002hに書き込む|
|CALL B(nn)|nnの値をd000hに書き込む|
|CALL C(nn,mm)|nn+mmの値をd000hに書き込む|

CALL Aは1バイトの値を取得します。
CALL Bは2バイトの値を取得します。

値が範囲を超えるとエラーがでます。

CALL Cは2バイトの値２つを加算しています。

引数はBASIC内部で式として処理しているのですが、これをマシン語内で演算したら速くなるのか気になりました。

実行した結果は以下の通りです。
|実行内容|時間(Z80)|時間(R800)|
|:--|:--|:--|
|1バイトの値を渡す|358|107|
|2バイトの値を渡す|360|103|
|式の中で足し算する|398|110|
|２つの値をマシン語で足し算する|432|132|

意外な結果になりました。
マシン語で足し算した方が速いと思っていたのにBASIC式の方が速かった。
もしかすると引数の取得にオーバーヘッドがかかっているのかもしれない。
また2バイトの値渡しはZ80とR800で逆転していますね。
16ビットレジスタはR800の方が得意なのでこういう結果なのでしょう。

### 拡張命令で何をするか？

今回のプログラムですが、ディスクにprogram.binが保存されています。
そのディスクイメージをwindowsでダウンロードしてprogram.binを取り出したのち、先頭の7バイトを削除すればROMイメージになります。

この開発方法でROMイメージを作るとしてゲームに利用できる命令を拡張したいですね。
具体的にはデータの圧縮展開とか複雑な計算とか。

```
例）
CALL OBJ(0,X,Y) '自分の座標
CALL OBJ(1,EX(0),EY(0)) '敵1の座標
CALL OBJ(2,EX(1),EY(1)) '敵2の座標
CALL OBJ(3,EX(2),EY(2)) '敵3の座標
CALL HIT(H) '自分と敵が当たっていればHに1を入れる
```
IF文をたくさん書くよりは高速化できそうな感じもする。
あとメモリ上にXY座標を置くからスプライトのまとめ表示命令を作るとさらなる高速化が期待できるか。

またフラッシュメモリに書き込む命令を拡張してもよい。
```
例)
CALL MINIT 'ROMに書き込むための準備
CALL MSAVE 'ページ２のBASICプログラムをROMに書き込む
CALL MLOAD 'ROMプログラムをページ２に書き込む
CALL MWRITE(ROMアドレス,RAMアドレス,バイト数)
CALL MREAD(RAMアドレス,ROMアドレス,バイト数)
```
これがあるとMSX-DOS無しで読み書きができるんだが・・。


### 最後に

BASICを拡張するというのは<A href="https://www.msx.org/wiki/Category:MSX-BASIC_Extensions">先人達</A>もやっているようですが、自作命令は夢がありますね。