aboutsummaryrefslogtreecommitdiff
path: root/libc/printf.c
diff options
context:
space:
mode:
authorPaul Selkirk <paul@psgd.org>2015-10-26 15:18:58 -0400
committerPaul Selkirk <paul@psgd.org>2015-10-26 15:18:58 -0400
commit26f12903dab2fafeaaefb02349763618ce96d070 (patch)
treeb37cceea7014770ea2ae039928e57b6f02c0aaa2 /libc/printf.c
Based on user/ft/stm32-dev-bridge, without the project-specific build
directories (and duplicated code).
Diffstat (limited to 'libc/printf.c')
-rw-r--r--libc/printf.c412
1 files changed, 412 insertions, 0 deletions
diff --git a/libc/printf.c b/libc/printf.c
new file mode 100644
index 0000000..78f210a
--- /dev/null
+++ b/libc/printf.c
@@ -0,0 +1,412 @@
+/*****************************************************************************
+Stripped-down printf()
+Chris Giese <geezer@execpc.com> http://my.execpc.com/~geezer
+Release date: Dec 12, 2003
+This code is public domain (no copyright).
+You can do whatever you want with it.
+
+Revised Dec 12, 2003
+- fixed vsprintf() and sprintf() in test code
+
+Revised Jan 28, 2002
+- changes to make characters 0x80-0xFF display properly
+
+Revised June 10, 2001
+- changes to make vsprintf() terminate string with '\0'
+
+Revised May 12, 2000
+- math in DO_NUM is now unsigned, as it should be
+- %0 flag (pad left with zeroes) now works
+- actually did some TESTING, maybe fixed some other bugs
+
+%[flag][width][.prec][mod][conv]
+flag: - left justify, pad right w/ blanks DONE
+ 0 pad left w/ 0 for numerics DONE
+ + always print sign, + or - no
+ ' ' (blank) no
+ # (???) no
+
+width: (field width) DONE
+
+prec: (precision) no
+
+conv: d,i decimal int DONE
+ u decimal unsigned DONE
+ o octal DONE
+ x,X hex DONE
+ f,e,g,E,G float no
+ c char DONE
+ s string DONE
+ p ptr DONE
+
+mod: N near ptr DONE
+ F far ptr no
+ h short (16-bit) int DONE
+ l long (32-bit) int DONE
+ L long long (64-bit) int no
+*****************************************************************************/
+#include <string.h> /* strlen() */
+#include <stdio.h> /* stdout, putchar(), fputs() (but not printf() :) */
+
+#if 1
+#include <stdarg.h> /* va_list, va_start(), va_arg(), va_end() */
+#else
+/* home-brew STDARG.H, also public-domain: */
+
+/* Assume: width of stack == width of int. Don't use sizeof(char *) or
+other pointer because sizeof(char *)==4 for LARGE-model 16-bit code.
+Assume: width is a power of 2 */
+#define STACK_WIDTH sizeof(int)
+
+/* Round up object width so it's an even multiple of STACK_WIDTH.
+Using & for division here, so STACK_WIDTH must be a power of 2. */
+#define TYPE_WIDTH(TYPE) \
+ ((sizeof(TYPE) + STACK_WIDTH - 1) & ~(STACK_WIDTH - 1))
+
+/* point the va_list pointer to LASTARG,
+then advance beyond it to the first variable arg */
+#define va_start(PTR, LASTARG) \
+ PTR = (va_list)((char *)&(LASTARG) + TYPE_WIDTH(LASTARG))
+
+#define va_end(PTR) /* nothing */
+
+/* Increment the va_list pointer, then return
+(evaluate to, actually) the previous value of the pointer.
+WHEEE! At last; a valid use for the C comma operator! */
+#define va_arg(PTR, TYPE) ( \
+ (char *)(PTR) += TYPE_WIDTH(TYPE) \
+ , \
+ *((TYPE *)((char *)(PTR) - TYPE_WIDTH(TYPE))) \
+ )
+/* Every other compiler/libc seems to be using 'void *', so...
+(I _was_ using 'unsigned char *') */
+typedef void *va_list;
+
+#endif
+
+/* flags used in processing format string */
+#define PR_LJ 0x01 /* left justify */
+#define PR_CA 0x02 /* use A-F instead of a-f for hex */
+#define PR_SG 0x04 /* signed numeric conversion (%d vs. %u) */
+#define PR_32 0x08 /* long (32-bit) numeric conversion */
+#define PR_16 0x10 /* short (16-bit) numeric conversion */
+#define PR_WS 0x20 /* PR_SG set and num was < 0 */
+#define PR_LZ 0x40 /* pad left with '0' instead of ' ' */
+#define PR_FP 0x80 /* pointers are far */
+#if 0
+/* largest number handled is 2^32-1, lowest radix handled is 8.
+2^32-1 in base 8 has 11 digits (add 5 for trailing NUL and for slop) */
+#define PR_BUFLEN 16
+#else
+/* largest number handled is 2^64-1, lowest radix handled is 8.
+2^64-1 in base 8 has 22 digits (add 2 for trailing NUL and for slop) */
+#define PR_BUFLEN 24
+#endif
+
+typedef int (*fnptr_t)(unsigned c, void **helper);
+/*****************************************************************************
+name: do_printf
+action: minimal subfunction for ?printf, calls function
+ 'fn' with arg 'ptr' for each character to be output
+returns:total number of characters output
+*****************************************************************************/
+int do_printf(const char *fmt, va_list args, fnptr_t fn, void *ptr)
+{
+ unsigned flags, actual_wd, count, given_wd;
+ unsigned char *where, buf[PR_BUFLEN];
+ unsigned char state, radix;
+ long num;
+
+ state = flags = count = given_wd = 0;
+/* begin scanning format specifier list */
+ for(; *fmt; fmt++)
+ {
+ switch(state)
+ {
+/* STATE 0: AWAITING % */
+ case 0:
+ if(*fmt != '%') /* not %... */
+ {
+ fn(*fmt, &ptr); /* ...just echo it */
+ count++;
+ break;
+ }
+/* found %, get next char and advance state to check if next char is a flag */
+ state++;
+ fmt++;
+ /* FALL THROUGH */
+/* STATE 1: AWAITING FLAGS (%-0) */
+ case 1:
+ if(*fmt == '%') /* %% */
+ {
+ fn(*fmt, &ptr);
+ count++;
+ state = flags = given_wd = 0;
+ break;
+ }
+ if(*fmt == '-')
+ {
+ if(flags & PR_LJ)/* %-- is illegal */
+ state = flags = given_wd = 0;
+ else
+ flags |= PR_LJ;
+ break;
+ }
+/* not a flag char: advance state to check if it's field width */
+ state++;
+/* check now for '%0...' */
+ if(*fmt == '0')
+ {
+ flags |= PR_LZ;
+ fmt++;
+ }
+ /* FALL THROUGH */
+/* STATE 2: AWAITING (NUMERIC) FIELD WIDTH */
+ case 2:
+ if(*fmt >= '0' && *fmt <= '9')
+ {
+ given_wd = 10 * given_wd +
+ (*fmt - '0');
+ break;
+ }
+/* not field width: advance state to check if it's a modifier */
+ state++;
+ /* FALL THROUGH */
+/* STATE 3: AWAITING MODIFIER CHARS (FNlh) */
+ case 3:
+ if(*fmt == 'F')
+ {
+ flags |= PR_FP;
+ break;
+ }
+ if(*fmt == 'N')
+ break;
+ if(*fmt == 'l')
+ {
+ flags |= PR_32;
+ break;
+ }
+ if(*fmt == 'h')
+ {
+ flags |= PR_16;
+ break;
+ }
+/* not modifier: advance state to check if it's a conversion char */
+ state++;
+ /* FALL THROUGH */
+/* STATE 4: AWAITING CONVERSION CHARS (Xxpndiuocs) */
+ case 4:
+ where = buf + PR_BUFLEN - 1;
+ *where = '\0';
+ switch(*fmt)
+ {
+ case 'X':
+ flags |= PR_CA;
+ /* FALL THROUGH */
+/* xxx - far pointers (%Fp, %Fn) not yet supported */
+ case 'x':
+ case 'p':
+ case 'n':
+ radix = 16;
+ goto DO_NUM;
+ case 'd':
+ case 'i':
+ flags |= PR_SG;
+ /* FALL THROUGH */
+ case 'u':
+ radix = 10;
+ goto DO_NUM;
+ case 'o':
+ radix = 8;
+/* load the value to be printed. l=long=32 bits: */
+DO_NUM: if(flags & PR_32)
+ num = va_arg(args, unsigned long);
+/* h=short=16 bits (signed or unsigned) */
+ else if(flags & PR_16)
+ {
+ if(flags & PR_SG)
+ num = va_arg(args, short);
+ else
+ num = va_arg(args, unsigned short);
+ }
+/* no h nor l: sizeof(int) bits (signed or unsigned) */
+ else
+ {
+ if(flags & PR_SG)
+ num = va_arg(args, int);
+ else
+ num = va_arg(args, unsigned int);
+ }
+/* take care of sign */
+ if(flags & PR_SG)
+ {
+ if(num < 0)
+ {
+ flags |= PR_WS;
+ num = -num;
+ }
+ }
+/* convert binary to octal/decimal/hex ASCII
+OK, I found my mistake. The math here is _always_ unsigned */
+ do
+ {
+ unsigned long temp;
+
+ temp = (unsigned long)num % radix;
+ where--;
+ if(temp < 10)
+ *where = temp + '0';
+ else if(flags & PR_CA)
+ *where = temp - 10 + 'A';
+ else
+ *where = temp - 10 + 'a';
+ num = (unsigned long)num / radix;
+ }
+ while(num != 0);
+ goto EMIT;
+ case 'c':
+/* disallow pad-left-with-zeroes for %c */
+ flags &= ~PR_LZ;
+ where--;
+ *where = (unsigned char)va_arg(args,
+ unsigned char);
+ actual_wd = 1;
+ goto EMIT2;
+ case 's':
+/* disallow pad-left-with-zeroes for %s */
+ flags &= ~PR_LZ;
+ where = va_arg(args, unsigned char *);
+EMIT:
+ actual_wd = strlen(where);
+ if(flags & PR_WS)
+ actual_wd++;
+/* if we pad left with ZEROES, do the sign now */
+ if((flags & (PR_WS | PR_LZ)) ==
+ (PR_WS | PR_LZ))
+ {
+ fn('-', &ptr);
+ count++;
+ }
+/* pad on left with spaces or zeroes (for right justify) */
+EMIT2: if((flags & PR_LJ) == 0)
+ {
+ while(given_wd > actual_wd)
+ {
+ fn(flags & PR_LZ ?
+ '0' : ' ', &ptr);
+ count++;
+ given_wd--;
+ }
+ }
+/* if we pad left with SPACES, do the sign now */
+ if((flags & (PR_WS | PR_LZ)) == PR_WS)
+ {
+ fn('-', &ptr);
+ count++;
+ }
+/* emit string/char/converted number */
+ while(*where != '\0')
+ {
+ fn(*where++, &ptr);
+ count++;
+ }
+/* pad on right with spaces (for left justify) */
+ if(given_wd < actual_wd)
+ given_wd = 0;
+ else given_wd -= actual_wd;
+ for(; given_wd; given_wd--)
+ {
+ fn(' ', &ptr);
+ count++;
+ }
+ break;
+ default:
+ break;
+ }
+ default:
+ state = flags = given_wd = 0;
+ break;
+ }
+ }
+ return count;
+}
+#if 1 /* testing */
+/*****************************************************************************
+SPRINTF
+*****************************************************************************/
+static int vsprintf_help(unsigned c, void **ptr)
+{
+ char *dst;
+
+ dst = *ptr;
+ *dst++ = (char)c;
+ *ptr = dst;
+ return 0 ;
+}
+/*****************************************************************************
+*****************************************************************************/
+int vsprintf(char *buf, const char *fmt, va_list args)
+{
+ int rv;
+
+ rv = do_printf(fmt, args, vsprintf_help, (void *)buf);
+ buf[rv] = '\0';
+ return rv;
+}
+/*****************************************************************************
+*****************************************************************************/
+int sprintf(char *buf, const char *fmt, ...)
+{
+ va_list args;
+ int rv;
+
+ va_start(args, fmt);
+ rv = vsprintf(buf, fmt, args);
+ va_end(args);
+ return rv;
+}
+/*****************************************************************************
+PRINTF
+You must write your own putchar()
+*****************************************************************************/
+int vprintf_help(unsigned c, void **ptr)
+{
+ putchar(c);
+ return 0 ;
+}
+/*****************************************************************************
+*****************************************************************************/
+int vprintf(const char *fmt, va_list args)
+{
+ return do_printf(fmt, args, vprintf_help, NULL);
+}
+/*****************************************************************************
+*****************************************************************************/
+int printf(const char *fmt, ...)
+{
+ va_list args;
+ int rv;
+
+ va_start(args, fmt);
+ rv = vprintf(fmt, args);
+ va_end(args);
+ return rv;
+}
+#endif
+#if 0
+/*****************************************************************************
+*****************************************************************************/
+int main(void)
+{
+ char buf[64];
+
+ sprintf(buf, "%lu score and %i years ago...\n", 4L, -7);
+ fputs(buf, stdout); /* puts() adds newline */
+
+ sprintf(buf, "-1L == 0x%lX == octal %lo\n", -1L, -1L);
+ fputs(buf, stdout); /* puts() adds newline */
+
+ printf("<%-8s> and <%8s> justified strings\n", "left", "right");
+ return 0;
+}
+#endif