123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- #include <stdbool.h>
- #include "nvim/ascii.h"
- #include "nvim/macros.h"
- #include "nvim/charset.h"
- #include "nvim/eval/typval.h"
- #include "nvim/vim.h"
- int hex2nr(int c)
- {
- if ((c >= 'a') && (c <= 'f')) {
- return c - 'a' + 10;
- }
- if ((c >= 'A') && (c <= 'F')) {
- return c - 'A' + 10;
- }
- return c - '0';
- }
- void vim_str2nr(const char_u *const start, int *const prep, int *const len,
- const int what, varnumber_T *const nptr,
- uvarnumber_T *const unptr, const int maxlen)
- {
- const char *ptr = (const char *)start;
- #define STRING_ENDED(ptr) \
- (!(maxlen == 0 || (int)((ptr) - (const char *)start) < maxlen))
- int pre = 0; // default is decimal
- const bool negative = (ptr[0] == '-');
- uvarnumber_T un = 0;
- if (negative) {
- ptr++;
- }
- if (what & STR2NR_FORCE) {
- // When forcing main consideration is skipping the prefix. Octal and decimal
- // numbers have no prefixes to skip. pre is not set.
- switch ((unsigned)what & (~(unsigned)STR2NR_FORCE)) {
- case STR2NR_HEX: {
- if (!STRING_ENDED(ptr + 2)
- && ptr[0] == '0'
- && (ptr[1] == 'x' || ptr[1] == 'X')
- && ascii_isxdigit(ptr[2])) {
- ptr += 2;
- }
- goto vim_str2nr_hex;
- }
- case STR2NR_BIN: {
- if (!STRING_ENDED(ptr + 2)
- && ptr[0] == '0'
- && (ptr[1] == 'b' || ptr[1] == 'B')
- && ascii_isbdigit(ptr[2])) {
- ptr += 2;
- }
- goto vim_str2nr_bin;
- }
- case STR2NR_OCT: {
- goto vim_str2nr_oct;
- }
- case 0: {
- goto vim_str2nr_dec;
- }
- default: {
- abort();
- }
- }
- } else if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN))
- && !STRING_ENDED(ptr + 1)
- && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') {
- pre = ptr[1];
- // Detect hexadecimal: 0x or 0X followed by hex digit
- if ((what & STR2NR_HEX)
- && !STRING_ENDED(ptr + 2)
- && (pre == 'X' || pre == 'x')
- && ascii_isxdigit(ptr[2])) {
- ptr += 2;
- goto vim_str2nr_hex;
- }
- // Detect binary: 0b or 0B followed by 0 or 1
- if ((what & STR2NR_BIN)
- && !STRING_ENDED(ptr + 2)
- && (pre == 'B' || pre == 'b')
- && ascii_isbdigit(ptr[2])) {
- ptr += 2;
- goto vim_str2nr_bin;
- }
- // Detect octal number: zero followed by octal digits without '8' or '9'
- pre = 0;
- if (!(what & STR2NR_OCT)) {
- goto vim_str2nr_dec;
- }
- for (int i = 2; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) {
- if (ptr[i] > '7') {
- goto vim_str2nr_dec;
- }
- }
- pre = '0';
- goto vim_str2nr_oct;
- } else {
- goto vim_str2nr_dec;
- }
- // Do the string-to-numeric conversion "manually" to avoid sscanf quirks.
- abort(); // Should’ve used goto earlier.
- #define PARSE_NUMBER(base, cond, conv) \
- do { \
- while (!STRING_ENDED(ptr) && (cond)) { \
- /* avoid ubsan error for overflow */ \
- if (un < UVARNUMBER_MAX / base) { \
- un = base * un + (uvarnumber_T)(conv); \
- } else { \
- un = UVARNUMBER_MAX; \
- } \
- ptr++; \
- } \
- } while (0)
- switch (pre) {
- case 'b':
- case 'B': {
- vim_str2nr_bin:
- PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0'));
- break;
- }
- case '0': {
- vim_str2nr_oct:
- PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0'));
- break;
- }
- case 0: {
- vim_str2nr_dec:
- PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0'));
- break;
- }
- case 'x':
- case 'X': {
- vim_str2nr_hex:
- PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr)));
- break;
- }
- }
- #undef PARSE_NUMBER
- if (prep != NULL) {
- *prep = pre;
- }
- if (len != NULL) {
- *len = (int)(ptr - (const char *)start);
- }
- if (nptr != NULL) {
- if (negative) { // account for leading '-' for decimal numbers
- // avoid ubsan error for overflow
- if (un > VARNUMBER_MAX) {
- *nptr = VARNUMBER_MIN;
- } else {
- *nptr = -(varnumber_T)un;
- }
- } else {
- if (un > VARNUMBER_MAX) {
- un = VARNUMBER_MAX;
- }
- *nptr = (varnumber_T)un;
- }
- }
- if (unptr != NULL) {
- *unptr = un;
- }
- #undef STRING_ENDED
- }
|