1 /*
2 * Copyright (c) 2026 Faraz Vahedi <kfv@kfv.io>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7 /*
8 * Shared helpers for strfromd, strfromf, and strfroml (C23 §7.24.1.3).
9 */
10
11 #include <ctype.h>
12 #include <errno.h>
13 #include <locale.h>
14 #include <stdlib.h>
15
16 #include "strfrom.h"
17
18 struct sf_buf {
19 char *s;
20 size_t n;
21 int pos;
22 };
23
24 /*
25 * Write c to b->s[b->pos] if within the n-1 writable bytes; always advance
26 * b->pos. n is the total buffer capacity, including the null slot.
27 */
28 static void
sf_putc(struct sf_buf * b,char c)29 sf_putc(struct sf_buf *b, char c)
30 {
31 if (b->n > 0 && (size_t)b->pos < b->n - 1)
32 b->s[b->pos] = c;
33 b->pos++;
34 }
35
36 /*
37 * Write len bytes from src.
38 */
39 static void
sf_write(struct sf_buf * b,const char * src,int len)40 sf_write(struct sf_buf *b, const char *src, int len)
41 {
42 int i;
43
44 for (i = 0; i < len; i++)
45 sf_putc(b, src[i]);
46 }
47
48 /*
49 * Write a null terminated string.
50 */
51 static void
sf_puts(struct sf_buf * b,const char * src)52 sf_puts(struct sf_buf *b, const char *src)
53 {
54 while (*src != '\0')
55 sf_putc(b, *src++);
56 }
57
58 /*
59 * Write count copies of c.
60 */
61 static void
sf_padc(struct sf_buf * b,char c,int count)62 sf_padc(struct sf_buf *b, char c, int count)
63 {
64 int i;
65
66 for (i = 0; i < count; i++)
67 sf_putc(b, c);
68 }
69
70 /*
71 * Seal the buffer: null terminate at min(pos, n-1).
72 */
73 static void
sf_seal(struct sf_buf * b)74 sf_seal(struct sf_buf *b)
75 {
76 if (b->n > 0)
77 b->s[(size_t)b->pos < b->n ? b->pos : b->n - 1] = '\0';
78 }
79
80 /*
81 * Emit the radix. Interfaces are defined as snprintf(3) (C23 §7.24.1.3p2),
82 * whose '.' is the LC_NUMERIC decimal point; localeconv() resolves the same
83 * per-thread locale as vfprintf(3). The radix may be multibyte.
84 */
85 static void
sf_putdp(struct sf_buf * b)86 sf_putdp(struct sf_buf *b)
87 {
88 sf_puts(b, localeconv()->decimal_point);
89 }
90
91 /*
92 * Emit "[eEpP][+-]ddd": exponent sign and decimal magnitude, zero-padded to at
93 * least mindig digits (2 for %e/%E and %g/%G, 1 for %a/%A per C23 §7.23.6.1).
94 *
95 * Handles the full exponent range of every supported type.
96 */
97 static void
sf_emit_exp(struct sf_buf * b,char ec,int exp,int mindig)98 sf_emit_exp(struct sf_buf *b, char ec, int exp, int mindig)
99 {
100 char buf[16];
101 int len;
102
103 sf_putc(b, ec);
104 if (exp < 0) {
105 sf_putc(b, '-');
106 exp = -exp;
107 } else {
108 sf_putc(b, '+');
109 }
110
111 len = 0;
112 do {
113 buf[len++] = '0' + exp % 10;
114 exp /= 10;
115 } while (exp != 0);
116 while (len < mindig)
117 buf[len++] = '0';
118 while (len > 0)
119 sf_putc(b, buf[--len]);
120 }
121
122 /*
123 * Parse "%[.prec]conv" per C23 §7.24.1.3.
124 *
125 * Returns the conversion specifier character, or '\0' with errno set to
126 * EDOOFUS if fmt is not one of the forms the standard permits; *prec is
127 * -1 when the precision is absent.
128 */
129 char
__sf_parse_fmt(const char * fmt,int * prec)130 __sf_parse_fmt(const char *fmt, int *prec)
131 {
132 const char *p;
133
134 *prec = -1;
135 if (*fmt != '%')
136 return ('\0');
137 p = fmt + 1;
138
139 if (*p == '.') {
140 *prec = 0;
141 while (*++p >= '0' && *p <= '9')
142 *prec = *prec * 10 + (*p - '0');
143 }
144
145 switch (*p) {
146 case 'a': case 'A':
147 case 'e': case 'E':
148 case 'f': case 'F':
149 case 'g': case 'G':
150 break;
151 default:
152 return ('\0');
153 }
154
155 return (p[1] == '\0' ? *p : '\0');
156 }
157
158 /*
159 * Report a format string the standard does not permit.
160 *
161 * Sets errno and renders "EDOOFUS" under the ordinary snprintf(3) truncation
162 * rules, which keeps a faulty caller alive and its output obviously wrong.
163 */
164 int
__sf_edoofus(char * s,size_t n)165 __sf_edoofus(char *s, size_t n)
166 {
167 struct sf_buf b;
168
169 errno = EDOOFUS;
170 b.s = s;
171 b.n = n;
172 b.pos = 0;
173 sf_puts(&b, "EDOOFUS");
174 sf_seal(&b);
175 return (b.pos);
176 }
177
178 /*
179 * Render Inf or NaN into b->s[0..n-1].
180 *
181 * is_nan: non-zero if digits[0] == 'N' (NaN), zero for Infinity.
182 */
183 static int
sf_special(struct sf_buf * b,char conv,int signflag,int is_nan)184 sf_special(struct sf_buf *b, char conv, int signflag, int is_nan)
185 {
186 int upper;
187
188 b->pos = 0;
189 upper = isupper((unsigned char)conv);
190 if (!is_nan && signflag)
191 sf_putc(b, '-');
192 sf_puts(b, is_nan ? (upper ? "NAN" : "nan") : (upper ? "INF" : "inf"));
193 sf_seal(b);
194 return (b->pos);
195 }
196
197 /*
198 * Render %e / %E.
199 *
200 * dtoa was called with mode=2, ndigits=prec+1. decpt is the position of the
201 * first significant digit relative to the decimal point (= exponent + 1), as
202 * returned by dtoa.
203 */
204 static int
sf_efmt(struct sf_buf * b,int prec,char conv,const char * digits,int ndig,int decpt,int signflag)205 sf_efmt(struct sf_buf *b, int prec, char conv,
206 const char *digits, int ndig, int decpt, int signflag)
207 {
208 int avail, copy;
209
210 b->pos = 0;
211 if (signflag)
212 sf_putc(b, '-');
213
214 /*
215 * Leading significant digit.
216 */
217 sf_putc(b, ndig > 0 ? digits[0] : '0');
218
219 if (prec > 0) {
220 sf_putdp(b);
221 avail = ndig > 1 ? ndig - 1 : 0;
222 copy = avail < prec ? avail : prec;
223 sf_write(b, digits + 1, copy);
224 sf_padc(b, '0', prec - copy);
225 }
226 sf_emit_exp(b, isupper((unsigned char)conv) ? 'E' : 'e', decpt - 1, 2);
227
228 sf_seal(b);
229 return (b->pos);
230 }
231
232 /*
233 * Render %f / %F.
234 *
235 * dtoa was called with mode=3, ndigits=prec. decpt gives the number of digits
236 * before the decimal point (may be <= 0).
237 */
238 static int
sf_ffmt(struct sf_buf * b,int prec,int signflag,const char * digits,int ndig,int decpt)239 sf_ffmt(struct sf_buf *b, int prec, int signflag,
240 const char *digits, int ndig, int decpt)
241 {
242 int avail, copy, rem, zc;
243
244 b->pos = 0;
245 if (signflag)
246 sf_putc(b, '-');
247
248 if (decpt <= 0) {
249 sf_putc(b, '0');
250 if (prec > 0) {
251 sf_putdp(b);
252 zc = -decpt < prec ? -decpt : prec;
253 rem = prec - zc;
254 copy = ndig < rem ? ndig : rem;
255 sf_padc(b, '0', zc);
256 sf_write(b, digits, copy);
257 sf_padc(b, '0', rem - copy);
258 }
259 } else {
260 /*
261 * decpt digits (or zeros) before the point.
262 */
263 copy = ndig < decpt ? ndig : decpt;
264 sf_write(b, digits, copy);
265 sf_padc(b, '0', decpt - copy);
266 if (prec > 0) {
267 sf_putdp(b);
268 avail = ndig - decpt;
269 if (avail < 0)
270 avail = 0;
271 copy = avail < prec ? avail : prec;
272 sf_write(b, digits + decpt, copy);
273 sf_padc(b, '0', prec - copy);
274 }
275 }
276
277 sf_seal(b);
278 return (b->pos);
279 }
280
281 /*
282 * Render %g / %G.
283 *
284 * dtoa was called with mode=2, ndigits=max(1, prec).
285 */
286 static int
sf_gfmt(struct sf_buf * b,int prec,char conv,const char * digits,int ndig,int decpt,int signflag)287 sf_gfmt(struct sf_buf *b, int prec, char conv,
288 const char *digits, int ndig, int decpt, int signflag)
289 {
290 int ep, copy, frac;
291
292 b->pos = 0;
293 /*
294 * Precision 0 is treated as 1 per C23 §7.23.6.1.
295 */
296 ep = prec == 0 ? 1 : prec;
297
298 /*
299 * Strip trailing zeros (no ALT flag is accepted).
300 */
301 while (ndig > 1 && digits[ndig - 1] == '0')
302 ndig--;
303
304 if (signflag)
305 sf_putc(b, '-');
306
307 /*
308 * Standard says −4 ≤ exponent < P test: exponent = decpt − 1
309 */
310 if (decpt > -4 && decpt <= ep) {
311 /*
312 * %f style, per C23 §7.23.6.1.
313 */
314 if (decpt <= 0) {
315 sf_putc(b, '0');
316 if (ndig > 0) {
317 sf_putdp(b);
318 sf_padc(b, '0', -decpt);
319 sf_write(b, digits, ndig);
320 }
321 } else {
322 copy = ndig < decpt ? ndig : decpt;
323 sf_write(b, digits, copy);
324 sf_padc(b, '0', decpt - copy);
325 frac = ndig - decpt;
326 if (frac > 0) {
327 sf_putdp(b);
328 sf_write(b, digits + decpt, frac);
329 }
330 }
331 } else {
332 /*
333 * %e style, per C23 §7.23.6.1.
334 */
335 sf_putc(b, ndig > 0 ? digits[0] : '0');
336 if (ndig > 1) {
337 sf_putdp(b);
338 sf_write(b, digits + 1, ndig - 1);
339 }
340 sf_emit_exp(b, isupper((unsigned char)conv) ? 'E' : 'e',
341 decpt - 1, 2);
342 }
343
344 sf_seal(b);
345 return (b->pos);
346 }
347
348 /*
349 * Render %a / %A.
350 *
351 * digits / ndig: hex significand digits from __hdtoa / __hldtoa; the first
352 * digit represents the integer part of the mantissa (normally '1').
353 *
354 * decpt: binary exponent such that p-exponent = decpt - 1.
355 *
356 * user_prec: digits after the hex point (-1 for shortest-exact).
357 */
358 static int
sf_afmt(struct sf_buf * b,int user_prec,char conv,const char * digits,int ndig,int decpt,int signflag)359 sf_afmt(struct sf_buf *b, int user_prec, char conv,
360 const char *digits, int ndig, int decpt, int signflag)
361 {
362 int after, copy;
363
364 b->pos = 0;
365 if (signflag)
366 sf_putc(b, '-');
367 sf_putc(b, '0');
368 sf_putc(b, isupper((unsigned char)conv) ? 'X' : 'x');
369
370 /*
371 * Integer part of the significand.
372 */
373 sf_putc(b, ndig > 0 ? digits[0] : '0');
374
375 after = ndig - 1;
376 if (user_prec < 0) {
377 /*
378 * Shortest-exact: emit only the non-zero significant tail.
379 */
380 if (after > 0) {
381 sf_putdp(b);
382 sf_write(b, digits + 1, after);
383 }
384 } else if (user_prec > 0) {
385 sf_putdp(b);
386 copy = after < user_prec ? after : user_prec;
387 sf_write(b, digits + 1, copy);
388 sf_padc(b, '0', user_prec - copy);
389 }
390 sf_emit_exp(b, isupper((unsigned char)conv) ? 'P' : 'p', decpt - 1, 1);
391
392 sf_seal(b);
393 return (b->pos);
394 }
395
396 /*
397 * Hex digit table for %a / %A, selected by specifier case.
398 */
399 const char *
__sf_xdigits(char conv)400 __sf_xdigits(char conv)
401 {
402 return (isupper((unsigned char)conv) ? "0123456789ABCDEF" :
403 "0123456789abcdef");
404 }
405
406 /*
407 * Map a decimal specifier (lowercased) to its gdtoa mode and digit count.
408 */
409 void
__sf_decimal_mode(char lc,int prec,int * mode,int * ndig_req)410 __sf_decimal_mode(char lc, int prec, int *mode, int *ndig_req)
411 {
412 switch (lc) {
413 case 'e':
414 *mode = 2;
415 *ndig_req = prec + 1;
416 break;
417 case 'f':
418 *mode = 3;
419 *ndig_req = prec;
420 break;
421 case 'g':
422 *mode = 2;
423 *ndig_req = prec == 0 ? 1 : prec;
424 break;
425 default:
426 __unreachable();
427 }
428 }
429
430 /*
431 * Render a finished %a / %A conversion. __hdtoa / __hldtoa always flag
432 * Inf/NaN with decpt == INT_MAX.
433 */
434 int
__sf_render_hex(char * s,size_t n,char conv,int prec,char * digits,char * dend,int decpt,int signflag)435 __sf_render_hex(char *s, size_t n, char conv, int prec,
436 char *digits, char *dend, int decpt, int signflag)
437 {
438 struct sf_buf b;
439 int ndig, usprec;
440
441 b.s = s;
442 b.n = n;
443 if (decpt == INT_MAX)
444 return (sf_special(&b, conv, signflag, digits[0] == 'N'));
445 ndig = (int)(dend - digits);
446 usprec = prec >= 0 ? prec : ndig - 1;
447 return (sf_afmt(&b, usprec, conv, digits, ndig, decpt, signflag));
448 }
449
450 /*
451 * Render a finished %e/%f/%g conversion. is_special is set when decpt holds
452 * the caller's Inf/NaN sentinel (dtoa: 9999, __ldtoa: INT_MAX).
453 */
454 int
__sf_render_decimal(char * s,size_t n,char conv,char lc,int prec,char * digits,char * dend,int decpt,int signflag,int is_special)455 __sf_render_decimal(char *s, size_t n, char conv, char lc, int prec,
456 char *digits, char *dend, int decpt, int signflag, int is_special)
457 {
458 struct sf_buf b;
459 int ndig;
460
461 b.s = s;
462 b.n = n;
463 if (is_special)
464 return (sf_special(&b, conv, signflag, digits[0] == 'N'));
465 ndig = (int)(dend - digits);
466 switch (lc) {
467 case 'e':
468 return (sf_efmt(&b, prec, conv, digits, ndig, decpt, signflag));
469 case 'f':
470 return (sf_ffmt(&b, prec, signflag, digits, ndig, decpt));
471 case 'g':
472 return (sf_gfmt(&b, prec, conv, digits, ndig, decpt, signflag));
473 default:
474 __unreachable();
475 }
476 }
477