/* * Copyright (c) 1993-1994 by Xerox Corporation. All rights reserved. * * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED * OR IMPLIED. ANY USE IS AT YOUR OWN RISK. * * Permission is hereby granted to use or copy this program * for any purpose, provided the above notices are retained on all copies. * Permission to modify the code and to distribute modified code is granted, * provided the above notices are retained, and a notice that the code was * modified is included with the above copyright notice. */ /* An sprintf implementation that understands cords. This is probably */ /* not terribly portable. It assumes an ANSI stdarg.h. It further */ /* assumes that I can make copies of va_list variables, and read */ /* arguments repeatedly by applyting va_arg to the copies. This */ /* could be avoided at some performance cost. */ /* We also assume that unsigned and signed integers of various kinds */ /* have the same sizes, and can be cast back and forth. */ /* We assume that void * and char * have the same size. */ /* All this cruft is needed because we want to rely on the underlying */ /* sprintf implementation whenever possible. */ /* Boehm, September 21, 1995 6:00 pm PDT */ #include "cord.h" #include "ec.h" #include <stdio.h> #include <stdarg.h> #include <string.h> #include "gc.h" #define CONV_SPEC_LEN 50 /* Maximum length of a single */ /* conversion specification. */ #define CONV_RESULT_LEN 50 /* Maximum length of any */ /* conversion with default */ /* width and prec. */ static int ec_len(CORD_ec x) { return(CORD_len(x[0].ec_cord) + (x[0].ec_bufptr - x[0].ec_buf)); } /* Possible nonumeric precision values. */ # define NONE -1 # define VARIABLE -2 /* Copy the conversion specification from CORD_pos into the buffer buf */ /* Return negative on error. */ /* Source initially points one past the leading %. */ /* It is left pointing at the conversion type. */ /* Assign field width and precision to *width and *prec. */ /* If width or prec is *, VARIABLE is assigned. */ /* Set *left to 1 if left adjustment flag is present. */ /* Set *long_arg to 1 if long flag ('l' or 'L') is present, or to */ /* -1 if 'h' is present. */ static int extract_conv_spec(CORD_pos source, char *buf, int * width, int *prec, int *left, int * long_arg) { register int result = 0; register int current_number = 0; register int saw_period = 0; register int saw_number; register int chars_so_far = 0; register char current; *width = NONE; buf[chars_so_far++] = '%'; while(CORD_pos_valid(source)) { if (chars_so_far >= CONV_SPEC_LEN) return(-1); current = CORD_pos_fetch(source); buf[chars_so_far++] = current; switch(current) { case '*': saw_number = 1; current_number = VARIABLE; break; case '0': if (!saw_number) { /* Zero fill flag; ignore */ break; } /* otherwise fall through: */ case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': saw_number = 1; current_number *= 10; current_number += current - '0'; break; case '.': saw_period = 1; if(saw_number) { *width = current_number; saw_number = 0; } current_number = 0; break; case 'l': case 'L': *long_arg = 1; current_number = 0; break; case 'h': *long_arg = -1; current_number = 0; break; case ' ': case '+': case '#': current_number = 0; break; case '-': *left = 1; current_number = 0; break; case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': case 'f': case 'e': case 'E': case 'g': case 'G': case 'c': case 'C': case 's': case 'S': case 'p': case 'n': case 'r': goto done; default: return(-1); } CORD_next(source); } return(-1); done: if (saw_number) { if (saw_period) { *prec = current_number; } else { *prec = NONE; *width = current_number; } } else { *prec = NONE; } buf[chars_so_far] = '\0'; return(result); } int CORD_vsprintf(CORD * out, CORD format, va_list args) { CORD_ec result; register int count; register char current; CORD_pos pos; char conv_spec[CONV_SPEC_LEN + 1]; CORD_ec_init(result); for (CORD_set_pos(pos, format, 0); CORD_pos_valid(pos); CORD_next(pos)) { current = CORD_pos_fetch(pos); if (current == '%') { CORD_next(pos); if (!CORD_pos_valid(pos)) return(-1); current = CORD_pos_fetch(pos); if (current == '%') { CORD_ec_append(result, current); } else { int width, prec; int left_adj = 0; int long_arg = 0; CORD arg; size_t len; if (extract_conv_spec(pos, conv_spec, &width, &prec, &left_adj, &long_arg) < 0) { return(-1); } current = CORD_pos_fetch(pos); switch(current) { case 'n': /* Assign length to next arg */ if (long_arg == 0) { int * pos_ptr; pos_ptr = va_arg(args, int *); *pos_ptr = ec_len(result); } else if (long_arg > 0) { long * pos_ptr; pos_ptr = va_arg(args, long *); *pos_ptr = ec_len(result); } else { short * pos_ptr; pos_ptr = va_arg(args, short *); *pos_ptr = ec_len(result); } goto done; case 'r': /* Append cord and any padding */ if (width == VARIABLE) width = va_arg(args, int); if (prec == VARIABLE) prec = va_arg(args, int); arg = va_arg(args, CORD); len = CORD_len(arg); if (prec != NONE && len > prec) { if (prec < 0) return(-1); arg = CORD_substr(arg, 0, prec); len = prec; } if (width != NONE && len < width) { char * blanks = GC_MALLOC_ATOMIC(width-len+1); memset(blanks, ' ', width-len); blanks[width-len] = '\0'; if (left_adj) { arg = CORD_cat(arg, blanks); } else { arg = CORD_cat(blanks, arg); } } CORD_ec_append_cord(result, arg); goto done; case 'c': if (width == NONE && prec == NONE) { register char c; c = va_arg(args, int); CORD_ec_append(result, c); goto done; } break; case 's': if (width == NONE && prec == NONE) { char * str = va_arg(args, char *); register char c; while (c = *str++) { CORD_ec_append(result, c); } goto done; } break; default: break; } /* Use standard sprintf to perform conversion */ { register char * buf; va_list vsprintf_args = args; /* The above does not appear to be sanctioned */ /* by the ANSI C standard. */ int max_size = 0; int res; if (width == VARIABLE) width = va_arg(args, int); if (prec == VARIABLE) prec = va_arg(args, int); if (width != NONE) max_size = width; if (prec != NONE && prec > max_size) max_size = prec; max_size += CONV_RESULT_LEN; if (max_size >= CORD_BUFSZ) { buf = GC_MALLOC_ATOMIC(max_size + 1); } else { if (CORD_BUFSZ - (result[0].ec_bufptr-result[0].ec_buf) < max_size) { CORD_ec_flush_buf(result); } buf = result[0].ec_bufptr; } switch(current) { case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': case 'c': if (long_arg <= 0) { (void) va_arg(args, int); } else if (long_arg > 0) { (void) va_arg(args, long); } break; case 's': case 'p': (void) va_arg(args, char *); break; case 'f': case 'e': case 'E': case 'g': case 'G': (void) va_arg(args, double); break; default: return(-1); } res = vsprintf(buf, conv_spec, vsprintf_args); len = (size_t)res; if ((char *)(GC_word)res == buf) { /* old style vsprintf */ len = strlen(buf); } else if (res < 0) { return(-1); } if (buf != result[0].ec_bufptr) { register char c; while (c = *buf++) { CORD_ec_append(result, c); } } else { result[0].ec_bufptr = buf + len; } } done:; } } else { CORD_ec_append(result, current); } } count = ec_len(result); *out = CORD_balance(CORD_ec_to_cord(result)); return(count); } int CORD_sprintf(CORD * out, CORD format, ...) { va_list args; int result; va_start(args, format); result = CORD_vsprintf(out, format, args); va_end(args); return(result); } int CORD_fprintf(FILE * f, CORD format, ...) { va_list args; int result; CORD out; va_start(args, format); result = CORD_vsprintf(&out, format, args); va_end(args); if (result > 0) CORD_put(out, f); return(result); } int CORD_vfprintf(FILE * f, CORD format, va_list args) { int result; CORD out; result = CORD_vsprintf(&out, format, args); if (result > 0) CORD_put(out, f); return(result); } int CORD_printf(CORD format, ...) { va_list args; int result; CORD out; va_start(args, format); result = CORD_vsprintf(&out, format, args); va_end(args); if (result > 0) CORD_put(out, stdout); return(result); } int CORD_vprintf(CORD format, va_list args) { int result; CORD out; result = CORD_vsprintf(&out, format, args); if (result > 0) CORD_put(out, stdout); return(result); }