1 |
/* formf.c -- formatted strings and error messages */ |
2 |
|
3 |
/* Copyright 1992 Godfrey Paul, University of Kent at Canterbury. |
4 |
* |
5 |
* You can do what you like with this source code as long as |
6 |
* you don't try to make money out of it and you include an |
7 |
* unaltered copy of this message (including the copyright). |
8 |
*/ |
9 |
|
10 |
char ukcprog_formf_sccsid[] = "$Id: formf.c,v 1.25 1999/07/15 11:10:21 djb1 Exp $ UKC"; |
11 |
|
12 |
#ifdef __STDC__ |
13 |
#include <stdarg.h> |
14 |
#else |
15 |
#include <varargs.h> |
16 |
#endif |
17 |
|
18 |
#include <stdio.h> |
19 |
#include <ctype.h> |
20 |
#include <errno.h> |
21 |
#include <stdlib.h> |
22 |
#include <string.h> |
23 |
#ifndef __STDC__ |
24 |
#include <memory.h> |
25 |
#endif |
26 |
|
27 |
#include "ukcprog.h" |
28 |
|
29 |
#ifndef MSDOS |
30 |
#ifndef __FreeBSD__ |
31 |
#ifndef linux |
32 |
extern int sys_nerr, errno; |
33 |
extern char *sys_errlist[]; |
34 |
#endif /* !linux */ |
35 |
#endif /* !__FreeBSD__ */ |
36 |
#endif /* !MSDOS */ |
37 |
|
38 |
|
39 |
/* forward declarations */ |
40 |
static void new_buffer PROTO((char ** p_buf, int *p_lim, bool *p_from_malloc)); |
41 |
static void concat PROTO((char **p_buf, int *p_pos, int* p_lim, |
42 |
bool *p_from_malloc, const char *str, int len)); |
43 |
static char *long_to_ascii PROTO((unsigned long unum, int base, |
44 |
bool want_uppercase)); |
45 |
static char *float_to_ascii PROTO((double d, int precision)); |
46 |
static char *efloat_to_ascii PROTO((double d, int precision, |
47 |
bool want_uppercase)); |
48 |
static char *gfloat_to_ascii PROTO((double d, int precision, |
49 |
bool want_uppercase)); |
50 |
|
51 |
#define MAX_NUM_LEN 40 /* buffer sizes used in numeric conversions */ |
52 |
#define MAX_FLOAT_LEN 128 |
53 |
|
54 |
|
55 |
/* |
56 |
* new_buffer() |
57 |
* Reallocate the given buffer to twice its current size. *p_lim is the |
58 |
* last usable character in the buffer. The buffer passed in might |
59 |
* not have been allocated by malloc(); this is indicated by *p_from_malloc. |
60 |
*/ |
61 |
static void |
62 |
new_buffer(p_buf, p_lim, p_from_malloc) |
63 |
char **p_buf; |
64 |
int *p_lim; |
65 |
bool *p_from_malloc; |
66 |
{ |
67 |
char *new; |
68 |
size_t size; |
69 |
|
70 |
size = *p_lim + 1; |
71 |
|
72 |
if (*p_from_malloc) |
73 |
new = realloc(*p_buf, size * 2); |
74 |
|
75 |
else { |
76 |
if ((new = malloc(size * 2)) != NULL) |
77 |
memcpy(new, *p_buf, size); |
78 |
*p_from_malloc = TRUE; |
79 |
} |
80 |
|
81 |
if (new == NULL) |
82 |
panic("malloc returned NULL in new_buffer"); |
83 |
|
84 |
*p_buf = new; |
85 |
*p_lim = (size * 2) - 1; |
86 |
} |
87 |
|
88 |
|
89 |
/* |
90 |
* concat() |
91 |
* Add exactly len bytes of str to the buffer, expanding it with |
92 |
* new_buffer() if we need extra space. Str must be at least len |
93 |
* bytes long. |
94 |
*/ |
95 |
static void |
96 |
concat(p_buf, p_pos, p_lim, p_from_malloc, str, len) |
97 |
char **p_buf; |
98 |
int *p_pos, *p_lim; |
99 |
bool *p_from_malloc; |
100 |
const char *str; |
101 |
int len; |
102 |
{ |
103 |
while (*p_lim - *p_pos < len) |
104 |
new_buffer(p_buf, p_lim, p_from_malloc); |
105 |
|
106 |
memcpy(&(*p_buf)[*p_pos], str, (size_t)len); |
107 |
*p_pos += len; |
108 |
} |
109 |
|
110 |
|
111 |
static char * |
112 |
long_to_ascii(unum, base, want_uppercase) |
113 |
unsigned long unum; |
114 |
int base; |
115 |
bool want_uppercase; |
116 |
{ |
117 |
static char nbuf[MAX_NUM_LEN + 1]; |
118 |
char *s; |
119 |
const char *digs; |
120 |
|
121 |
s = nbuf + MAX_NUM_LEN; |
122 |
|
123 |
digs = want_uppercase ? "0123456789ABCDEF" : "0123456789abcdef"; |
124 |
do { |
125 |
*--s = digs[unum % base]; |
126 |
unum /= base; |
127 |
} while (unum != 0); |
128 |
|
129 |
|
130 |
return s; |
131 |
} |
132 |
|
133 |
|
134 |
static char * |
135 |
float_to_ascii(d, precision) |
136 |
double d; |
137 |
int precision; |
138 |
{ |
139 |
static char buf[MAX_FLOAT_LEN]; |
140 |
|
141 |
sprintf(buf, "%.*f", precision, d); |
142 |
return buf; |
143 |
} |
144 |
|
145 |
|
146 |
static char * |
147 |
efloat_to_ascii(d, precision, want_uppercase) |
148 |
double d; |
149 |
int precision; |
150 |
bool want_uppercase; |
151 |
{ |
152 |
static char buf[MAX_FLOAT_LEN]; |
153 |
const char *format; |
154 |
|
155 |
if (want_uppercase) |
156 |
format = "%.*E"; |
157 |
else |
158 |
format = "%.*e"; |
159 |
|
160 |
sprintf(buf, format, precision, d); |
161 |
return buf; |
162 |
} |
163 |
|
164 |
|
165 |
|
166 |
static char * |
167 |
gfloat_to_ascii(d, precision, want_uppercase) |
168 |
double d; |
169 |
int precision; |
170 |
bool want_uppercase; |
171 |
{ |
172 |
static char buf[MAX_FLOAT_LEN]; |
173 |
const char *format; |
174 |
|
175 |
if (want_uppercase) |
176 |
format = "%.*G"; |
177 |
else |
178 |
format = "%.*g"; |
179 |
|
180 |
sprintf(buf, format, precision, d); |
181 |
return buf; |
182 |
} |
183 |
|
184 |
#ifdef VMS |
185 |
static const char * |
186 |
vms_error_text(unsigned long msgid) |
187 |
{ |
188 |
unsigned long status; |
189 |
unsigned short msglen; |
190 |
static char buf[256 + 1]; |
191 |
static struct dsc$descriptor_s msg = { |
192 |
sizeof buf - 1, /* length */ |
193 |
DSC$K_DTYPE_T, /* data type (8 bit chars) */ |
194 |
DSC$K_CLASS_S, /* class (fixed length) */ |
195 |
buf |
196 |
}; |
197 |
|
198 |
status = SYS$GETMSG(msgid, &msglen, &msg, 0, 0); |
199 |
if (status != SS$_NORMAL && status != SS$_MSGNOTFND) |
200 |
sprintf(buf, "Unknown VMS message ID 0x%x", msgid); |
201 |
else |
202 |
buf[msglen] = '\0'; |
203 |
|
204 |
return buf; |
205 |
} |
206 |
#endif /* VMS */ |
207 |
|
208 |
/* |
209 |
* formf() |
210 |
* Returns a pointer to a string formatted from the arguments given. |
211 |
* If the string has been obtained from malloc(), the return value will |
212 |
* be different to the buffer pased in; it is the caller's responsibility |
213 |
* to free() this when no longer required. |
214 |
*/ |
215 |
char * |
216 |
formf(buffer_space, buffer_size, format, args) |
217 |
char *buffer_space; |
218 |
int buffer_size; |
219 |
const char *format; |
220 |
va_list args; |
221 |
{ |
222 |
bool left_justify, print_sign, zero_pad, alternate_form; |
223 |
bool is_negative, precision_given, want_uppercase, from_malloc; |
224 |
int min_field_width, precision, modifier, base, pos, lim, len; |
225 |
const char *fmt, *alternate_prefix, *s; |
226 |
char *buf, str[2], errno_buffer[42]; |
227 |
int alternate_size, saved_errno; |
228 |
unsigned long u; |
229 |
long i; |
230 |
int *ip; |
231 |
double d; |
232 |
|
233 |
saved_errno = errno; /* for use later in %m format */ |
234 |
|
235 |
/* |
236 |
* The formatted string is prepared in a buffer pointed to by buf. |
237 |
* This starts out pointing to a buffer passed in as an argument, |
238 |
* but if this fills, a new one is obtained from malloc(). |
239 |
* Pos is the index of the position for the *next* character |
240 |
* in the output string, and lim is the index of the last |
241 |
* usable character. |
242 |
*/ |
243 |
from_malloc = FALSE; |
244 |
buf = buffer_space; |
245 |
lim = buffer_size - 1; |
246 |
pos = 0; |
247 |
fmt = format; |
248 |
|
249 |
for (;;) { |
250 |
min_field_width = 0; |
251 |
precision_given = FALSE; |
252 |
precision = 0; |
253 |
modifier = 0; |
254 |
left_justify = FALSE; |
255 |
print_sign = FALSE; |
256 |
zero_pad = FALSE; |
257 |
alternate_form = FALSE; |
258 |
|
259 |
/* Find next argument for conversion */ |
260 |
while (*fmt != '\0' && *fmt != '%') { |
261 |
if (pos == lim) |
262 |
new_buffer(&buf, &lim, &from_malloc); |
263 |
|
264 |
buf[pos++] = *fmt++; |
265 |
} |
266 |
|
267 |
if (*fmt == '\0') { |
268 |
buf[pos] = '\0'; |
269 |
return buf; /* end of format string */ |
270 |
} |
271 |
|
272 |
/* flags, in any order */ |
273 |
for (;;) { |
274 |
if (*++fmt == '\0') |
275 |
panic("confused format flags in formf"); |
276 |
|
277 |
if (*fmt == '-') |
278 |
left_justify = TRUE; |
279 |
|
280 |
else if (*fmt == '+') |
281 |
print_sign = TRUE; |
282 |
|
283 |
else if (*fmt == '0') |
284 |
zero_pad = TRUE; |
285 |
|
286 |
else if (*fmt == '#') |
287 |
alternate_form = TRUE; |
288 |
|
289 |
else |
290 |
break; /* end of flags */ |
291 |
|
292 |
} |
293 |
|
294 |
/* minimum field width */ |
295 |
if (*fmt == '*') { |
296 |
min_field_width = va_arg(args, int); |
297 |
++fmt; |
298 |
} else |
299 |
while (isdigit(*fmt)) { |
300 |
min_field_width *= 10; |
301 |
min_field_width += *fmt - '0'; |
302 |
++fmt; |
303 |
} |
304 |
|
305 |
if (*fmt == '.') { |
306 |
|
307 |
/* precision */ |
308 |
if (*++fmt == '*') { |
309 |
precision_given = TRUE; |
310 |
precision = va_arg(args, int); |
311 |
++fmt; |
312 |
} else if (isdigit(*fmt)) { |
313 |
precision_given = TRUE; |
314 |
precision = 0; |
315 |
do { |
316 |
precision *= 10; |
317 |
precision += *fmt - '0'; |
318 |
++fmt; |
319 |
} while (isdigit(*fmt)); |
320 |
} |
321 |
} |
322 |
|
323 |
if (strchr("hlL", *fmt) != NULL) |
324 |
modifier = *fmt++; |
325 |
|
326 |
want_uppercase = TRUE; |
327 |
alternate_prefix = "0X"; |
328 |
alternate_size = 2; |
329 |
is_negative = FALSE; |
330 |
|
331 |
switch (*fmt) { |
332 |
case 'd': |
333 |
case 'i': |
334 |
if (modifier == 'h') |
335 |
i = va_arg(args, int); |
336 |
else if (modifier == 'l') |
337 |
i = va_arg(args, long); |
338 |
else |
339 |
i = va_arg(args, int); |
340 |
is_negative = i < 0; |
341 |
u = is_negative ? -i : i; |
342 |
s = long_to_ascii(u, 10, want_uppercase); |
343 |
break; |
344 |
|
345 |
case 'o': |
346 |
base = 8; |
347 |
alternate_prefix = "0"; |
348 |
alternate_size = 1; |
349 |
goto gencase; |
350 |
case 'p': |
351 |
alternate_prefix = "0x"; |
352 |
alternate_size = 2; |
353 |
want_uppercase = FALSE; |
354 |
base = 16; |
355 |
print_sign = FALSE; |
356 |
u = (unsigned long)va_arg(args, char *); |
357 |
s = long_to_ascii(u, base, want_uppercase); |
358 |
#ifdef MSDOS |
359 |
/* The traditional format for pointers on |
360 |
* MSDOS is segment:offset. NOTE: we depend |
361 |
* on long_to_ascii() returning a buffer with |
362 |
* space before the beginning. |
363 |
*/ |
364 |
if ((len = strlen(s)) <= 8) { |
365 |
s -= 8 - len; |
366 |
memset(s, '0', 8 - len); |
367 |
|
368 |
--s; |
369 |
memmove(s, s + 1, 4); |
370 |
s[4] = ':'; |
371 |
} |
372 |
#endif |
373 |
break; |
374 |
case 'x': |
375 |
alternate_prefix = "0x"; |
376 |
alternate_size = 2; |
377 |
want_uppercase = FALSE; |
378 |
/* fall through */; |
379 |
case 'X': |
380 |
base = 16; |
381 |
goto gencase; |
382 |
case 'u': |
383 |
base = 10; |
384 |
gencase: if (modifier == 'h') |
385 |
u = va_arg(args, int); |
386 |
else if (modifier == 'l') |
387 |
u = va_arg(args, long); |
388 |
else |
389 |
u = va_arg(args, int); |
390 |
s = long_to_ascii(u, base, want_uppercase); |
391 |
break; |
392 |
|
393 |
case 'c': |
394 |
str[0] = (char)va_arg(args, int); /* promoted char */ |
395 |
str[1] = '\0'; |
396 |
s = str; |
397 |
print_sign = FALSE; |
398 |
break; |
399 |
|
400 |
case '%': |
401 |
str[0] = '%'; |
402 |
str[1] = '\0'; |
403 |
s = str; |
404 |
print_sign = FALSE; |
405 |
break; |
406 |
|
407 |
case 's': |
408 |
s = va_arg(args, char *); |
409 |
if (s == NULL) |
410 |
panic("null pointer for %s in formf"); |
411 |
print_sign = FALSE; |
412 |
break; |
413 |
case 'm': |
414 |
errno = 0; |
415 |
s = strerror(saved_errno); |
416 |
if (errno == EINVAL) { |
417 |
sprintf(errno_buffer, |
418 |
"errno %d out of range", |
419 |
saved_errno); |
420 |
s = errno_buffer; |
421 |
} |
422 |
print_sign = FALSE; |
423 |
break; |
424 |
#ifdef VMS |
425 |
case 'M': |
426 |
s = vms_error_text(va_arg(args, unsigned)); |
427 |
print_sign = FALSE; |
428 |
break; |
429 |
#endif /* VMS */ |
430 |
case 'f': |
431 |
d = va_arg(args, double); |
432 |
is_negative = d < 0.0; |
433 |
if (is_negative) |
434 |
d = -d; |
435 |
if (!precision_given) |
436 |
precision = 6; /* from K&R */ |
437 |
s = float_to_ascii(d, precision); |
438 |
break; |
439 |
case 'e': |
440 |
want_uppercase = FALSE; /* fall through */ |
441 |
case 'E': |
442 |
d = va_arg(args, double); |
443 |
is_negative = d < 0.0; |
444 |
if (is_negative) |
445 |
d = -d; |
446 |
if (!precision_given) |
447 |
precision = 6; /* from K&R */ |
448 |
s = efloat_to_ascii(d, precision, |
449 |
want_uppercase); |
450 |
break; |
451 |
case 'g': |
452 |
want_uppercase = FALSE; /* fall through */ |
453 |
case 'G': |
454 |
d = va_arg(args, double); |
455 |
is_negative = d < 0.0; |
456 |
if (is_negative) |
457 |
d = -d; |
458 |
if (!precision_given) |
459 |
precision = 6; /* from K&R */ |
460 |
s = gfloat_to_ascii(d, precision, |
461 |
want_uppercase); |
462 |
break; |
463 |
case 'n': |
464 |
ip = va_arg(args, int *); |
465 |
*ip = pos; |
466 |
|
467 |
++fmt; /* step over format character */ |
468 |
continue; |
469 |
|
470 |
default: |
471 |
panic("illegal format in formf"); |
472 |
s = NULL; /* to satisfy gcc */ |
473 |
break; |
474 |
} |
475 |
|
476 |
len = strlen(s); |
477 |
|
478 |
/* truncate strings if requested */ |
479 |
if ((*fmt == 's' || *fmt == 'm') |
480 |
&& precision_given && precision < len) |
481 |
len = precision; |
482 |
|
483 |
if (!left_justify) { |
484 |
const char *fillch_str = zero_pad ? "0" : " "; |
485 |
|
486 |
if ((is_negative || print_sign) && zero_pad) { |
487 |
concat(&buf, &pos, &lim, &from_malloc, |
488 |
is_negative ? "-" : "+", 1); |
489 |
is_negative = print_sign = FALSE; |
490 |
--min_field_width; |
491 |
} |
492 |
|
493 |
if (alternate_form && alternate_prefix != NULL) { |
494 |
concat(&buf, &pos, &lim, &from_malloc, |
495 |
alternate_prefix, alternate_size); |
496 |
min_field_width -= alternate_size; |
497 |
alternate_form = FALSE; |
498 |
} |
499 |
|
500 |
while (len < min_field_width) { |
501 |
concat(&buf, &pos, &lim, &from_malloc, |
502 |
fillch_str, 1); |
503 |
--min_field_width; |
504 |
} |
505 |
} |
506 |
|
507 |
if (is_negative || print_sign) { |
508 |
concat(&buf, &pos, &lim, &from_malloc, |
509 |
is_negative ? "-" : "+", 1); |
510 |
--min_field_width; |
511 |
} |
512 |
|
513 |
if (alternate_form && alternate_prefix != NULL) { |
514 |
concat(&buf, &pos, &lim, &from_malloc, |
515 |
alternate_prefix, alternate_size); |
516 |
min_field_width -= alternate_size; |
517 |
} |
518 |
|
519 |
|
520 |
concat(&buf, &pos, &lim, &from_malloc, s, len); |
521 |
min_field_width -= len; |
522 |
|
523 |
if (left_justify) { |
524 |
while (min_field_width > 0) { |
525 |
concat(&buf, &pos, &lim, &from_malloc, " ", 1); |
526 |
--min_field_width; |
527 |
} |
528 |
} |
529 |
|
530 |
++fmt; /* step over format character */ |
531 |
} |
532 |
} |