feat(stdlib): numeric string conversions
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
#ifndef VLIBC_INTERNAL_STRTOX_H
|
||||
#define VLIBC_INTERNAL_STRTOX_H
|
||||
|
||||
/*
|
||||
* vlibc — internal numeric-scan engine (todo 11).
|
||||
*
|
||||
* This header declares the shared core of the strtol/strtoul family. The
|
||||
* engine performs C23 7.24.1.4 steps 1-7 — whitespace skip, optional sign,
|
||||
* base resolution (0/2..36 with the 0x/0 prefix rules), and saturating
|
||||
* digit accumulation — and reports the subject sequence's magnitude, sign,
|
||||
* and overflow status. The public wrappers in src/stdlib/strtox_impl.c own
|
||||
* the per-type clamping and errno policy (signed clamps at LONG_MIN/LONG_MAX
|
||||
* with ERANGE; unsigned negates in the return type modulo 2^width, with
|
||||
* ERANGE only when the magnitude itself overflows the type, C23 7.24.1.4p8).
|
||||
*
|
||||
* Todo 14 (strtoimax/strtoumax, which on x86_64 are long/unsigned long)
|
||||
* consumes this header and reuses the engine unchanged; the wcsto* family
|
||||
* only needs a byte-level shim before it.
|
||||
*
|
||||
* The helpers below are static inline so the engine and every wrapper share
|
||||
* one C-locale whitespace/digit definition without a call boundary.
|
||||
*/
|
||||
|
||||
#include "libc.h"
|
||||
|
||||
/* Scan results. */
|
||||
#define STRTOX_OK 0 /* subject sequence present (digits > 0) */
|
||||
#define STRTOX_EMPTY 1 /* no subject sequence; *endptr = nptr */
|
||||
#define STRTOX_BADBASE 2 /* base not in {0} union [2, 36]; *endptr = nptr */
|
||||
|
||||
/*
|
||||
* Scan the subject sequence at nptr (C23 7.24.1.4 steps 1-7).
|
||||
*
|
||||
* On STRTOX_OK: *mag holds the magnitude (exact unless *over), *neg the
|
||||
* sign, *endptr the position past the last digit, *over whether the
|
||||
* magnitude exceeded unsigned long long (callers must clamp + ERANGE).
|
||||
* On STRTOX_EMPTY / STRTOX_BADBASE: *endptr is set to nptr, *mag and *neg
|
||||
* are zeroed; the caller sets errno = EINVAL for STRTOX_BADBASE and
|
||||
* returns 0.
|
||||
*/
|
||||
// NOLINTBEGIN(bugprone-reserved-identifier)
|
||||
hidden int
|
||||
__strtox_scan(const char *nptr, char **endptr, int base, int *neg, unsigned long long *mag,
|
||||
int *over);
|
||||
// NOLINTEND(bugprone-reserved-identifier)
|
||||
|
||||
/* C-locale whitespace: exactly ' ' and '\t'..'\r'. */
|
||||
static inline int
|
||||
strtox_isspace(int c)
|
||||
{
|
||||
return c == ' ' || (unsigned int)c - '\t' < 5U;
|
||||
}
|
||||
|
||||
/*
|
||||
* Value of c as a base-36 digit, or -1 when c is not an ASCII letter/digit.
|
||||
* c | 0x20 folds 'A'..'F' to 'a'..'f'; the unsigned cast keeps negative
|
||||
* values out of the range checks (same arithmetic-range idiom as ctype.c).
|
||||
*/
|
||||
static inline int
|
||||
strtox_digit(int c)
|
||||
{
|
||||
if ((unsigned int)c - '0' < 10U)
|
||||
{
|
||||
return c - '0';
|
||||
}
|
||||
if ((unsigned int)(c | 0x20) - 'a' < 26U)
|
||||
{
|
||||
return (c | 0x20) - 'a' + 10;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif /* VLIBC_INTERNAL_STRTOX_H */
|
||||
+1028
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,149 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include "../internal/strtox.h"
|
||||
|
||||
/*
|
||||
* Shared integer subject-sequence scan (todo 11). See src/internal/strtox.h
|
||||
* for the contract; this file is the engine the strtol/strtoul wrappers and
|
||||
* todo 14's strtoimax/strtoumax build on.
|
||||
*
|
||||
* Prefix rules (C23 7.24.1.4p3-4):
|
||||
* - base 0: "0x"/"0X" followed by a hex digit selects base 16 and the
|
||||
* prefix is consumed; a bare leading 0 selects base 8 and the 0 is the
|
||||
* subject itself ("0" is a valid integer constant, so the subject is
|
||||
* never empty after an octal prefix); anything else is base 10.
|
||||
* - base 16: "0x"/"0X" followed by a hex digit is consumed; without a
|
||||
* hex digit the "0" is itself a valid hex digit and the x terminates
|
||||
* the subject.
|
||||
* - "0x" without a hex digit under base 0 is no integer-constant form,
|
||||
* so there is no subject sequence at all.
|
||||
*
|
||||
* Endptr always lands at the first character that is not part of the
|
||||
* subject sequence, or at nptr when there is no subject (C23 7.24.1.4p5,
|
||||
* even when whitespace or a sign was consumed).
|
||||
*/
|
||||
|
||||
int
|
||||
__strtox_scan(const char *nptr, char **endptr, int base, int *neg, unsigned long long *mag,
|
||||
int *over)
|
||||
{
|
||||
const char *s = nptr;
|
||||
int sg = 0;
|
||||
int ov = 0;
|
||||
int digits = 0;
|
||||
int consumed_prefix = 0;
|
||||
unsigned long long m = 0;
|
||||
|
||||
while (strtox_isspace(*s))
|
||||
{
|
||||
s++;
|
||||
}
|
||||
if (*s == '+' || *s == '-')
|
||||
{
|
||||
sg = (*s == '-');
|
||||
s++;
|
||||
}
|
||||
if (base != 0 && (base < 2 || base > 36))
|
||||
{
|
||||
if (endptr)
|
||||
{
|
||||
*endptr = (char *)nptr;
|
||||
}
|
||||
*neg = 0;
|
||||
*mag = 0;
|
||||
*over = 0;
|
||||
return STRTOX_BADBASE;
|
||||
}
|
||||
|
||||
if (base == 0 || base == 16)
|
||||
{
|
||||
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
|
||||
{
|
||||
int d2 = strtox_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 = (char *)nptr;
|
||||
}
|
||||
*neg = 0;
|
||||
*mag = 0;
|
||||
*over = 0;
|
||||
return STRTOX_EMPTY;
|
||||
}
|
||||
/* base 16: the 0 itself is the subject; the x ends it. */
|
||||
}
|
||||
else if (base == 0 && s[0] == '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)
|
||||
{
|
||||
/* No prefix: a decimal subject (base 0 auto-detection). */
|
||||
base = 10;
|
||||
}
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
int d = strtox_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 = (char *)nptr;
|
||||
}
|
||||
*neg = 0;
|
||||
*mag = 0;
|
||||
*over = 0;
|
||||
return STRTOX_EMPTY;
|
||||
}
|
||||
if (endptr)
|
||||
{
|
||||
*endptr = (char *)s;
|
||||
}
|
||||
*neg = sg;
|
||||
*mag = m;
|
||||
*over = ov;
|
||||
return STRTOX_OK;
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "../internal/strtox.h"
|
||||
|
||||
/*
|
||||
* Public integer wrappers over the __strtox_scan engine (todo 11).
|
||||
*
|
||||
* Clamping policy (C23 7.24.1.4p7-8):
|
||||
* - signed: a magnitude above the type maximum (with the sign folded in:
|
||||
* -2^(N-1) is representable, -(2^(N-1)+1) is not) clamps to LONG_MAX /
|
||||
* LONG_MIN with errno ERANGE;
|
||||
* - unsigned: a magnitude above the type maximum clamps to ULONG_MAX
|
||||
* with errno ERANGE; a minus sign negates the value in the return type
|
||||
* (modulo 2^N) and is never a range error by itself.
|
||||
* - base outside {0} union [2, 36]: errno EINVAL, return 0, *endptr set
|
||||
* to nptr (POSIX.1-2008).
|
||||
*/
|
||||
|
||||
long
|
||||
strtol(const char *restrict nptr, char **restrict endptr, int base)
|
||||
{
|
||||
int neg;
|
||||
int over;
|
||||
unsigned long long mag;
|
||||
int rc = __strtox_scan(nptr, endptr, base, &neg, &mag, &over);
|
||||
|
||||
if (rc != STRTOX_OK)
|
||||
{
|
||||
if (rc == STRTOX_BADBASE)
|
||||
{
|
||||
errno = EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (over != 0 || mag > (unsigned long long)LONG_MAX + (unsigned long long)(neg != 0))
|
||||
{
|
||||
errno = ERANGE;
|
||||
return neg != 0 ? LONG_MIN : LONG_MAX;
|
||||
}
|
||||
return neg != 0 ? (long)(0ULL - mag) : (long)mag;
|
||||
}
|
||||
|
||||
unsigned long
|
||||
strtoul(const char *restrict nptr, char **restrict endptr, int base)
|
||||
{
|
||||
int neg;
|
||||
int over;
|
||||
unsigned long long mag;
|
||||
int rc = __strtox_scan(nptr, endptr, base, &neg, &mag, &over);
|
||||
|
||||
if (rc != STRTOX_OK)
|
||||
{
|
||||
if (rc == STRTOX_BADBASE)
|
||||
{
|
||||
errno = EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (over != 0 || mag > (unsigned long long)ULONG_MAX)
|
||||
{
|
||||
errno = ERANGE;
|
||||
return ULONG_MAX;
|
||||
}
|
||||
return neg != 0 ? (unsigned long)(0ULL - mag) : (unsigned long)mag;
|
||||
}
|
||||
|
||||
long long
|
||||
strtoll(const char *restrict nptr, char **restrict endptr, int base)
|
||||
{
|
||||
int neg;
|
||||
int over;
|
||||
unsigned long long mag;
|
||||
int rc = __strtox_scan(nptr, endptr, base, &neg, &mag, &over);
|
||||
|
||||
if (rc != STRTOX_OK)
|
||||
{
|
||||
if (rc == STRTOX_BADBASE)
|
||||
{
|
||||
errno = EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (over != 0 || mag > (unsigned long long)LLONG_MAX + (unsigned long long)(neg != 0))
|
||||
{
|
||||
errno = ERANGE;
|
||||
return neg != 0 ? LLONG_MIN : LLONG_MAX;
|
||||
}
|
||||
return neg != 0 ? (long long)(0ULL - mag) : (long long)mag;
|
||||
}
|
||||
|
||||
unsigned long long
|
||||
strtoull(const char *restrict nptr, char **restrict endptr, int base)
|
||||
{
|
||||
int neg;
|
||||
int over;
|
||||
unsigned long long mag;
|
||||
int rc = __strtox_scan(nptr, endptr, base, &neg, &mag, &over);
|
||||
|
||||
if (rc != STRTOX_OK)
|
||||
{
|
||||
if (rc == STRTOX_BADBASE)
|
||||
{
|
||||
errno = EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (over != 0 || mag > ULLONG_MAX)
|
||||
{
|
||||
errno = ERANGE;
|
||||
return ULLONG_MAX;
|
||||
}
|
||||
return neg != 0 ? 0ULL - mag : mag;
|
||||
}
|
||||
|
||||
/*
|
||||
* atoi/atol/atoll: strto* with no endptr and base 10 (C23 7.24.1.2). The
|
||||
* result truncation is that of the target type; out-of-range input is
|
||||
* undefined behavior, so no additional checking is performed.
|
||||
*/
|
||||
int
|
||||
atoi(const char *nptr)
|
||||
{
|
||||
return (int)strtol(nptr, NULL, 10);
|
||||
}
|
||||
|
||||
long
|
||||
atol(const char *nptr)
|
||||
{
|
||||
return strtol(nptr, NULL, 10);
|
||||
}
|
||||
|
||||
long long
|
||||
atoll(const char *nptr)
|
||||
{
|
||||
return strtoll(nptr, NULL, 10);
|
||||
}
|
||||
Reference in New Issue
Block a user