summaryrefslogtreecommitdiff
path: root/xprintf.c
diff options
context:
space:
mode:
Diffstat (limited to 'xprintf.c')
-rw-r--r--xprintf.c583
1 files changed, 583 insertions, 0 deletions
diff --git a/xprintf.c b/xprintf.c
new file mode 100644
index 0000000..384fab4
--- /dev/null
+++ b/xprintf.c
@@ -0,0 +1,583 @@
+/*
+ * Copyright (c) 2010 Richard Braun.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+
+#include "xprintf.h"
+
+/*
+ * Formatting flags.
+ *
+ * FORMAT_LOWER must be 0x20 as it is OR'd with digits, eg.
+ * '0': 0x30 | 0x20 => 0x30 ('0')
+ * 'A': 0x41 | 0x20 => 0x61 ('a')
+ */
+#define FORMAT_ALT_FORM 0x01 /* "Alternate form" */
+#define FORMAT_ZERO_PAD 0x02 /* Zero padding on the left */
+#define FORMAT_LEFT_JUSTIFY 0x04 /* Align text on the left */
+#define FORMAT_BLANK 0x08 /* Blank space before positive number */
+#define FORMAT_SIGN 0x10 /* Always place a sign (either + or -) */
+#define FORMAT_LOWER 0x20 /* To lowercase (for %x) */
+#define FORMAT_CONV_SIGNED 0x40 /* Format specifies signed conversion */
+
+enum {
+ MODIFIER_NONE,
+ MODIFIER_CHAR,
+ MODIFIER_SHORT,
+ MODIFIER_LONG,
+ MODIFIER_LONGLONG,
+ MODIFIER_PTR, /* Used only for %p */
+ MODIFIER_SIZE,
+ MODIFIER_PTRDIFF
+};
+
+enum {
+ SPECIFIER_INVALID,
+ SPECIFIER_INT,
+ SPECIFIER_CHAR,
+ SPECIFIER_STR,
+ SPECIFIER_NRCHARS,
+ SPECIFIER_PERCENT
+};
+
+/*
+ * Size for the temporary number buffer. The minimum base is 8 so 3 bits
+ * are consumed per digit. Add one to round up. The conversion algorithm
+ * doesn't use the null byte.
+ */
+#define MAX_NUM_SIZE (((sizeof(unsigned long long) * CHAR_BIT) / 3) + 1)
+
+/*
+ * Special size for xvsnprintf(), used by xsprintf()/xvsprintf() when the
+ * buffer size is unknown.
+ */
+#define XPRINT_NOLIMIT ((size_t)-1)
+
+/*
+ * Size of the static buffer used by xprintf()/xvprintf().
+ */
+#define XPRINT_BUFSIZE 1024
+
+static char xprint_buffer[XPRINT_BUFSIZE];
+static pthread_mutex_t xprint_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static const char digits[] = "0123456789ABCDEF";
+
+static inline char * xputchar(char *str, char *end, char c)
+{
+ if (str < end)
+ *str = c;
+
+ str++;
+
+ return str;
+}
+
+static inline int xisdigit(char c)
+{
+ return (c >= '0') && (c <= '9');
+}
+
+int xprintf(const char *format, ...)
+{
+ va_list ap;
+ int length;
+
+ va_start(ap, format);
+ length = xvprintf(format, ap);
+ va_end(ap);
+
+ return length;
+}
+
+int xvprintf(const char *format, va_list ap)
+{
+ size_t size;
+ int length;
+
+ pthread_mutex_lock(&xprint_mutex);
+ length = xvsnprintf(xprint_buffer, sizeof(xprint_buffer), format, ap);
+ size = ((unsigned int)length >= sizeof(xprint_buffer))
+ ? sizeof(xprint_buffer) - 1
+ : (unsigned int)length;
+ fwrite(xprint_buffer, 1, size, stdout);
+ pthread_mutex_unlock(&xprint_mutex);
+
+ return length;
+}
+
+int xsprintf(char *str, const char *format, ...)
+{
+ va_list ap;
+ int length;
+
+ va_start(ap, format);
+ length = xvsprintf(str, format, ap);
+ va_end(ap);
+
+ return length;
+}
+
+int xvsprintf(char *str, const char *format, va_list ap)
+{
+ return xvsnprintf(str, XPRINT_NOLIMIT, format, ap);
+}
+
+int xsnprintf(char *str, size_t size, const char *format, ...)
+{
+ va_list ap;
+ int length;
+
+ va_start(ap, format);
+ length = xvsnprintf(str, size, format, ap);
+ va_end(ap);
+
+ return length;
+}
+
+int xvsnprintf(char *str, size_t size, const char *format, va_list ap)
+{
+ unsigned long long n;
+ int i, len, found, flags, width, precision, modifier, specifier, shift;
+ unsigned char r, base, mask;
+ char c, *s, *start, *end, sign, tmp[MAX_NUM_SIZE];
+
+ start = str;
+
+ if (size == 0)
+ end = NULL;
+ else if (size == XPRINT_NOLIMIT)
+ end = (char *)-1;
+ else
+ end = start + size - 1;
+
+ while ((c = *format) != '\0') {
+ if (c != '%') {
+ str = xputchar(str, end, c);
+ format++;
+ continue;
+ }
+
+ /* Flags */
+
+ found = 1;
+ flags = 0;
+
+ do {
+ format++;
+ c = *format;
+
+ switch (c) {
+ case '#':
+ flags |= FORMAT_ALT_FORM;
+ break;
+ case '0':
+ flags |= FORMAT_ZERO_PAD;
+ break;
+ case '-':
+ flags |= FORMAT_LEFT_JUSTIFY;
+ break;
+ case ' ':
+ flags |= FORMAT_BLANK;
+ break;
+ case '+':
+ flags |= FORMAT_SIGN;
+ break;
+ default:
+ found = 0;
+ break;
+ }
+ } while (found);
+
+ /* Width */
+
+ if (xisdigit(c)) {
+ width = 0;
+
+ while (xisdigit(c)) {
+ width = width * 10 + (c - '0');
+ format++;
+ c = *format;
+ }
+ } else if (c == '*') {
+ width = va_arg(ap, int);
+
+ if (width < 0) {
+ flags |= FORMAT_LEFT_JUSTIFY;
+ width = -width;
+ }
+
+ format++;
+ c = *format;
+ } else {
+ width = 0;
+ }
+
+ /* Precision */
+
+ if (c == '.') {
+ format++;
+ c = *format;
+
+ if (xisdigit(c)) {
+ precision = 0;
+
+ while (xisdigit(c)) {
+ precision = precision * 10 + (c - '0');
+ format++;
+ c = *format;
+ }
+ } else if (c == '*') {
+ precision = va_arg(ap, int);
+
+ if (precision < 0)
+ precision = 0;
+
+ format++;
+ c = *format;
+ } else {
+ precision = 0;
+ }
+ } else {
+ /* precision is >= 0 only if explicit */
+ precision = -1;
+ }
+
+ /* Length modifier */
+
+ switch (c) {
+ case 'h':
+ case 'l':
+ format++;
+
+ if (c == *format) {
+ modifier = (c == 'h') ? MODIFIER_CHAR : MODIFIER_LONGLONG;
+ goto skip_modifier;
+ } else {
+ modifier = (c == 'h') ? MODIFIER_SHORT : MODIFIER_LONG;
+ c = *format;
+ }
+
+ break;
+ case 'z':
+ modifier = MODIFIER_SIZE;
+ goto skip_modifier;
+ case 't':
+ modifier = MODIFIER_PTRDIFF;
+skip_modifier:
+ format++;
+ c = *format;
+ break;
+ default:
+ modifier = MODIFIER_NONE;
+ break;
+ }
+
+ /* Specifier */
+
+ switch (c) {
+ case 'd':
+ case 'i':
+ flags |= FORMAT_CONV_SIGNED;
+ case 'u':
+ base = 10;
+ goto integer;
+ case 'o':
+ base = 8;
+ goto integer;
+ case 'p':
+ flags |= FORMAT_ALT_FORM;
+ modifier = MODIFIER_PTR;
+ case 'x':
+ flags |= FORMAT_LOWER;
+ case 'X':
+ base = 16;
+integer:
+ specifier = SPECIFIER_INT;
+ break;
+ case 'c':
+ specifier = SPECIFIER_CHAR;
+ break;
+ case 's':
+ specifier = SPECIFIER_STR;
+ break;
+ case 'n':
+ specifier = SPECIFIER_NRCHARS;
+ break;
+ case '%':
+ specifier = SPECIFIER_PERCENT;
+ break;
+ default:
+ specifier = SPECIFIER_INVALID;
+ break;
+ }
+
+ /* Output */
+
+ switch (specifier) {
+ case SPECIFIER_INT:
+ switch (modifier) {
+ case MODIFIER_CHAR:
+ if (flags & FORMAT_CONV_SIGNED)
+ n = (signed char)va_arg(ap, int);
+ else
+ n = (unsigned char)va_arg(ap, int);
+ break;
+ case MODIFIER_SHORT:
+ if (flags & FORMAT_CONV_SIGNED)
+ n = (short)va_arg(ap, int);
+ else
+ n = (unsigned short)va_arg(ap, int);
+ break;
+ case MODIFIER_LONG:
+ if (flags & FORMAT_CONV_SIGNED)
+ n = va_arg(ap, long);
+ else
+ n = va_arg(ap, unsigned long);
+ break;
+ case MODIFIER_LONGLONG:
+ if (flags & FORMAT_CONV_SIGNED)
+ n = va_arg(ap, long long);
+ else
+ n = va_arg(ap, unsigned long long);
+ break;
+ case MODIFIER_PTR:
+ n = (unsigned long)va_arg(ap, void *);
+ break;
+ case MODIFIER_SIZE:
+ if (flags & FORMAT_CONV_SIGNED)
+ n = va_arg(ap, ssize_t);
+ else
+ n = va_arg(ap, size_t);
+ break;
+ case MODIFIER_PTRDIFF:
+ n = va_arg(ap, ptrdiff_t);
+ break;
+ default:
+ if (flags & FORMAT_CONV_SIGNED)
+ n = va_arg(ap, int);
+ else
+ n = va_arg(ap, unsigned int);
+ break;
+ }
+
+ if ((flags & FORMAT_LEFT_JUSTIFY) || (precision >= 0))
+ flags &= ~FORMAT_ZERO_PAD;
+
+ sign = 0;
+
+ if (flags & FORMAT_ALT_FORM) {
+ /* '0' for octal */
+ width--;
+
+ /* '0x' or '0X' for hexadecimal */
+ if (base == 16)
+ width--;
+ } else if (flags & FORMAT_CONV_SIGNED) {
+ if ((long long)n < 0) {
+ sign = '-';
+ width--;
+ n = -(long long)n;
+ } else if (flags & FORMAT_SIGN) {
+ /* FORMAT_SIGN must precede FORMAT_BLANK. */
+ sign = '+';
+ width--;
+ } else if (flags & FORMAT_BLANK) {
+ sign = ' ';
+ width--;
+ }
+ }
+
+ /* Conversion, in reverse order */
+
+ i = 0;
+
+ if (n == 0) {
+ if (precision != 0)
+ tmp[i++] = '0';
+ } else if (base == 10) {
+ /*
+ * Try to avoid 64 bits operations if the processor doesn't
+ * support them. Note that even when using modulus and
+ * division operators close to each other, the compiler may
+ * forge two calls to __udivdi3() and __umoddi3() instead of
+ * one to __udivmoddi3(), whereas processor instructions are
+ * generally correctly used once, giving both the remainder
+ * and the quotient, through plain or reciprocal division.
+ */
+#ifndef __LP64__
+ if (modifier == MODIFIER_LONGLONG) {
+#endif /* __LP64__ */
+ do {
+ r = n % 10;
+ n /= 10;
+ tmp[i++] = digits[r];
+ } while (n != 0);
+#ifndef __LP64__
+ } else {
+ unsigned long m;
+
+ m = (unsigned long)n;
+
+ do {
+ r = m % 10;
+ m /= 10;
+ tmp[i++] = digits[r];
+ } while (m != 0);
+ }
+#endif /* __LP64__ */
+ } else {
+ mask = base - 1;
+ shift = (base == 8) ? 3 : 4;
+
+ do {
+ r = (unsigned char)n & mask;
+ n >>= shift;
+ tmp[i++] = digits[r] | (flags & FORMAT_LOWER);
+ } while (n != 0);
+ }
+
+ if (i > precision)
+ precision = i;
+
+ width -= precision;
+
+ if (!(flags & (FORMAT_LEFT_JUSTIFY | FORMAT_ZERO_PAD)))
+ while (width-- > 0)
+ str = xputchar(str, end, ' ');
+
+ if (flags & FORMAT_ALT_FORM) {
+ str = xputchar(str, end, '0');
+
+ if (base == 16)
+ str = xputchar(str, end, 'X' | (flags & FORMAT_LOWER));
+ } else if (sign) {
+ str = xputchar(str, end, sign);
+ }
+
+ if (!(flags & FORMAT_LEFT_JUSTIFY)) {
+ c = (flags & FORMAT_ZERO_PAD) ? '0' : ' ';
+
+ while (width-- > 0)
+ str = xputchar(str, end, c);
+ }
+
+ while (i < precision--)
+ str = xputchar(str, end, '0');
+
+ while (i-- > 0)
+ str = xputchar(str, end, tmp[i]);
+
+ while (width-- > 0)
+ str = xputchar(str, end, ' ');
+
+ break;
+ case SPECIFIER_CHAR:
+ c = (unsigned char)va_arg(ap, int);
+
+ if (!(flags & FORMAT_LEFT_JUSTIFY))
+ while (--width > 0)
+ str = xputchar(str, end, ' ');
+
+ str = xputchar(str, end, c);
+
+ while (--width > 0)
+ str = xputchar(str, end, ' ');
+
+ break;
+ case SPECIFIER_STR:
+ s = va_arg(ap, char *);
+
+ if (s == NULL)
+ s = "(null)";
+
+ len = 0;
+
+ for (len = 0; s[len] != '\0'; len++)
+ if (len == precision)
+ break;
+
+ if (!(flags & FORMAT_LEFT_JUSTIFY))
+ while (len < width--)
+ str = xputchar(str, end, ' ');
+
+ for (i = 0; i < len; i++) {
+ str = xputchar(str, end, *s);
+ s++;
+ }
+
+ while (len < width--)
+ str = xputchar(str, end, ' ');
+
+ break;
+ case SPECIFIER_NRCHARS:
+ if (modifier == MODIFIER_CHAR) {
+ signed char *ptr = va_arg(ap, signed char *);
+ *ptr = str - start;
+ } else if (modifier == MODIFIER_SHORT) {
+ short *ptr = va_arg(ap, short *);
+ *ptr = str - start;
+ } else if (modifier == MODIFIER_LONG) {
+ long *ptr = va_arg(ap, long *);
+ *ptr = str - start;
+ } else if (modifier == MODIFIER_LONGLONG) {
+ long long *ptr = va_arg(ap, long long *);
+ *ptr = str - start;
+ } else if (modifier == MODIFIER_SIZE) {
+ ssize_t *ptr = va_arg(ap, ssize_t *);
+ *ptr = str - start;
+ } else if (modifier == MODIFIER_PTRDIFF) {
+ ptrdiff_t *ptr = va_arg(ap, ptrdiff_t *);
+ *ptr = str - start;
+ } else {
+ int *ptr = va_arg(ap, int *);
+ *ptr = str - start;
+ }
+
+ break;
+ case SPECIFIER_PERCENT:
+ case SPECIFIER_INVALID:
+ str = xputchar(str, end, '%');
+ break;
+ default:
+ break;
+ }
+
+ if (specifier != SPECIFIER_INVALID)
+ format++;
+ }
+
+ if (str < end)
+ *str = '\0';
+ else if (end != NULL)
+ *end = '\0';
+
+ return str - start;
+}