From 643f468295fa58049ba3cbdcfaa40a12424c91f0 Mon Sep 17 00:00:00 2001 From: cuz Date: Thu, 30 Nov 2000 23:04:24 +0000 Subject: [PATCH] _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 --- libsrc/common/.cvsignore | 1 - libsrc/common/Makefile | 17 +- libsrc/common/_printf.c | 264 --------------- libsrc/common/_printf.h | 6 +- libsrc/common/_printf.s | 691 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 705 insertions(+), 274 deletions(-) delete mode 100644 libsrc/common/_printf.c create mode 100644 libsrc/common/_printf.s diff --git a/libsrc/common/.cvsignore b/libsrc/common/.cvsignore index 2314f56cb..24e4f8b79 100644 --- a/libsrc/common/.cvsignore +++ b/libsrc/common/.cvsignore @@ -1,7 +1,6 @@ _afailed.s _fopen.s _hextab.s -_printf.s abort.s bsearch.s calloc.s diff --git a/libsrc/common/Makefile b/libsrc/common/Makefile index 48db0cdbf..9bdb80e3b 100644 --- a/libsrc/common/Makefile +++ b/libsrc/common/Makefile @@ -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 \ diff --git a/libsrc/common/_printf.c b/libsrc/common/_printf.c deleted file mode 100644 index 40f2aace2..000000000 --- a/libsrc/common/_printf.c +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Helper function for the printf family. - */ - - - -#include -#include -#include -#include -#include -#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; - } - } - - } -} - - - diff --git a/libsrc/common/_printf.h b/libsrc/common/_printf.h index aa5802f03..03e8f7307 100644 --- a/libsrc/common/_printf.h +++ b/libsrc/common/_printf.h @@ -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 */ }; diff --git a/libsrc/common/_printf.s b/libsrc/common/_printf.s new file mode 100644 index 000000000..f0013c7ae --- /dev/null +++ b/libsrc/common/_printf.s @@ -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 + 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 + 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+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 + -- 2.39.5