struct/dstr-putf.c: Don't segfault on `*' width/precision indicators.
[mLib] / struct / dstr-putf.c
1 /* -*-c-*-
2 *
3 * `printf'-style formatting for dynamic strings
4 *
5 * (c) 1999 Straylight/Edgeware
6 */
7
8 /*----- Licensing notice --------------------------------------------------*
9 *
10 * This file is part of the mLib utilities library.
11 *
12 * mLib is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU Library General Public License as
14 * published by the Free Software Foundation; either version 2 of the
15 * License, or (at your option) any later version.
16 *
17 * mLib is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU Library General Public License for more details.
21 *
22 * You should have received a copy of the GNU Library General Public
23 * License along with mLib; if not, write to the Free
24 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
25 * MA 02111-1307, USA.
26 */
27
28 /*----- Header files ------------------------------------------------------*/
29
30 #include "config.h"
31
32 #include <ctype.h>
33 #include <math.h>
34 #include <stdarg.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38
39 #ifdef HAVE_FLOAT_H
40 # include <float.h>
41 #endif
42
43 #include "dstr.h"
44
45 /*----- Tunable constants -------------------------------------------------*/
46
47 /*
48 * For each format specifier, at least @DSTR_PUTFSTEP@ bytes are ensured
49 * before writing the formatted result.
50 */
51
52 #define DSTR_PUTFSTEP 64 /* Buffer size for @putf@ */
53
54 /*----- Main code ---------------------------------------------------------*/
55
56 /* --- @dstr_vputf@ --- *
57 *
58 * Arguments: @dstr *d@ = pointer to a dynamic string block
59 * @const char *p@ = pointer to @printf@-style format string
60 * @va_list *ap@ = argument handle
61 *
62 * Returns: The number of characters written to the string.
63 *
64 * Use: As for @dstr_putf@, but may be used as a back-end to user-
65 * supplied functions with @printf@-style interfaces.
66 */
67
68 int dstr_vputf(dstr *d, const char *p, va_list *ap)
69 {
70 const char *q = p;
71 size_t n = d->len;
72 size_t sz;
73 dstr dd = DSTR_INIT;
74
75 while (*p) {
76 unsigned f;
77 int wd, prec;
78
79 #define f_short 1u
80 #define f_long 2u
81 #define f_Long 4u
82 #define f_wd 8u
83 #define f_prec 16u
84
85 /* --- Most stuff gets passed on through --- */
86
87 if (*p != '%') {
88 p++;
89 continue;
90 }
91
92 /* --- Dump out what's between @q@ and @p@ --- */
93
94 DPUTM(d, q, p - q);
95 p++;
96
97 /* --- Sort out the various silly flags and things --- */
98
99 DPUTC(&dd, '%');
100 f = 0;
101 sz = DSTR_PUTFSTEP;
102
103 for (;;) {
104 switch (*p) {
105
106 /* --- Various simple flags --- */
107
108 case '+':
109 case '-':
110 case '#':
111 case '0':
112 goto putch;
113 case 'h':
114 f |= f_short;
115 goto putch;
116 case 'l':
117 f |= f_long;
118 goto putch;
119 case 'L':
120 f |= f_Long;
121 goto putch;
122 case 0:
123 goto finished;
124
125 /* --- Field widths and precision specifiers --- */
126
127 {
128 int *ip;
129
130 case '.':
131 DPUTC(&dd, '.');
132 ip = &prec;
133 f |= f_prec;
134 p++;
135 goto getnum;
136 case '*':
137 ip = &wd;
138 f |= f_wd;
139 goto getnum;
140 default:
141 if (isdigit((unsigned char)*p)) {
142 f |= f_wd;
143 ip = &wd;
144 goto getnum;
145 }
146 DPUTC(d, *p);
147 goto formatted;
148 getnum:
149 *ip = 0;
150 if (*p == '*') {
151 *ip = va_arg(*ap, int);
152 DENSURE(&dd, DSTR_PUTFSTEP);
153 dd.len += sprintf(dd.buf + dd.len, "%i", *ip);
154 p++;
155 } else {
156 *ip = *p - '0';
157 DPUTC(&dd, *p);
158 p++;
159 while (isdigit((unsigned char)*p)) {
160 DPUTC(&dd, *p);
161 *ip = 10 * *ip + *p++ - '0';
162 }
163 }
164 break;
165 }
166
167 /* --- Output formatting --- */
168
169 case 'd': case 'i': case 'x': case 'X': case 'o': case 'u':
170 DPUTC(&dd, *p);
171 DPUTZ(&dd);
172 if ((f & f_prec) && prec + 16 > sz)
173 sz = prec + 16;
174 if ((f & f_wd) && wd + 1> sz)
175 sz = wd + 1;
176 DENSURE(d, sz);
177 if (f & f_long)
178 d->len += sprintf(d->buf + d->len, dd.buf,
179 va_arg(*ap, unsigned long));
180 else
181 d->len += sprintf(d->buf + d->len, dd.buf,
182 va_arg(*ap, unsigned int));
183 goto formatted;
184
185 case 'e': case 'E': case 'f': case 'F': case 'g': case 'G':
186 #ifdef HAVE_FLOAT_H
187 DPUTC(&dd, *p);
188 DPUTZ(&dd);
189 if (*p == 'f') {
190 size_t mx = (f & f_Long ? LDBL_MAX_10_EXP : DBL_MAX_10_EXP) + 16;
191 if (mx > sz)
192 sz = mx;
193 }
194 if (!(f & f_prec))
195 prec = 6;
196 else
197 sz += prec + 16;
198 if ((f & f_wd) && wd + 1 > sz)
199 sz = wd + 1;
200 DENSURE(d, sz);
201 if (f & f_Long)
202 d->len += sprintf(d->buf + d->len, dd.buf,
203 va_arg(*ap, long double));
204 else
205 d->len += sprintf(d->buf + d->len, dd.buf,
206 va_arg(*ap, double));
207 goto formatted;
208 #else
209 DPUTS(d, "<no float support>");
210 #endif
211
212 case 'c':
213 DPUTC(&dd, *p);
214 DPUTZ(&dd);
215 if ((f & f_wd) && wd + 1> sz)
216 sz = wd + 1;
217 DENSURE(d, sz);
218 d->len += sprintf(d->buf + d->len, dd.buf,
219 va_arg(*ap, unsigned));
220 goto formatted;
221
222 case 's': {
223 const char *s = va_arg(*ap, const char *);
224 sz = strlen(s);
225 DPUTC(&dd, *p);
226 DPUTZ(&dd);
227 if (f & f_prec)
228 sz = prec;
229 if ((f & f_wd) && wd > sz)
230 sz = wd;
231 DENSURE(d, sz + 1);
232 d->len += sprintf(d->buf + d->len, dd.buf, s);
233 goto formatted;
234 }
235
236 case 'p':
237 DPUTC(&dd, *p);
238 DPUTZ(&dd);
239 if ((f & f_prec) && prec + 16 > sz)
240 sz = prec + 16;
241 if ((f & f_wd) && wd + 1> sz)
242 sz = wd + 1;
243 DENSURE(d, sz);
244 d->len += sprintf(d->buf + d->len, dd.buf,
245 va_arg(*ap, const void *));
246 goto formatted;
247
248 case 'n':
249 if (f & f_long)
250 *va_arg(*ap, long *) = (long)(d->len - n);
251 else if (f & f_short)
252 *va_arg(*ap, short *) = (short)(d->len - n);
253 else
254 *va_arg(*ap, int *) = (int)(d->len - n);
255 goto formatted;
256
257 /* --- Other random stuff --- */
258
259 putch:
260 DPUTC(&dd, *p);
261 p++;
262 break;
263 }
264 }
265
266 formatted:
267 DRESET(&dd);
268 q = ++p;
269
270 #undef f_short
271 #undef f_long
272 #undef f_Long
273 #undef f_wd
274 #undef f_prec
275 }
276
277 DPUTM(d, q, p - q);
278 finished:
279 DPUTZ(d);
280 DDESTROY(&dd);
281 return (d->len - n);
282 }
283
284 /* --- @dstr_putf@ --- *
285 *
286 * Arguments: @dstr *d@ = pointer to a dynamic string block
287 * @const char *p@ = pointer to @printf@-style format string
288 * @...@ = argument handle
289 *
290 * Returns: The number of characters written to the string.
291 *
292 * Use: Writes a piece of text to a dynamic string, doing @printf@-
293 * style substitutions as it goes. Intended to be robust if
294 * faced with malicious arguments, but not if the format string
295 * itself is malicious.
296 */
297
298 int dstr_putf(dstr *d, const char *p, ...)
299 {
300 int n;
301 va_list ap;
302 va_start(ap, p);
303 n = dstr_vputf(d, p, &ap);
304 va_end(ap);
305 return (n);
306 }
307
308 /*----- That's all, folks -------------------------------------------------*/