feat(inttypes): PRI/SCN macros + strtoimax family

This commit is contained in:
2026-09-03 22:16:12 -04:00
parent 26786e9027
commit 14b0136a8e
3 changed files with 980 additions and 0 deletions
+268
View File
@@ -0,0 +1,268 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <inttypes.h>
#include <errno.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
/*
* inttypes.h conversions (todo 14).
*
* strtoimax/strtoumax are thin delegations: on x86_64 LP64 intmax_t is
* long and uintmax_t is unsigned long (GCC __INTMAX_TYPE__), so strtol and
* strtoul are ABI-identical — same input, same endptr, same errno policy
* (ERANGE clamps, EINVAL for a base outside {0} union [2, 36]). They share
* the todo-11 engine in src/stdlib/strtox.c through those calls.
*
* wcstoimax/wcstoumax cannot reuse the byte-based engine directly, so this
* file carries a self-contained wide scan: a wchar_t mirror of the
* __strtox_scan algorithm (C-locale whitespace L' ' and L'\t'..L'\r',
* optional sign, base 0/2..36 with the 0x/0 prefix rules, saturating u64
* magnitude, same clamping policy). wchar_t is a 32-bit code unit here
* (GCC __WCHAR_TYPE__ is int), so digits are matched by ASCII codepoint
* exactly as strtox_digit matches bytes; non-ASCII code units are never
* digits under the C locale. Todo 50 (wchar.h) may later unify this scan
* with the narrow engine.
*/
/* Wide-scan results; mirrors the STRTOX_* contract of the narrow engine. */
#define WCSTOX_OK 0
#define WCSTOX_EMPTY 1
#define WCSTOX_BADBASE 2
/* C-locale whitespace for wide code units: L' ' and L'\t'..L'\r'. */
static int
wcstox_isspace(wchar_t c)
{
return c == L' ' || (unsigned int)c - L'\t' < 5U;
}
/* Value of c as a base-36 digit, or -1 when it is not an ASCII letter or
* digit; | 0x20 folds 'A'..'F' to 'a'..'f' (same idiom as strtox_digit). */
static int
wcstox_digit(wchar_t c)
{
if ((unsigned int)c - L'0' < 10U)
{
return (int)c - L'0';
}
if ((unsigned int)(c | 0x20) - L'a' < 26U)
{
return (int)(c | 0x20) - L'a' + 10;
}
return -1;
}
/*
* Scan the subject sequence at nptr (C23 7.8.2.4 steps 1-7, the wide
* mirror of __strtox_scan). On WCSTOX_OK: *mag holds the magnitude (exact
* unless *over), *neg the sign, *endptr the position past the last digit.
* On WCSTOX_EMPTY / WCSTOX_BADBASE: *endptr is set to nptr, *mag and *neg
* are zeroed; the caller sets errno = EINVAL for WCSTOX_BADBASE.
*/
static int
wcstox_scan(const wchar_t *nptr, wchar_t **endptr, int base, int *neg, unsigned long long *mag,
int *over)
{
const wchar_t *s = nptr;
int sg = 0;
int ov = 0;
int digits = 0;
int consumed_prefix = 0;
unsigned long long m = 0;
while (wcstox_isspace(*s))
{
s++;
}
if (*s == L'+' || *s == L'-')
{
sg = (*s == L'-');
s++;
}
if (base != 0 && (base < 2 || base > 36))
{
if (endptr)
{
*endptr = (wchar_t *)nptr;
}
*neg = 0;
*mag = 0;
*over = 0;
return WCSTOX_BADBASE;
}
if (base == 0 || base == 16)
{
if (s[0] == L'0' && (s[1] == L'x' || s[1] == L'X'))
{
int d2 = wcstox_digit(s[2]);
if (d2 >= 0 && d2 < 16)
{
base = 16;
s += 2;
}
else if (base == 0)
{
/* "0x" with no hex digit is no integer-constant form. */
if (endptr)
{
*endptr = (wchar_t *)nptr;
}
*neg = 0;
*mag = 0;
*over = 0;
return WCSTOX_EMPTY;
}
/* base 16: the 0 itself is the subject; the x ends it. */
}
else if (base == 0 && s[0] == L'0')
{
/* Octal prefix: the 0 opens the subject even when no octal
* digit follows ("0" is itself a valid integer constant). */
base = 8;
consumed_prefix = 1;
s++;
}
else if (base == 0)
{
base = 10;
}
}
for (;;)
{
int d = wcstox_digit(*s);
if (d < 0 || d >= base)
{
break;
}
if (ov == 0)
{
unsigned long long lim =
(ULLONG_MAX - (unsigned long long)d) / (unsigned long long)base;
if (m > lim)
{
ov = 1;
}
else
{
m = (m * (unsigned long long)base) + (unsigned long long)d;
}
}
digits++;
s++;
}
if (digits == 0 && consumed_prefix == 0)
{
if (endptr)
{
*endptr = (wchar_t *)nptr;
}
*neg = 0;
*mag = 0;
*over = 0;
return WCSTOX_EMPTY;
}
if (endptr)
{
*endptr = (wchar_t *)s;
}
*neg = sg;
*mag = m;
*over = ov;
return WCSTOX_OK;
}
intmax_t
strtoimax(const char *restrict nptr, char **restrict endptr, int base)
{
/* ABI identity: intmax_t is long on x86_64 LP64. */
return strtol(nptr, endptr, base);
}
uintmax_t
strtoumax(const char *restrict nptr, char **restrict endptr, int base)
{
/* ABI identity: uintmax_t is unsigned long on x86_64 LP64. */
return strtoul(nptr, endptr, base);
}
intmax_t
wcstoimax(const wchar_t *restrict nptr, wchar_t **restrict endptr, int base)
{
int neg;
int over;
unsigned long long mag;
int rc = wcstox_scan(nptr, endptr, base, &neg, &mag, &over);
if (rc != WCSTOX_OK)
{
if (rc == WCSTOX_BADBASE)
{
errno = EINVAL;
}
return 0;
}
if (over != 0 || mag > (unsigned long long)INTMAX_MAX + (unsigned long long)(neg != 0))
{
errno = ERANGE;
return neg != 0 ? INTMAX_MIN : INTMAX_MAX;
}
return neg != 0 ? (intmax_t)(0ULL - mag) : (intmax_t)mag;
}
uintmax_t
wcstoumax(const wchar_t *restrict nptr, wchar_t **restrict endptr, int base)
{
int neg;
int over;
unsigned long long mag;
int rc = wcstox_scan(nptr, endptr, base, &neg, &mag, &over);
if (rc != WCSTOX_OK)
{
if (rc == WCSTOX_BADBASE)
{
errno = EINVAL;
}
return 0;
}
if (over != 0 || mag > UINTMAX_MAX)
{
errno = ERANGE;
return UINTMAX_MAX;
}
return neg != 0 ? (uintmax_t)(0ULL - mag) : (uintmax_t)mag;
}
/*
* imaxabs: INTMAX_MIN is not representable as a positive value; the
* unsigned computation wraps it back to INTMAX_MIN (implementation-defined
* by the uintmax_t -> intmax_t conversion, no errno), matching GCC's
* two's complement semantics. The const attribute lives in <inttypes.h>.
*/
intmax_t
imaxabs(intmax_t n)
{
return n >= 0 ? n : (intmax_t)((uintmax_t)0 - (uintmax_t)n);
}
imaxdiv_t
imaxdiv(intmax_t numer, intmax_t denom)
{
imaxdiv_t r;
r.quot = numer / denom;
r.rem = numer % denom;
return r;
}