HelenOS sources

root/uspace/lib/c/common/strtol.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. _digit_value
  2. _max_value
  3. _prefixbase
  4. _strtoumax
  5. _strtosigned
  6. _strtounsigned
  7. strtol
  8. strtoul
  9. strtoll
  10. strtoull
  11. strtoimax
  12. strtoumax
  13. atoi
  14. atol
  15. atoll
  16. str_uint8_t
  17. str_uint16_t
  18. str_uint32_t
  19. str_uint64_t
  20. str_int64_t
  21. str_size_t

/*
 * Copyright (c) 2005 Martin Decky
 * Copyright (c) 2008 Jiri Svoboda
 * Copyright (c) 2011 Martin Sucha
 * Copyright (c) 2011 Oleg Romanenko
 * Copyright (c) 2011 Jiri Zarevucky
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** @addtogroup libc
 * @{
 */
/** @file
 */

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
#include <str.h>

// FIXME: The original HelenOS functions return EOVERFLOW instead
//        of ERANGE. It's a pointless distinction from standard functions,
//        so we should change that. Beware the callers though.

// TODO: more unit tests

static inline int _digit_value(int c)
{
        if (isdigit(c)) {
                return c - '0';
        } else if (islower(c)) {
                return c - 'a' + 10;
        } else if (isupper(c)) {
                return c - 'A' + 10;
        }
        return INT_MAX;
}

/*
 * FIXME: workaround for GCC "optimizing" the overflow check
 * into soft-emulated 128b multiplication using `__multi3`,
 * which we don't currently implement.
 */
__attribute__((noinline)) static uintmax_t _max_value(int base)
{
        return UINTMAX_MAX / base;
}

static inline int _prefixbase(const char *restrict *nptrptr, bool nonstd)
{
        const char *nptr = *nptrptr;

        if (nptr[0] != '0')
                return 10;

        if (nptr[1] == 'x' || nptr[1] == 'X') {
                if (_digit_value(nptr[2]) < 16) {
                        *nptrptr += 2;
                        return 16;
                }
        }

        if (nonstd) {
                switch (nptr[1]) {
                case 'b':
                case 'B':
                        if (_digit_value(nptr[2]) < 2) {
                                *nptrptr += 2;
                                return 2;
                        }
                        break;
                case 'o':
                case 'O':
                        if (_digit_value(nptr[2]) < 8) {
                                *nptrptr += 2;
                                return 8;
                        }
                        break;
                case 'd':
                case 'D':
                case 't':
                case 'T':
                        if (_digit_value(nptr[2]) < 10) {
                                *nptrptr += 2;
                                return 10;
                        }
                        break;
                }
        }

        return 8;
}

static inline uintmax_t _strtoumax(
    const char *restrict nptr, char **restrict endptr, int base,
    bool *restrict sgn, errno_t *err, bool nonstd)
{
        assert(nptr != NULL);
        assert(sgn != NULL);

        const char *first = nptr;

        /* Skip leading whitespace. */

        while (isspace(*nptr)) {
                nptr++;
        }

        /* Parse sign, if any. */

        switch (*nptr) {
        case '-':
                *sgn = true;
                nptr++;
                break;
        case '+':
                nptr++;
                break;
        }

        /* Figure out the base. */

        if (base == 0)
                base = _prefixbase(&nptr, nonstd);

        if (base == 16 && !nonstd) {
                /*
                 * Standard strto* functions allow hexadecimal prefix to be
                 * present when base is explicitly set to 16.
                 * Our nonstandard str_* functions don't allow it.
                 * I don't know if that is intended, just matching the original
                 * functionality here.
                 */

                if (nptr[0] == '0' && (nptr[1] == 'x' || nptr[1] == 'X') &&
                    _digit_value(nptr[2]) < base)
                        nptr += 2;
        }

        if (base < 2 || base > 36) {
                *err = EINVAL;
                return 0;
        }

        /* Must be at least one digit. */

        if (_digit_value(*nptr) >= base) {
                /* No digits on input. */
                if (endptr != NULL)
                        *endptr = (char *) first;
                return 0;
        }

        /* Read the value.  */

        uintmax_t result = 0;
        uintmax_t max = _max_value(base);
        int digit;

        while (digit = _digit_value(*nptr), digit < base) {
                if (result > max ||
                    __builtin_add_overflow(result * base, digit, &result)) {

                        *err = nonstd ? EOVERFLOW : ERANGE;
                        result = UINTMAX_MAX;
                        break;
                }

                nptr++;
        }

        /* Set endptr. */

        if (endptr != NULL) {
                /*
                 * Move the pointer to the end of the number,
                 * in case it isn't there already.
                 * This can happen when the number has legal formatting,
                 * but is out of range of the target type.
                 */
                while (_digit_value(*nptr) < base) {
                        nptr++;
                }

                *endptr = (char *) nptr;
        }

        return result;
}

static inline intmax_t _strtosigned(const char *nptr, char **endptr, int base,
    intmax_t min, intmax_t max, errno_t *err, bool nonstd)
{
        bool sgn = false;
        uintmax_t number = _strtoumax(nptr, endptr, base, &sgn, err, nonstd);

        if (number > (uintmax_t) max) {
                if (sgn && (number - 1 == (uintmax_t) max)) {
                        return min;
                }

                *err = nonstd ? EOVERFLOW : ERANGE;
                return (sgn ? min : max);
        }

        return (sgn ? -number : number);
}

static inline uintmax_t _strtounsigned(const char *nptr, char **endptr, int base,
    uintmax_t max, errno_t *err, bool nonstd)
{
        bool sgn = false;
        uintmax_t number = _strtoumax(nptr, endptr, base, &sgn, err, nonstd);

        if (nonstd && sgn) {
                /* Do not allow negative values */
                *err = EINVAL;
                return 0;
        }

        if (number > max) {
                *err = nonstd ? EOVERFLOW : ERANGE;
                return max;
        }

        return (sgn ? -number : number);
}

/** Convert initial part of string to long int according to given base.
 * The number may begin with an arbitrary number of whitespaces followed by
 * optional sign (`+' or `-'). If the base is 0 or 16, the prefix `0x' may be
 * inserted and the number will be taken as hexadecimal one. If the base is 0
 * and the number begin with a zero, number will be taken as octal one (as with
 * base 8). Otherwise the base 0 is taken as decimal.
 *
 * @param nptr          Pointer to string.
 * @param[out] endptr   If not NULL, function stores here pointer to the first
 *                      invalid character.
 * @param base          Zero or number between 2 and 36 inclusive.
 * @return              Result of conversion.
 */
long strtol(const char *nptr, char **endptr, int base)
{
#if !__STDC_HOSTED__
        errno_t errno;
#endif

        return _strtosigned(nptr, endptr, base, LONG_MIN, LONG_MAX, &errno, false);
}

/** Convert initial part of string to unsigned long according to given base.
 * The number may begin with an arbitrary number of whitespaces followed by
 * optional sign (`+' or `-'). If the base is 0 or 16, the prefix `0x' may be
 * inserted and the number will be taken as hexadecimal one. If the base is 0
 * and the number begin with a zero, number will be taken as octal one (as with
 * base 8). Otherwise the base 0 is taken as decimal.
 *
 * @param nptr          Pointer to string.
 * @param[out] endptr   If not NULL, function stores here pointer to the first
 *                      invalid character
 * @param base          Zero or number between 2 and 36 inclusive.
 * @return              Result of conversion.
 */
unsigned long strtoul(const char *nptr, char **endptr, int base)
{
#if !__STDC_HOSTED__
        errno_t errno;
#endif

        return _strtounsigned(nptr, endptr, base, ULONG_MAX, &errno, false);
}

long long strtoll(const char *nptr, char **endptr, int base)
{
#if !__STDC_HOSTED__
        errno_t errno;
#endif

        return _strtosigned(nptr, endptr, base, LLONG_MIN, LLONG_MAX, &errno, false);
}

unsigned long long strtoull(const char *nptr, char **endptr, int base)
{
#if !__STDC_HOSTED__
        errno_t errno;
#endif

        return _strtounsigned(nptr, endptr, base, ULLONG_MAX, &errno, false);
}

intmax_t strtoimax(const char *nptr, char **endptr, int base)
{
#if !__STDC_HOSTED__
        errno_t errno;
#endif

        return _strtosigned(nptr, endptr, base, INTMAX_MIN, INTMAX_MAX, &errno, false);
}

uintmax_t strtoumax(const char *nptr, char **endptr, int base)
{
#if !__STDC_HOSTED__
        errno_t errno;
#endif

        return _strtounsigned(nptr, endptr, base, UINTMAX_MAX, &errno, false);
}

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);
}

/** Convert string to uint8_t.
 *
 * @param nptr   Pointer to string.
 * @param endptr If not NULL, pointer to the first invalid character
 *               is stored here.
 * @param base   Zero or number between 2 and 36 inclusive.
 * @param strict Do not allow any trailing characters.
 * @param result Result of the conversion.
 *
 * @return EOK if conversion was successful.
 *
 */
errno_t str_uint8_t(const char *nptr, const char **endptr, unsigned int base,
    bool strict, uint8_t *result)
{
        assert(result != NULL);

        errno_t rc = EOK;
        char *lendptr = (char *) nptr;

        uintmax_t r = _strtounsigned(nptr, &lendptr, base, UINT8_MAX, &rc, true);

        if (endptr)
                *endptr = lendptr;

        if (rc != EOK)
                return rc;

        if (strict && *lendptr != '\0')
                return EINVAL;

        *result = r;
        return EOK;
}

/** Convert string to uint16_t.
 *
 * @param nptr   Pointer to string.
 * @param endptr If not NULL, pointer to the first invalid character
 *               is stored here.
 * @param base   Zero or number between 2 and 36 inclusive.
 * @param strict Do not allow any trailing characters.
 * @param result Result of the conversion.
 *
 * @return EOK if conversion was successful.
 *
 */
errno_t str_uint16_t(const char *nptr, const char **endptr, unsigned int base,
    bool strict, uint16_t *result)
{
        assert(result != NULL);

        errno_t rc = EOK;
        char *lendptr = (char *) nptr;

        uintmax_t r = _strtounsigned(nptr, &lendptr, base, UINT16_MAX, &rc, true);

        if (endptr)
                *endptr = lendptr;

        if (rc != EOK)
                return rc;

        if (strict && *lendptr != '\0')
                return EINVAL;

        *result = r;
        return EOK;
}

/** Convert string to uint32_t.
 *
 * @param nptr   Pointer to string.
 * @param endptr If not NULL, pointer to the first invalid character
 *               is stored here.
 * @param base   Zero or number between 2 and 36 inclusive.
 * @param strict Do not allow any trailing characters.
 * @param result Result of the conversion.
 *
 * @return EOK if conversion was successful.
 *
 */
errno_t str_uint32_t(const char *nptr, const char **endptr, unsigned int base,
    bool strict, uint32_t *result)
{
        assert(result != NULL);

        errno_t rc = EOK;
        char *lendptr = (char *) nptr;

        uintmax_t r = _strtounsigned(nptr, &lendptr, base, UINT32_MAX, &rc, true);

        if (endptr)
                *endptr = lendptr;

        if (rc != EOK)
                return rc;

        if (strict && *lendptr != '\0')
                return EINVAL;

        *result = r;
        return EOK;
}

/** Convert string to uint64_t.
 *
 * @param nptr   Pointer to string.
 * @param endptr If not NULL, pointer to the first invalid character
 *               is stored here.
 * @param base   Zero or number between 2 and 36 inclusive.
 * @param strict Do not allow any trailing characters.
 * @param result Result of the conversion.
 *
 * @return EOK if conversion was successful.
 *
 */
errno_t str_uint64_t(const char *nptr, const char **endptr, unsigned int base,
    bool strict, uint64_t *result)
{
        assert(result != NULL);

        errno_t rc = EOK;
        char *lendptr = (char *) nptr;

        uintmax_t r = _strtounsigned(nptr, &lendptr, base, UINT64_MAX, &rc, true);

        if (endptr)
                *endptr = lendptr;

        if (rc != EOK)
                return rc;

        if (strict && *lendptr != '\0')
                return EINVAL;

        *result = r;
        return EOK;
}

/** Convert string to int64_t.
 *
 * @param nptr   Pointer to string.
 * @param endptr If not NULL, pointer to the first invalid character
 *               is stored here.
 * @param base   Zero or number between 2 and 36 inclusive.
 * @param strict Do not allow any trailing characters.
 * @param result Result of the conversion.
 *
 * @return EOK if conversion was successful.
 *
 */
errno_t str_int64_t(const char *nptr, const char **endptr, unsigned int base,
    bool strict, int64_t *result)
{
        assert(result != NULL);

        errno_t rc = EOK;
        char *lendptr = (char *) nptr;

        intmax_t r = _strtosigned(nptr, &lendptr, base, INT64_MIN, INT64_MAX, &rc, true);

        if (endptr)
                *endptr = lendptr;

        if (rc != EOK)
                return rc;

        if (strict && *lendptr != '\0')
                return EINVAL;

        *result = r;
        return EOK;
}

/** Convert string to size_t.
 *
 * @param nptr   Pointer to string.
 * @param endptr If not NULL, pointer to the first invalid character
 *               is stored here.
 * @param base   Zero or number between 2 and 36 inclusive.
 * @param strict Do not allow any trailing characters.
 * @param result Result of the conversion.
 *
 * @return EOK if conversion was successful.
 *
 */
errno_t str_size_t(const char *nptr, const char **endptr, unsigned int base,
    bool strict, size_t *result)
{
        assert(result != NULL);

        errno_t rc = EOK;
        char *lendptr = (char *) nptr;

        uintmax_t r = _strtounsigned(nptr, &lendptr, base, SIZE_MAX, &rc, true);

        if (endptr)
                *endptr = lendptr;

        if (rc != EOK)
                return rc;

        if (strict && *lendptr != '\0')
                return EINVAL;

        *result = r;
        return EOK;
}

/** @}
 */

/* [<][>][^][v][top][bottom][index][help] */
HelenOS homepage, sources at GitHub