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