1/*
2 * strtoul.c --
3 *
4 *	Source code for the "strtoul" library procedure.
5 *
6 * Copyright (c) 1988 The Regents of the University of California.
7 * Copyright (c) 1994 Sun Microsystems, Inc.
8 *
9 * See the file "license.terms" for information on usage and redistribution of
10 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 * RCS: @(#) $Id: strtoul.c,v 1.7 2007/04/16 13:36:34 dkf Exp $
13 */
14
15#include "tclInt.h"
16
17/*
18 * The table below is used to convert from ASCII digits to a numerical
19 * equivalent. It maps from '0' through 'z' to integers (100 for non-digit
20 * characters).
21 */
22
23static char cvtIn[] = {
24    0, 1, 2, 3, 4, 5, 6, 7, 8, 9,		/* '0' - '9' */
25    100, 100, 100, 100, 100, 100, 100,		/* punctuation */
26    10, 11, 12, 13, 14, 15, 16, 17, 18, 19,	/* 'A' - 'Z' */
27    20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
28    30, 31, 32, 33, 34, 35,
29    100, 100, 100, 100, 100, 100,		/* punctuation */
30    10, 11, 12, 13, 14, 15, 16, 17, 18, 19,	/* 'a' - 'z' */
31    20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
32    30, 31, 32, 33, 34, 35};
33
34/*
35 *----------------------------------------------------------------------
36 *
37 * strtoul --
38 *
39 *	Convert an ASCII string into an integer.
40 *
41 * Results:
42 *	The return value is the integer equivalent of string. If endPtr is
43 *	non-NULL, then *endPtr is filled in with the character after the last
44 *	one that was part of the integer. If string doesn't contain a valid
45 *	integer value, then zero is returned and *endPtr is set to string.
46 *
47 * Side effects:
48 *	None.
49 *
50 *----------------------------------------------------------------------
51 */
52
53unsigned long int
54strtoul(
55    CONST char *string,		/* String of ASCII digits, possibly preceded
56				 * by white space. For bases greater than 10,
57				 * either lower- or upper-case digits may be
58				 * used. */
59    char **endPtr,		/* Where to store address of terminating
60				 * character, or NULL. */
61    int base)			/* Base for conversion.  Must be less than 37.
62				 * If 0, then the base is chosen from the
63				 * leading characters of string: "0x" means
64				 * hex, "0" means octal, anything else means
65				 * decimal. */
66{
67    register CONST char *p;
68    register unsigned long int result = 0;
69    register unsigned digit;
70    int anyDigits = 0;
71    int negative=0;
72    int overflow=0;
73
74    /*
75     * Skip any leading blanks.
76     */
77
78    p = string;
79    while (isspace(UCHAR(*p))) {
80	p += 1;
81    }
82    if (*p == '-') {
83        negative = 1;
84        p += 1;
85    } else {
86        if (*p == '+') {
87            p += 1;
88        }
89    }
90
91    /*
92     * If no base was provided, pick one from the leading characters of the
93     * string.
94     */
95
96    if (base == 0) {
97	if (*p == '0') {
98	    p += 1;
99	    if ((*p == 'x') || (*p == 'X')) {
100		p += 1;
101		base = 16;
102	    } else {
103		/*
104		 * Must set anyDigits here, otherwise "0" produces a "no
105		 * digits" error.
106		 */
107
108		anyDigits = 1;
109		base = 8;
110	    }
111	} else {
112	    base = 10;
113	}
114    } else if (base == 16) {
115	/*
116	 * Skip a leading "0x" from hex numbers.
117	 */
118
119	if ((p[0] == '0') && ((p[1] == 'x') || (p[1] == 'X'))) {
120	    p += 2;
121	}
122    }
123
124    /*
125     * Sorry this code is so messy, but speed seems important. Do different
126     * things for base 8, 10, 16, and other.
127     */
128
129    if (base == 8) {
130	unsigned long maxres = ULONG_MAX >> 3;
131
132	for ( ; ; p += 1) {
133	    digit = *p - '0';
134	    if (digit > 7) {
135		break;
136	    }
137	    if (result > maxres) { overflow = 1; }
138	    result = (result << 3);
139	    if (digit > (ULONG_MAX - result)) { overflow = 1; }
140	    result += digit;
141	    anyDigits = 1;
142	}
143    } else if (base == 10) {
144	unsigned long maxres = ULONG_MAX / 10;
145
146	for ( ; ; p += 1) {
147	    digit = *p - '0';
148	    if (digit > 9) {
149		break;
150	    }
151	    if (result > maxres) { overflow = 1; }
152	    result *= 10;
153	    if (digit > (ULONG_MAX - result)) { overflow = 1; }
154	    result += digit;
155	    anyDigits = 1;
156	}
157    } else if (base == 16) {
158	unsigned long maxres = ULONG_MAX >> 4;
159
160	for ( ; ; p += 1) {
161	    digit = *p - '0';
162	    if (digit > ('z' - '0')) {
163		break;
164	    }
165	    digit = cvtIn[digit];
166	    if (digit > 15) {
167		break;
168	    }
169	    if (result > maxres) { overflow = 1; }
170	    result = (result << 4);
171	    if (digit > (ULONG_MAX - result)) { overflow = 1; }
172	    result += digit;
173	    anyDigits = 1;
174	}
175    } else if (base >= 2 && base <= 36) {
176	unsigned long maxres = ULONG_MAX / base;
177
178	for ( ; ; p += 1) {
179	    digit = *p - '0';
180	    if (digit > ('z' - '0')) {
181		break;
182	    }
183	    digit = cvtIn[digit];
184	    if (digit >= ( (unsigned) base )) {
185		break;
186	    }
187	    if (result > maxres) { overflow = 1; }
188	    result *= base;
189	    if (digit > (ULONG_MAX - result)) { overflow = 1; }
190	    result += digit;
191	    anyDigits = 1;
192	}
193    }
194
195    /*
196     * See if there were any digits at all.
197     */
198
199    if (!anyDigits) {
200	p = string;
201    }
202
203    if (endPtr != 0) {
204	/* unsafe, but required by the strtoul prototype */
205	*endPtr = (char *) p;
206    }
207
208    if (overflow) {
209	errno = ERANGE;
210	return ULONG_MAX;
211    }
212    if (negative) {
213	return -result;
214    }
215    return result;
216}
217