Note: I made a few corrections in red. Most notably the placement of the utility in memory. See below. The disk image has been corrected.

I like to make utilities for BASIC. Something that helps me do something a little easier than would normally be possible in a vanilla version of Commodore V2 BASIC. I’ve written a graphic wedge, petscii plotter, explored how to use USR() differently. One of the things about that is I write the utility in an assembler and then often convert them into BASIC, DATA statements to load into my BASIC programs.

For the most part, this is not a big deal if the program is small enough. But once the programs get large, like the graphic wedge, there is a lot of room for error, and honestly, who wants to transcribe 3K of memory into data statements.

In the past, I’ve written a BASIC program to PEEK into memory and print up line numbers that get entered into the program and it works great. The problem is you need to type in the basic program into your existing program you want to add the data statements into and then erase it when it’s done. It would be much better to have a machine language routine that would do this for you. That’s what I set out to do.

My first attempt at this was to just build onto the BASIC program in memory by tacking on to the end of the BASIC program. I started by pulling the data from the ‘start of variable’ pointer located at 45, 46 ($2d, $2e) and coding my basic lines directly into memory. Even though I had some initial success, it proved to be very complicated as I seemed to be missing some details that would cause the editor to crash randomly after the data statements were written. I’m sure I didn’t get all the pointers set correctly when the routine was done. The other interesting thing that happened was that no matter what line numbers I decided to use for the data statements, they were always at the end of the BASIC program. This makes sense because when editing normally, BASIC, will insert your program line correctly at the time you hit enter. My routine was just placing the data statements at the end of the program regardless of the line numbers. It also wouldn’t care if you repeated line numbers. Just for fun, I had all my data statements numbered 0 as well. No issues with that, until I wanted to do some regular editing of the program and then things went crazy pretty quickly.

So I decided to go the same route as I did with my BASIC version of this utility where I would print program lines on the screen, fill the keyboard buffer up with carriage returns and place the cursor on the printed program lines before handing control back to the editor. The editor would essentially hit return over the printed lines, entering them into the program. The last line was a RUN statement that started the program up to print up the next set of lines. My machine language routine does the same thing except it only does one line at a time and uses an SYS statement to get back into the routine.

The next issue was how to take a byte in memory and turn it into a decimal number to be printed on the screen. I had the added difficult in that since I was doing this on the screen, I need to do both 16 bit number (for the BASIC line numbers) and 8 bit numbers for the data statements.

After doing some research, I found a solution from and article written by Jim Butterfield in the July 1983 issue of COMPUTE! magazine. In the article he uses BCD mode to convert 16 bits into decimal numbers ready for output to the screen. I’ve had to modify his code a little to make it work for my needs, but it was just what I needed to make it happen.

The program is a little longer than I had hoped. I wanted to fit it in the cassette buffer located at 828. I didn’t want to put it at 49152 because many of the utilities I write end up there. I ended up taking 512 bytes from the top of BASIC ram which feels relatively safe. The program adjusts the pointer at 55, 56 to make the space unavailable to BASIC.

Using the Program

To use the program you will need to follow these steps:

First, load your machine language routine followed by this utility. You must load it using load”datamaker”,8,1 to place it in the correct memory location. Once it’s loaded, perform a NEW command right away followed by SYS 32256 SYS 40448( Messed this up. I put it 512 bytes before 32768 not 40960 – this has been corrected). This will set the pointers for BASIC memory and the USR() function. Next, load your BASIC program that you want to add the data statements to.

I’ve added a second program to the image called datamaker.bas which is a BASIC loader for the utility. Simply load and run the program and it will install and start the utility for you as well has give you some instructions. (I used the utility to create the data statements in this program.)

With all of that loaded, write the USR() function in direct mode. Here’s what it should look like (the Z is arbitrary):

Z = USR(start)(end)(line_number)

Start is the beginning of your machine language routine, end is the end of your machine language routine, and line number is the starting BASIC line number for your data statements.

So making data statements for a routine located at 828 to 900 with your data statements starting at line number 1000, the command would look like this:

Z = USR(828)(900)(1000)

There is a caveat to this. If your memory locations or your line numbers are equal to or above 32768, you’ll need to enter them a little differently. This is because commodore BASIC will treat your numbers as signed integers with a value between -32768 and +32767. It’s not a huge issue. If your values are above 32767, enter them like this (49152 to 49500 as the example)

Z = USR(49152-65536)(49500-65536)(1000)

This will give you the correct unsigned 16 bit integer in the ml routine.

Once you hit enter, the program will clear the screen and print a program line at the top, followed by a SYS statement. It will then hand control back to the editor with a home key and two enters key presses in the keyboard buffer to make the editor enter the program line into your program. It with then hand control back to the utility.

Once the utility runs through all of the memory specified in the USR() function, it adds a -1 to the data statements and terminates. The only thing left is to write a loader in your BASIC program to read the data and poke it in the appropriate memory. A simple loader looks like this: (routine located at memory 828)

10 m=828
20 read b:if b=-1 then end
30 poke m,b
40 m=m+1
50 goto 20

Below is a full listing of the program. It was written for Kick Assembler.

/*	DataMaker 

	Routine for making data statements of machine language
	routines. 2020 Defiance Studios with a lot of help from a Jim
	Butterfield's article 'Numberic Output, Part III' in COMPUTE! 
	Magazine, July 1983.
	
	SYS 32256 sets up the USR() function
	Z = USR(start)(end)(line_number)
		start	= starting address of your ML routine
		end 	= ending address of your ML routine
		line_number = starting BASIC line number for your data statements
		
		NOTE:	Numbers over 32767 need to have 65536 subtracted from their
				value because of how BASIC handles signed integers. 
				
				example (below 32768)
				Z = USR(828)(900)(1000)
				
				example (above 32767)
				Z = USR(49152-65536)(49300-65536)(1000)
*/


.label chrout = $ffd2	// Kernal print routine
.label keybuf = $0277	// keyboard buffer
.label frmnum = $ad8a	// get and evaluate data for type mismatch
.label usradd = $0311	// pointer for USR funtion for ml
.label fac2ya = $b1aa	// fac to int in y(lo) a(hi)

.encoding "petscii_upper"	// encoded to use CHR$ codes and not Screen codes


*=$9e00	"Setup"			// SYS 40448
Setup:
	lda #<Start			// get the start address of our USR routine
	ldy #>Start			// lo and hi
	sta usradd			// put the address in location 785 & 786
	sty usradd+1		// so BASIC knows what to do with USR
	dec $38				// reduce memtop by 512 bytes
	dec $38
	jmp $a65e			// and run a clr statement


* = * "Main Routine"	
Start:					// USR()  data starts as float in fac
	jsr fac2ya			// grab float from fac and
	sty $fb				// convert to 16bit signed integer with 
	sta $fc				// lo in .y and hi in .a. Store in memory
	
	jsr frmnum			// grab data after USR()  and put in fac
	
	jsr fac2ya			// grab float from fac and
	sty mlEnd			// convert to 16bit signed integer with 
	sta mlEnd+1
	
	jsr frmnum			// grab data after USR()  and put in fac
	
	jsr fac2ya			// grab float from fac and
	sty basicS			// convert to 16bit signed integer with 
	sta basicS+1
	
	clc 				// add 2 to mem end so it doesn't
	lda mlEnd			// come up short. More graceful way to
	adc #$02			// do this I'm sure.
	sta mlEnd
	lda mlEnd+1
	adc #$00
	sta mlEnd+1
	
S2:						
	lda #$93			// clear the screen
	jsr chrout
	lda #$00			// set line counter to zero
	sta lLength

LineNum:
	lda basicS			// get line number and
	sta work			// put it into the hi/lo
	lda basicS+1		// work locations for 
	sta work1			// Jim's routine.
	ldy #$00
	jsr A1				// Jump to Jim's routine.

dataStatement:
	ldx #$00			// create an index to write
L1:						// 'DATA' after the line number
	lda data,x			// and leave a space
	beq StartDecode
	jsr chrout
	inx
	jmp L1
	
StartDecode:			// grab a byte to be turned into 
	ldy #$00			// a DATA statement. $fb,$fc point 
	lda ($fb),y			// to the current byte.
	bne notZero			// Check to see if byte is Zero
	lda #$30			// if it is, just print the zero and
	jsr chrout			// increase line length by two
	inc lLength			// and skip Jim's routine.
	inc lLength
	jmp nZPass
	
notZero:				// if not zero, load work with the
	sta work			// byte and work1 with zero since
	lda #$00			// we are not working with a 16 bit number
	sta work1
	jsr A1				// and go to Jim's routine
	
nZPass:
	lda lLength			// check to make sure the line is not
	cmp #23				// too long. If it is, time for a new line.
	bcs NewLine
	lda #$2c			// if not, print a comma and go to 
	jsr chrout			// the next momory. 
	jsr IncMem
	cmp mlEnd+1			// check to make sure the current memory
	bcc StartDecode		// is less than the end memory.
	lda $fb				// hi byte first, then low
	cmp mlEnd			// add next byte if still <mlEnd
	bcc StartDecode
	lda #$2d			// if we hit the end of our ml, print
	jsr chrout			// a -1 in the data statement to mark
	lda #$31			// the end of data and skip writing
	jsr chrout			// the SYS statment afterwards.
	jmp skipSys

NewLine:				// Data line ready for processing, first
	lda #$0d			// go to the next line an increase the 
	jsr chrout			// basic line number for the next line as
	jsr IncBAS			// well as the byte to processed.
	jsr IncMem	
	ldx #$00
	
L2:
	lda sys,x			// print the SYS command after the program line
	beq EL2				// to get back into the machine code after line
	jsr chrout			// processing.
	inx
	jmp L2
	
EL2:
	lda #<S2			// this prints the number to SYS to. This is
	sta work			// soft coded so that this program can easily be
	lda #>S2			// assembled in other locations without having 
	sta work1			// to manage this
	jsr A1				// work and work1 are set and Jim's routine is called.
	
skipSys:
	lda #$0d			// if at the end of ML data, SYS is skipped to end the
	jsr chrout			// process. See above in nZPass where skipped from. A carriage 
	lda #$13			// return is added here in case USR() was called with a print
	sta keybuf			// statement to get it to the next line, then the 
	lda #$0d			// keyboard buffer is filled with a home key, followed
	sta keybuf+1		// by two carriage returns and the keyboard que is set to 3.
	sta keybuf+2		// control is then handed back to the BASIC editor where 
	lda #$03			// the cursor has been set to the data line and enter was hit
	sta $c6				// twice to enter the line and activate the SYS statement to
	rts					// get back into the ML routine.
	
	/* This is a modified numeric output routine from 
		Jim Butterfield, published in July 1983 COMPUTE!
		magazine, titled Numeric Output Part III.
		input routine modified for this program */
	
A1:
	lda #$00			// clear output area
	sta out1
	sta out2
	sta out3
	sta zsup
	ldx #15				// get ready to move 16 bits out of work
	
B:
	asl work			// move bit out of work
	rol work1
	sei					// disable IRQ and 
	sed					// switch to decimal mode
	lda out1			// move bit (decimally) into out
	adc out1
	sta out1
	lda out2
	adc out2
	sta out2
	lda out3
	adc out3
	sta out3	
	cld					// clear decimal mode and
	cli					// enable IRQ
	dex					// repeat for next bit
	bpl B
	ldx #2				// prepare to output 3 bytes (6 digits, 16 bit numbers)
	
C:
	lda out1,x			// get bytes, high order first, for output
	pha
	lsr					// output high order digit
	lsr
	lsr
	lsr
	jsr put
	pla
	and #$0f			// get low order digit
	jsr put
	dex					// get next byte
	bpl C
	rts
	
put:					// zero supress routine
	cmp zsup
	bne D
	rts

D:						// convert numeric, kill zero suppression
	ora #$30
	sta zsup

E:
	inc lLength			// increase line length by one
	jmp chrout			// output character
	
	// End Jim Butterfield routine
	
IncMem:					// Add routine to increment the memory
	clc					// location of the current byte to be
	lda $fb				// written into the data statement.
	adc #$01	
	sta $fb
	lda $fc
	adc #$00	
	sta $fc	
	rts	

IncBAS:					// Add routine to increment the BASIC
	clc					// line number by two for data statements.
	lda basicS			// This can be changed to any increment 
	adc #$02			// desired by the user here.
	sta basicS
	lda basicS+1
	adc #$00	
	sta basicS+1
	rts		
	
						// Variables used by this program 
	
work:					
	.byte $00
	
work1:
	.byte $00
	
out1:
	.byte $00
	
out2:
	.byte $00
	
out3:
	.byte $00
	
zsup:
	.byte $00
	
basicS:
	.byte $00,$00
	
mlEnd:
	.byte $00,$00
	
lLength:
	.byte $00
	
data:
	.text " DATA "
	.byte $00

sys:
	.text "SYS "
	.byte $00