J
Jordan Abel
I have written this function and was wondering if anyone else could
point out if there's anything wrong with it. The purpose is to
substitute for printf when in a situation where low-level [beyond the
scope of this newsgroup] I/O has to be used for whatever reason, and
memory allocation cannot be used [for risk of it failing or whatever
else] - mainly it's become for me an exercise in interpreting
printf-like format strings, and i thought i'd post it here to see if
anyone can point out any bugs in it - i.e. anything that would cause
undefined behavior, and any valid format strings it might fail to
interpret [there's functionality it doesn't implement, but it should at
least pop off all the same arguments for any given valid format string
that a real printf function will.]
[is this the appropriate newsgroup for this kind of thing?]
______
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <wchar.h>
int write(int fd,char*buf,size_t len);
#if 0 /*if your system doesn't have this platform-specific function*/
static FILE * fdtab[] = { stdin, stdout, stderr, 0, 0, 0, 0, 0, 0, 0 };
int write(int fd,char *buf,size_t len) {
if(fd > 0 || !fdtab[fd]) {
return -1;
}
return fwrite(buf,1,len,fdtab[fd]);
}
#endif
#define LENGTH 64
static char buf[LENGTH];
static int pos;
static char const udigit[16] = "0123456789ABCDEF";
static char const ldigit[16] = "0123456789abcdef";
static const char *digits;
static void flush(int fd) {
write(fd,buf,pos);
pos=0;
}
static inline void eputc(int fd, int c) {
buf[pos++]=c;
if(pos==LENGTH) flush(fd);
}
static inline void eputwc(int fd, wint_t c) {
int x = wctob(c); eputc(fd,x<0?'?':x);
}
static void eputws(int fd, wchar_t *s) {
while(*s) { eputwc(fd,*s); s++; }
}
static void eputs(int fd, char *s) {
while(*s) { eputc(fd,*s); s++; }
}
static char * uimaxtoa(uintmax_t n, int base) {
static char buf[CHAR_BIT*sizeof(n)+2];
char *endptr=buf+(sizeof buf)-2;
do {
*endptr--=digits[n%base];
n/=base;
} while(n);
return endptr+1;
}
static char * pcvt(void *p) {
static char buf[sizeof(void *)*CHAR_BIT];
/* well, we can always hope this will fit - anyone know a more
* portable way to implement %p? */
sprintf(buf,"%p",p);
return buf;
}
static char * tcvt(time_t t) {
static char buf[] = "YYYY-MM-DD HH:MM:SS";
strftime(buf,sizeof buf,"%Y-%m-%d %H:%M:%S",gmtime(&t));
return buf;
}
void eprintf(int fd, const char *fmt, ...) {
int c;
va_list ap;
int islong=0;
uintmax_t uarg;
intmax_t iarg;
va_start(ap,fmt);
loop:
for(;*fmt&&*fmt!='%';fmt++)
eputc(fd,*fmt);
if(!*fmt) goto end;
if(*fmt++ == '%') {
islong=0;
digits=ldigit;
top:
c = *fmt++;
retry:
switch(c) {
case 'c':
if(islong) eputwc(fd,va_arg(ap,wint_t));
else eputc(fd,va_arg(ap,int));
break;
/* obsolete, but it doesn't conflict with anything. */
case 'D': case 'O': case 'U': case 'C': case 'S':
islong=1; c=tolower(c); goto retry;
case 'A': case 'E': case 'F': case 'G': case 'X':
digits=udigit; c = tolower(c); goto retry;
case 'd': case 'i':
switch(islong) {
case 0: iarg=va_arg(ap,int); break;
case 1: iarg=va_arg(ap,long int); break;
case 2: iarg=va_arg(ap,long long int); break;
// this is wrong on non-twos-complement systems
case 3: iarg=va_arg(ap,size_t); break;
case 4: iarg=va_arg(ap,ptrdiff_t); break;
case 5: iarg=va_arg(ap,intmax_t); break;
}
if(iarg<0) {
eputc(fd,'-');
uarg=-iarg;
} else {
uarg=iarg;
}
goto cvt;
case 'o': case 'u': case 'x':
switch(islong) {
case 0: uarg=va_arg(ap,unsigned); break;
case 1: uarg=va_arg(ap,long unsigned); break;
case 2: uarg=va_arg(ap,long long unsigned); break;
case 3: uarg=va_arg(ap,size_t); break;
// this is wrong on non-twos-complement systems.
case 4: uarg=va_arg(ap,ptrdiff_t); break;
case 5: uarg=va_arg(ap,uintmax_t); break;
}
cvt:
eputs(fd,uimaxtoa(uarg,c=='o'?8c=='x'?16:10)));
break;
case 'p':
eputs(fd,pcvt(va_arg(ap,void*))); break;
case 's':
if(islong)eputws(fd,va_arg(ap,wchar_t *));
else eputs(fd,va_arg(ap,char *));
break;
/* my own extensions, they print an int representing an errno value and
* a time_t, respectively. %! calls strerror(). */
case '!':
eputs(fd,strerror(va_arg(ap,int))); break;
case 'T':
eputs(fd,tcvt(va_arg(ap,time_t))); break;
case 0:
goto end;
default:
eputs(fd,"%?"); /*fall*/
case '%':
write(fd,&c,1); break;
case 'l': islong++; if(islong > 2) islong=2; goto top;
case 'j': islong=5; goto top;
case 't': islong=4; goto top;
case 'z': islong=3; goto top;
// here's the stuff we don't implement
case 'n':
switch(islong) {
case 0: (void)va_arg(ap,int *); break;
case 1: (void)va_arg(ap,long *); break;
case 2: (void)va_arg(ap,long long *); break;
case 3: (void)va_arg(ap,size_t *); break;
// this is wrong on non-twos-complement systems.
case 4: (void)va_arg(ap,ptrdiff_t *); break;
case 5: (void)va_arg(ap,intmax_t *); break;
} break;
case '*':
(void)va_arg(ap,int);
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '.': case '#': case '+': case '-': case ' ':
case '\'':
goto top;
case '$':
/* this, i can't even pretend to support - eh, well, it's a posix thing,
* rather than a C thing, anyway. */
eputs(fd,"%$?????\n");
flush(fd);
return;
/* I don't feel like messing with floating point right now */
case 'L':
islong=2; goto top;
case 'a': case 'e': case 'f': case 'g':
if(islong == 2) (void)va_arg(ap, long double);
else if(islong == 2) (void)va_arg(ap, double);
eputs(fd,"%FLT?");
break;
}
}
goto loop;
end:
flush(fd);
}
point out if there's anything wrong with it. The purpose is to
substitute for printf when in a situation where low-level [beyond the
scope of this newsgroup] I/O has to be used for whatever reason, and
memory allocation cannot be used [for risk of it failing or whatever
else] - mainly it's become for me an exercise in interpreting
printf-like format strings, and i thought i'd post it here to see if
anyone can point out any bugs in it - i.e. anything that would cause
undefined behavior, and any valid format strings it might fail to
interpret [there's functionality it doesn't implement, but it should at
least pop off all the same arguments for any given valid format string
that a real printf function will.]
[is this the appropriate newsgroup for this kind of thing?]
______
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <wchar.h>
int write(int fd,char*buf,size_t len);
#if 0 /*if your system doesn't have this platform-specific function*/
static FILE * fdtab[] = { stdin, stdout, stderr, 0, 0, 0, 0, 0, 0, 0 };
int write(int fd,char *buf,size_t len) {
if(fd > 0 || !fdtab[fd]) {
return -1;
}
return fwrite(buf,1,len,fdtab[fd]);
}
#endif
#define LENGTH 64
static char buf[LENGTH];
static int pos;
static char const udigit[16] = "0123456789ABCDEF";
static char const ldigit[16] = "0123456789abcdef";
static const char *digits;
static void flush(int fd) {
write(fd,buf,pos);
pos=0;
}
static inline void eputc(int fd, int c) {
buf[pos++]=c;
if(pos==LENGTH) flush(fd);
}
static inline void eputwc(int fd, wint_t c) {
int x = wctob(c); eputc(fd,x<0?'?':x);
}
static void eputws(int fd, wchar_t *s) {
while(*s) { eputwc(fd,*s); s++; }
}
static void eputs(int fd, char *s) {
while(*s) { eputc(fd,*s); s++; }
}
static char * uimaxtoa(uintmax_t n, int base) {
static char buf[CHAR_BIT*sizeof(n)+2];
char *endptr=buf+(sizeof buf)-2;
do {
*endptr--=digits[n%base];
n/=base;
} while(n);
return endptr+1;
}
static char * pcvt(void *p) {
static char buf[sizeof(void *)*CHAR_BIT];
/* well, we can always hope this will fit - anyone know a more
* portable way to implement %p? */
sprintf(buf,"%p",p);
return buf;
}
static char * tcvt(time_t t) {
static char buf[] = "YYYY-MM-DD HH:MM:SS";
strftime(buf,sizeof buf,"%Y-%m-%d %H:%M:%S",gmtime(&t));
return buf;
}
void eprintf(int fd, const char *fmt, ...) {
int c;
va_list ap;
int islong=0;
uintmax_t uarg;
intmax_t iarg;
va_start(ap,fmt);
loop:
for(;*fmt&&*fmt!='%';fmt++)
eputc(fd,*fmt);
if(!*fmt) goto end;
if(*fmt++ == '%') {
islong=0;
digits=ldigit;
top:
c = *fmt++;
retry:
switch(c) {
case 'c':
if(islong) eputwc(fd,va_arg(ap,wint_t));
else eputc(fd,va_arg(ap,int));
break;
/* obsolete, but it doesn't conflict with anything. */
case 'D': case 'O': case 'U': case 'C': case 'S':
islong=1; c=tolower(c); goto retry;
case 'A': case 'E': case 'F': case 'G': case 'X':
digits=udigit; c = tolower(c); goto retry;
case 'd': case 'i':
switch(islong) {
case 0: iarg=va_arg(ap,int); break;
case 1: iarg=va_arg(ap,long int); break;
case 2: iarg=va_arg(ap,long long int); break;
// this is wrong on non-twos-complement systems
case 3: iarg=va_arg(ap,size_t); break;
case 4: iarg=va_arg(ap,ptrdiff_t); break;
case 5: iarg=va_arg(ap,intmax_t); break;
}
if(iarg<0) {
eputc(fd,'-');
uarg=-iarg;
} else {
uarg=iarg;
}
goto cvt;
case 'o': case 'u': case 'x':
switch(islong) {
case 0: uarg=va_arg(ap,unsigned); break;
case 1: uarg=va_arg(ap,long unsigned); break;
case 2: uarg=va_arg(ap,long long unsigned); break;
case 3: uarg=va_arg(ap,size_t); break;
// this is wrong on non-twos-complement systems.
case 4: uarg=va_arg(ap,ptrdiff_t); break;
case 5: uarg=va_arg(ap,uintmax_t); break;
}
cvt:
eputs(fd,uimaxtoa(uarg,c=='o'?8c=='x'?16:10)));
break;
case 'p':
eputs(fd,pcvt(va_arg(ap,void*))); break;
case 's':
if(islong)eputws(fd,va_arg(ap,wchar_t *));
else eputs(fd,va_arg(ap,char *));
break;
/* my own extensions, they print an int representing an errno value and
* a time_t, respectively. %! calls strerror(). */
case '!':
eputs(fd,strerror(va_arg(ap,int))); break;
case 'T':
eputs(fd,tcvt(va_arg(ap,time_t))); break;
case 0:
goto end;
default:
eputs(fd,"%?"); /*fall*/
case '%':
write(fd,&c,1); break;
case 'l': islong++; if(islong > 2) islong=2; goto top;
case 'j': islong=5; goto top;
case 't': islong=4; goto top;
case 'z': islong=3; goto top;
// here's the stuff we don't implement
case 'n':
switch(islong) {
case 0: (void)va_arg(ap,int *); break;
case 1: (void)va_arg(ap,long *); break;
case 2: (void)va_arg(ap,long long *); break;
case 3: (void)va_arg(ap,size_t *); break;
// this is wrong on non-twos-complement systems.
case 4: (void)va_arg(ap,ptrdiff_t *); break;
case 5: (void)va_arg(ap,intmax_t *); break;
} break;
case '*':
(void)va_arg(ap,int);
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '.': case '#': case '+': case '-': case ' ':
case '\'':
goto top;
case '$':
/* this, i can't even pretend to support - eh, well, it's a posix thing,
* rather than a C thing, anyway. */
eputs(fd,"%$?????\n");
flush(fd);
return;
/* I don't feel like messing with floating point right now */
case 'L':
islong=2; goto top;
case 'a': case 'e': case 'f': case 'g':
if(islong == 2) (void)va_arg(ap, long double);
else if(islong == 2) (void)va_arg(ap, double);
eputs(fd,"%FLT?");
break;
}
}
goto loop;
end:
flush(fd);
}