/* See {jsprinf.h} */ /* Last edited on 2024-12-21 04:45:56 by stolfi */ #include #include #include #include #include typedef int64_t ptrdiff_t; /* Should be right for any modern machine. */ #include #include /* These functions are based on the similar ones from the GNU {libiberty} package, which is copyright (C) 1994-2024 Free Software Foundation, Inc. See the GNU Library General Public version 2 or later for use rights. */ /* INTERNAL PROTOTYPES */ char* jsprintf_va(const char *fmt, va_list args); /* Replacement for {libiberty's} {vasprintf}, etc. Same as {jsprintf} but takes a single {va_list} as argument instead of a variable number of individual values. */ size_t jsprintf_buffer_size(const char *fmt, va_list args); /* Returns an upper bound to the the length of the string that would be produced by {sprintf} with the format {fmt} and value list {args} function. Used by {jsprintf_va}. */ void jsprintf_process_format_code(char **p_P, uint64_t *buf_size_P, va_list ap); /* Expects that {*p_P} is a pointer to the '%' of a formatting spec in a format string, and that the value list {ap} is positioned to the next unprocessed argument of {jsprintf}. Parses that formatting spec and consumes all the {va} arguments that it uses, computing the maximum length {len} of the string that could be generated by that code. Upon exit, {*p_P} will be pointing to the next char in the format string after that code, and {*buf_size_P} will have been incremented by {len}. Does NOT accept the "$" flag in the formatting code. */ /* IMPLEMENTATIONS */ char* jsprintf(const char *fmt, ...) { va_list ap; va_start(ap, fmt); char *bufptr = jsprintf_va(fmt, ap); va_end(ap); return bufptr; } char* jsprintf_va(const char *fmt, va_list args) { size_t buf_size = jsprintf_buffer_size(fmt, args); char *bufptr = talloc(buf_size, char); int32_t status = vsnprintf(bufptr, buf_size, fmt, args); demand(status >= 0, "invalid format and/or values"); demand(status < buf_size, "allocation size error"); return bufptr; } size_t jsprintf_buffer_size(const char *fmt, va_list args) { char *p = (char *)fmt; /* Add one to make sure that it is never zero, which might cause malloc to return NULL. */ uint64_t buf_size = strlen(fmt) + 1; va_list ap; va_copy(ap, args); while ((*p) != '\0') { if ((*p) == '%') { jsprintf_process_format_code(&p, &buf_size, ap); } else { p++; buf_size++; } } va_end(ap); return (size_t)buf_size; } void jsprintf_process_format_code(char **p_P, uint64_t *buf_size_P, va_list ap) { char *p = (*p_P); uint64_t buf_size = (*buf_size_P); assert((*p) == '%'); p++; int32_t prec = 0; while (strchr ("-+ #0", (*p))) { ++p; } if ((*p) == '*') { ++p; buf_size += (uint64_t)abs(va_arg(ap, int)); } else { buf_size += strtoul(p, (char **)(&p), 10); } if ((*p) == '.') { ++p; if ((*p) == '*') { ++p; buf_size += (uint64_t)abs(va_arg(ap, int)); } else { buf_size += strtoul(p, (char **)(&p), 10); } } do { switch (*p) { case 'h': ++p; continue; case 'l': case 'L': ++prec; ++p; continue; case 'z': prec = 3; ++p; continue; case 't': prec = 4; ++p; continue; default: break; } break; } while(1); /* Should be big enough for any fmt specifier except %s and floats. */ buf_size += 30; switch (*p) { case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': switch (prec) { case 0: (void) va_arg(ap, int); break; case 1: (void) va_arg(ap, long int); break; case 2: (void) va_arg(ap, long long int); break; case 3: (void) va_arg(ap, size_t); break; case 4: (void) va_arg(ap, ptrdiff_t); break; } break; case 'c': (void) va_arg(ap, int); break; case 'f': case 'e': case 'E': case 'g': case 'G': if (prec == 0) { (void) va_arg(ap, double); /* Since an ieee double can have an exponent of 308, we'll make the buffer wide enough to cover the gross case. */ buf_size += 308; } else { (void) va_arg(ap, long double); buf_size += 4932; } break; case 's': buf_size += strlen(va_arg(ap, char*)); break; case 'p': case 'n': (void) va_arg(ap, char*); break; } (*p_P) = p; (*buf_size_P) = buf_size; }