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