In
http://codebase64.org/doku.php?id=magazines:discovery3 Steven Judd describes a BASIC extension with an own tokenizer. This should easily be adapted to your need.
Anyway, here we go:
The vector eval fn ($030a) points to $CE86 in the BASIC ROM.
At this time within formula evaluation, BASIC expects a single operand. This can be a number, a variable (floating or integer), a function result, or an expression in parenthesis. BASIC has not yet looked at the first byte of the operand.
The begin of the routine at $ce86 reads thus:
Code: Select all
.C:ce86 A9 00 LDA #$00
.C:ce88 85 0D STA $0D ; assume numeric value
.C:ce8a 20 73 00 JSR $0073 ; CHRGET first byte of operand,
; could also be a function token
The operand shall be returned in FAC#1. Furthermore, the TXTPTR has to be advanced behind the operand. This can be deduced from the following code continuation, where we assume, that {PI} was the token returned from CHRGET:
Code: Select all
.C:ce8d B0 03 BCS $CE92 ; branch taken
.C:ce8f 4C F3 DC JMP $DCF3
.C:ce92 20 13 D1 JSR $D113 ; Is the token an alphabetic character?
.C:ce95 90 03 BCC $CE9A ; No. branch taken
.C:ce97 4C 28 CF JMP $CF28
.C:ce9a C9 FF CMP #$FF ; Is it {PI}?
.C:ce9c D0 0F BNE $CEAD ; Yes. branch not taken
.C:ce9e A9 A8 LDA #$A8
.C:cea0 A0 CE LDY #$CE
.C:cea2 20 A2 DB JSR $DBA2 ; load FAC#1 from memory
.C:cea5 4C 73 00 JMP $0073 ; CHRGET behind {PI}, and exit.
>C:cea8 82 49 0f da a1 ; value of {PI}
The replacement routine copies the start of the original ROM routines, checks for the DIV, or MOD tokens, and re-enters the ROM routine at $ce8d, if neither of them is there:
Code: Select all
.EvalV
LDA #$00
STA $0D
JSR $0073
PHP ; store flags on stack
CMP #{DIV token}
BEQ EvalV_00
CMP #{MOD token}
BEQ EvalV_01
PLP ; restore flags
JMP &CE8D ; re-enter ROM eval fn
.EvalV_00
PLP ; clean up stack
JMP EvalDIV ; jumps to custom DIV routine
.EvalV_01
PLP ; clean up stack
JMP EvalMOD ; jumps to custom MOD routine
If more functions are implemented, a jump table is more efficient, just like BASIC does it.
Both the DIV, and MOD functions take two arguments in parenthesis, separated by comma. So it makes sense to provide a common subroutine. First we set aside two 5 byte buffers for numerator, and denominator:
Code: Select all
.Numerator
EQUB 0:EQUB 0:EQUB 0:EQUB 0:EQUB 0
.Denominator
EQUB 0:EQUB 0:EQUB 0:EQUB 0:EQUB 0
BASIC provides three calls to check for '(', ')', and ','. If the correct char is not there, these calls end our program with '?SYNTAX ERROR'. Otherwise TXTPTR is advanced. We're still on the function token, so the first action we take is to advance TXTPTR to (hopefully) the opening parenthesis of the function:
Code: Select all
.GetArgs
JSR $0073
JSR $CEFA ; check for '(', and advance
JSR $CD8A ; get first operand, and store in FAC#1
LDX #<Numerator
LDY #>Numerator
JSR $DBD4 ; Store FAC#1 to memory at address (Y/X)
JSR $CEFD ; check for ',', and advance
...
There now already has been a recursive formula evaluation, which resulted in the Numerator. If we now read in the denominator, we must push the Numerator value on the stack, as a recursive call of DIV, or MOD at this stage would otherwise destroy the stored value!
Code: Select all
...
LDX #0
.GetArgs_00
LDA Numerator,X
PHA ; Push the 5 bytes of the Numerator to the stack
INX
CPX #5
BNE GetArgs_00
JSR $CD8A ; get second operand, and store in FAC#1
LDX #<Denominator
LDY #>Denominator
JSR $DBD4 ; Store FAC#1 to memory at address (Y/X)
LDX #4
.GetArgs_01
PLA ; Restore the 5 Bytes of the Numerator from stack
STA Numerator,X
DEX
CPX #$FF
BNE GetArgs_01
JMP $CEF7 ; Check for ')', advance TXTPTR, and exit.
Both EvalDIV, and EvalMOD now start their business with a JSR GetArgs to fetch their arguments. And TXTPTR then is already behind the whole function call, as required.
Code: Select all
.EvalDIV
JSR GetArgs
; ** commented out, since the Denominator is already in FAC#1 after JSR GetArgs
;LDA #<Denominator
;LDY #>Denominator
;JSR $DBA2 ; Load Denominator in FAC#1
LDA #<Numerator
LDY #>Numerator
JSR $DB0F ; Divide value at (Y/A) by FAC#1, result in FAC#1
; Denominator=0 results in a '?DIVISION BY ZERO' error
JMP $DCCC ; Perform INT() on FAC#1, result in FAC#1:=INT(N/D)
Since Q=INT(N/D) is shared by both DIV, and MOD, EvalMOD simply calls EvalDIV first:
Code: Select all
.EvalMOD
JSR EvalDIV ; return FAC#1:=INT(N/D) (=Q)
LDA #<Denominator
LDY #>Denominator
JSR $DA28 ; Multiply FAC#1 by value at (Y/A), result in FAC#1:=Q*D
LDA #<Numerator
LDY #>Numerator
JMP $D850 ; Subtract FAC#1 from value at (Y/A), result in FAC#1:=N-Q*D (=R!)