[cc65] printf for ca65

From: MagerValp <MagerValp1cling.gu.se>
Date: 2005-03-22 14:51:07
Using a little bit of macro magic, I managed to convince ca65 to allow
printf-like calls in assembly code:

	jsr fs_dir_root
	jsr fs_dir_open
  @readdir:
	jsr fs_dir_read
	bcs @eod

	printf "%04x%04x\t%s\n", fs_dir_node, fs_dir_node + 2, fs_dir_name
	jmp @readdir
  @eod:

It saves A/X/Y, making it simple to use inside loops, and it's a lot
more readable (imho) than a bunch of calls to console_out/strout/
hexout. The printf macro translates the line above into:

	pha
	phx
	phy
	ldax #arglist
	jsr console_printf
	ply
	plx
	pla

	.pushseg
	.rodata
  string:
	.asciiz "%04x%04x\t%s\n"
  arglist:
	.addr string
	.addr fs_dir_node + 2
	.addr fs_dir_node
	.addr fs_dir_name

	.popseg

The code I'm working on now is all asm and doesn't use the C runtime
(and thus no argument stack), so it only works almost, but not quite,
like the printf we all know and love. The following argument types are
supported:

	%s	null terminated string
	%d	16-bit unsigned int, printed in decimal
	%x	16-bit unsigned int, printed in hex
	%c	8-bit char

%d and %x support leading 0s, and field widths. The rest are left as
an excercise for the reader :) The following escape chars are also
translated to ascii (at runtime):

	\e	escape (27)
	\a	bell (7)
	\b	backspace (g)
	\f	formfeed (12)
	\n	newline (10)
	\r	return (13)
	\t	tab (9)
	\\	backslash (92)

The code expects console_out to print the char in A (so just point it
to $ffd2 or similar), and console_strout is fed a pointer to a null
terminated string in A/X.

Enjoy!


---------------------------- begin ldax.i ------------------------------
; load A/X
	.macro ldax arg
	.if (.match (.left (1, arg), #))	; immediate mode
	lda #<(.right (.tcount (arg)-1, arg))
	ldx #>(.right (.tcount (arg)-1, arg))
	.else					; assume absolute or zero page
	lda arg
	ldx 1+(arg)
	.endif
	.endmacro

; store A/X
	.macro stax arg
	sta arg
	stx 1+(arg)
	.endmacro	
----------------------------- end ldax.i -------------------------------

--------------------------- begin printf.i -----------------------------
	.import console_printf


	.macro printfargs arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9
	.ifnblank arg1
	    .addr arg1
	    printfargs arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9
	.endif
	.endmacro

	.macro printf str, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9

	.local arglist
	.local string

	pha
	.ifpc02
	phx
	phy
	.else
	txa
	pha
	tya
	pha
	.endif
	ldax #arglist
	jsr console_printf
	.ifpc02
	ply
	plx
	.else
	pla
	tay
	pla
	tax
	.endif
	pla

	.pushseg
	.rodata
	.if (.match(str, ""))
string:
	    .asciiz str
arglist:
	    .addr string
	.else
arglist:
	    .addr str
	.endif

	printfargs arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9

	.popseg

	.endmacro
---------------------------- end printf.i ------------------------------

--------------------------- begin printf.s -----------------------------
	.include "ldax.i"


	.export console_printf


	.import console_out
	.import console_strout


	.zeropage

strptr:		.res 2
argptr:		.res 2
valptr:		.res 2
ysave:		.res 1
arg:		.res 1
fieldwidth:	.res 1
fieldwcnt:	.res 1
leadzero:	.res 1
argtemp:	.res 1
int:		.res 2
num:		.res 5
ext:		.res 2


	.code

console_printf:
	stax argptr
	ldy #0
	lda (argptr),y
	sta strptr
	iny
	lda (argptr),y
	sta strptr + 1
	iny
	sty arg

	ldy #0
@nextchar:
	lda (strptr),y
	bne :+
	rts
:
	cmp #'%'
	beq @printarg

	cmp #'\'
	beq @printescape

	jsr console_out

@next:
	iny
	bne @nextchar

	inc strptr + 1
	jmp @nextchar

@printescape:
	iny
	bne :+
	inc strptr + 1
:	lda (strptr),y
	ldx #esc_count - 1
:	cmp esc_code,x
	beq @escmatch
	dex
	bpl :-
	bmi @next
@escmatch:
	lda esc_char,x
	jsr console_out
	jmp @next

@printarg:
	lda #0
	sta fieldwidth
	sta leadzero
	lda #$ff
	sta fieldwcnt
@argnext:
	iny
	bne :+
	inc strptr + 1
:
	tya
	pha

	lda (strptr),y

	cmp #'0'		; check for field width
	bcc @notdigit
	cmp #'9'+1
	bcs @notdigit
	and #$0f
	bne :+			; check for leading 0
	inc fieldwcnt
	bne :+
	lda #$80
	sta leadzero
	pla
	tay
	jmp @argnext
:
	pha			; multiply old value by 10
	asl fieldwidth
	lda fieldwidth
	asl
	asl
	clc
	adc fieldwidth
	sta fieldwidth
	pla
	clc			; add new value
	adc fieldwidth
	sta fieldwidth
	pla
	tay
	jmp @argnext

@notdigit:
	cmp #'s'
	beq @argstr

	cmp #'d'
	beq @argint

	cmp #'x'
	beq @arghex

	cmp #'c'
	beq @argchar

@argdone:
	pla
	tay
	jmp @next

@argstr:
	jsr @argax
	jsr console_strout

	jmp @argdone

@argint:
	jsr @argax
	stax valptr
	jsr @valax
	jsr printint

	jmp @argdone

@arghex:
	jsr @argax
	stax valptr
	jsr @valax
	jsr printhex

	jmp @argdone

@argchar:
	jsr @argax
	stax valptr
	ldy #0
	lda (valptr),y
	jsr console_out

	jmp @argdone

@argax:
	ldy arg
	lda (argptr),y
	pha
	iny
	lda (argptr),y
	tax
	iny
	sty arg
	pla
	rts

@valax:
	ldy #0
	lda (valptr),y
	pha
	iny
	lda (valptr),y
	tax
	pla
	rts

@printx:
	txa
	lsr
	lsr
	lsr
	lsr
	tay
	lda hex2asc,y
	jsr console_out
	txa
	and #$0f
	tay
	lda hex2asc,y
	jmp console_out


; print 16-bit hexadecimal number
printhex:
	tay
	and #$0f
	sta num + 3
	tya
	lsr
	lsr
	lsr
	lsr
	sta num + 2

	txa
	and #$0f
	sta num + 1
	txa
	lsr
	lsr
	lsr
	lsr
	sta num

	lda #4
	sec
	sbc fieldwidth
	tax
	bpl :+
	jsr printlong
:
	cpx #4
	beq @nowidth

@printlead:
	lda num,x
	bne @printrest
	lda #' '
	bit leadzero
	bpl :+
	lda #'0'
:	jsr console_out
	inx
	cpx #3
	bne @printlead

@nowidth:
	ldx #0
:	lda num,x
	bne @printrest
	inx
	cpx #4
	bne :-
	lda #'0'
	jsr console_out
	rts

@printrest:
	lda num,x
	tay
	lda hex2asc,y
	jsr console_out
	inx
	cpx #4
	bne @printrest
	rts


printlong:
	lda #' '
	bit leadzero
	bpl :+
	lda #'0'
:	jsr console_out
	inx
	bne :-
	rts


; print a 16-bit integer
printint:
	stax int

	ldx #4
@next:
	lda #0
	sta num,x
	jsr div10
	lda ext
	sta num,x
	dex
	bpl @next

	lda fieldwidth
	beq @nowidth
	lda #5
	sec
	sbc fieldwidth
	tax
	bpl :+
	jsr printlong
:
@printlead:
	lda num,x
	bne @print

	lda #' '
	bit leadzero
	bpl :+
	lda #'0'
:	jsr console_out
	inx
	cpx #5
	bne @printlead
	beq @printzero

@nowidth:
	inx
	cpx #5
	beq @printzero
	lda num,x
	beq @nowidth

@print:
	clc
	adc #'0'
	jsr console_out
	inx
	cpx #5
	beq @done
@printall:
	lda num,x
	jmp @print

@done:
	rts

@printzero:
	lda #'0'
	jmp console_out


; 16/16-bit division, from the fridge
; int/aux -> int, remainder in ext
div10:
	lda #0
	sta ext+1
	ldy #$10
@dloop:
	asl int
	rol int+1
	rol
	rol ext+1
	pha
	cmp #10
	lda ext+1
	sbc #0		; is this a nop?
	bcc @div2
	sta ext+1
	pla
	sbc #10
	pha
	inc int
@div2:
	pla
	dey
	bne @dloop
	sta ext
	rts


	.rodata

msg_unimplemented:
	.byte "<unimplemented>",0

hex2asc:
	.byte "0123456789abcdef"

esc_code:
	.byte "eabfnrt", '\'
esc_count	= * - esc_code
esc_char:
	.byte 27, 7, 8, 12, 10, 13, 9, '\'
---------------------------- end printf.s ------------------------------

-- 
    ___          .     .  .         .       . +  .         .      o   
  _|___|_   +   .  +     .     +         .  Per Olofsson, arkadspelare
    o-o    .      .     .   o         +          MagerValp@cling.gu.se
     -       +            +    .     http://www.cling.gu.se/~cl3polof/
----------------------------------------------------------------------
To unsubscribe from the list send mail to majordomo@musoftware.de with
the string "unsubscribe cc65" in the body(!) of the mail.
Received on Tue Mar 22 14:51:11 2005

This archive was generated by hypermail 2.1.8 : 2005-03-22 14:51:18 CET