Page 3 of 15

Re: Neolithic Tiny Basic

Posted: Tue Jan 14, 2025 11:16 am
by barnacle
Sorry, but the simulator won't let me cut and paste from the window, so it's screen grab time...
Screenshot from 2025-01-14 11-46-13.png
Which looks as if my subtract routine is working as expected: I think here that in all cases, for _unsigned_ comparison, the carry is SET if the first parameter is greater or equal to the second. Except when comparing zero with zero, which is probably not a problem...

For _signed_ comparison, the high bit of the result is ZERO if the first parameter is greater or equal to the second.

Which means I can use the routine as it is, as the carry is preserved back to the calling routine, and the result is also returned (albeit in y:a so I'll have to juggle). Which means I can do away with my compare routine, just use the subtract routine instead, saving speed and stack space. Decoding the result can be done by the caller.

Neil

Re: Neolithic Tiny Basic

Posted: Tue Jan 14, 2025 11:19 am
by barnacle
Um, except when comparing anything with zero... the carry isn't set.
Which makes sense: invert zero to get -1, +1 = 0, add zero to anything and the carry will never be set. The question is, will this be a problem?

Neil

Re: Neolithic Tiny Basic

Posted: Tue Jan 14, 2025 2:10 pm
by barnacle
So, I wrote a different subtract routine which uses the sbc instruction rather than the original approach of just inverting one operand and dropping through to the add routine. It gives the same results, as expected, but now the carry makes more sense:
Screenshot from 2025-01-14 15-02-12.png
Now we have it working for comparison with zero, too... for _unsigned_ if the first column is greater or equal to the second column, the carry is set; for _signed_ comparison if the first column is greater or equal to the second, the top bit of the result is zero.

Neil

Re: Neolithic Tiny Basic

Posted: Tue Jan 14, 2025 3:41 pm
by rwiker
I was wondering whether the overflow flag might be useful for these comparisons, and found this page:

https://www.righto.com/2012/12/the-6502 ... ained.html

Looks like a good explanation of integer operations on the 6502, in general.

Re: Neolithic Tiny Basic

Posted: Tue Jan 14, 2025 5:15 pm
by barnacle
Ah, always good to read Ken Shirriff (plus his occasional appearances with Curious Marc!).

Curiously, my original signed comparison used the V flag in a variant of the approach used in section 6.1 of the tutorial: http://www.6502.org/tutorials/compare_beyond.html but unfortunately that doesn't work for unsigned numbers.

Neil

edit: to my recollection I have never actually used the V flag since 1978...

Re: Neolithic Tiny Basic

Posted: Tue Jan 21, 2025 7:12 am
by barnacle
Ah, if only I thought things through...

After all the effort to sort out the comparison, and have a generalised subtraction routine that could be used anywhere... I finally get around to realising that it's smaller and faster to do the comparison bit directly. The general API involves putting the first parameter on the stack, the second in Y:A, and calling a routine which accesses that first parameter via zp,x addressing. The sixteen bit result is returned in Y:A.

Which is fine, but (a) means that I can't use the invert and add method to subtract (it doesn't properly sort out the flags when subtracting zero) which means I have excess code there:

Code: Select all

e61a :                  libsub16:
e61a : da               	phx
e61b : ba               	tsx
e61c : 85b3             	sta maths1
e61e : 84b4             	sty maths1+1		; save minuend
e620 : 38               	sec
e621 : bd0401           	lda CALLER_LO,x
e624 : e5b3             	sbc maths1
e626 : 85b3             	sta maths1
e628 : bd0501           	lda CALLER_HI,x
e62b : e5b4             	sbc maths1+1
e62d : a8               	tay
e62e : a5b3             	lda maths1
e630 : fa               	plx
e631 : 60               	rts
Twenty-four bytes instead of three that a call to invert and drop straight through to libadd16 would have...

And the compare operation is also smaller if I do it inline:

Code: Select all

                        ;				if (space > insert_length)
e58e : a5b2             				lda slu_space+1
e590 : 48               				pha
e591 : a5b1             				lda slu_space
e593 : 48               				pha
e594 : a502             				lda buffer+2
e596 : a000             				ldy #0
e598 : 201ae6           				jsr libsub16
e59b : fa               				plx
e59c : fa               				plx
                        				;vs
e59d : 38               				sec
e59e : a5b1             				lda slu_space
e5a0 : c502             				cmp buffer+2
e5a2 : a5b2             				lda slu_space+1
e5a4 : e900             				sbc #0
                        				
e5a6 : 9047             				bcc sl_11
Fifteen bytes for the API call, versus nine for the inline version.

So I won't be doing it that way any longer :mrgreen:

The subtract routine is required for evaluation of variables, but that's signed and the output flag is not (as far as I know at this point) necessary, so if I get the basic functional and find it isn't, that will go back as it was - invert and drop through, saving a further twenty-one bytes.

Neil

Re: Neolithic Tiny Basic

Posted: Sat Jan 25, 2025 9:03 pm
by barnacle
It is now possible to enter numbered program lines and have them appear in the right place in memory (or disappear, if that is the intent). It started as around eighty lines of C and it definitely takes up more lines as machine code - though there are a few obvious places where space can be saved. There are a couple of calculations which could be performed once, rather than down each program branch... also a couple of cases where I copy a variable, then add to the variable and put it somewhere else, which could interleave the steps to save a couple of loads.

But I figure, get it working, then optimise!

Code: Select all

                        ;-----------------------------------------------------
                        ;
                        ; storeline
                        ; The line has been tokenised and enumerated in buffer
                        ; If the line is not in the store, insert it in its co
                        ; If it is in the store, replace it (or if empty, dele
                        ; On entry, y:a has the line number
                        ; There is a lot going on here...
                        
                        	bss
00ad =                  slu_this_line	ds 2
00af =                  slu_line_found	ds 2		; store_line vars
00b1 =                  slu_space		ds 2		; remaining space in memory
00b3 =                  slu_ex_length	ds 1
                        
                        	code
e4f8 :                  store_line:
                        ;	uint16_t line_found;
                        ;	bool line_exists = false;
                        ;	int insert_length = 0;
                        ;	int existing_length = 0;
                        ;	int space;
e4f8 : 85ad             	sta slu_this_line
e4fa : 84ae             	sty slu_this_line+1			; save this_line
                        ;{		
                        ;	// seek this line in memory
                        ;	mem_ptr = find_line(this_line);
e4fc : 2099e3           	jsr find_line
e4ff : 8550             	sta mem_ptr
e501 : 8451             	sty mem_ptr+1
                        ;	line_found = * (uint16_t *) mem_ptr;	
e503 : 2089e3           	jsr star_int16
e506 : 85af             	sta slu_line_found
e508 : 84b0             	sty slu_line_found+1
                        ;	insert_length = * (uint8_t *) &buffer[2];
                        	; insert_length remains at buffer[2]
                        ;	space = &mem[MEM_END] - mem_last;
e50a : 38               	sec
e50b : a9ff             	lda #lo MEM_END
e50d : e558             	sbc mem_last
e50f : 85b1             	sta slu_space
e511 : a97f             	lda #hi MEM_END
e513 : e559             	sbc mem_last+1
e515 : 85b2             	sta slu_space+1			
                        ;	printf ("%d free\n", space);
                        ;	
                        ;	if ((0 == line_found) && (insert_length > 4))	// emp
e517 : a5af             	lda slu_line_found
e519 : 05b0             	ora slu_line_found+1
e51b : d049             	bne sl_10					; line found != 0
e51d : a904             	lda #4
e51f : c502             	cmp buffer+2
e521 : b043             	bcs sl_10					; empty line, skip
                        ;	{ // 10
                        ;		// we point at the next available storage memory
                        ;		if (space > insert_length)
e523 : 38               		sec
e524 : a5b1             		lda slu_space
e526 : c502             		cmp buffer+2
e528 : a5b2             		lda slu_space+1
e52a : e900             		sbc #0
e52c : 9031             		bcc sl_05		; insufficient space
                        ;		{ // 05
                        ;			memmove (mem_ptr, buffer, insert_length);
e52e : a550             			lda mem_ptr
e530 : 859c             			sta mm_to
e532 : a551             			lda mem_ptr+1
e534 : 859d             			sta mm_to+1		; destination mem_ptr
e536 : a900             			lda #lo buffer
e538 : 859a             			sta mm_from
e53a : a900             			lda #hi buffer
e53c : 859b             			sta mm_from+1	; source buffer
e53e : a502             			lda buffer+2
e540 : 859e             			sta mm_size
e542 : 649f             			stz mm_size+1
e544 : 2042e3           			jsr memmove	
                        ;			mem_ptr += insert_length;
e547 : 18               			clc
e548 : a550             			lda mem_ptr
e54a : 6502             			adc buffer+2
e54c : 8550             			sta mem_ptr
e54e : a551             			lda mem_ptr+1
e550 : 6900             			adc #0
e552 : 8551             			sta mem_ptr+1
                        ;			mem_last = mem_ptr;
                        			COPY16 mem_ptr, mem_last
e554 : a550            >			lda mem_ptr
e556 : 8558            >			sta  mem_last
e558 : a551            >			lda mem_ptr+1
e55a : 8559            >			sta  mem_last+1
                        
e55c : 4ca4e6           			jmp sl_20
                        ;		}
e55f :                  sl_05:
                        ;		else
                        ;		{ // 10
                        ;			err = ERR_MEM;
e55f : a901             			lda #ERR_MEM
e561 : 8557             			sta err
e563 : 4ca4e6           			jmp sl_20
                        ;		}
                        ;	}	
e566 :                  sl_10:
                        ;	else if (line_found != 0)
                        ;	{
e566 : a5af             	lda slu_line_found
e568 : 05b0             	ora slu_line_found+1
e56a : d003             	bne sl_101
e56c : 4ca4e6           	jmp sl_19			; line zero was found, we're done
                        ;		if (line_found != this_line)
e56f :                  sl_101:	
e56f : a5b0             		lda slu_line_found+1
e571 : c5ae             		cmp slu_this_line+1
e573 : d006             		bne sl_102
e575 : a5af             		lda slu_line_found
e577 : c5ad             		cmp slu_this_line
e579 : f05f             		beq sl_14		
                        ;		{ // 14
                        ;			// we need to shuffle up and insert
e57b :                  sl_102:
                        ;			if (insert_length > 4)
e57b : a502             			lda buffer+2
e57d : c904             			cmp #4
e57f : 9059             			bcc sl_13
                        ;			{ // 13 
                        ;				// the line is valid, it needs space
e581 : 38               				sec
e582 : a5b1             				lda slu_space
e584 : c502             				cmp buffer+2
e586 : a5b2             				lda slu_space+1
e588 : e900             				sbc #0
e58a : 9047             				bcc sl_11
                        ;				{ // 11
                        ;					memmove (mem_ptr + insert_length, mem_ptr, mem_l
e58c : 18               					clc
e58d : a550             					lda mem_ptr
e58f : 859a             					sta mm_from		; while we have it to hand
e591 : 6502             					adc buffer+2
e593 : 859c             					sta mm_to		
e595 : a551             					lda mem_ptr+1	
e597 : 859b             					sta mm_from+1	; ditto 	
e599 : 6900             					adc #0
e59b : 859d             					sta mm_to+1
e59d : 38               					sec
e59e : a558             					lda mem_last
e5a0 : e550             					sbc mem_ptr
e5a2 : 859e             					sta mm_size
e5a4 : a559             					lda mem_last+1
e5a6 : e551             					sbc mem_ptr+1
e5a8 : 859f             					sta mm_size+1
e5aa : 2042e3           					jsr memmove					
                        ;					mem_last += insert_length;
e5ad : 18               					clc
e5ae : a558             					lda mem_last
e5b0 : 6502             					adc buffer+2
e5b2 : 8558             					sta mem_last
e5b4 : a559             					lda mem_last+1
e5b6 : 6900             					adc #0
e5b8 : 8559             					sta mem_last+1
                        ;					memmove (mem_ptr, buffer, insert_length);
e5ba : a550             					lda mem_ptr
e5bc : 859c             					sta mm_to
e5be : a551             					lda mem_ptr+1
e5c0 : 859d             					sta mm_to+1
e5c2 : a900             					lda #lo buffer
e5c4 : 859a             					sta mm_from
e5c6 : a900             					lda #hi buffer
e5c8 : 859b             					sta mm_from+1
e5ca : a502             					lda buffer+2
e5cc : 859e             					sta mm_size
e5ce : 649f             					stz mm_size+1
e5d0 : 2042e3           					jsr memmove
                        ;				}
e5d3 :                  sl_11:
                        ;				else
                        ;				{ // 12
                        ;					err = ERR_MEM;
e5d3 : a901             					lda #ERR_MEM
e5d5 : 8557             					sta err
e5d7 : 4ca4e6           					jmp sl_20
                        ;				}
e5da :                  sl_12:
                        ;			}
                        ;			// we don't insert an empty line!
e5da :                  sl_13:
                        ;		}
e5da :                  sl_14:
                        ;		else
                        ;		{ // 19
                        ;			// we need to replace or delete this existing line
                        ;			existing_length = (* (uint8_t *)(mem_ptr + 2))
e5da : a002             			ldy #2
e5dc : b150             			lda (mem_ptr),y
e5de : 85b3             			sta slu_ex_length
                        ;			if (insert_length > 4)
e5e0 : a904             			lda #4
e5e2 : c502             			cmp buffer+2
e5e4 : 9003             			bcc sl_141
e5e6 : 4c65e6           			jmp sl_17		; empty line
                        ;			{ // 17
                        ;				if (space > insert_length)
e5e9 :                  sl_141:
e5e9 : 38               				sec
e5ea : a5b1             				lda slu_space
e5ec : c502             				cmp buffer+2
e5ee : a5b2             				lda slu_space+1
e5f0 : e900             				sbc #0
e5f2 : 906a             				bcc sl_15
                        ;				{ // 15
                        ;					// replace the line
                        ;					// probably need to move existing memory to fit 
                        ;					memmove(mem_ptr + insert_length, mem_ptr + exist
e5f4 : 18               					clc
e5f5 : a550             					lda mem_ptr
e5f7 : 6502             					adc buffer+2
e5f9 : 859c             					sta mm_to
e5fb : a551             					lda mem_ptr+1
e5fd : 6900             					adc #0
e5ff : 859d             					sta mm_to+1		; mem_ptr + insert_length
e601 : 18               					clc
e602 : a550             					lda mem_ptr
e604 : 65b3             					adc slu_ex_length
e606 : 859a             					sta mm_from
e608 : a551             					lda mem_ptr+1
e60a : 6900             					adc #0
e60c : 859b             					sta mm_from+1	; mem_ptr + existing_length
e60e : 38               					sec
e60f : a558             					lda mem_last
e611 : e550             					sbc mem_ptr
e613 : 859e             					sta mm_size
e615 : a559             					lda mem_last+1
e617 : e551             					sbc mem_ptr+1
e619 : 859f             					sta mm_size+1
e61b : 38               					sec
e61c : a59e             					lda	mm_size
e61e : e5b3             					sbc slu_ex_length
e620 : 859e             					sta mm_size
e622 : a59f             					lda mm_size+1
e624 : e900             					sbc #0
e626 : 859f             					sta mm_size+1	;size
e628 : 2042e3           					jsr memmove
                        ;					memmove(mem_ptr, buffer, insert_length);
e62b : a550             					lda mem_ptr
e62d : 859c             					sta mm_to
e62f : a551             					lda mem_ptr+1
e631 : 859d             					sta mm_to+1
e633 : a900             					lda #lo buffer
e635 : 859a             					sta mm_from
e637 : a900             					lda #hi buffer
e639 : 859b             					sta mm_from+1
e63b : a502             					lda buffer+2
e63d : 859e             					sta mm_size
e63f : 649f             					stz mm_size+1
e641 : 2042e3           					jsr memmove
                        ;					mem_last += insert_length;
e644 : 18               					clc
e645 : a558             					lda mem_last
e647 : 6502             					adc buffer+2
e649 : 8558             					sta mem_last
e64b : a559             					lda mem_last+1
e64d : 6900             					adc #0
e64f : 8559             					sta mem_last+1
                        ;					mem_last -= existing_length;
e651 : 38               					sec
e652 : a558             					lda mem_last
e654 : e5b3             					sbc slu_ex_length
e656 : 8558             					sta mem_last
e658 : a559             					lda mem_last+1
e65a : e900             					sbc #0
e65c : 8559             					sta mem_last+1
                        ;				}
e65e :                  sl_15:
                        ;				else
                        ;				{ // 16
                        ;					err = ERR_MEM;
e65e : a901             					lda #ERR_MEM
e660 : 8557             					sta err
e662 : 4ca4e6           					jmp sl_20
                        ;				}
e665 :                  sl_16:
                        ;			}
e665 :                  sl_17:
                        ;			else
                        ;			{ // 18
                        ;				// it's empty; delete the line
                        ;				memmove(mem_ptr, mem_ptr + existing_length, mem_l
e665 : a550             				lda mem_ptr
e667 : 859c             				sta mm_to
e669 : a551             				lda mem_ptr+1
e66b : 859d             				sta mm_to+1		; dest mem_ptr
e66d : 18               				clc
e66e : a550             				lda mem_ptr
e670 : 65b3             				adc slu_ex_length
e672 : 859a             				sta mm_from
e674 : a551             				lda mem_ptr+1
e676 : 6900             				adc #0
e678 : 859b             				sta mm_from+1	; source mem_ptr+existing_length
e67a : 38               				sec
e67b : a558             				lda mem_last
e67d : e550             				sbc mem_ptr
e67f : 859e             				sta mm_size
e681 : a559             				lda mem_last+1
e683 : e551             				sbc mem_ptr+1
e685 : 859f             				sta mm_size+1
e687 : 38               				sec
e688 : a59e             				lda	mm_size
e68a : e5b3             				sbc slu_ex_length
e68c : 859e             				sta mm_size
e68e : a59f             				lda mm_size+1
e690 : e900             				sbc #0
e692 : 859f             				sta mm_size+1	;size
e694 : 2042e3           				jsr memmove
                        ;				mem_last -= existing_length;
e697 : 38               				sec
e698 : a558             				lda mem_last
e69a : e5b3             				sbc slu_ex_length
e69c : 8558             				sta mem_last
e69e : a559             				lda mem_last+1
e6a0 : e900             				sbc #0
e6a2 : 8559             				sta mem_last+1
                        ;			}
e6a4 :                  sl_18:
                        ;		}
e6a4 :                  sl_19:
                        ;	}
e6a4 :                  sl_20:
                        ;	*(int16_t *) mem_last = 0;		// mark end of memory
e6a4 : a000             	ldy #0
e6a6 : a900             	lda #0
e6a8 : 9158             	sta (mem_last),y
e6aa : c8               	iny
e6ab : 9158             	sta (mem_last),y
e6ad : 60               	rts
                        ;}
A mere 427 bytes... I can do better! But it does work, so that's the first stage done!

It works only if:
  • The input line is in buffer
  • The first two characters in buffer are the line number as an int16
  • The third character in buffer is the total length of the line in the buffer
  • I tokenise the text in the buffer, but that's not necessary to store it
but since all my text goes in that way, no problem...

Neil

Re: Neolithic Tiny Basic

Posted: Tue Jan 28, 2025 8:51 am
by barnacle
Hmm, after a couple of bugfixes and some factoring, it's a whole five bytes shorter...

Factoring short fragments carry almost as much overhead as actual code saved, it seems.

Neil

Re: Neolithic Tiny Basic

Posted: Tue Jan 28, 2025 1:50 pm
by drogon
barnacle wrote:
Hmm, after a couple of bugfixes and some factoring, it's a whole five bytes shorter...

Factoring short fragments carry almost as much overhead as actual code saved, it seems.

Neil
Sometimes you have to look at the bigger picture...

In my current quest to squeeze as much functionality into my own TinyBasic, I've managed to "find" some 200 bytes so I can fit in the EEPROM protection code, code to read the on-board button and some wiring-like GPIO commands to save peek/poke on the VIA. I have the very real constraint of 4KB of ROM space to work with, so refactoring, re-writing, changing order to make more effective use of a branch, using branches rather than jmp's and so on. I've even resorted to treating the 2 byte NMI vector as valid code space because it can never happen in my system.

Even something that seemingly only saves one byte has been worth it.

And often it's been at the expense of speed - a fair trade off, given my TB isn't going to win any speed contests - and right now to give me even more space I'm considering a re-write of my code to read in a decimal number - right now I multiply the 'accumulator' by 2, then 2 again, then add in the original accumulator, so now it's *5 then double that to get *10... I'm considering doubling the accumulator then in a loop add it to itself 4 times. Slower and it saves just 5 bytes but might be worth it one day..

Don't give up... There's always a byte to be saved somewhere!

-Gordon

Re: Neolithic Tiny Basic

Posted: Tue Jan 28, 2025 3:52 pm
by barnacle
Oh yes. My usual approach is make it work, then make it fast (and/or elegant!).

The snag with that store_line routine is that there's a lot of places that look as if it _should_ help to factor out... for example there are two places where one variable is added to another, result in the first:

Code: Select all

; mem_last += insert_length
	clc
	lda mem_last
	adc buffer+2
	sta mem_last
	lda mem_last+1
	adc #0
	sta mem_last+1
(thirteen bytes)

Which are replaced by a call to hsl_3 (yeah, yeah, I gave up on meaningful names!)

Code: Select all

; mem_last += insert_length
hsl_3:
	clc
	lda mem_last
	adc buffer+2
	sta mem_last
	lda mem_last+1
	adc #0
	sta mem_last+1
	rts
Fourteen bytes, but also two calls - so a total now of twenty bytes instead of twenty-six.

There's also an existing 16 bit addition, but that requires two bytes pushing on the stack, the other parameter into y:a, and the result in y:a which needs saving at the target, and a couple of bytes still on the stack to get rid of. Significantly larger than the original version :)

So it's worth doing, but it's not always obvious what the best approach is... and other similar operations may save only one or two bytes.

So I'm leaving that where it is for now; it's moved on to the state where I can enter code (any line order) _and_ type 'list' and get a list! Whee!
Screenshot from 2025-01-28 16-51-18.png
Neil

Re: Neolithic Tiny Basic

Posted: Tue Jan 28, 2025 4:10 pm
by barnacle
Oh damn, every time I look at it...

Code: Select all

; mem_last += insert_length
	clc
	lda mem_last
	adc buffer+2
	sta mem_last
	lda mem_last+1
	adc #0
	sta mem_last+1
Is always adding an eight bit value to a sixteen bit, so it could be replaced by

Code: Select all

; mem_last += insert_length
	clc
	lda mem_last
	adc buffer+2
	sta mem_last
	bcc $+4            ; $ is at start of bcc
	inc mem_last+1
saving another two bytes.

Neil

Re: Neolithic Tiny Basic

Posted: Wed Jan 29, 2025 4:31 pm
by barnacle
To my great surprise, the print command pretty much worked first time:
Screenshot from 2025-01-29 17-23-15.png
The % prefix tells the numeric output to be right-justified in a seven-wide column. It is correctly evaluating expressions.

I can't check the two string output modes until I have the assign command working, and at the moment there's it's not displaying multiple parameters, which it should.

Neil

Re: Neolithic Tiny Basic

Posted: Wed Jan 29, 2025 8:01 pm
by barnacle
Multiple parameters are now printed; a jump to the wrong place :)

Neil

Re: Neolithic Tiny Basic

Posted: Tue Feb 04, 2025 8:23 am
by barnacle
Spot the obvious mistake, which took me a week of hair-pulling to find:

Code: Select all

isalpha:
	cmp #'a'
	bcc is_no
	cmp #'z' + 1
	bcc is_yes				; it's a lower case letter
	cmp #'A'
	bcc is_no
	cmp #'Z' + 1
	bcc is_yes				; it's upper case
							; or drop through to is_no
;-----------------------------------------------------------------------
is_no:
	clc
	rts
should be

Code: Select all

isalpha:
	cmp #'A'
	bcc is_no
	cmp #'Z' + 1
	bcc is_yes				; it's a lower case letter
	cmp #'a'
	bcc is_no
	cmp #'z' + 1
	bcc is_yes				; it's upper case
							; or drop through to is_no
;-----------------------------------------------------------------------
is_no:
	clc
	rts
Subtle... who'da thunk that 'A' was less than 'a' ? :mrgreen:

Neil

p.s. shame the 6502 doesn't have a complement carry instruction...

Re: Neolithic Tiny Basic

Posted: Tue Feb 04, 2025 12:01 pm
by Yuri
barnacle wrote:
p.s. shame the 6502 doesn't have a complement carry instruction...
Maybe something like so?

Code: Select all

is_alpha:
    ora #$20
    cmp #$7B
    ror
    eor #$80
    rol
    rts
EDIT: I'll get the right values in there sooner or later... @_@

Blah, still flawed with values less than 'A', feel like you don't need 4 compares tho....