| /* |
| * 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 = (char)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; |
| int max_size = 0; |
| int res; |
| # ifdef __va_copy |
| __va_copy(vsprintf_args, args); |
| # else |
| # if defined(__GNUC__) /* and probably in other cases */ |
| va_copy(vsprintf_args, args); |
| # else |
| vsprintf_args = args; |
| # endif |
| # endif |
| 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); |
| } |