「7+7÷7+7×7-7」に挑戦(3/3)

eyecatch_calculator_3 HD6301/HD6303

四則演算ルーチンが完成したので、ようやく式計算を実装します。

電卓を作ろうと決めた時、演算子の優先順位をどう実現するか…はっきり言ってどうすればいいのかさっぱり分かりませんでした。
ネットで探してみても、構文解釈の方法ばかりで

これってコンパイラ向けの情報なのでは?

というものばかりでした。インデックスレジスタが1本しかないHD6301/3だとこういうメモリ間の移動がたくさんある方法は苦手だと思うんです。

ネットサーフィンを繰り返すうちに下記のページを見つけました。

この方法だと複雑な式でも一方通行で処理していけばいいのでうまくいきそうです。

数式評価のEBNFとフローチャート

上記ページを参考に数式評価のEBNF定義を作ってみました。剰余演算子%を追加しています。

expr = term, { "+" | "-", term } ;
term = factor, { "*" | "/" | "%", factor } ;
factor = number | "(", expr, ")" ;
number = [ "+" | "-" ], Digit, { Digit } ;
Digit = "0" | "1" | "2" | "3" | … | "8" | "9" ;

これを元にフローチャートを作成しました。

数式評価のフローチャート

おそらくこれでEBNFを再現できていると思います。

2+3×4であれば下フローのように処理が進むはずです。

数式評価フロー例1

(2+3)×4であれば

数式評価フロー例2

となるはずです。

数式評価ルーチン(eval_expression)

インデックスレジスタにテキストバッファの開始位置を入れて本ルーチンを呼び出せば、数式を評価して結果をDレジスタに代入して返します。
終了後のインデックスレジスタは次の文字の位置を示しています。

Rx_BUFFER       .eq     $0100   ; SCI Rx Buffer ($0100-0148,73byte)
CSTACK          .eq     $0149   ; Calculate stack ($0149-0170,40byte)

       .or     $80
CStackPtr       .bs     2       ; Calculate stack Pointer

eval_expression:
        ldd     #CSTACK+40+1    ; Initialize calculate stack pointer.
        std     <CStackPtr
        bsr     expr_3rd
        ; // Extracting Result
        pshx
        ldx     <CStackPtr      ; X <- calculate stack pointer
        ldd     0,x
        pulx
        sec
        rts

expr_3rd:
        bsr     expr_2nd
.loop   jsr     skip_space
        cmpb    #'+'
        bne     :minus
        inx
        bsr     expr_2nd
        jsr     CS_add
        bra     :loop
.minus  cmpb    #'-'
        bne     :end
        inx
        bsr     expr_2nd
        jsr     CS_sub
        bra     :loop
.end    rts

expr_2nd:
        bsr     expr_1st
.loop   jsr     skip_space
        cmpb    #'*'
        bne     :div
        inx
        bsr     expr_1st
        jsr     CS_mul
        bra     :loop
.div    cmpb    #'/'
        bne     :mod
        inx
        bsr     expr_1st
        jsr     CS_div
        bra     :loop
.mod    cmpb    #'%'
        bne     :end
        inx
        bsr     expr_1st
        jsr     CS_mod
        bra     :loop
.end    rts

expr_1st:
        jsr     skip_space
        jsr     get_int_from_decimal    ; Is it decimal number?
        bcc     :paren          ; No. Check parenthesis.
        bra     :push           ; Yes. Push to calculate stack and return.
.paren  cmpb    #'('
        bne     :err04
        inx
        bsr     expr_3rd
        cmpb    #')'
        bne     :err04
        inx
        rts
.push   pshx
        ldx     <CStackPtr      ; X <- calculate stack pointer
        dex
        dex
        cpx     #CSTACK-2
        bcs     :err08
        std     0,x
        stx     <CStackPtr
        pulx
        rts
.err04  ldaa    #4              ; "Illegal expression"
        jmp     write_err_msg
.err08  ldaa    #8              ; "Calculate stack overflow"
        jmp     write_err_msg

Rx_BUFFERの直後にCSTACK(Calculate STACK:計算用スタック)を40バイト分確保しました。
また、ゼロページに計算用スタックポインター保存のために2バイト分確保しています。インデックスレジスタが1本しかないので頻繁に入れ替えが必要になります。

ルーチンの構造はほぼフローチャートの通りです。
expr_3rdがExpression、expr_2ndがterm、expr_1stがfactorに相当します。

eval_expressionは計算用スタックとスタックポインタの初期化と数式評価後にスタックから結果を取り出してリターンするだけです。

四則演算ルーチンの名前はCSCalculate Stack)という接頭辞をつけました。

76行目ではスタックが溢れないかチェックしています。20レベルも取ってあるのでオーバーフローすることはないと思いますが、念のためです。

メインルーチン

init:   tsx
        stx     <StackPointer

main:
        ldab    #'>'
        jsr     write_char
        jsr     read_line
        ldx     #Rx_BUFFER
        jsr     eval_expression
        pshx
        jsr     write_integer
        jsr     write_crlf
        pulx                    ; Show string after expression.
        ldab    0,x
        beq     :end
        jsr     write_line
        jsr     write_crlf
.end    bra     main

メインルーチンは非常に単純です。

1,2行目で現在のスタックポインタ値を保存しておきます。

5,6行目はプロンプト”>”を出力し、7行目で一行入力ルーチンを呼び出します。
8行目でテキストバッファの先頭アドレスをインデックスレジスタに設定した後にeval_expressionを呼び出します。

10行目でテキストバッファポインタをpshxで退避し、計算結果を表示します。

インデックスレジスタを退避するのはwrite_integerでインデックスレジスタが破壊されるためです。呼び出す側とサブルーチン側のどちらで退避するかはまだはっきりと決めていません。

13行目以降は数式評価されずに余った文字列を表示します。どこまで評価されたかチェックするためです。

コンソール整数電卓のソースコード

完成したソースコードはこちらです(utf-8)。
今回はリストファイルとSレコードファイルも用意しました。

Tiny MoniterのLコマンドでS19ファイルを読み込んで実行します。

実行します

コンソール電卓実行画面1

2+3×4も(2+3)×4もきちんと答えを出せています。

「7+7÷7+7×7-7」も正しい答えを出せました!

コンソール電卓実行画面2

割り算も期待通りの結果になっています。

コメント

タイトルとURLをコピーしました