Page 1 of 15
Neolithic Tiny Basic
Posted: Sun Nov 10, 2024 9:00 am
by barnacle
The steps that led to the C version of NTB are documented in the Crenshaw thread here:
viewtopic.php?f=2&t=8175
This thread is to document the conversion of that C code to a generic 65c02 build, so the details don't get lost. I'll be posting snippets of code and eventually a complete listing here; please feel free to jump in if I'm doing anything silly or looking lost. I believe that once I have a basic structure it should drop together pretty easily.
The code won't be the fastest, but I'm aiming for simplicity; it can be optimised later.
The assembler I am using is as65 from
http://www.kingswood-consulting.co.uk/assemblers/ and I invoke it: as65 tiny.asm -h0 -ltiny.lst -s2 -x to produce a listing, which I find handy to check what code is actually being generated; as65 has an automatic internal optimiser which for example will replace 'jmp' with 'bra' if the target is in range, and a few other things.
API
Because I want to copy the C program, I feel it will be easiest to use a c-style API for calling functiond. It makes heavy use of the stack, but I don't think too heavy, so I don't have a separate stack to manage (at the moment; that's always subject to change)
- Functions can take one or more eight or sixteen bit arguments, and return zero or one eight or sixteen bit results in A or Y:A.
- Function arguments are counted from the left:
Code: Select all
result = divide (first, second) ; divide first by second
- Sixteen bit arguments are passed in Y:A and on the stack if necessary; if there is more than one argument they are pushed on the stack from the left, with the final argument being placed in Y:A.
- All functions must preserve the X register; it is used as a stack frame pointer for the calling routine. Other registers may be thumped as required.
- Local variables are pushed as required to the stack, generally as sixteen bit words.
- The X register is used as a frame pointer; constant offsets are provided for convenient reference
A couple of short examples:
Code: Select all
c16c : libadd16: ; add TOS to y:a
c16c : da phx ; save caller's stack frame
c16d : ba tsx ; set local frame pointer
c16e : 8556 sta maths1 ; we hold the high byte in y
c170 : 18 clc
c171 : 7d0401 adc CALLER_LO,x
c174 : 8556 sta maths1
c176 : 98 tya
c177 : 7d0501 adc CALLER_HI,x
c17a : a8 tay ; high result back in y
c17b : a556 lda maths1 ; collect low result
c17d : fa plx ; clean the stack
c17e : 60 rts
c17f : libsub16: ; subtract y:a from TOS
c17f : da phx
c180 : ba tsx
c181 : 5a phy
c182 : 48 pha ; build stack frame
c183 : 38 sec
c184 : bd0401 lda CALLER_LO,x ; we need to subtract LOCAL from CAL
c187 : f5ff sbc LOCAL_LO,x
c189 : 95ff sta LOCAL_LO,x
c18b : bd0501 lda CALLER_HI,x
c18e : fd0001 sbc LOCAL_HI,x
c191 : 9d0001 sta LOCAL_HI,x
c194 : 68 pla ; load y:a and restore stack
c195 : 7a ply
c196 : fa plx
c197 : 60 rts
As you can see from these examples, the stack looks like this once we're in them:
Code: Select all
second argument (2)
return address (2)
x register (1)
local argument (2)
stack pointer here --->
and the convenience definitions
Code: Select all
00ff = LOCAL_LO equ 0xff
0100 = LOCAL_HI equ 0x100
0104 = CALLER_LO equ 0x104
0105 = CALLER_HI equ 0x105
provide an x offset to the argument. Note that the function can see (and directly alter) variables in the caller's stack; I strongly caution against allowing it to do so! Further local variables can be accessed at 0xfe,x, 0xfd,x and so on. I wish WDC had included a stack-pointer-relative addressing mode!
Which brings me to the first question: does an zero page x-indexed memory wrap around, or go where I expect it to? I note that the optimiser has substituted a zero page instruction in the examples above...
Neil

Re: Neolithic Tiny Basic
Posted: Sun Nov 10, 2024 10:54 am
by John West
Which brings me to the first question: does an zero page x-indexed memory wrap around, or go where I expect it to?
Zero page indexing wraps:
will read from $007f, not $017f.
Re: Neolithic Tiny Basic
Posted: Sun Nov 10, 2024 12:21 pm
by barnacle
Thanks, that was a good catch; I need to tell the assembler not to optimise, then.
It's very inconvenient of it
Neil
Re: Neolithic Tiny Basic
Posted: Sun Nov 10, 2024 5:37 pm
by barrym95838
When Woz wanted to use "negative" stack indexing with X he had to "hard-code" abs,X opcodes, because it's not generally easy to convince most assemblers to avoid zp,X when they see a base address less than $0100.
If you have room in zero-page, my first recommendation in the optimization process would be to move the formal argument stack there and keep the call stack in page one clean for addresses and local temporaries.
Example (untested):
Code: Select all
libsub16: ; subtract y:a from TOS (27 bytes)
phx
tsx
phy
pha ; build stack frame
sec
lda CALLER_LO,x ; we need to subtract LOCAL from CAL
sbc LOCAL_LO,x
sta LOCAL_LO,x
lda CALLER_HI,x
sbc LOCAL_HI,x
sta LOCAL_HI,x
pla ; load y:a and restore stack
ply
plx
rts
becomes
Code: Select all
libsub16: ; subtract y:a from TOS (17 bytes)
sec ; we're going to add the two's complement
pha ; save a (strictly necessary?)
eor #$ff
adc 0,x ; x points to top of zp argument stack
sta 0,x
tya
eor #$ff
adc 1,x
sta 1,x
pla ; restore a (strictly necessary?)
rts
More trickery would be to place libsub16 directly before libadd16. libsub16 would set carry and one's complement Y:A, then BRA or employ a naked BIT opcode to enter libadd16 after the CLC. This optimization assumes that you don't need to preserve Y:A, in which case you don't need as many PHA/PLA pairs, saving several more bytes:
Code: Select all
libsub16: ; subtract y:a from TOS (10 bytes)
sec ; we're going to add the two's complement
pha ; save low byte
tya
eor #$ff ; complement high byte
tay
pla ; restore low byte
eor #$ff ; complement low byte
dcb $89 ; naked BIT# opcode to skip over the clc
libadd16: ; add y:a to TOS (11 bytes)
clc
adc 0,x
sta 0,x
tya
adc 1,x
sta 1,x
rts
Re: Neolithic Tiny Basic
Posted: Sun Nov 10, 2024 6:52 pm
by barnacle
It's a thought, Mike, but I need to think about how much stack I'm going to need. For the moment, I think I'll hard-code the 16-bit opcode where necessary (i.e. everywhere there's a local variable) - this assembler doesn't appear to have a word override. I'd like to try and keep the function API constant for everything, if possible.
There are obvious places where one can select the right order of functions to save bytes, and as you point out the subtract routine can be shorter, but... let's see. (A thought: since I need an invert routine to make divide work, I can use that to invert the subtrahend and use add instead...)
Neil
edit: aka first make it work, then make it quick/small

Re: Neolithic Tiny Basic
Posted: Sun Nov 10, 2024 8:39 pm
by barnacle
Ah, this ought to work; the subtract routine is now three bytes long...
Code: Select all
;negate: change the sign of an int16_t by inverting th
; adding one. The variable arrives and returns in Y:A
c174 : negate:
c174 : 18 clc
c175 : 49ff eor #0xff
c177 : 6901 adc #1
c179 : 48 pha
c17a : 98 tya
c17b : 49ff eor #0xff
c17d : 6900 adc #0
c17f : a8 tay
c180 : 68 pla
c181 : 60 rts
;libsub16: minuend pushed to stack, subtrahend in y:a,
; we subtract by negating y:a and then adding it to TO
c182 : libsub16: ; subtract y:a from TOS
c182 : 2074c1 jsr negate
; libadd16: augand pushed to stack, addend in y:a, =>
c185 : libadd16: ; add TOS to y:a
c185 : da phx ; save caller's stack frame
c186 : ba tsx ; set local frame pointer
c187 : 8556 sta maths1 ; we hold the high byte in y
c189 : 18 clc
c18a : 7d0401 adc CALLER_LO,x
c18d : 8556 sta maths1
c18f : 98 tya
c190 : 7d0501 adc CALLER_HI,x
c193 : a8 tay ; high result back in y
c194 : a556 lda maths1 ; collect low result
c196 : fa plx ; clean the stack
c197 : 60 rts
Neil
Re: Neolithic Tiny Basic
Posted: Wed Nov 13, 2024 8:00 am
by barnacle
Another chunk of code; divide the TOS by the int16_t y:a; result in y:a and remainder in zero page 'remainder' (I need the remainder later).
Also uses four other zero page bytes (as two words): dividend (that which will be divided) and divisor (that by which we will divide). Those bytes are shared with the other arithmetic routines but given different names.
Code: Select all
;libdiv16: dividend on stack, divisor in y:a, result i
; in remainder
; NOTE: if either (or both) parameter is negative, we
; we do the division; if just one is negative, we inve
c24a : libdiv16:
c24a : da phx ; caller stack frame
c24b : ba tsx
c24c : 645c stz sign ; assume both arguments are positive
c24e : 8556 sta divisor ; save the low byte
c250 : 98 tya ; but wait: is it negative?
c251 : 100b bpl ldv_01 ; positive, so skip
c253 : a556 lda divisor ; else negate it; y still holds high by
c255 : 20f1c1 jsr negate
c258 : 8556 sta divisor ; save negated low byte
c25a : a980 lda #0x80
c25c : 855c sta sign ; flag that we've inverted
c25e : ldv_01:
c25e : 8457 sty divisor+1 ; save the divisor
c260 : bd0401 lda CALLER_LO,x
c263 : 8558 sta dividend
c265 : bc0501 ldy CALLER_HI,x
c268 : 98 tya ; check for sign of this argument
c269 : 100d bpl ldv_02
c26b : a558 lda dividend
c26d : 20f1c1 jsr negate
c270 : 8558 sta dividend ; negated low byte
c272 : a55c lda sign
c274 : 4980 eor #0x80 ; sign is now 0x80 if just one arg is neg
c276 : 855c sta sign ; but 0x00 if both were same sign
c278 : ldv_02:
c278 : 8459 sty dividend+1 ; and dividend
; core routine taken from https://www.llx.com/Neil/a2
; divides dividend by divisor, leaves result in divid
; remainder in remainder
; dividend and divisor are both positive
c27a : 645a stz remainder
c27c : 645b stz remainder+1 ; remainder initialised to zero
c27e : a210 ldx #16 ; bit counter
c280 : ldv_1:
c280 : 0658 asl dividend
c282 : 2659 rol dividend+1
c284 : 265a rol remainder
c286 : 265b rol remainder+1 ; move high bit of dividend into re
c288 : a55a lda remainder
c28a : 38 sec
c28b : e556 sbc divisor
c28d : a8 tay ; trial subtraction
c28e : a55b lda remainder+1
c290 : e557 sbc divisor+1
c292 : 9006 bcc ldv_2
c294 : 855b sta remainder+1 ; and if it worked, save it
c296 : 845a sty remainder
c298 : e658 inc dividend ; slide the result in as the low bit
; of dividend
c29a : ldv_2:
c29a : ca dex
c29b : d0e3 bne ldv_1 ; and back sixteen times
c29d : a558 lda dividend
c29f : a459 ldy dividend+1 ; return the result in y:a
c2a1 : a65c ldx sign ; check the sign of the result
c2a3 : 1003 bpl ldv_exit
c2a5 : 20f1c1 jsr negate
c2a8 : ldv_exit:
c2a8 : fa plx ; restore stack frame
c2a9 : 60 rts
I'm not currently able to test this (my test board is for some reason having hiccups, and I don't know whether that's because I've broken it or I've just forgotten a step in loading it) and I can't find an online emulator that can do 65c02. And of course my own 6502 emulator from five years back is NMOS only...
Neil
Re: Neolithic Tiny Basic
Posted: Wed Nov 13, 2024 8:07 am
by barnacle
And a partial (unfinished) chunk: to output y:a as a signed decimal string. This uses a block of seven bytes in zero page; the first holds flags to indicate whether the number is negative, whether it is right or left justified, and the current digit position. Digits slot in from numbuf[5] to numbuf[1] with a terminating /0 at numbuf[6].
Code: Select all
c160 : putn: ; output y:a as a string with leading zeroes
; suppressed. Right justified if carry set
; The number is generated by dividing the arg
; by ten repeatedly, filling in numbuf from the
; right as we go, and finally adding the minus
; sign as required
c160 : da phx ; save caller stack frame
c161 : 5a phy
c162 : 48 pha ; save arg as local variable
c163 : a906 lda #6
c165 : 855d sta numbuf ; use buffer[0] for args and char positi
c167 : a900 lda #0
c169 : 8563 sta numbuf+6 ; mark the end of the string
c16b : 9006 bcc putn_1
c16d : a55d lda numbuf
c16f : 0940 ora #0x40
c171 : e65d inc numbuf ; bit 6 set = right justify
c173 : putn_1:
c173 : aa tax
c174 : 98 tya ; check high byte of arg
c175 : 100e bpl putn_2 ; if arg is negative
c177 : a55d lda numbuf
c179 : 0980 ora #0x80
c17b : 855d sta numbuf ; bit 7 set = negative number
c17d : 8a txa
c17e : 20f1c1 jsr negate ; and make the arg positive
c181 : fa plx
c182 : fa plx
c183 : 5a phy
c184 : 48 pha ; replace arg on stack with positive version
c185 : putn_2:
c185 : a000 ldy #0
c187 : a90a lda #10
c189 : 204ac2 jsr libdiv16 ; we want to divide the argument by ten
c18c : fa plx
c18d : fa plx
c18e : 5a phy
c18f : 48 pha ; replace arg by arg/10
c190 : a55a lda remainder ; we only care about the low byte
c192 : 0930 ora #'0' ; make it ascii
c194 : a8 tay ; keep it handy
c195 : a55d lda numbuf ; get the char count
c197 : 3a dec a
c198 : 855d sta numbuf ; decrement the char position
c19a : 2907 and #0x7 ; isolate the count bits
c19c : aa tax
c19d : 98 tya ; get the digit back
c19e : 955d sta numbuf,x ; put it in the buffer from the right
c1a0 : e002 cpx #2 ; we count 6-5-4-3-2
c1a2 : d0e1 bne putn_2 ; so loop if we're not there yet
; now we have to consider the leading zeros, the left
; alignment, and the sign of the output
Neil
Re: Neolithic Tiny Basic
Posted: Thu Nov 14, 2024 8:51 pm
by barnacle
Testing basic arithmetic, now I've got enough of a framework to see what's happening... obviously lots more testing required, but so far we have proved that
Code: Select all
6 + 3 = 9
6 - 3 = 3
6 * 3 = 18 (0x12)
Haven't quite dared do the divide yet
Neil
edit: this is running on my Neo version of Grant Searle's minimal design, which means I have to pop an eeprom between it and a homebrew programmer and back for each iteration; it's getting a bit tedious but for some reason the Romless with the LV5183 chips doesn't want to talk to me at the moment and I have no idea why.
edit 2:
Amazing!
Re: Neolithic Tiny Basic - putn
Posted: Fri Nov 15, 2024 4:29 pm
by barnacle
We progress: this is the output from (lots of) calls to puts, which outputs a signed 16-bit integer in decimal, either left or right justified; the latter in a six-wide cell. Both formats include a trailing space, to match the C version.
Code: Select all
Neolithic Tiny Basic
1
-1
12
-12
123
-123
1234
-1234
12345
-12345
1
-1
12
-12
123
-123
1234
-1234
12345
-12345
The code itself is reasonably compact - 119 bytes, perhaps that can be improved - and relies on the division and negate routines in the arithmetic library (I'll post that soon).
Code: Select all
;-----------------------------------------------------
c27b : putn: ; output y:a as a string with leading zeroes
; suppressed. Right justified if carry set.
; The number is generated by dividing the arg
; by ten repeatedly, filling in numbuf from the
; right as we go, and finally adding the minus
; sign as required.
; Irrespective of justification, we follow the
; number with a space; we always print at least
; one digit.
c27b : da phx ; save caller stack frame
c27c : 5a phy
c27d : 48 pha ; save arg as local variable
c27e : aa tax ; and temporarily in x
c27f : 6464 stz numbuf+7 ; mark the end of the string
c281 : a920 lda #' '
c283 : 8563 sta numbuf+6
c285 : a906 lda #6
c287 : 9002 bcc putn_1
c289 : 0980 ora #0x80 ; right justified
c28b : putn_1:
c28b : 855d sta numbuf ; use buffer[0] for args and char positi
c28d : 98 tya ; check high byte of arg
c28e : 100e bpl putn_2 ; if arg is negative
c290 : a55d lda numbuf
c292 : 0940 ora #0x40
c294 : 855d sta numbuf ; bit 6 set = negative number
c296 : 8a txa
c297 : 2036c3 jsr negate ; and make the arg positive
c29a : fa plx
c29b : fa plx
c29c : 5a phy
c29d : 48 pha ; replace arg on stack with positive version
c29e : putn_2:
c29e : a000 ldy #0
c2a0 : a90a lda #10
c2a2 : 208ec3 jsr libdiv16 ; we want to divide the argument by ten
c2a5 : fa plx
c2a6 : fa plx
c2a7 : 5a phy
c2a8 : 48 pha ; replace arg by arg/10
c2a9 : a55a lda remainder ; we only care about the low byte
c2ab : 0930 ora #'0' ; make it ascii
c2ad : a8 tay ; keep it handy
c2ae : a55d lda numbuf ; get the char count
c2b0 : 3a dec a
c2b1 : 855d sta numbuf ; decrement the char position
c2b3 : 2907 and #0x7 ; isolate the count bits
c2b5 : aa tax
c2b6 : 98 tya ; get the digit back
c2b7 : 955d sta numbuf,x ; put it in the buffer from the right
c2b9 : e001 cpx #1 ; we count 6-5-4-3-2
c2bb : d0e1 bne putn_2 ; so loop if we're not there yet
; now we have to consider the leading zeros, the left
; alignment, and the sign of the output
c2bd : a201 ldx #1 ; blank the leading zeros
c2bf : putn_3:
c2bf : b55d lda numbuf,x
c2c1 : c930 cmp #'0' ; are we looking at a zero?
c2c3 : d009 bne putn_4 ; no, we're done
c2c5 : a920 lda #' ' ; replace with a space
c2c7 : 955d sta numbuf,x
c2c9 : e8 inx
c2ca : e005 cpx #5 ; don't eat the last zero!
c2cc : d0f1 bne putn_3
c2ce : putn_4:
c2ce : 065d asl numbuf ; save the justification bit in carry
; so now the sign bit is in bit 7
c2d0 : ca dex ; x was pointing to the last space; now it mi
; be pointing at the flags byte
c2d1 : a55d lda numbuf
c2d3 : a020 ldy #' ' ; we're done with the flags but might need
c2d5 : 845d sty numbuf ; space for a minus sign
c2d7 : 2980 and #0x80
c2d9 : f005 beq putn_5 ; no negative sign required
c2db : a92d lda #'-'
c2dd : 955d sta numbuf,x ; else insert one
c2df : ca dex
c2e0 : putn_5:
LYA numbuf ; point to the start of the buffer
c2e0 : a95d > lda # lo numbuf
c2e2 : a000 > ldy # hi numbuf
c2e4 : b005 bcs putn_8 ; right justified
; unfortunately, I can't add x to a but I note that
; page addresses for puts aren't used until we call
; end. So... for left justified
c2e6 : e8 inx ; correct for the start of numbuf
c2e7 : 8652 stx put_ptr ; naughty naughty, not my variable!
c2e9 : 6552 adc put_ptr ; carry is already clear
; and we're pointing at the right place
c2eb : putn_8:
c2eb : 203ec2 jsr puts
c2ee : fa plx
c2ef : fa plx
c2f0 : fa plx
c2f1 : 60 rts
Neil
edit: corrected to make it work with a zero! (I forgot to test that)
Re: Neolithic Tiny Basic
Posted: Fri Nov 15, 2024 5:03 pm
by barnacle
Here are the arithmetic routines: 183 bytes covering the usual four operators, and negation. As always, parameters are passed both on the stack before calling, with the second in Y:A, and the result returned in Y:A (for division, a zero page word 'remainder' is also used). For speed and simplicity, these routines share some zero page space.
Code: Select all
;-----------------------------------------------------
;
; arithmetic and logic
;negate: change the sign of an int16_t by inverting th
; adding one. The variable arrives and returns in Y:A
c336 : negate:
c336 : 18 clc
c337 : 49ff eor #0xff ; this doesn't affect carry
c339 : 6901 adc #1 ; but this does...
c33b : 48 pha
c33c : 98 tya
c33d : 49ff eor #0xff
c33f : 6900 adc #0 ; and this uses the carry from above
c341 : a8 tay
c342 : 68 pla
c343 : 60 rts
;libsub16: minuend pushed to stack, subtrahend in y:a,
; we subtract by negating y:a and then adding it to TO
c344 : libsub16: ; subtract y:a from TOS
c344 : 2036c3 jsr negate ; negate and drop through to libadd16
; libadd16: augand pushed to stack, addend in y:a, =>
c347 : libadd16: ; add TOS to y:a
c347 : da phx ; save caller's stack frame
c348 : ba tsx ; set local frame pointer
c349 : 8556 sta maths1 ; we hold the high byte in y
c34b : 18 clc
c34c : 7d0401 adc CALLER_LO,x
c34f : 8556 sta maths1
c351 : 98 tya
c352 : 7d0501 adc CALLER_HI,x
c355 : a8 tay ; high result back in y
c356 : a556 lda maths1 ; collect low result
c358 : fa plx ; clean the stack
c359 : 60 rts
;libmul16: multiplicand on the stack, multiplier in y:
; This code is taken from the example p206 of the West
; Programming Manual, and uses two zero page words (fo
c35a : libmul16: ; signed multiply
c35a : da phx ; caller stack frame
c35b : ba tsx
c35c : 8556 sta maths1
c35e : 8457 sty maths1+1 ; save the multiplier
c360 : bd0401 lda CALLER_LO,x
c363 : 8558 sta maths2
c365 : bd0501 lda CALLER_HI,x
c368 : 8559 sta maths2+1 ; and multiplicand
c36a : a200 ldx #0
c36c : a000 ldy #0 ; clear the result
c36e : lml_m1:
c36e : a556 lda maths1
c370 : 0557 ora maths1+1 ; is the operand zero? We're done...
c372 : f015 beq lml_exit
c374 : 4657 lsr maths1+1 ; get lowest bit into carry
c376 : 6656 ror maths1
c378 : 9009 bcc lml_m2 ; nothing to do if it's zero
c37a : 18 clc ; else add maths2 to result
c37b : 98 tya
c37c : 6558 adc maths2
c37e : a8 tay
c37f : 8a txa
c380 : 6559 adc maths2+1
c382 : aa tax
c383 : lml_m2:
c383 : 0658 asl maths2 ; multiply maths2 by two
c385 : 2659 rol maths2+1
c387 : 80e5 jmp lml_m1 ; and back for the next bit
c389 : lml_exit:
; result is in x:y which isn't where we want it...
c389 : 98 tya ; the low byte
c38a : da phx
c38b : 7a ply ; and the high
c38c : fa plx ; restore stack frame
c38d : 60 rts
;libdiv16: dividend on stack, divisor in y:a, result i
; in remainder
; NOTE: if either (or both) parameter is negative, we
; we do the division; if just one is negative, we inve
c38e : libdiv16:
c38e : da phx ; caller stack frame
c38f : ba tsx
c390 : 645c stz sign ; assume both arguments are positive
c392 : 8556 sta divisor ; save the low byte
c394 : 98 tya ; but wait: is it negative?
c395 : 100b bpl ldv_01 ; positive, so skip
c397 : a556 lda divisor ; else negate it; y still holds high by
c399 : 2036c3 jsr negate
c39c : 8556 sta divisor ; save negated low byte
c39e : a980 lda #0x80
c3a0 : 855c sta sign ; flag that we've inverted
c3a2 : ldv_01:
c3a2 : 8457 sty divisor+1 ; save the divisor
c3a4 : bd0401 lda CALLER_LO,x
c3a7 : 8558 sta dividend
c3a9 : bc0501 ldy CALLER_HI,x
c3ac : 98 tya ; check for sign of this argument
c3ad : 100d bpl ldv_02
c3af : a558 lda dividend
c3b1 : 2036c3 jsr negate
c3b4 : 8558 sta dividend ; negated low byte
c3b6 : a55c lda sign
c3b8 : 4980 eor #0x80 ; sign is now 0x80 if just one arg is neg
c3ba : 855c sta sign ; but 0x00 if both were same sign
c3bc : ldv_02:
c3bc : 8459 sty dividend+1 ; and dividend
; core routine taken from https://www.llx.com/Neil/a2
; divides dividend by divisor, leaves result in divid
; remainder in remainder
; dividend and divisor are both positive
c3be : 645a stz remainder
c3c0 : 645b stz remainder+1 ; remainder initialised to zero
c3c2 : a210 ldx #16 ; bit counter
c3c4 : ldv_1:
c3c4 : 0658 asl dividend
c3c6 : 2659 rol dividend+1
c3c8 : 265a rol remainder
c3ca : 265b rol remainder+1 ; move high bit of dividend into re
c3cc : a55a lda remainder
c3ce : 38 sec
c3cf : e556 sbc divisor
c3d1 : a8 tay ; trial subtraction
c3d2 : a55b lda remainder+1
c3d4 : e557 sbc divisor+1
c3d6 : 9006 bcc ldv_2
c3d8 : 855b sta remainder+1 ; and if it worked, save it
c3da : 845a sty remainder
c3dc : e658 inc dividend ; slide the result in as the low bit
; of dividend
c3de : ldv_2:
c3de : ca dex
c3df : d0e3 bne ldv_1 ; and back sixteen times
c3e1 : a558 lda dividend
c3e3 : a459 ldy dividend+1 ; return the result in y:a
c3e5 : a65c ldx sign ; check the sign of the result
c3e7 : 1003 bpl ldv_exit
c3e9 : 2036c3 jsr negate
c3ec : ldv_exit:
c3ec : fa plx ; restore stack frame
c3ed : 60 rts
Neil
Re: Neolithic Tiny Basic
Posted: Fri Nov 15, 2024 5:06 pm
by barnacle
Apologies for the truncated comments in the listing above. It's down to the assembler listing chopping them according to no obvious rule I can discern. Better to perhaps provide the source rather than the listing?
Re: Neolithic Tiny Basic
Posted: Sun Nov 17, 2024 7:59 pm
by barnacle
Gradually working my way up the ladder: the expression parser can now cope with addition and subtraction; multiplication and division aren't far away (the code is very similar) and recursive brackets will follow.
I am deliberately keeping the code as close to the C code as I can; later I can go and prune unnecessary loads of the accumulator, for example. It's not going to be the fastest code ever, but it's amazing how easy it is to translate...
Neil
Re: Neolithic Tiny Basic
Posted: Mon Nov 18, 2024 8:04 pm
by barnacle
The expression parser appears to be complete (at least for now; I can't check the variables until I have an assignment function written).
Factor, Term, and Expression are just 202 bytes, not counting the support stuff like the arithmetic routines.
Code: Select all
Neolithic Tiny Basic
2*2*2*4*355/(3*113)= 33
1+2*3= 7
(1+2)*3= 9
(just test code, nothing fancy yet!).
Next step is to enter program text into the 65c02 memory space, with tokenisation. Should be fun - I need to find something small to behave like strncasecmp()...
Neil
Re: Neolithic Tiny Basic
Posted: Sat Nov 23, 2024 10:21 pm
by barnacle
Wow, that took longer than it should have done.
Squish_buffer() has to tokenise the input stream (but not between quotes, of course) and required the generation of a token matching routine (reasonably straightforward), a routine to test _all_ tokens against text, memmove, and strlen equivalents.
I think they're all working now.
So now I have 'just' to get the text into memory... at least I can continue to follow the working example of the C code.
Neil (just under 2k at the moment, but there's at least 1/4k of debug which will come out at the end.)