The Boston Diaries

The ongoing saga of a programmer who doesn't live in Boston, nor does he even like Boston, but yet named his weblog/journal “The Boston Diaries.”

Go figure.

Tuesday, November 26, 2024

The definitive guide to writing assembly language subroutines for Extended Color BASIC

And in keeping with documenting 40 year old technology, I'm documenting how to call assembly language subroutines for Extended Color BASIC.

One major difference between Color BASIC and Extended Color BASIC is how to define the address to call. No longer do you have to poke the address into memory, but use the BASIC command DEFUSRn (where n is between 0 and 9). And you can define up to 10 such routines.

From that point on, existing code written for Color BASIC will just work. VARTYP will still have the value type, FP0 will still be a floating point value or contain an address to a string descriptor, and all the functions defined for Color BASIC still function the same.

But there are some major differences. First off, the registers are defined upon entry now. The A register contains the contents of VARTYP and the condition codes are set appropriately, so that one can immedately do a conditional branch to check the type (BEQ for a number, BMI for a string). The X register either points to FP0 (technically, one byte prior to FP0 for internal reasons) if the parameter is a number, or it points to the string descriptor if the parameter is a string. And if the parameter is a string, the B register has the length of the string. So the chksum function can be rewritten to read:

CHKSTR		equ	$B146
GIVBF		equ	$B4F3

		org	$7F00

checksum	jsr	CHKSTR
		lda	,x
		ldx	2,x
		clrb
.sum		addb	,x+
		deca
		bne	.sum
		comb
		jmp	GIVBF
		end

Not a big change, but no longer do we have to load the string descriptor pointer from memory.

The biggest change however, is the ability to return a string from an assembly language subroutine. No longer do you have to modify the passed in string descriptor, you can return a new string. To do so, you call RSVPSTR to reserve the space, put the length into the BASIC variable STRDES (name from the Unravelled Series and located at address $0056), the pointer into STRDES+2, and call GIVSTR (name I came up with since it's not named in the Unravelled series and located at address $B54C). Here is the revised ROT-13 code for Extended Color BASIC:

rot13		jsr	CHKSTR
		tfr	x,y		; save string descriptor
		jsr	RSVPSTR		; B is already set
		stb	STRDES		; save new string length
		stx	STRDES + 2	; and the space for it
		ldy	2,y		; get stirng data, which RSVPSTR may have moved

.loop		lda	,y+		; ROT-13 blah blah ... 
		cmpa	#'A'
		blo	.out
		cmpa	#'Z'
		bhi	.out
		adda	#13
		cmpa	#'Z'
		bls	.out
		suba	#26
.out		sta	,x+
		decb
		bne	.loop

		jmp	GIVSTR		; return new string to BASIC
		end

And this:

110 X$ = USR0("ABCXYZ")
120 PRINT X$

will work as expected—X$ will be the ROT-13 version of the string literal. And with that, we're done. That's all there is to interfacing an assembly language subroutine to Color BASIC and Extended Color BASIC.

I'm not sure why this wasn't documented in Getting Started With Extended Color BASIC—perhaps no one talked about the changes to Extended Color BASIC to the documentation department, or there wasn't time before shipping the updated BASIC, or what. It's clear from the Unravelled series that the change was a deliberate change to make it easier to interface with BASIC and cache useful values in the various registers, but for some reason, it never got documented. What is documented will work, you can pass in a string as:

110 X$="ABCXYZ" : X=USR0(VARPTR(X$))

but it's not needed as I've shown. And here's one last example, defining multiple subroutines that show handling of various parmaters of strings and numbers and returning strings or numbers (here are links to basic.i and basic-fp.i):

		include	"basic.i"
		include	"basic-fp.i"

		.opt	basic strspace 300
		.opt	basic defusr0 num2num
		.opt	basic defusr1 fp2fp
		.opt	basic defusr2 num2str
		.opt	basic defusr3 str2num
		.opt	basic defusr4 str2str
		.opt	basic defusr5 son2son
		.opt	basic defusr6 son2nos

		org	$7F00

;***************************************************************************

num2num		jsr	CHKNUM		; check for numeric argument
		jsr	INTCVT		; convert to integer
		coma			; 1s complement
		comb
		jmp	GIVABF		; return it to BASIC

;***************************************************************************

fp2fp		jsr	CHKNUM		; check for numeric argument
		ldx	#.pi		; * 3.1415926
		jmp	CB.FMULx

.pi		.float	3.1415926

;***************************************************************************

num2str		jsr	CHKNUM		; check for number
		jsr	INTCVT		; convert to integer
		bmi	.minus		; if negative, return 'MINUS'
		beq	.zero		; if zero, return "ZERO"
		ldx	#.textplus	; else return "PLUS"
		bra	.return
.minusinf	ldx	#.textminus
		bra	.return
.zero		ldx	#.textzero
.return		ldb	,x+		; get length
		stb	STRDES + _STRLEN	; save length
		stx	STRDES + _STRPTR	; save text
		jmp	GIVSTR			; return string to BASIC

.textminus	ascii	'MINUS'c	; a "counted" string
.textzero	ascii	'ZERO'c		; where the first byte is the length
.textplus	ascii	'PLUS'c		; of the string

;***************************************************************************

str2num		jsr	CHKSTR		; check for string
		lda	_STRLEN,x	; get length
		ldx	_STRPTR,x	; get string data
		clrb			; clear checksum
.sum		addb	,x+		; add next byte
		deca			; if more, do more
		bne	.sum
		comb			; complement checksum
		jmp	GIVBF		; return it to BASIC

;***************************************************************************

str2str		jsr	CHKSTR		; check for string
		tfr	x,y		; save descriptor
		jsr	RSVPSTR		; reserve space for return string
		stb	STRDES + _STRLEN	; save length
		stx	STRDES + _STRPTR	; and allocated space
		ldy	_STRPTR,y	; get original string

.loop		lda	,y+		; get character
		cmpa	#'A'		; < 'A', just store
		blo	.store
		cmpa	#'Z'		; if <= 'Z', convert to lower case
		bls	.lower
		cmpa	#'a'		; if between 'a' and 'z',
		blo	.store		; convert to upper case
		cmpa	#'z'
		bhi	.store
		anda	#$5F
		bra	.store
.lower		ora	#$20
.store		sta	,x+		; save character in new string
		decb			; if more, do more
		bne	.loop
		jmp	GIVSTR		; return new string to BASIC

;***************************************************************************

son2son		beq	.num		; if 0, number (string or number to string or number)
		lsrb			; cut length in half
		stb	_STRLEN,x
		rts
.num		ldx	#.one		; add 1.0 to number
		jmp	CB.FADDx	; and return new value to BASIC

.one		.float	1.0

;***************************************************************************

son2nos		bmi	.string		; if minus, string (string or number to number or string)
		ldb	#4		; return "0" to BASIC, len of 1
		ldx	#num2str.textzero+1	; text of "ZERO"
		stb	STRDES + _STRLEN
		stx	STRDES + _STRPTR
		jmp	GIVSTR

.string		tfr	a,b		; transfer type to B
		jmp	GIVBF		; and return it to BASIC

;***************************************************************************

		end

And the resulting BASIC file output from my assembler:

10 DATA189,177,67,189,179,237,67,83,126,180,244,189,177,67,142,127,20,126,186,202,130,73,15,218,104,189,177,67,189,179,237,43,7,39,10,142,127,67,32,8,142,127,57,32,3,142,127,62,230,128,215,86,159,88,126,181,76,4,45,73,78,70,4,90,69,82,79,4,43,73,78
20 DATA70,189,177,70,166,132,174,2,95,235,128,74,38,251,83,126,180,243,189,177,70,31,18,189,181,109,215,86,159,88,16,174,34,166,160,129,65,37,18,129,90,35,12,129,97,37,10,129,122,34,6,132,95,32,2,138,32,167,128,90,38,227,126,181,76,39,4,84,231,132
30 DATA57,142,127,148,126,185,194,129,0,0,0,0,43,12,198,4,142,127,63,215,86,159,88,126,181,76,31,137,126,180,243
40 CLEAR300,32511:FORA=32512TO32683:READB:POKEA,B:NEXT:DEFUSR0=32512:DEFUSR1=32523:DEFUSR2=32537:DEFUSR3=32584:DEFUSR4=32601:DEFUSR5=32648:DEFUSR6=32665

Obligatory Picture

An abstract representation of where you're coming from]

Obligatory Contact Info

Obligatory Feeds

Obligatory Links

Obligatory Miscellaneous

Obligatory AI Disclaimer

No AI was used in the making of this site, unless otherwise noted.

You have my permission to link freely to any entry here. Go ahead, I won't bite. I promise.

The dates are the permanent links to that day's entries (or entry, if there is only one entry). The titles are the permanent links to that entry only. The format for the links are simple: Start with the base link for this site: https://boston.conman.org/, then add the date you are interested in, say 2000/08/01, so that would make the final URL:

https://boston.conman.org/2000/08/01

You can also specify the entire month by leaving off the day portion. You can even select an arbitrary portion of time.

You may also note subtle shading of the links and that's intentional: the “closer” the link is (relative to the page) the “brighter” it appears. It's an experiment in using color shading to denote the distance a link is from here. If you don't notice it, don't worry; it's not all that important.

It is assumed that every brand name, slogan, corporate name, symbol, design element, et cetera mentioned in these pages is a protected and/or trademarked entity, the sole property of its owner(s), and acknowledgement of this status is implied.

Copyright © 1999-2024 by Sean Conner. All Rights Reserved.