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