/* Copyright 1996 Acorn Computers Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* printf.c: ANSI draft (X3J11 Oct 86) part of section 4.9 code */ /* Copyright (C) Codemist Ltd., 1988 */ /* version 0.05b */ /* printf and its friends return the number of characters planted. In */ /* the case of sprintf this does not include the terminating '\0'. */ /* Consider using ftell instead of charcount in printf (see scanf). */ /* see c.ansilib re possible use of #define NO_FLOATING_POINT. */ #define __system_io 1 /* makes stdio.h declare more */ /* sprintf wants to know about the system part of the FILE descriptor, but it doesn't need to know about _extradata */ typedef struct _extradata {void *dummy;} _extradata; #include "hostsys.h" #include "kernel.h" #include <stdio.h> #include <stdarg.h> #include <ctype.h> #include <string.h> #define intofdigit(x) ((x)-'0') #ifndef _LJUSTIFY /* allow 'flat' compilation of library */ /* The following typedef and type of __vfprintf must match fpprintf.c */ typedef int (*fp_print)(int ch, double *d, char buff[], int flags, char **lvprefix, int *lvprecision, int *lvbefore_dot, int *lvafter_dot); /* HIDDEN EXPORTS */ int __vfprintf(FILE *p, const char *fmt, va_list args, fp_print fp_display_fn, int lf_terminates); int _fprintf(FILE *fp, const char *fmt, ...); int _printf(const char *fmt, ...); int _sprintf(char *buff, const char *fmt, ...); int _snprintf(char *buff, size_t n, const char *fmt, ...); int _vfprintf(FILE *p, const char *fmt, va_list args); int _vsprintf(char *buff, const char *fmt, va_list a); /* The following take LF or 0 terminated format strings so they work */ /* with format strings fetched from message files */ int _fprintf_lf(FILE *fp, const char *fmt, ...); int _sprintf_lf(char *buff, const char *fmt, ...); /* The following macros must match those in fpprintf.c: */ /* The code here tends to be a bit generous about meaningless flag */ /* combinations of options in the format string - it chooses the set */ /* that it likes and prints according to that style without commenting */ /* on the redundant options. */ #define _LJUSTIFY 01 #define _SIGNED 02 #define _BLANKER 04 #define _VARIANT 010 #define _PRECGIVEN 020 #define _LONGSPECIFIER 040 #define _SHORTSPEC 0100 #define _PADZERO 0200 /* *** DEPRECATED FEATURE *** */ #define _FPCONV 0400 #define _CHARSPEC 01000 #define _LONGLONGSPEC 02000 #endif /* _LJUSTIFY */ #define pr_padding(ch, n, p) while(--n>=0) charcount++, putc(ch, p); #define pre_padding(p) \ if (!(flags&_LJUSTIFY)) \ { char padchar = flags & _PADZERO ? '0' : ' '; \ pr_padding(padchar, width, p); } #define post_padding(p) \ if (flags&_LJUSTIFY) \ { pr_padding(' ', width, p); } #ifdef never /* do it this way? */ static int pr_num(unsigned int v, int flags, char *prefix, int width, int precision, FILE *p) {} #endif static int printf_display(FILE *p, int flags, int ch, int precision, int width, unsigned long long v, fp_print fp_display_fn, char *prefix, char *hextab, double *d) { int charcount = 0; int len = 0, before_dot = -1, after_dot = -1; char buff[32]; /* used to accumulate value to print */ /* here at the end of the switch statement I gather together code that */ /* is concerned with displaying integers. */ /* AM: maybe this would be better as a proc if we could get arg count down */ if ((flags & _FPCONV+_PRECGIVEN)==0) precision = 1; switch (ch) { case 'p': case 'X': case 'x': while(v!=0) { buff[len++] = hextab[v & 0xf]; v = v >> 4; } break; case 'o': while(v!=0) { buff[len++] = '0' + (v & 07); v = v >> 3; } break; case 'u': case 'i': case 'd': while (v != 0) { unsigned long long vDiv10 = v / 10U; buff[len++] = '0' + v - vDiv10 * 10U; v = vDiv10; } break; #ifndef NO_FLOATING_POINT case 'f': case 'F': case 'g': case 'G': case 'e': case 'E': case 'a': case 'A': len = fp_display_fn(ch, d, buff, flags, /* The following arguments are set by fp_display_fn */ &prefix, &precision, &before_dot, &after_dot); break; #else /* If floating point is not supported I display ALL %e, %f and %g */ /* items as 0.0 */ default: buff[0] = '0'; buff[1] = '.'; buff[2] = '0'; len = 3; break; #endif } /* now work out how many leading '0's are needed for precision specifier */ /* _FPCONV is the case of FP printing in which case extra digits to make */ /* up the precision come within the number as marked by characters '<' */ /* and '>' in the buffer. */ if (flags & _FPCONV) { precision = 0; if (before_dot>0) precision = before_dot-1; if (after_dot>0) precision += after_dot-1; } else if ((precision -= len)<0) precision = 0; /* and how much padding is needed */ width -= (precision + len + strlen(prefix)); /* AM: ANSI appear (Oct 86) to suggest that the padding (even if with '0') */ /* occurs before the possible sign! Treat this as fatuous for now. */ if (!(flags & _PADZERO)) pre_padding(p); { int c; /* prefix */ while((c=*prefix++)!=0) { putc(c, p); charcount++; } } pre_padding(p); /* floating point numbers are in buff[] the normal way around, while */ /* integers have been pushed in with the digits in reverse order. */ if (flags & _FPCONV) { int i, c; for (i = 0; i<len; i++) { switch (c = buff[i]) { case '<': pr_padding('0', before_dot, p); break; case '>': pr_padding('0', after_dot, p); break; default: putc(c, p); charcount++; break; } } } else { pr_padding('0', precision, p); charcount += len; while((len--)>0) putc(buff[len], p); } /* By here if the padding has already been printed width will be zero */ post_padding(p); return charcount; } int __vfprintf(FILE *p, const char *fmt, va_list args, fp_print fp_display_fn, int lf_terminates) /* ACN: I apologize for this function - it seems long and ugly. Some of */ /* that is dealing with all the jolly flag options available with */ /* printf, and rather a lot more is a cautious floating point print */ /* package that takes great care to avoid the corruption of its */ /* input by rounding, and to generate consistent decimal versions */ /* of all possible values in all possible formats. */ { int ch, charcount = 0; while ((ch = *fmt++) != 0 && (!lf_terminates || ch != '\n')) { if (ch != '%') { putc(ch,p); charcount++; } else { int flags = 0, width = 0, precision = 0; /* The initialisation of hextab is spurious in that it will be set */ /* to a real string before use, but necessary in that passing unset */ /* parameters to functions is illegal in C. */ char *prefix, *hextab = 0; unsigned long long v; #ifndef NO_FLOATING_POINT double d; #endif /* This decodes all the nasty flags and options associated with an */ /* entry in the format list. For some entries many of these options */ /* will be useless, but I parse them all the same. */ for (;;) { switch (ch = *fmt++) { /* '-' Left justify converted flag. Only relevant if width specified */ /* explicitly and converted value is too short to fill it. */ case '-': flags = _LJUSTIFY | (flags & ~_PADZERO); continue; /* '+' Always print either '+' or '-' at start of numbers. */ case '+': flags |= _SIGNED; continue; /* ' ' Print either ' ' or '-' at start of numbers. */ case ' ': flags |= _BLANKER; continue; /* '#' Variant on main print routine (effect varies across different */ /* styles, but for instance %#x puts 0x on the front of displayed */ /* numbers. */ case '#': flags |= _VARIANT; continue; /* '0' Leading blanks are printed as zeros */ /* This is a *** DEPRECATED FEATURE *** (precision subsumes) */ case '0': flags |= _PADZERO; continue; default: break; } break; } /* now look for 'width' spec */ { int t = 0; if (ch=='*') { t = va_arg(args, int); /* If a negative width is passed as an argument I take its absolute */ /* value and use the negativeness to indicate the presence of the '-' */ /* flag (left justification). If '-' was already specified I lose it. */ if (t<0) { t = - t; flags ^= _LJUSTIFY; } ch = *fmt++; } else { while (isdigit(ch)) { t = t*10 + intofdigit(ch); ch = *fmt++; } } width = t>=0 ? t : 0; /* disallow -ve arg */ } if (ch == '.') /* precision spec */ { int t = 0; ch = *fmt++; if (ch=='*') { t = va_arg(args, int); ch = *fmt++; } else while (isdigit(ch)) { t = t*10 + intofdigit(ch); ch = *fmt++; } if (t >= 0) flags |= _PRECGIVEN, precision = t; } if (ch=='l' || ch=='L' || ch=='z' || ch=='t') /* 'l' Indicate that a numeric argument is 'long'. Here int and long */ /* are the same (32 bits) and so I can ignore this flag! */ /* 'L' Marks floating arguments as being of type long double. Here this */ /* is the same as just double, and so I can ignore the flag. */ /* 'z' Indicates that a numeric argument is 'size_t', or that a %n */ /* argument is a pointer to a size_t. We can ignore it. */ /* 't' Indicates that a numeric argument is 'ptrdiff_t', or that a %n */ /* argument is a pointer to a ptrdiff_t. We can ignore it. */ { int last = ch; flags |= _LONGSPECIFIER; ch = *fmt++; /* 'll' Indicates that a numeric argument is 'long long', or that a %n */ /* argument is a pointer to long long int. */ if (ch=='l' && last =='l') { flags |= _LONGLONGSPEC; ch = *fmt++; } } else if (ch=='h') /* 'h' Indicates that an integer value is to be treated as short. */ { flags |= _SHORTSPEC; ch = *fmt++; /* 'hh' Indicates that an integer value is to be treated as char. */ if (ch=='h') { flags |= _CHARSPEC; ch = *fmt++; } } else if (ch=='j') /* 'j' Indicates that a numeric argument is '[u]intmax_t', or than a %n */ /* argument is a pointer to intmax_t. */ { flags |= _LONGSPECIFIER|_LONGLONGSPEC; ch = *fmt++; } /* Now the options have been decoded - I can process the main dispatch */ switch (ch) { /* %c causes a single character to be fetched from the argument list */ /* and printed. This is subject to padding. */ case 'c': ch = va_arg(args, int); /* drop through */ /* %? where ? is some character not properly defined as a command char */ /* for printf causes ? to be displayed with padding and field widths */ /* as specified by the various modifers. %% is handled by this general */ /* mechanism. */ default: width--; /* char width is 1 */ pre_padding(p); putc(ch, p); charcount++; post_padding(p); continue; /* If a '%' occurs at the end of a format string (possibly with a few */ /* width specifiers and qualifiers after it) I end up here with a '\0' */ /* in my hand. Unless I do something special the fact that the format */ /* string terminated gets lost... */ /* Ditto for '\n' terminated strings. "%\n" doesn't mean anything anyway */ case '\n': case 0: fmt--; continue; /* %n assigns the number of chars printed so far to the next arg (which */ /* is expected to be of type (int *), or (long long *) if 'j' or 'll'. */ case 'n': if (flags & _LONGLONGSPEC) { long long *xp = va_arg(args, long long *); *xp = charcount; } else { int *xp = va_arg(args, int *); *xp = charcount; } continue; /* %s prints a string. If a precision is given it can limit the number */ /* of characters taken from the string, and padding and justification */ /* behave as usual. */ case 's': { char *str = va_arg(args, char *); int i, n; if (flags&_PRECGIVEN) { n = 0; while ((n < precision) && (str[n] != 0)) n++; } else n = strlen(str); width -= n; pre_padding(p); for (i=0; i<n; i++) putc(str[i], p); charcount += n; post_padding(p); } continue; /* %x prints in hexadecimal. %X does the same, but uses upper case */ /* when printing things that are not (decimal) digits. */ /* I can share some messy decoding here with the code that deals with */ /* octal and decimal output via %o and %d. */ case 'X': v = (flags & _LONGLONGSPEC) ? va_arg(args, unsigned long long) : va_arg(args, unsigned int); if (flags & _SHORTSPEC) v = (unsigned short)v; if (flags & _CHARSPEC) v = (unsigned char)v; hextab = "0123456789ABCDEF"; prefix = ((flags&_VARIANT) != 0 && v != 0)? "0X" : ""; if (flags & _PRECGIVEN) flags &= ~_PADZERO; break; case 'x': v = (flags & _LONGLONGSPEC) ? va_arg(args, unsigned long long) : va_arg(args, unsigned int); if (flags & _SHORTSPEC) v = (unsigned short)v; if (flags & _CHARSPEC) v = (unsigned char)v; hextab = "0123456789abcdef"; prefix = ((flags&_VARIANT) != 0 && v != 0)? "0x" : ""; if (flags & _PRECGIVEN) flags &= ~_PADZERO; break; /* %p is for printing a pointer - I print it as a hex number with the */ /* precision always forced to 8. */ case 'p': v = (unsigned int)va_arg(args, void *); hextab = "0123456789abcdef"; prefix = (flags&_VARIANT) ? "@" : ""; flags |= _PRECGIVEN; precision = 8; break; case 'o': v = (flags & _LONGLONGSPEC) ? va_arg(args, unsigned long long) : va_arg(args, unsigned int); if (flags & _SHORTSPEC) v = (unsigned short)v; if (flags & _CHARSPEC) v = (unsigned char)v; prefix = (flags&_VARIANT) ? "0" : ""; if (flags & _PRECGIVEN) flags &= ~_PADZERO; break; case 'u': v = (flags & _LONGLONGSPEC) ? va_arg(args, unsigned long long) : va_arg(args, unsigned int); if (flags & _SHORTSPEC) v = (unsigned short)v; if (flags & _CHARSPEC) v = (unsigned char)v; prefix = ""; if (flags & _PRECGIVEN) flags &= ~_PADZERO; break; case 'i': case 'd': { long long w; w = (flags & _LONGLONGSPEC) ? va_arg(args, long long) : va_arg(args, int); if (flags & _SHORTSPEC) w = (signed short)w; if (flags & _CHARSPEC) w = (signed char)w; if (w<0) v = 0ULL-w, prefix = "-"; else v = w, prefix = (flags&_SIGNED) ? "+" : (flags&_BLANKER) ? " " : ""; } if (flags & _PRECGIVEN) flags &= ~_PADZERO; break; case 'f': case 'F': case 'e': case 'E': case 'g': case 'G': case 'a': case 'A': flags |= _FPCONV; if (!(flags & _PRECGIVEN)) precision = 6; #ifndef NO_FLOATING_POINT d = va_arg(args, double); /* technically, for the call to printf_display() below to */ /* be legal and not reference an undefined variable we */ /* need to do the following (overwritten in fp_display_fn) */ /* (It also stops dataflow analysis (-fa) complaining!) */ prefix = 0, hextab = 0, v = 0; #else /* NO_FLOATING_POINT */ { int w = va_arg(args, int); w = va_arg(args, int); /* If the pre-processor symbol FLOATING_POINT is not set I assume that */ /* floating point is not available, and so support %e, %f, %g and %a */ /* with a fragment of code that skips over the relevant argument. */ /* I also assume that a double takes two int-sized arg positions. */ prefix = (flags&_SIGNED) ? "+" : (flags&_BLANKER) ? " " : ""; } #endif /* NO_FLOATING_POINT */ break; } charcount += printf_display(p, flags, ch, precision, width, v, fp_display_fn, prefix, hextab, &d); continue; } } return ferror(p) && !(p->__flag & _IOSTRG) ? EOF : charcount; } static int no_fp_display(int ch, double *d, char buff[], int flags, char **lvprefix, int *lvprecision, int *lvbefore_dot, int *lvafter_dot) { ch = ch; d = d; buff = buff; flags = flags; lvprefix = lvprefix; lvprecision = lvprecision; lvbefore_dot = lvbefore_dot; lvafter_dot = lvafter_dot; return 0; } int _fprintf(FILE *fp, const char *fmt, ...) { va_list a; int n; va_start(a, fmt); n = __vfprintf(fp, fmt, a, no_fp_display, 0); va_end(a); return n; } int _fprintf_lf(FILE *fp, const char *fmt, ...) { va_list a; int n; va_start(a, fmt); n = __vfprintf(fp, fmt, a, no_fp_display, 1); va_end(a); return n; } int _printf(const char *fmt, ...) { va_list a; int n; va_start(a, fmt); n = __vfprintf(stdout, fmt, a, no_fp_display, 0); va_end(a); return n; } int _sprintf(char *buff, const char *fmt, ...) { FILE hack; va_list a; /*************************************************************************/ /* Note that this code interacts in a dubious way with the putc macro. */ /*************************************************************************/ int length; va_start(a, fmt); memclr(&hack, sizeof(FILE)); hack.__flag = _IOSTRG+_IOWRITE; hack.__ptr = (unsigned char *)buff; hack.__ocnt = 0x7fffffff; length = __vfprintf(&hack, fmt, a, no_fp_display, 0); putc(0,&hack); va_end(a); return(length); } int _snprintf(char *buff, size_t n, const char *fmt, ...) { FILE hack; va_list a; /*************************************************************************/ /* Note that this code interacts in a dubious way with the putc macro. */ /*************************************************************************/ int length; va_start(a, fmt); memclr(&hack, sizeof(FILE)); hack.__flag = _IOSTRG+_IOWRITE; hack.__ptr = (unsigned char *)buff; hack.__ocnt = n == 0 ? 0 : n-1; length = __vfprintf(&hack, fmt, a, no_fp_display, 0); if (n != 0) *hack.__ptr = 0; va_end(a); return(length); } int _sprintf_lf(char *buff, const char *fmt, ...) { FILE hack; va_list a; /*************************************************************************/ /* Note that this code interacts in a dubious way with the putc macro. */ /*************************************************************************/ int length; va_start(a, fmt); memclr(&hack, sizeof(FILE)); hack.__flag = _IOSTRG+_IOWRITE; hack.__ptr = (unsigned char *)buff; hack.__ocnt = 0x7fffffff; length = __vfprintf(&hack, fmt, a, no_fp_display, 1); putc(0,&hack); va_end(a); return(length); } int _vfprintf(FILE *p, const char *fmt, va_list args) { return __vfprintf(p, fmt, args, no_fp_display, 0); } int _vsprintf(char *buff, const char *fmt, va_list a) { FILE hack; /*************************************************************************/ /* Note that this code interacts in a dubious way with the putc macro. */ /*************************************************************************/ int length; memclr(&hack, sizeof(FILE)); hack.__flag = _IOSTRG+_IOWRITE; hack.__ptr = (unsigned char *)buff; hack.__ocnt = 0x7fffffff; length = __vfprintf(&hack, fmt, a, no_fp_display, 0); putc(0,&hack); return(length); } /* End of printf.c */