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

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: 

E-mail: