]> granicus.if.org Git - php/commitdiff
introducing spprintf and vspprintf
authorMarcus Boerger <helly@php.net>
Wed, 10 Apr 2002 01:09:22 +0000 (01:09 +0000)
committerMarcus Boerger <helly@php.net>
Wed, 10 Apr 2002 01:09:22 +0000 (01:09 +0000)
#mail follows

main/spprintf.c [new file with mode: 0644]
main/spprintf.h [new file with mode: 0644]

diff --git a/main/spprintf.c b/main/spprintf.c
new file mode 100644 (file)
index 0000000..6bc1330
--- /dev/null
@@ -0,0 +1,653 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 4                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2002 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 2.02 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available at through the world-wide-web at                           |
+   | http://www.php.net/license/2_02.txt.                                 |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Author: Marcus Boerger <helly@php.net>                               |
+   +----------------------------------------------------------------------+
+ */
+
+/* This is the spprintf implementation.
+ * It has emerged from apache snprintf. See original header:
+ */
+
+/* ====================================================================
+ * Copyright (c) 1995-1998 The Apache Group.  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.
+ *
+ * 3. All advertising materials mentioning features or use of this
+ *    software must display the following acknowledgment:
+ *    "This product includes software developed by the Apache Group
+ *    for use in the Apache HTTP server project (http://www.apache.org/)."
+ *
+ * 4. The names "Apache Server" and "Apache Group" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission.
+ *
+ * 5. Redistributions of any form whatsoever must retain the following
+ *    acknowledgment:
+ *    "This product includes software developed by the Apache Group
+ *    for use in the Apache HTTP server project (http://www.apache.org/)."
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
+ * EXPRESSED 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 APACHE GROUP OR
+ * ITS CONTRIBUTORS 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Group and was originally based
+ * on public domain software written at the National Center for
+ * Supercomputing Applications, University of Illinois, Urbana-Champaign.
+ * For more information on the Apache Group and the Apache HTTP server
+ * project, please see <http://www.apache.org/>.
+ *
+ * This code is based on, and used with the permission of, the
+ * SIO stdio-replacement strx_* functions by Panos Tsirigotis
+ * <panos@alumni.cs.colorado.edu> for xinetd.
+ */
+
+#include "php.h"
+#include "snprintf.h"
+
+#define FALSE           0
+#define TRUE            1
+#define NUL             '\0'
+#define INT_NULL        ((int *)0)
+
+#define S_NULL          "(null)"
+#define S_NULL_LEN      6
+
+#define FLOAT_DIGITS    6
+#define EXPONENT_LENGTH 10
+
+/*
+ * NUM_BUF_SIZE is the size of the buffer used for arithmetic conversions
+ *
+ * XXX: this is a magic number; do not decrease it
+ */
+#define NUM_BUF_SIZE    512
+
+
+/*
+ * Size for realloc operations
+ */
+#define SPPRINTF_BLOCK_SIZE 10
+
+/*
+ * Descriptor for buffer area
+ */
+struct xbuf_area {
+       char    *buf;      /* pointer to buffer */
+       size_t  size;
+       size_t  max_len;
+       char    *buf_end;  /* pointer to buffer end or ~0 */
+       char    *nextb;    /* pointer to next byte to read/write   */
+};
+
+typedef struct xbuf_area xbuffy;
+
+/* Resize xbuf so that add bytes can be added. Reallocation is done
+ * in defined block size to minimize calls to realloc.
+ */
+static int xbuf_resize(xbuffy *xbuf, size_t add) 
+{
+       char *buf;
+       int     ret;
+       size_t size, offset;
+
+       if (xbuf->buf) {
+               offset = xbuf->nextb - xbuf->buf;
+               if (offset+add < xbuf->size) {
+                       return 0; /* do not change size if not necessary */
+               }
+       } else {
+               offset = 0;
+       }
+       if (add<SPPRINTF_BLOCK_SIZE) {
+               size = xbuf->size + SPPRINTF_BLOCK_SIZE;
+       } else {
+               size = xbuf->size + add;
+       }
+       if (xbuf->max_len && size > xbuf->max_len) {
+               size = xbuf->max_len;
+               ret = 1;
+       } else {
+               ret = 0;
+       }
+       
+       buf = erealloc(xbuf->buf, size+1); /* alloc space for NUL */
+       
+       if (!buf) {
+               return 1;
+       } else {
+               xbuf->buf = buf;
+               xbuf->buf_end = xbuf->max_len ? &buf[size] : (char *) ~0;
+               xbuf->nextb = buf+offset;
+               xbuf->size = size;
+               return ret;
+       }
+}
+
+/* Initialise xbuffy with size spprintf_BLOCK_SIZE
+ */
+static int xbuf_init(xbuffy *xbuf, size_t max_len) 
+{
+       xbuf->buf = NULL;
+       xbuf->size = 0;
+       xbuf->max_len = max_len;
+       return xbuf_resize(xbuf, 0); /* NOT max_len */
+}
+
+/*
+ * The INS_CHAR macro inserts a character in the buffer and writes
+ * the buffer back to disk if necessary
+ * It uses the char pointers sp and bep:
+ *      sp points to the next available character in the buffer
+ *      bep points to the end-of-buffer+1
+ * While using this macro, note that the nextb pointer is NOT updated.
+ *
+ * NOTE: Evaluation of the c argument should not have any side-effects
+ */
+#define INS_CHAR_NR(xbuf, ch, cc)       \
+       if (xbuf->nextb < xbuf->buf_end) {  \
+               *(xbuf->nextb++) = ch;          \
+               cc++;                           \
+       }
+
+#define INS_CHAR(xbuf, ch, cc)          \
+       xbuf_resize(xbuf, 1);               \
+       INS_CHAR_NR(xbuf, ch, cc)
+
+/*
+ * Macro that does padding. The padding is done by printing
+ * the character ch.
+ */
+#define PAD(xbuf, width, len, ch)       \
+       if (width > len) {                  \
+               xbuf_resize(xbuf, width-len);   \
+               do {                            \
+                       INS_CHAR_NR(xbuf, ch, cc);  \
+                       width--;                    \
+               }                               \
+               while (width > len);\
+       }
+
+#define NUM(c) (c - '0')
+
+#define STR_TO_DEC(str, num)            \
+       num = NUM(*str++);                  \
+       while (isdigit((int)*str)) {        \
+               num *= 10;                      \
+               num += NUM(*str++);             \
+    }
+
+/*
+ * This macro does zero padding so that the precision
+ * requirement is satisfied. The padding is done by
+ * adding '0's to the left of the string that is going
+ * to be printed.
+ */
+#define FIX_PRECISION(adjust, precision, s, s_len)  \
+    if (adjust)                                                            \
+               while (s_len < precision) {                 \
+                       *--s = '0';                             \
+                       s_len++;                                \
+               }
+
+/*
+ * Prefix the character ch to the string str
+ * Increase length
+ * Set the has_prefix flag
+ */
+#define PREFIX(str, length, ch) \
+       *--str = ch;                \
+       length++;                   \
+       has_prefix = YES
+
+
+
+/*
+ * Do format conversion placing the output in buffer
+ */
+static int xbuf_format_converter(register xbuffy * xbuf, const char *fmt, va_list ap)
+{
+       register int cc = 0;
+       register int i;
+
+       register char *s = NULL;
+       char *q;
+       int s_len;
+
+       register int min_width = 0;
+       int precision = 0;
+       enum {
+               LEFT, RIGHT
+       } adjust;
+       char pad_char;
+       char prefix_char;
+
+       double fp_num;
+       wide_int i_num = (wide_int) 0;
+       u_wide_int ui_num;
+
+       char num_buf[NUM_BUF_SIZE];
+       char char_buf[2];                       /* for printing %% and %<unknown> */
+
+       /*
+        * Flag variables
+        */
+       boolean_e is_long;
+       boolean_e alternate_form;
+       boolean_e print_sign;
+       boolean_e print_blank;
+       boolean_e adjust_precision;
+       boolean_e adjust_width;
+       bool_int is_negative;
+
+       while (*fmt) {
+               if (*fmt != '%') {
+                       INS_CHAR(xbuf, *fmt, cc);
+               } else {
+                       /*
+                        * Default variable settings
+                        */
+                       adjust = RIGHT;
+                       alternate_form = print_sign = print_blank = NO;
+                       pad_char = ' ';
+                       prefix_char = NUL;
+
+                       fmt++;
+
+                       /*
+                        * Try to avoid checking for flags, width or precision
+                        */
+                       if (isascii((int)*fmt) && !islower((int)*fmt)) {
+                               /*
+                                * Recognize flags: -, #, BLANK, +
+                                */
+                               for (;; fmt++) {
+                                       if (*fmt == '-')
+                                               adjust = LEFT;
+                                       else if (*fmt == '+')
+                                               print_sign = YES;
+                                       else if (*fmt == '#')
+                                               alternate_form = YES;
+                                       else if (*fmt == ' ')
+                                               print_blank = YES;
+                                       else if (*fmt == '0')
+                                               pad_char = '0';
+                                       else
+                                               break;
+                               }
+
+                               /*
+                                * Check if a width was specified
+                                */
+                               if (isdigit((int)*fmt)) {
+                                       STR_TO_DEC(fmt, min_width);
+                                       adjust_width = YES;
+                               } else if (*fmt == '*') {
+                                       min_width = va_arg(ap, int);
+                                       fmt++;
+                                       adjust_width = YES;
+                                       if (min_width < 0) {
+                                               adjust = LEFT;
+                                               min_width = -min_width;
+                                       }
+                               } else
+                                       adjust_width = NO;
+
+                               /*
+                                * Check if a precision was specified
+                                *
+                                * XXX: an unreasonable amount of precision may be specified
+                                * resulting in overflow of num_buf. Currently we
+                                * ignore this possibility.
+                                */
+                               if (*fmt == '.') {
+                                       adjust_precision = YES;
+                                       fmt++;
+                                       if (isdigit((int)*fmt)) {
+                                               STR_TO_DEC(fmt, precision);
+                                       } else if (*fmt == '*') {
+                                               precision = va_arg(ap, int);
+                                               fmt++;
+                                               if (precision < 0)
+                                                       precision = 0;
+                                       } else
+                                               precision = 0;
+                               } else
+                                       adjust_precision = NO;
+                       } else
+                               adjust_precision = adjust_width = NO;
+
+                       /*
+                        * Modifier check
+                        */
+                       if (*fmt == 'l') {
+                               is_long = YES;
+                               fmt++;
+                       } else
+                               is_long = NO;
+
+                       /*
+                        * Argument extraction and printing.
+                        * First we determine the argument type.
+                        * Then, we convert the argument to a string.
+                        * On exit from the switch, s points to the string that
+                        * must be printed, s_len has the length of the string
+                        * The precision requirements, if any, are reflected in s_len.
+                        *
+                        * NOTE: pad_char may be set to '0' because of the 0 flag.
+                        *   It is reset to ' ' by non-numeric formats
+                        */
+                       switch (*fmt) {
+                               case 'u':
+                                       if (is_long)
+                                               i_num = va_arg(ap, u_wide_int);
+                                       else
+                                               i_num = (wide_int) va_arg(ap, unsigned int);
+                                       /*
+                                        * The rest also applies to other integer formats, so fall
+                                        * into that case.
+                                        */
+                               case 'd':
+                               case 'i':
+                                       /*
+                                        * Get the arg if we haven't already.
+                                        */
+                                       if ((*fmt) != 'u') {
+                                               if (is_long)
+                                                       i_num = va_arg(ap, wide_int);
+                                               else
+                                                       i_num = (wide_int) va_arg(ap, int);
+                                       };
+                                       s = ap_php_conv_10(i_num, (*fmt) == 'u', &is_negative,
+                                                               &num_buf[NUM_BUF_SIZE], &s_len);
+                                       FIX_PRECISION(adjust_precision, precision, s, s_len);
+
+                                       if (*fmt != 'u') {
+                                               if (is_negative)
+                                                       prefix_char = '-';
+                                               else if (print_sign)
+                                                       prefix_char = '+';
+                                               else if (print_blank)
+                                                       prefix_char = ' ';
+                                       }
+                                       break;
+
+
+                               case 'o':
+                                       if (is_long)
+                                               ui_num = va_arg(ap, u_wide_int);
+                                       else
+                                               ui_num = (u_wide_int) va_arg(ap, unsigned int);
+                                       s = ap_php_conv_p2(ui_num, 3, *fmt,
+                                                               &num_buf[NUM_BUF_SIZE], &s_len);
+                                       FIX_PRECISION(adjust_precision, precision, s, s_len);
+                                       if (alternate_form && *s != '0') {
+                                               *--s = '0';
+                                               s_len++;
+                                       }
+                                       break;
+
+
+                               case 'x':
+                               case 'X':
+                                       if (is_long)
+                                               ui_num = (u_wide_int) va_arg(ap, u_wide_int);
+                                       else
+                                               ui_num = (u_wide_int) va_arg(ap, unsigned int);
+                                       s = ap_php_conv_p2(ui_num, 4, *fmt,
+                                                               &num_buf[NUM_BUF_SIZE], &s_len);
+                                       FIX_PRECISION(adjust_precision, precision, s, s_len);
+                                       if (alternate_form && i_num != 0) {
+                                               *--s = *fmt;    /* 'x' or 'X' */
+                                               *--s = '0';
+                                               s_len += 2;
+                                       }
+                                       break;
+
+
+                               case 's':
+                                       s = va_arg(ap, char *);
+                                       if (s != NULL) {
+                                               s_len = strlen(s);
+                                               if (adjust_precision && precision < s_len)
+                                                       s_len = precision;
+                                       } else {
+                                               s = S_NULL;
+                                               s_len = S_NULL_LEN;
+                                       }
+                                       pad_char = ' ';
+                                       break;
+
+
+                               case 'f':
+                               case 'e':
+                               case 'E':
+                                       fp_num = va_arg(ap, double);
+
+                                       if (zend_isnan(fp_num)) {
+                                               s = "nan";
+                                               s_len = 3;
+                                       } else if (zend_isinf(fp_num)) {
+                                               s = "inf";
+                                               s_len = 3;
+                                       } else {
+                                               s = ap_php_conv_fp(*fmt, fp_num, alternate_form,
+                                                                       (adjust_precision == NO) ? FLOAT_DIGITS : precision,
+                                                                       &is_negative, &num_buf[1], &s_len);
+                                               if (is_negative)
+                                                       prefix_char = '-';
+                                               else if (print_sign)
+                                                       prefix_char = '+';
+                                               else if (print_blank)
+                                                       prefix_char = ' ';
+                                       }
+                                       break;
+
+
+                               case 'g':
+                               case 'G':
+                                       if (adjust_precision == NO)
+                                               precision = FLOAT_DIGITS;
+                                       else if (precision == 0)
+                                               precision = 1;
+                                       /*
+                                        * * We use &num_buf[ 1 ], so that we have room for the sign
+                                        */
+                                       s = ap_php_gcvt(va_arg(ap, double), precision, &num_buf[1]);
+                                       if (*s == '-')
+                                               prefix_char = *s++;
+                                       else if (print_sign)
+                                               prefix_char = '+';
+                                       else if (print_blank)
+                                               prefix_char = ' ';
+
+                                       s_len = strlen(s);
+
+                                       if (alternate_form && (q = strchr(s, '.')) == NULL)
+                                               s[s_len++] = '.';
+                                       if (*fmt == 'G' && (q = strchr(s, 'e')) != NULL)
+                                               *q = 'E';
+                                       break;
+
+
+                               case 'c':
+                                       char_buf[0] = (char) (va_arg(ap, int));
+                                       s = &char_buf[0];
+                                       s_len = 1;
+                                       pad_char = ' ';
+                                       break;
+
+
+                               case '%':
+                                       char_buf[0] = '%';
+                                       s = &char_buf[0];
+                                       s_len = 1;
+                                       pad_char = ' ';
+                                       break;
+
+
+                               case 'n':
+                                       *(va_arg(ap, int *)) = cc;
+                                       break;
+
+                                       /*
+                                        * Always extract the argument as a "char *" pointer. We 
+                                        * should be using "void *" but there are still machines 
+                                        * that don't understand it.
+                                        * If the pointer size is equal to the size of an unsigned
+                                        * integer we convert the pointer to a hex number, otherwise 
+                                        * we print "%p" to indicate that we don't handle "%p".
+                                        */
+                               case 'p':
+                                       ui_num = (u_wide_int) va_arg(ap, char *);
+
+                                       if (sizeof(char *) <= sizeof(u_wide_int))
+                                               s = ap_php_conv_p2(ui_num, 4, 'x', &num_buf[NUM_BUF_SIZE], &s_len);
+                                       else {
+                                               s = "%p";
+                                               s_len = 2;
+                                       }
+                                       pad_char = ' ';
+                                       break;
+
+
+                               case NUL:
+                                       /*
+                                        * The last character of the format string was %.
+                                        * We ignore it.
+                                        */
+                                       continue;
+
+
+                                       /*
+                                        * The default case is for unrecognized %'s.
+                                        * We print %<char> to help the user identify what
+                                        * option is not understood.
+                                        * This is also useful in case the user wants to pass
+                                        * the output of format_converter to another function
+                                        * that understands some other %<char> (like syslog).
+                                        * Note that we can't point s inside fmt because the
+                                        * unknown <char> could be preceded by width etc.
+                                        */
+                               default:
+                                       char_buf[0] = '%';
+                                       char_buf[1] = *fmt;
+                                       s = char_buf;
+                                       s_len = 2;
+                                       pad_char = ' ';
+                                       break;
+                       }
+
+                       if (prefix_char != NUL) {
+                               *--s = prefix_char;
+                               s_len++;
+                       }
+                       if (adjust_width && adjust == RIGHT && min_width > s_len) {
+                               if (pad_char == '0' && prefix_char != NUL) {
+                                       INS_CHAR(xbuf, *s, cc)
+                                               s++;
+                                       s_len--;
+                                       min_width--;
+                               }
+                               PAD(xbuf, min_width, s_len, pad_char);
+                       }
+                       /*
+                        * Print the string s. 
+                        */
+                       xbuf_resize(xbuf, s_len);
+                       for (i = s_len; i != 0; i--) {
+                               INS_CHAR_NR(xbuf, *s, cc);
+                               s++;
+                       }
+
+                       if (adjust_width && adjust == LEFT && min_width > s_len)
+                               PAD(xbuf, min_width, s_len, pad_char);
+               }
+               fmt++;
+       }
+       return (cc);
+}
+
+
+/*
+ * This is the general purpose conversion function.
+ */
+int vspprintf(char **pbuf, size_t max_len, const char *format, va_list ap)
+{
+       xbuffy xbuf;
+       int cc;
+
+       assert(pbuf != NULL);
+       /*
+        * First initialize the descriptor
+        * Notice that if no length is given, we initialize buf_end to the
+        * highest possible address.
+        */
+       if (xbuf_init(&xbuf, max_len)) {
+               *pbuf = NULL;
+               return 0;
+       } else {
+               /*
+                * Do the conversion
+                */
+               cc = xbuf_format_converter(&xbuf, format, ap);
+               if (xbuf.nextb <= xbuf.buf_end)
+                       *(xbuf.nextb) = '\0';
+               *pbuf = xbuf.buf;
+               return cc;
+       }
+}
+
+
+int spprintf(char **pbuf, size_t max_len, const char *format, ...)
+{
+       int cc;
+       va_list ap;
+
+       va_start(ap, format);
+       vspprintf(pbuf, max_len, format, ap);
+       va_end(ap);
+       return (cc);
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: sw=4 ts=4 fdm=marker
+ * vim<600: sw=4 ts=4
+ */
diff --git a/main/spprintf.h b/main/spprintf.h
new file mode 100644 (file)
index 0000000..3c0b0ab
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 4                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2002 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 2.02 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available at through the world-wide-web at                           |
+   | http://www.php.net/license/2_02.txt.                                 |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Author: Marcus Boerger <helly@php.net>                               |
+   +----------------------------------------------------------------------+
+ */
+
+#ifndef SPPRINTF_H
+#define SPPRINTF_H
+
+#include "snprintf.h"
+
+extern int spprintf( char **pbuf, size_t max_len, const char *format, ...);
+
+extern int vspprintf(char **pbuf, size_t max_len, const char *format, va_list ap);
+
+#endif /* SNPRINTF_H */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ */