1
0
mirror of https://github.com/cc65/cc65.git synced 2025-01-10 19:29:45 +00:00

_printf rewritten in assembler - basic tests ok, needs some more tests and

optimizations.


git-svn-id: svn://svn.cc65.org/cc65/trunk@502 b7a2c559-68d2-44c3-8de9-860c34a00d81
This commit is contained in:
cuz 2000-11-30 23:04:24 +00:00
parent d3e6d87662
commit 643f468295
5 changed files with 705 additions and 274 deletions

View File

@ -1,7 +1,6 @@
_afailed.s
_fopen.s
_hextab.s
_printf.s
abort.s
bsearch.s
calloc.s

View File

@ -15,16 +15,17 @@ C_OBJS = fclose.o fgets.o fprintf.o calloc.o _fopen.o\
fputs.o fread.o fwrite.o gets.o realloc.o bsearch.o strxfrm.o\
printf.o _hextab.o vfprintf.o fdopen.o strtok.o\
_afailed.o fopen.o fgetc.o fputc.o puts.o gets.o perror.o getchar.o\
_printf.o vprintf.o vsprintf.o sprintf.o abort.o qsort.o putchar.o\
vprintf.o vsprintf.o sprintf.o abort.o qsort.o putchar.o\
errormsg.o cprintf.o vcprintf.o freopen.o locale.o fsetpos.o\
fgetpos.o rewind.o fseek.o ftell.o
S_OBJS = _fdesc.o \
_file.o \
_hadd.o \
_heap.o \
_oserror.o \
_stksize.o \
S_OBJS = _fdesc.o \
_file.o \
_hadd.o \
_heap.o \
_oserror.o \
_printf.o \
_stksize.o \
_swap.o \
_sys.o \
abs.o \
@ -58,7 +59,7 @@ S_OBJS = _fdesc.o \
memcpy.o \
memset.o \
rand.o \
setjmp.o \
setjmp.o \
stkcheck.o \
strcat.o \
strchr.o \

View File

@ -1,264 +0,0 @@
/*
* Helper function for the printf family.
*/
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <ctype.h>
#include "_printf.h"
/* Use static variables for locals */
#pragma staticlocals (1);
int _printf (struct outdesc* d, const char* f, va_list ap)
{
outfunc fout; /* Output function */
unsigned char type; /* variable argument type */
char str [20]; /* string buffer */
char c; /* Current format char */
char leftjust; /* left justify string */
char addsign; /* always add + or - */
char addblank; /* add blank instead of + */
char altform; /* alternate form? */
char padchar; /* pad with space or zeroes? */
char islong; /* l modifier found */
unsigned arglen; /* length of argument string */
unsigned prec; /* Precision */
unsigned width; /* Width of output field */
int i; /* Integer value */
long l; /* Long value */
char* sptr; /* pointer to argument string */
register char* s; /* work pointer to argument string */
/* Remember the format string in a register variable for shorter code */
register const char* format = f;
/* Remember the output function in a local variable for speed and size */
fout = d->fout;
/* */
d->ccount = 0;
while (c = *format++) {
if (c != '%') {
fout (d, &c, 1);
continue;
}
/* %%? */
if (*format == '%') {
fout (d, format, 1);
++format;
continue;
}
/* format is: %[flags][width][.precision][mod]type */
/* flags */
leftjust = addsign = addblank = altform = 0;
do {
switch (c = *format) {
case '-':
leftjust = 1;
break;
case '+':
addsign = 1;
break;
case '#':
altform = 1;
break;
case ' ':
addblank = 1;
break;
default:
goto flags_done;
}
++format;
} while (1);
flags_done:
/* width */
padchar = ' ';
if (*format == '0') {
padchar = '0';
++format;
}
if (*format == '*') {
width = va_arg (ap, int);
++format;
} else {
width = 0;
while (isdigit (c = *format)) {
width = width * 10 + (c - '0');
++format;
}
}
/* precision */
prec = 0;
if (*format == '.') {
++format;
if (*format == '*') {
prec = va_arg (ap, int);
++format;
} else {
while (isdigit (c = *format)) {
prec = prec * 10 + (c - '0');
++format;
}
}
}
/* modifiers */
islong = 0;
while (strchr ("FNhlL", c = *format)) {
switch (c) {
case 'l':
islong = 1;
break;
}
++format;
}
/* Check the format specifier */
sptr = s = str;
type = *format++;
switch (type) {
case 'c':
str [0] = va_arg (ap, char);
str [1] = 0;
break;
case 'd':
case 'i':
if (islong) {
l = va_arg (ap, long);
if (l >= 0) {
if (addsign) {
*s++ = '+';
} else if (addblank) {
*s++ = ' ';
}
}
ltoa (l, s, 10);
} else {
i = va_arg (ap, int);
if (i >= 0) {
if (addsign) {
*s++ = '+';
} else if (addblank) {
*s++ = ' ';
}
}
itoa (i, s, 10);
}
break;
case 'n':
*va_arg (ap, int*) = d->ccount;
continue;
case 'o':
if (islong) {
l = va_arg (ap, unsigned long);
if (altform && (l || prec)) {
*s++ = '0';
}
ultoa (l, s, 8);
} else {
i = va_arg (ap, unsigned);
if (altform && (i || prec)) {
*s++ = '0';
}
utoa (i, s, 8);
}
break;
case 's':
sptr = va_arg (ap, char*);
break;
case 'u':
if (islong) {
ultoa (va_arg (ap, unsigned long), str, 10);
} else {
utoa (va_arg (ap, unsigned), str, 10);
}
break;
case 'x':
case 'X':
if (altform) {
*s++ = '0';
*s++ = 'X';
}
if (islong) {
ultoa (va_arg (ap, unsigned long), s, 16);
} else {
utoa (va_arg (ap, unsigned), s, 16);
}
if (type == 'x') {
strlower (str);
}
break;
default:
/* Unknown type char - skip it */
continue;
}
/* Do argument string formatting */
arglen = strlen (sptr);
if (prec && prec < arglen) {
arglen = prec;
}
if (width > arglen) {
width -= arglen; /* padcount */
} else {
width = 0;
}
/* Do padding on the left side if needed */
if (!leftjust) {
/* argument right justified */
while (width) {
fout (d, &padchar, 1);
--width;
}
}
/* Output the argument string */
fout (d, sptr, arglen);
/* Output right padding bytes if needed */
if (leftjust) {
/* argument left justified */
while (width) {
fout (d, &padchar, 1);
--width;
}
}
}
}

View File

@ -20,9 +20,13 @@ typedef void (*outfunc) (struct outdesc* desc, const char* buf, unsigned count);
/* Control structure passed to the low level worker function.
* Beware: This function will access the structure on the assembly level,
* so check this when altering the structure.
*/
struct outdesc {
outfunc fout; /* Routine used to output data */
int ccount; /* Character counter */
outfunc fout; /* Routine used to output data */
void* ptr; /* Data internal to print routine */
unsigned uns; /* Data internal to print routine */
};

691
libsrc/common/_printf.s Normal file
View File

@ -0,0 +1,691 @@
;
; _printf: Basic layer for all printf type functions.
;
; Ullrich von Bassewitz, 21.10.2000
;
.export __printf
.import popax, pushax, pusheax, push1, axlong, axulong
.import __ctype
.import _ltoa, _ultoa
.import _strlower, _strlen
.import jmpvec
.importzp sp, ptr1, ptr2, tmp1, regbank, sreg
.macpack generic
; ----------------------------------------------------------------------------
; We will store variables into the register bank in the zeropage. Define
; equates for these variables.
ArgList = regbank+0 ; Argument list pointer
Format = regbank+2 ; Format string
OutData = regbank+4 ; Function parameters
.code
; ----------------------------------------------------------------------------
; Get one character from the format string and increment the pointer. Will
; return zero in Y.
GetFormatChar:
ldy #0
lda (Format),y
IncFormatPtr:
inc Format
bne @L1
inc Format+1
@L1: rts
; ----------------------------------------------------------------------------
; Output a pad character: outfunc (d, &padchar, 1)
OutputPadChar:
lda PadChar
; ----------------------------------------------------------------------------
; Call the output function with one character in A
Output1:
sta CharArg
jsr PushOutData
lda #<CharArg
ldx #>CharArg
jsr pushax
jsr push1
jmp (OutFunc) ; fout (OutData, &CharArg, 1)
; ----------------------------------------------------------------------------
; Decrement the argument list pointer by 2
DecArgList2:
lda ArgList
sub #2
sta ArgList
bcs @L1
dec ArgList+1
@L1: rts
; ----------------------------------------------------------------------------
; Get an unsigned int or long argument depending on the IsLong flag.
GetUnsignedArg:
lda IsLong ; Check flag
bne GetLongArg ; Long sets all
jsr GetIntArg ; Get an integer argument
jmp axulong ; Convert to unsigned long
; ----------------------------------------------------------------------------
; Get an signed int or long argument depending on the IsLong flag.
GetSignedArg:
lda IsLong ; Check flag
bne GetLongArg ; Long sets all
jsr GetIntArg ; Get an integer argument
jmp axlong ; Convert to long
; ----------------------------------------------------------------------------
; Get a long argument from the argument list. Returns 0 in Y.
GetLongArg:
jsr GetIntArg ; Get high word
sta sreg
stx sreg+1
; Run into GetIntArg fetching the low word
; ----------------------------------------------------------------------------
; Get an integer argument from the argument list. Returns 0 in Y.
GetIntArg:
jsr DecArgList2
ldy #1
lda (ArgList),y
tax
dey
lda (ArgList),y
rts
; ----------------------------------------------------------------------------
; Add the a new digit in X to the value in ptr1. Does leave Y alone.
AddDigit:
txa ; Move digit into A
sub #'0' ; Make number from ascii digit
pha
lda ptr1
ldx ptr1+1
asl ptr1
rol ptr1+1 ; * 2
asl ptr1
rol ptr1+1 ; * 4
add ptr1
sta ptr1
txa
adc ptr1+1
sta ptr1+1 ; * 5
asl ptr1
rol ptr1+1 ; * 10
pla
add ptr1 ; Add digit value
sta ptr1
bcc @L1
inc ptr1+1
@L1: rts
; ----------------------------------------------------------------------------
; Read an integer from the format string. Will return zero in Y.
ReadInt:
ldy #0
sty ptr1
sty ptr1+1 ; Start with zero
@L1: lda (Format),y ; Get format string character
tax ; Format character --> X
lda __ctype,x ; Get character classification
and #$04 ; Digit?
beq @L9 ; Jump if done
jsr AddDigit ; Add the digit to ptr1
jsr IncFormatPtr ; Skip the character
jmp @L1
@L9: lda ptr1
ldx ptr1+1 ; Load result
rts
; ----------------------------------------------------------------------------
; Put a character into the argument buffer and increment the buffer index
PutBuf: ldy BufIdx
inc BufIdx
sta Buf,y
rts
; ----------------------------------------------------------------------------
; Get a pointer to the current buffer end
GetBufPtr:
lda #<Buf
ldx #>Buf
add BufIdx
bcc @L1
inx
@L1: rts
; ----------------------------------------------------------------------------
; Push OutData onto the software stack
PushOutData:
lda OutData
ldx OutData+1
jmp pushax
; ----------------------------------------------------------------------------
; Output Width pad characters
;
PadLoop:
jsr OutputPadChar
OutputPadding:
inc Width
bne PadLoop
inc Width+1
bne PadLoop
rts
; ----------------------------------------------------------------------------
; Output the argument itself: outfunc (d, str, arglen);
;
OutputArg:
jsr PushOutData
lda Str
ldx Str+1
jsr pushax
lda ArgLen
ldx ArgLen+1
jsr pushax
jmp (OutFunc)
; ----------------------------------------------------------------------------
;
__printf:
; Save the register bank variables into the save area
ldx #5
Save: lda regbank,x
sta RegSave,x
dex
bpl Save
; Get the parameters from the stack
jsr popax ; Argument list pointer
sta ArgList
stx ArgList+1
jsr popax ; Format string
sta Format
stx Format+1
jsr popax ; Output descriptor
sta OutData
stx OutData+1
; Initialize the output counter in the output descriptor to zero
lda #0
tay
sta (OutData),y
iny
sta (OutData),y
; Get the output function from the output descriptor and remember it
iny
lda (OutData),y
sta OutFunc
iny
lda (OutData),y
sta OutFunc+1
; Start parsing the format string
MainLoop:
jsr GetFormatChar ; Get one char, zero in Y
tax ; End of format string reached?
bne NotDone ; Continue of end not reached
; End of format string reached. Restore the zeropage registers and return.
ldx #5
Rest: lda RegSave,x
sta regbank,x
dex
bpl Rest
rts
; Still a valid format character. Check for '%' and a '%%' sequence. Output
; anything that is not a format specifier. On intro, Y is zero.
NotDone:
cmp #'%'
bne @L1
lda (Format),y ; Check for "%%"
cmp #'%'
bne FormatSpec ; Jump if really a format specifier
jsr IncFormatPtr ; Skip the second '%'
@L1: jsr Output1 ; Output the character...
jmp MainLoop ; ...and continue
; We have a real format specifier
; Format is: %[flags][width][.precision][mod]type
; Y is zero on entry.
FormatSpec:
; Initialize the flags
lda #0
ldx #FormatVarSize-1
@L1: sta FormatVars,x
dex
bpl @L1
; Start with reading the flags if there are any
ldx #$FF ; "true" flag
ReadFlags:
lda (Format),y ; Get next char...
cmp #'-'
bne @L1
stx LeftJust
beq @L4
@L1: cmp #'+'
bne @L2
stx AddSign
beq @L4
@L2: cmp #' '
bne @L3
stx AddBlank
beq @L4
@L3: cmp #'#'
bne ReadPadding
stx AltForm
@L4: jsr IncFormatPtr
jmp ReadFlags ; ...and start over
; Done with flags, read the pad char. Y is still zero if we come here.
ReadPadding:
ldx #' ' ; PadChar
cmp #'0'
bne @L1
tax ; PadChar is '0'
jsr IncFormatPtr
lda (Format),y ; Read current for later
@L1: stx PadChar
; Read the width. Even here, Y is still zero. A contains the current character
; from the format string
ReadWidth:
cmp #'*'
bne @L1
jsr IncFormatPtr
jsr GetIntArg ; Width is an additional argument
jmp @L2
@L1: jsr ReadInt ; Read integer from format string...
@L2: sta Width
stx Width+1 ; ...and remember in Width
; Read the precision. Even here, Y is still zero.
sty Prec ; Assume Precision is zero
sty Prec+1
lda (Format),y ; Load next format string char
cmp #'.' ; Precision given?
bne ReadMod ; Branch if no precision given
ReadPrec:
jsr IncFormatPtr ; Skip the '.'
lda (Format),y
cmp #'*' ; Variable precision?
bne @L1
jsr IncFormatPtr ; Skip the '*'
jsr GetIntArg ; Get integer argument
jmp @L2
@L1: jsr ReadInt ; Read integer from format string
@L2: sta Prec
stx Prec+1
; Read the modifiers. Y is still zero.
ReadMod:
lda (Format),y
cmp #'F'
beq @L1 ; Read and ignore this one
cmp #'N'
beq @L1 ; Read and ignore this one
cmp #'h'
beq @L1 ; Read and ignore this one
cmp #'L'
beq @L1 ; Read and ignore this one
cmp #'l'
bne DoFormat
lda #$FF
sta IsLong
@L1: jsr IncFormatPtr
jmp ReadMod
; Initialize the argument buffer pointers. We use a static buffer (ArgBuf) to
; assemble strings. A zero page index (BufIdx) is used to keep the current
; write position. A pointer to the buffer (Str) is used to point to the the
; argument in case we will not use the buffer but a user supplied string.
; Y is zero when we come here.
DoFormat:
sty BufIdx ; Clear BufIdx
ldx #<Buf
stx Str
ldx #>Buf
stx Str+1
; Skip the current format character, then check it (current char in A)
jsr IncFormatPtr
; Is it a character?
cmp #'c'
bne CheckInt
; It is a character
jsr GetIntArg ; Get the argument (promoted to int)
jsr PutBuf
lda #0 ; Place it as zero terminated string...
jsr PutBuf ; ...into the buffer
jmp HaveArg ; Done
; Is it an integer?
CheckInt:
cmp #'d'
beq @L1
cmp #'i'
bne CheckCount
; It is an integer
@L1: ldx #0
lda AddBlank ; Add a blank for positives?
beq @L2 ; Jump if no
ldx #' '
@L2: lda AddSign ; Add a plus for positives (precedence)?
beq @L3
ldx #'+'
@L3: stx Leader
; Integer argument
jsr GetSignedArg ; Get argument as a long
ldy sreg+1 ; Check sign
bmi @Int1
ldy Leader
beq @Int1
sty Buf
inc BufIdx
@Int1: jsr pusheax
jsr GetBufPtr
jsr pushax
lda #10
jsr _ltoa ; ltoa (va_arg (ap, long), s, 10);
jmp HaveArg
; Is it a count pseudo format?
CheckCount:
cmp #'n'
bne CheckOctal
; It is a count pseudo argument
jsr GetIntArg
sta ptr1
stx ptr1+1 ; Get user supplied pointer
ldy #0
lda (OutData),y ; Low byte of OutData->ccount
sta (ptr1),y
iny
lda (OutData),y ; High byte of OutData->ccount
sta (ptr1),y
jmp MainLoop ; Done
; Check for an octal digit
CheckOctal:
cmp #'o'
bne CheckString
; Integer in octal representation
jsr GetSignedArg ; Get argument as a long
ldy AltForm ; Alternative form?
beq @Oct1 ; Jump if no
tay ; Save low byte of value
stx tmp1
ora tmp1
ora sreg
ora sreg+1
ora Prec
ora Prec+1 ; Check if value or Prec != 0
beq @Oct1
lda #'0'
sta Buf
inc BufIdx
tya ; Restore low byte
@Oct1: jsr pusheax ; Push value
jsr GetBufPtr ; Get buffer pointer...
jsr pushax ; ...and push it
lda #8 ; Load base
jsr _ltoa ; ltoa (l, s, 8);
jmp HaveArg
; Check for a string specifier (%s)
CheckString:
cmp #'s'
bne CheckUnsigned
; It's a string
jsr GetIntArg ; Get 16bit argument
sta Str
stx Str+1
jmp HaveArg
; Check for an unsigned integer (%u)
CheckUnsigned:
cmp #'u'
bne CheckHex
; It's an unsigned integer
jsr GetUnsignedArg ; Get argument as unsigned long
jsr pusheax
jsr GetBufPtr ; Get buffer pointer...
jsr pushax ; ...and push it
lda #10 ; Load base
jsr _ultoa ; ultoa (l, s, 10);
jmp HaveArg
; Check for a hexadecimal integer (%x)
CheckHex:
cmp #'x'
beq @IsHex
cmp #'X'
bne UnknownFormat
; Hexadecimal integer
@IsHex: pha ; Save the format spec
lda AltForm
beq @L1
lda #'0'
jsr PutBuf
lda #'X'
jsr PutBuf
@L1: jsr GetUnsignedArg ; Get argument as an unsigned long
jsr pusheax
jsr GetBufPtr ; Get buffer pointer...
jsr pushax ; ...and push it
lda #16 ; Load base
jsr _ultoa ; ultoa (l, s, 16);
pla ; Get the format spec
cmp #'x' ; Lower case?
bne @L2
lda Str
ldx Str+1
jsr _strlower ; Make characters lower case
@L2: jmp HaveArg
; Unsigned format character, skip it
UnknownFormat:
jmp MainLoop
; We have the argument, do argument string formatting
HaveArg:
; ArgLen = strlen (Str);
lda Str
ldx Str+1
jsr _strlen ; Get length of argument
sta ArgLen
stx ArgLen+1
; if (Prec && Prec < ArgLen) ArgLen = Prec;
lda Prec
ora Prec+1
beq @L1
ldx Prec
cpx ArgLen
lda Prec+1
tay
sbc ArgLen+1
bcc @L1
stx ArgLen
sty ArgLen+1
; if (Width > ArgLen) {
; Width -= ArgLen; /* padcount */
; } else {
; Width = 0;
; }
; Since width is used as a counter below, calculate -(width+1)
@L1: sec
lda Width
sbc ArgLen
tax
lda Width+1
sbc ArgLen+1
bcs @L2
lda #0
tax
@L2: eor #$FF
sta Width+1
txa
eor #$FF
sta Width
; /* Do padding on the left side if needed */
; if (!leftjust) {
; /* argument right justified */
; while (width) {
; fout (d, &padchar, 1);
; --width;
; }
; }
lda LeftJust
bne @L3
jsr OutputPadding
; Output the argument itself
@L3: jsr OutputArg
; /* Output right padding bytes if needed */
; if (leftjust) {
; /* argument left justified */
; while (width) {
; fout (d, &padchar, 1);
; --width;
; }
; }
lda LeftJust
beq @L4
jsr OutputPadding
; Done, parse next chars from format string
@L4: jmp MainLoop
; ----------------------------------------------------------------------------
; Local data (all static)
.bss
; Save area for the zero page registers
RegSave: .res 6
; Stuff from OutData. Is used as a vector and must be aligned
.align 2
OutFunc: .word 0
; One character argument for OutFunc
CharArg: .byte 0
; Format variables
FormatVars:
LeftJust: .byte 0
AddSign: .byte 0
AddBlank: .byte 0
AltForm: .byte 0
PadChar: .byte 0
Width: .word 0
Prec: .word 0
IsLong: .byte 0
Leader: .byte 0
BufIdx: .byte 0 ; Argument string pointer
FormatVarSize = * - FormatVars
; Argument buffer and pointer
Buf: .res 20
Str: .word 0
ArgLen: .res 2