|
|
1.1 moko 1: /** @file
2: Parser: pa_int support functions
3:
1.9 ! moko 4: Copyright (c) 2001-2026 Art. Lebedev Studio (https://www.artlebedev.com)
1.1 moko 5: Authors: Konstantin Morshnev <moko@design.ru>, Alexandr Petrosian <paf@design.ru>
6: */
7:
1.8 moko 8: #include "pa_common.h"
1.1 moko 9: #include "pa_int.h"
10: #include "pa_string.h"
11: #include "pa_exception.h"
12:
1.9 ! moko 13: volatile const char * IDENT_PA_INT_C="$Id: pa_int.C,v 1.8 2026/01/07 18:11:33 moko Exp $" IDENT_PA_INT_H;
1.1 moko 14:
1.3 moko 15: #ifdef PA_WIDE_INT
16: int check4int(pa_wint avalue){
17: if( (avalue > INT_MAX) || (avalue < INT_MIN))
18: throw Exception("number.format", 0, "%s is out of regular int range", pa_itoa(avalue));
19: return (int)avalue;
20: }
21: #endif
1.5 moko 22:
23: char* pa_itoa(int i){ // custom to support INT_MIN
24: const int base=10;
25: char buf[MAX_NUMBER + 1];
26: char* pos=buf + MAX_NUMBER;
27: *pos='\0';
28:
29: bool negative=i < 0;
30: unsigned int n= i < 0 ? 0u-(unsigned int)i : (unsigned int)i;
31:
32: do {
33: *(--pos)=(char)(n % base) + '0';
34: n/=base;
35: } while (n > 0);
36:
37: if (negative) {
38: *(--pos) = '-';
39: }
40: return pa_strdup(pos, buf + MAX_NUMBER - pos);
41: }
42:
1.1 moko 43: // pa_atoui is based on Manuel Novoa III _strto_l for uClibc
44:
45: template<typename T> inline T pa_ato_any(const char *str, int base, const String* problem_source,const T max){
46: T result = 0;
47: const char *pos = str;
48:
49: while (isspace(*pos)) /* skip leading whitespace */
50: ++pos;
51:
52: if (base == 16 && *pos == '0') { /* handle option prefix */
53: ++pos;
54: if (*pos == 'x' || *pos == 'X') {
55: ++pos;
56: }
57: }
58:
59: if (base == 0) { /* dynamic base */
60: base = 10; /* default is 10 */
61: if (*pos == '0') {
62: ++pos;
63: if (*pos == 'x' || *pos == 'X'){
64: ++pos;
65: base=16;
66: }
67: }
68: }
69:
70: if (base < 2 || base > 16) /* illegal base */
71: throw Exception(PARSER_RUNTIME, 0, "base to must be an integer from 2 to 16");
72: if (*pos == '-')
73: throw Exception("number.format", problem_source, problem_source ? "out of range (negative)" : "'%s' is out if range (negative)", str);
74:
75: T cutoff = max / base;
76: int cutoff_digit = (int)(max - cutoff * base);
77:
78: while(true) {
79: int digit;
80:
81: if ((*pos >= '0') && (*pos <= '9')) {
82: digit = (*pos - '0');
83: } else if (*pos >= 'a') {
84: digit = (*pos - 'a' + 10);
85: } else if (*pos >= 'A') {
86: digit = (*pos - 'A' + 10);
87: } else break;
88:
89: if (digit >= base) {
90: break;
91: }
92:
93: ++pos;
94:
95: /* adjust number, with overflow check */
96: if ((result > cutoff) || ((result == cutoff) && (digit > cutoff_digit))) {
97: throw Exception("number.format", problem_source, problem_source ? "out of range (int)" : "'%s' is out of range (int)", str);
98: } else {
99: result = result * base + digit;
100: }
101: }
102:
103: while(char c=*pos++)
104: if(!isspace(c))
105: throw Exception("number.format", problem_source, problem_source ? "invalid number (int)" : "'%s' is an invalid number (int)", str);
106:
107: return result;
108: }
109:
110: unsigned int pa_atoui(const char *str, int base, const String* problem_source){
111: if(!str)
112: return 0;
113:
114: return pa_ato_any<unsigned int>(str, base, problem_source, UINT_MAX);
115: }
116:
117: uint64_t pa_atoul(const char *str, int base, const String* problem_source){
118: if(!str)
119: return 0;
120:
121: return pa_ato_any<uint64_t>(str, base, problem_source, ULLONG_MAX);
122: }
123:
124: int pa_atoi(const char* str, int base, const String* problem_source) {
125: if(!str)
126: return 0;
127:
128: while(isspace(*str))
129: str++;
130:
131: if(!*str)
132: return 0;
133:
134: const char *str_copy=str;
135: bool negative=false;
136: if(str[0]=='-') {
137: negative=true;
138: str++;
139: if(!*str || isspace(*str))
140: throw Exception("number.format", problem_source, problem_source ? "invalid number (int)" : "'%s' is an invalid number (int)", str_copy);
141: } else if(str[0]=='+') {
142: str++;
143: if(!*str || isspace(*str))
144: throw Exception("number.format", problem_source, problem_source ? "invalid number (int)" : "'%s' is an invalid number (int)", str_copy);
145: }
146:
147: if(negative){
1.6 moko 148: const uint min_abs = (uint)0 - (uint)INT_MIN;
1.1 moko 149: uint result=pa_ato_any<uint>(str, base, problem_source, min_abs);
150: if(result==min_abs) return INT_MIN;
151: return -(int)result;
152: } else {
153: return (int)pa_ato_any<uint>(str, base, problem_source, INT_MAX);
154: }
155: }
156:
157: pa_wint pa_atowi(const char* str, int base, const String* problem_source) {
158: if(!str)
159: return 0;
160:
161: while(isspace(*str))
162: str++;
163:
164: if(!*str)
165: return 0;
166:
167: const char *str_copy=str;
168: bool negative=false;
169: if(str[0]=='-') {
170: negative=true;
171: str++;
172: if(!*str || isspace(*str))
173: throw Exception("number.format", problem_source, problem_source ? "invalid number (int)" : "'%s' is an invalid number (int)", str_copy);
174: } else if(str[0]=='+') {
175: str++;
176: if(!*str || isspace(*str))
177: throw Exception("number.format", problem_source, problem_source ? "invalid number (int)" : "'%s' is an invalid number (int)", str_copy);
178: }
179:
180: if(negative){
1.7 moko 181: const pa_uwint min_abs = (pa_uwint)0 - (pa_uwint)PA_WINT_MIN;
1.1 moko 182: pa_uwint result=pa_ato_any<pa_uwint>(str, base, problem_source, min_abs);
183: if(result==min_abs) return PA_WINT_MIN;
184: return -(pa_wint)result;
185: } else {
186: return (pa_wint)pa_ato_any<pa_uwint>(str, base, problem_source, PA_WINT_MAX);
187: }
188: }
189:
190: double pa_atod(const char* str, const String* problem_source /* never null */) {
191: if(!str)
192: return 0;
193:
194: while(isspace(*str))
195: str++;
196:
197: if(!*str)
198: return 0;
199:
200: bool negative=false;
201: if(str[0]=='-') {
202: negative=true;
203: str++;
204: if(!*str || isspace(*str))
205: throw Exception("number.format", problem_source, "invalid number (double)");
206: } else if(str[0]=='+') {
207: str++;
208: if(!*str || isspace(*str))
209: throw Exception("number.format", problem_source, "invalid number (double)");
210: }
211:
212: double result;
213: if(str[0]=='0') {
214: if(str[1]=='x' || str[1]=='X') {
215: // 0xABC
216: result=(double)pa_atoul(str, 0, problem_source);
217: return negative ? -result : result;
218: } else {
219: // skip leading 0000, to disable octal interpretation
220: do str++; while(*str=='0');
221: }
222: }
223:
224: char *error_pos;
225: result=strtod(str, &error_pos);
226:
227: while(const char c=*error_pos++)
228: if(!isspace(c))
229: throw Exception("number.format", problem_source, "invalid number (double)");
230:
231: return negative ? -result : result;
232: }
233:
1.3 moko 234:
235: // format: %[flags][width][.precision]type https://msdn.microsoft.com/ru-ru/library/56e442dc(en-us,VS.80).aspx
236: // flags: '-', '+', ' ', '#', '0' https://msdn.microsoft.com/ru-ru/library/8aky45ct(en-us,VS.80).aspx
237: // width, precision: non negative decimal number
1.6 moko 238:
239: #define MAX_FORMAT_LEN 10 // %+0XX.XXg\0
240:
1.3 moko 241: enum FormatType {
242: FormatInvalid,
243: FormatInt,
244: FormatUInt,
245: FormatDouble
246: };
247:
248: FormatType format_type(const char* fmt){
249: enum FormatState {
250: Percent,
251: Flags,
252: Width,
253: Precision,
254: Done
255: } state=Percent;
256:
257: FormatType result=FormatInvalid;
258:
259: const char* pos=fmt;
260: while(char c=*(pos++)){
261: switch(state){
262: case Percent:
263: if(c=='%'){
264: state=Flags;
265: } else {
266: return FormatInvalid; // 1st char must be '%' only
267: }
268: break;
269: case Flags:
270: if(strchr("-+ #0", c)!=0){
271: break;
272: }
273: // go to the next step
274: case Width:
275: if(c=='.'){
276: state=Precision;
277: break;
278: }
279: // go to the next step
280: case Precision:
281: if(c>='0' && c<='9'){
282: if(state == Flags) state=Width; // no more flags
283: break;
284: } else if(c=='d' || c=='i'){
285: result=FormatInt;
286: } else if(strchr("feEgG", c)!=0){
287: result=FormatDouble;
288: } else if(strchr("uoxX", c)!=0){
289: result=FormatUInt;
290: } else {
291: return FormatInvalid; // invalid char
292: }
293: state=Done;
294: break;
295: case Done:
296: return FormatInvalid; // no chars allowed after 'type'
297: }
298: }
1.6 moko 299: return pos-fmt > MAX_FORMAT_LEN ? FormatInvalid : result;
1.3 moko 300: }
301:
1.4 moko 302: #ifdef PA_WIDE_INT
303:
304: #if defined(_MSC_VER) && (_MSC_VER < 1900)
305: // old VS: %I64d
306: #define PA_INTMOD "I64"
307: #else
308: // C99: %lld
309: #define PA_INTMOD "ll"
310: #endif
311:
312: static const char* wide_int_fmt(const char *fmt, char *dst){
313: const size_t len = strlen(fmt);
314: const size_t head = len - 1;
315: memcpy(dst, fmt, head);
316: memcpy(dst + head, PA_INTMOD, strlen(PA_INTMOD));
317: dst[head + strlen(PA_INTMOD)] = fmt[head];
318: dst[head + strlen(PA_INTMOD) + 1] = '\0';
319: return dst;
320: }
321: #endif
322:
1.3 moko 323:
324: const char* format_double(double value, const char* fmt) {
325: char local_buf[MAX_NUMBER];
326: int size=-1;
327:
328: if(fmt && strlen(fmt)){
329: switch(format_type(fmt)){
330: case FormatDouble:
331: size=snprintf(local_buf, sizeof(local_buf), fmt, value);
332: break;
333: case FormatUInt:
334: if(value >= 0){ // on Apple M1 (uint)<negative value> is 0
1.4 moko 335: #ifdef PA_WIDE_INT
1.6 moko 336: char fmt_buf[MAX_FORMAT_LEN+4];
1.4 moko 337: size=snprintf(local_buf, sizeof(local_buf), wide_int_fmt(fmt, fmt_buf), (unsigned long long)clip2wint(value)); // WUINT_MAX == WINT_MAX
338: #else
1.3 moko 339: size=snprintf(local_buf, sizeof(local_buf), fmt, clip2uint(value));
1.4 moko 340: #endif
341: } else {
342: // for unsigned formats, wrap negatives to uint as unsigned wint would exceed the 53-bit range
343: size=snprintf(local_buf, sizeof(local_buf), fmt, clip2int(value));
1.3 moko 344: }
1.4 moko 345: break;
346: case FormatInt:{
347: #ifdef PA_WIDE_INT
1.6 moko 348: char fmt_buf[MAX_FORMAT_LEN+4];
1.4 moko 349: size=snprintf(local_buf, sizeof(local_buf), wide_int_fmt(fmt, fmt_buf), (long long)clip2wint(value));
350: #else
1.3 moko 351: size=snprintf(local_buf, sizeof(local_buf), fmt, clip2int(value));
1.4 moko 352: #endif
1.3 moko 353: break;
1.4 moko 354: }
1.3 moko 355: case FormatInvalid:
356: throw Exception(PARSER_RUNTIME, 0, "Incorrect format string '%s' was specified.", fmt);
357: }
358: } else
359: return pa_itoa(clip2wint(value));
360:
361: if(size < 0 || size >= MAX_NUMBER-1){ // on win32 we manually reduce max size while printing
362: throw Exception(PARSER_RUNTIME, 0, "Error occurred white executing snprintf with format string '%s'.", fmt);
363: }
364:
365: return pa_strdup(local_buf, (size_t)size);
366: }
367: