四則演算ルーチンが完成したので、ようやく式計算を実装します。
電卓を作ろうと決めた時、演算子の優先順位をどう実現するか…はっきり言ってどうすればいいのかさっぱり分かりませんでした。
ネットで探してみても、構文解釈の方法ばかりで
これってコンパイラ向けの情報なのでは?
というものばかりでした。インデックスレジスタが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であれば下フローのように処理が進むはずです。
(2+3)×4であれば
となるはずです。
数式評価ルーチン(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は計算用スタックとスタックポインタの初期化と数式評価後にスタックから結果を取り出してリターンするだけです。
四則演算ルーチンの名前はCS(Calculate 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ファイルを読み込んで実行します。
実行します
2+3×4も(2+3)×4もきちんと答えを出せています。
「7+7÷7+7×7-7」も正しい答えを出せました!
割り算も期待通りの結果になっています。
コメント