MSX Datapack wiki化計画

5章 マシン語とのリンクの変更点

  • 追加された行はこのように表示されます。
  • 削除された行はこのように表示されます。
{{include unfinished_page}}

,{t},{BSIZE:1 0|SIZE:100%}
,{BSIZE:0}←[[4章 BASICの内部構造]]                                                            ,{BSIZE:0|RIGHT}[[6章 内部ルーチン]]                                                              →

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

!!5.1 USR関数

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

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


,例{SIZE:100|CENTER}


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

    DEF USR=&HC000
    A=USR(0)

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

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

'''表2.18 レジスタAに代入される引数の型'''
,{t},{BSIZE:2 0}
,{c},{BSIZE:0|SIZE:20},{BSIZE:0|SIZE:60},{BSIZE:0|SIZE:300}
,,2     ,2バイト整数型
,,3     ,文字列型
,,4     ,単精度実数型
,,8     ,倍精度実数型


{{svg 図2.27.svg}}
'''図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){{anchor ABS}}
,機 能{SIZE:100|CENTER}
     テキストから1文字取り出します。

,コール手順{SIZE:100|CENTER}
     HL  テキストを指すアドレス
 
,戻り値{SIZE:100|CENTER}
     HL     取り出した文字のアドレス
     A      取り出した文字
     Zフラグ   文末(「:」または00H)ならばON
     CYフラグ  0〜9ならばON
 
,変更レジスタ{SIZE:100|CENTER}
 
,解 説{SIZE:100|CENTER}
     (HL+1)の場所のテキストから1文字取り出します。スペースはスキップされます。
 
 {{svg CHRGTR.svg}}
!FRMEVL(4C64H/MAIN){{anchor ABS}}
,機 能{SIZE:100|CENTER}
     テキスト中の式を評価します。
,コール手順{SIZE:100|CENTER}
     HL  テキスト中の式の先頭アドレス
,戻り値{SIZE:100|CENTER}
     HL  式の次のアドレス
     【VALTYP(0F663H)】    型に応じて2、3、4、8の値が入る 
     【DAC(0F7F6H)】      式の評価結果
,変更レジスタ{SIZE:100|CENTER}
     すべて
,解 説{SIZE:100|CENTER}
     式を評価し、型に応じて出力します。5.2 USR関数の引数と戻り値によるデータの受け渡し参照。
{{svg FRMEVL.svg}}
!FRMQNT(542FH/MAIN){{anchor ABS}}
,機 能{SIZE:100|CENTER}
     式を2バイト整数型で評価します。
,コール手順{SIZE:100|CENTER}
     HL  テキスト中の式の先頭アドレス
,戻り値{SIZE:100|CENTER}
     HL  式の次のアドレス
     DE  式の評価結果
,変更レジスタ{SIZE:100|CENTER}
     すべて
,解 説{SIZE:100|CENTER}
     式を評価し、整数型(INT)で出力します。結果が2バイト整数型の範囲に納まらない場合には、「Overflow」エラーを発生し、BASICコマンドレベルに戻ります。
!GETBYT(521CH/MAIN){{anchor ABS}}
,機 能{SIZE:100|CENTER}
     式を1バイト整数型で評価します。
,コール手順{SIZE:100|CENTER}
     HL  テキスト中の式の先頭アドレス
,戻り値{SIZE:100|CENTER}
     HL  式の次のアドレス
     A、E  式の評価結果(AとEには同じ値が入る)
,変更レジスタ{SIZE:100|CENTER}
     すべて
,解 説{SIZE:100|CENTER}
     式を評価し、1バイト型で出力します。結果が1バイト整数型の範囲に納まらない場合には、「Illegal function call」エラーを発生し、BASICコマンドレベルに戻ります。
!FRESTR(67D0H/MAIN){{anchor ABS}}
,機 能{SIZE:100|CENTER}
     文字列を登録します。
,コール手順{SIZE:100|CENTER}
     【VALTYP(0F663H)】    型(文字列でないとエラーになる)
     【DAC(0F7F6H)】      ストリングディスクリプタへのポインタ
,戻り値{SIZE:100|CENTER}
     HL  ストリングディスクリプタへのポインタ
,変更レジスタ{SIZE:100|CENTER}
     すべて
,解 説{SIZE:100|CENTER}
     FRMEVLで得た文字列型の結果を文字列領域に登録し、そのストリングディスクリプタを得ます。文字列を評価した場合はFRMEVLと組み合わせて以下のように使用するのが一般的です。

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

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

 
            [図の挿入]
 
 
{{svg PTRGET.svg}}
!CRUNCH(42B2H/MAIN){{anchor ABS}}
,機 能{SIZE:100|CENTER}
     BASICのステートメントを中間コードに変換します。
 
 
,コール手順{SIZE:100|CENTER}
     HL     00Hで終わるASCII形式のテキストの先頭アドレス
 
 
 
     HL   00Hで終わるASCII形式のテキストの先頭アドレス
,戻り値{SIZE:100|CENTER}
     【KBUF(0F41FH)】      中間コード
 
 
,変更レジスタ{SIZE:100|CENTER}
     すべて
 
,解 説{SIZE:100|CENTER}
     BASICステートメントを中間コードに変換します([[表2.17参照]])。
 
 
 
 
 
            [図の挿入]
 
 
 
 

{{svg CRUNCH.svg}}
!NEWSTT(4601H/MAIN){{anchor ABS}}
,機 能{SIZE:100|CENTER}
     任意のテキストを実行します。
 
 
,コール手順{SIZE:100|CENTER}
     HL     実行するテキストのアドレス
 
 
 
     HL  実行するテキストのアドレス
,戻り値{SIZE:100|CENTER}
      なし
 
 
,変更レジスタ{SIZE:100|CENTER}
     すべて
 
,解 説{SIZE:100|CENTER}
     テキストの実行を行いますテキストは下図の状態となっている必要があります。
 
 
 
 
 
            [図の挿入]
 
 
 

 
{{svg NEWSTT.svg}}
     これらの内部ルーチンは、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カートリッジでの使用という条件がつきますが、将来にわたって互換性が保証されている方法ですので、市販のソフトウェアで採用するのに最適です。
 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
,書式{SIZE:100}
,書式{SIZE:100|CENTER}
CALL FOO(<文字変数>,<文字列>)

,機能{SIZE:100}
,機能{SIZE:100|CENTER}
<文字列>を大文字に変換して、<文字変数>に代入します。

,文例{SIZE:100}
,文例{SIZE:100|CENTER}
 CALL FOO(A$,"This is an example")
 PRINT A$
 【表示】THIS IS AN EXAMPLE

::CALL BAR
,書式{SIZE:100}
,書式{SIZE:100|CENTER}
CALL BAR(<数値変数>,<数式1>,<数式2>)

,機能{SIZE:100}
,機能{SIZE:100|CENTER}
<数式1>と<数式2>を足し算して、その結果を<数値変数>に代入します。

,文例{SIZE:100}
,文例{SIZE:100|CENTER}
 CALL BAR(A,100,23)
 PRINT A
 【表示】123

,{t},{BSIZE:1 0|SIZE:100%}
,{BSIZE:0}←[[4章 BASICの内部構造]]                                                            ,{BSIZE:0|RIGHT}[[6章 内部ルーチン]]                                                              →