MSX Datapack wiki化計画

5章 マシン語とのリンク

 これまで説明したように、MSX BASICは強力な機能を備えていますが、さらに実行速度を上げたり、MSX2のハードウェア機能を限界まで引き出す場合は、マシン語を使用しなければなりません。この章では、その必要が生じた時のために、BASICからマシン語プログラムを呼び出す方法、およびその際に知っておくべき情報について説明します。

 5.1 USR関数

 BASICからのマシン語の呼び出しは、以下の手順で行います。USR関数のカッコの中の値は、引数としてマシン語に渡すためのものです。引数は数式、文字式いずれでもかまいません。

  1. DEF USR文で、マシン語プログラムの実行開始アドレスを指定する。
  2. USR関数でマシン語を呼び出す
  3. マシン語からBASICに戻る場合はRET命令(C9H)を実行する

 C000H番地が実行アドレスであるマシン語プログラムを呼び出す。

    DEF USR=&HC000
    A=USR(0)

 5.2 USR関数の引数と戻り値によるデータの受け渡し

 BASICからマシン語に引数を渡す場合、マシン語側ではレジスタAの内容でその型を判断します(表2.18)。目的の値は引数の型により、図2.27のような形式で格納されていますから、それに応じて値を受け取ります。例として、文字列型の引数を受け取るプログラムをリスト2.5に示します。

表2.18 レジスタAに代入される引数の型

2 2バイト整数型
3 文字列型
4 単精度実数型
8 倍精度実数型


図2.27 引数による値のわたされ方

リスト2.5 文字型の引数の使用例

;
;       USR関数で渡した文字列をそのまま表示する
;          使用法:DEF USR=&Hxxxx: A$=USR("STRING")
;
chput   equ    00a2h              ; character output

rdarg::
        cp     3
        ret    nz                 ; parameter is not string
  
        push   de
        pop    ix                 ; IX:= string descripter
        ld     a,(ix+0)           ; get string length
        ld     l,(ix+1)           ; get string pointer(low)
        ld     h,(ix+2)           ; get string pointer(high)
        or     a
        ret    z                  ; if length = 0
rd1:
        push   af
        ld     a,(hl)             ; get a character
        call   chput              ; get a character
        pop    af
        dec    a
        ret    z
        inc    hl

        jr     rd1

        end

 逆に、引数として渡された値をマシン語の中で処理することによって、USR関数の値としてBASICに引き渡すこともできます。この場合、【VALTYP(0F663H)】を変更することにより、戻り値をBASICからの引数の型以外のものに変更することも可能です。ただし、文字列型の場合、文字数を変えることはできません。

 5.3 命令の増設

 MSXでは現在「CMD」と「IPL」という予約語が未使用となっており、この予約語のフック(それぞれFE0DH、FE03H)を自分で作成した任意のマシン語ルーチンにジャンプするよう書き換えるだけで、新しい命令を作成することができます。簡単な例をリスト2.6に示します。

リスト 2.6 CMD命令の増設

;
;    CMDコマンドの使用法(CAPSのON/OFF)
;    コマンドの初期化:  DEF USR=&Hxxxx: A=USR(0)
;    コマンドの使用法:  CMD
;
chgcap  equ       0132h            ; CAPS LAMP on/off
capst   equ       0fcabh           ; CAPS LOCK status
hcmd    equ       0fe0dh           ; CMD HOOK

;    CMD コマンドの増設

start::
        ld       bc,5              ;NEW HOOK SET
        ld       de,hcmd
        ld       hl,hdat
        ldir
        ret

; HOOK に書き込むデータ

hdat:
        pop      af
        jp       capkey
        nop

; CMD コマンドの実体

capkey:
        call     chgcap
        ld       a,(capst)
        cpl
        ld       (capst),a
        ret

        end

 フックに書き込む最初の「pop af」は、「CMD」の実行時にスタックに積まれるエラー処理アドレスを捨てるためのものです。これを入れておかないと、「RET」命令でBASICに戻るはずが、エラー処理ルーチンにジャンプしてしまいます。ユーザールーチン内でエラーを出したい場合は、このアドレスを取っておくのも1つの方法です。
 なお、これらのフックは本来は将来の拡張用にリザーブしてある場所なので、商用のアプリケーションプログラムで使用してはいけません。

 5.4 BASICの内部ルーチン

 より複雑なステートメントの拡張を行うには、CMD命令にも引数を渡せると便利です。マシン語を呼び出した際、HLレジスタがBASICテキスト中の「CMD」の次の位置を指していますから、これを利用して後に続く文字列の評価を行えば目的は果たせます。これらを行うために有効な内部ルーチンを以下で説明します。


CHRGTR(4666H/MAIN)

機 能

     テキストから1文字取り出します。

コール手順

     HL  テキストを指すアドレス
 

戻り値
     HL     取り出した文字のアドレス
     A      取り出した文字
     Zフラグ   文末(「:」または00H)ならばON
     CYフラグ  0〜9ならばON
 
変更レジスタ
 
解 説
     (HL+1)の場所のテキストから1文字取り出します。スペースはスキップされます。
 
 

FRMEVL(4C64H/MAIN)

機 能

     テキスト中の式を評価します。

コール手順
     HL  テキスト中の式の先頭アドレス
戻り値
     HL  式の次のアドレス
     【VALTYP(0F663H)】    型に応じて2、3、4、8の値が入る 
     【DAC(0F7F6H)】      式の評価結果
変更レジスタ
     すべて
解 説
     式を評価し、型に応じて出力します。5.2 USR関数の引数と戻り値によるデータの受け渡し参照。

FRMQNT(542FH/MAIN)

機 能

     式を2バイト整数型で評価します。

コール手順
     HL  テキスト中の式の先頭アドレス
戻り値
     HL  式の次のアドレス
     DE  式の評価結果
変更レジスタ
     すべて
解 説
     式を評価し、整数型(INT)で出力します。結果が2バイト整数型の範囲に納まらない場合には、「Overflow」エラーを発生し、BASICコマンドレベルに戻ります。

GETBYT(521CH/MAIN)

機 能

     式を1バイト整数型で評価します。

コール手順
     HL  テキスト中の式の先頭アドレス
戻り値
     HL  式の次のアドレス
     A、E  式の評価結果(AとEには同じ値が入る)
変更レジスタ
     すべて
解 説
     式を評価し、1バイト型で出力します。結果が1バイト整数型の範囲に納まらない場合には、「Illegal function call」エラーを発生し、BASICコマンドレベルに戻ります。

FRESTR(67D0H/MAIN)

機 能

     文字列を登録します。

コール手順
     【VALTYP(0F663H)】    型(文字列でないとエラーになる)
     【DAC(0F7F6H)】      ストリングディスクリプタへのポインタ
戻り値
     HL  ストリングディスクリプタへのポインタ
変更レジスタ
     すべて
解 説
     FRMEVLで得た文字列型の結果を文字列領域に登録し、そのストリングディスクリプタを得ます。文字列を評価した場合はFRMEVLと組み合わせて以下のように使用するのが一般的です。

              :
              :
        call       fmevl
        push       hl             ; テキストポインタをセーブする
        call       FRESTR
        ex         de,hl          ; ストリングディスクリプタを得る
        pop        hl
        ld         a,(de)         ; 入力された文字列の長さを得る
              :
              :

PTRGET(5EA4H/MAIN)

機 能

     変数の格納アドレスを獲得します。

コール手順
     HL   テキスト中の変数名の先頭アドレス
     【SUBFLG(0F6A5H)】    0=単純変数、0以外=配列変数
戻り値
     HL   変数名の次のアドレス
     DE   目的の変数の内容が格納されているアドレス
変更レジスタ
     すべて
解 説
     変数(または配列変数)の格納アドレスを得ます。目的の変数の領域が確保されていなかった場合は、領域の確保も行われます。【SUBFLG(0F6A5H)】の値を0以外に設定しておくと配列の個々の要素ではなく、その配列の先頭アドレスが得られます。
     BASICで、USR関数により実行中の機械語プログラムから、BASICの変数(ここではS%)に直接結果を返すには、以下のようにします。

vars:
        defd     'S%',0
        ld       hl,vars
        call     ptrget


CRUNCH(42B2H/MAIN)

機 能

     BASICのステートメントを中間コードに変換します。

コール手順
     HL   00Hで終わるASCII形式のテキストの先頭アドレス
戻り値
     【KBUF(0F41FH)】      中間コード
変更レジスタ
     すべて
解 説
     BASICステートメントを中間コードに変換します(表2.17参照?)。

NEWSTT(4601H/MAIN)

機 能

     任意のテキストを実行します。

コール手順
     HL  実行するテキストのアドレス
戻り値
      なし
変更レジスタ
     すべて
解 説
     テキストの実行を行いますテキストは下図の状態となっている必要があります。

     これらの内部ルーチンは、BASICテキストを対象とするものであるため、エラーが発生した場合は、BASICと同様のエラー処理が行われます。その場合【H.ERRO(0FFB1H)】の書き換えにより、ユーザーがエラー処理を行うこともできます。その場合は、Eレジスタにエラー番号が入ります。リスト2.7を参照してください。

リスト 2.7 エラー処理ルーチンの変更

;
;        エラー処理ルーチンの変更
;          使用法:DEF USR=&Hxxxx:  A=USR(0)
;
herr       equ     0ffb1h              ; error HOOK
synerr     equ     2                   ; syntax error code
chput      equ     00a2h               ; character output BIOS
newstt     equ     04601h              ; run
readyr     equ     0409bh

;        エラー処理ルーチンの変更

start::
           ld      bc,5                ; set new HOOK
           ld      de,herr
           ld      hl,hdat
           ldir
           ret

hdat:
           jp      error
           nop
           nop

;        エラー処理ルーチンの実体

error:
           ld      a,e                 ; when in error,E holds error code
           cp      synerr              ; syntax error?
           ret     nz                  ; no

           ld      hl,data1            ; yes
loop:      ld      a,(hl)              ; put new error message
           cp      '$'
           jr      z,exit
           push    hl
           call    chput
           pop     hl
           inc     hl
           jr      loop
exit:
           jp      readyr

data1:
           defm    'マチガッテイマス'  ; new error start
           defb    07h,07h,07h,'$

           end

 5.5 拡張ステートメント

 4.4で紹介したのは、「CMD」という未使用の予約語を使って、BASICの機能を拡張する方法でした。しかし、この方法は予約語を使っている関係で、市販のアプリケーションソフトウェアでは使用できません。ここで紹介する拡張ステートメントは、ROMカートリッジでの使用という条件がつきますが、将来にわたって互換性が保証されている方法ですので、市販のソフトウェアで採用するのに最適です。
 拡張ステートメントはCALL命令で始まり、以下の書式で使用します。

CALL <拡張ステートメント名>[(<引数>[,<引数>...])]

 拡張ステートメントは、MSXのカートリッジヘッダーのSTATEMENTに拡張ステートメントルーチンのアドレスを書き込むことにより実行できます。その実現方法などについては、「xxx カートリッジヘッダー」をご参照ください。
 添付のフロッピーディスクに、BASICの拡張ステートメントのサンプルプログラムが入っています。ファイル名は「FOO_BAR.MAC」です。このソースプログラムを、

M80 = foo_bar

でアセンブルして、

L80 /P:4000,foo_bar,foo_bar /n/x/e

でリンクすると、FOO_BAR.HEXというファイルができます。
 このファイルをPROMライターに転送してROMに書き込み、カートリッジにすると「FOO」と「BAR」という拡張ステートメントが使用できるようになります。「FOO」と「BAR」は以下の機能を持っています。

CALL FOO
書式

CALL FOO(<文字変数>,<文字列>)

機能

<文字列>を大文字に変換して、<文字変数>に代入します。

文例
CALL FOO(A$,"This is an example")
PRINT A$
【表示】THIS IS AN EXAMPLE
CALL BAR
書式

CALL BAR(<数値変数>,<数式1>,<数式2>)

機能

<数式1>と<数式2>を足し算して、その結果を<数値変数>に代入します。

文例
CALL BAR(A,100,23)
PRINT A
【表示】123