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

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.5     ! moko       12: volatile const char * IDENT_PA_INT_C="$Id: pa_int.C,v 1.4 2026/01/07 01:10:05 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){
                    147:                const uint min_abs = (uint)(-( (uint)INT_MIN + 1 )) + 1;
                    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){
                    180:                const pa_uwint min_abs = (pa_uwint)(-( (pa_wint)PA_WINT_MIN + 1 )) + 1;
                    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
                    237: enum FormatType {
                    238:        FormatInvalid,
                    239:        FormatInt,
                    240:        FormatUInt,
                    241:        FormatDouble
                    242: };
                    243: 
                    244: FormatType format_type(const char* fmt){
                    245:        enum FormatState {
                    246:                Percent,
                    247:                Flags,
                    248:                Width,
                    249:                Precision,
                    250:                Done
                    251:        } state=Percent;
                    252: 
                    253:        FormatType result=FormatInvalid;
                    254: 
                    255:        const char* pos=fmt;
                    256:        while(char c=*(pos++)){
                    257:                switch(state){
                    258:                        case Percent:
                    259:                                if(c=='%'){
                    260:                                        state=Flags;
                    261:                                } else {
                    262:                                        return FormatInvalid; // 1st char must be '%' only
                    263:                                }
                    264:                                break;
                    265:                        case Flags:
                    266:                                if(strchr("-+ #0", c)!=0){
                    267:                                        break;
                    268:                                }
                    269:                                // go to the next step
                    270:                        case Width:
                    271:                                if(c=='.'){
                    272:                                        state=Precision;
                    273:                                        break;
                    274:                                }
                    275:                                // go to the next step
                    276:                        case Precision:
                    277:                                if(c>='0' && c<='9'){
                    278:                                        if(state == Flags) state=Width; // no more flags
                    279:                                        break;
                    280:                                } else if(c=='d' || c=='i'){
                    281:                                        result=FormatInt;
                    282:                                } else if(strchr("feEgG", c)!=0){
                    283:                                        result=FormatDouble;
                    284:                                } else if(strchr("uoxX", c)!=0){
                    285:                                        result=FormatUInt;
                    286:                                } else {
                    287:                                        return FormatInvalid; // invalid char
                    288:                                }
                    289:                                state=Done;
                    290:                                break;
                    291:                        case Done:
                    292:                                return FormatInvalid; // no chars allowed after 'type'
                    293:                }
                    294:        }
                    295:        return result;
                    296: }
                    297: 
1.4       moko      298: #ifdef PA_WIDE_INT
                    299: 
                    300: #if defined(_MSC_VER) && (_MSC_VER < 1900)
                    301: // old VS: %I64d
                    302: #define PA_INTMOD "I64"
                    303: #else
                    304: // C99: %lld
                    305: #define PA_INTMOD "ll"
                    306: #endif
                    307: 
                    308: static const char* wide_int_fmt(const char *fmt, char *dst){
                    309:        const size_t len = strlen(fmt);
                    310:        const size_t head = len - 1;
                    311:        memcpy(dst, fmt, head);
                    312:        memcpy(dst + head, PA_INTMOD, strlen(PA_INTMOD));
                    313:        dst[head + strlen(PA_INTMOD)] = fmt[head];
                    314:        dst[head + strlen(PA_INTMOD) + 1] = '\0';
                    315:        return dst;
                    316: }
                    317: #endif
                    318: 
1.3       moko      319: 
                    320: const char* format_double(double value, const char* fmt) {
                    321:        char local_buf[MAX_NUMBER];
                    322:        int size=-1;
                    323: 
                    324:        if(fmt && strlen(fmt)){
                    325:                switch(format_type(fmt)){
                    326:                        case FormatDouble:
                    327:                                size=snprintf(local_buf, sizeof(local_buf), fmt, value);
                    328:                                break;
                    329:                        case FormatUInt:
                    330:                                if(value >= 0){ // on Apple M1 (uint)<negative value> is 0
1.4       moko      331: #ifdef PA_WIDE_INT
                    332:                                        char fmt_buf[strlen(fmt)+4];
                    333:                                        size=snprintf(local_buf, sizeof(local_buf), wide_int_fmt(fmt, fmt_buf), (unsigned long long)clip2wint(value)); // WUINT_MAX == WINT_MAX
                    334: #else
1.3       moko      335:                                        size=snprintf(local_buf, sizeof(local_buf), fmt, clip2uint(value));
1.4       moko      336: #endif
                    337:                                } else {
                    338:                                        // for unsigned formats, wrap negatives to uint as unsigned wint would exceed the 53-bit range
                    339:                                        size=snprintf(local_buf, sizeof(local_buf), fmt, clip2int(value));
1.3       moko      340:                                }
1.4       moko      341:                                break;
                    342:                        case FormatInt:{
                    343: #ifdef PA_WIDE_INT
                    344:                                char fmt_buf[strlen(fmt)+4];
                    345:                                size=snprintf(local_buf, sizeof(local_buf), wide_int_fmt(fmt, fmt_buf), (long long)clip2wint(value));
                    346: #else
1.3       moko      347:                                size=snprintf(local_buf, sizeof(local_buf), fmt, clip2int(value));
1.4       moko      348: #endif
1.3       moko      349:                                break;
1.4       moko      350:                                }
1.3       moko      351:                        case FormatInvalid:
                    352:                                throw Exception(PARSER_RUNTIME, 0, "Incorrect format string '%s' was specified.", fmt);
                    353:                }
                    354:        } else
                    355:                return pa_itoa(clip2wint(value));
                    356: 
                    357:        if(size < 0 || size >= MAX_NUMBER-1){ // on win32 we manually reduce max size while printing
                    358:                throw Exception(PARSER_RUNTIME, 0, "Error occurred white executing snprintf with format string '%s'.", fmt);
                    359:        }
                    360: 
                    361:        return pa_strdup(local_buf, (size_t)size);
                    362: }
                    363: 

E-mail: