Annotation of parser3/src/main/pa_int.C, revision 1.9

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: 

E-mail: