kmp_str.cpp 22.3 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751
/*
 * kmp_str.cpp -- String manipulation routines.
 */

//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "kmp_str.h"

#include <stdarg.h> // va_*
#include <stdio.h> // vsnprintf()
#include <stdlib.h> // malloc(), realloc()

#include "kmp.h"
#include "kmp_i18n.h"

/* String buffer.

   Usage:

   // Declare buffer and initialize it.
   kmp_str_buf_t  buffer;
   __kmp_str_buf_init( & buffer );

   // Print to buffer.
   __kmp_str_buf_print(& buffer, "Error in file \"%s\" line %d\n", "foo.c", 12);
   __kmp_str_buf_print(& buffer, "    <%s>\n", line);

   // Use buffer contents. buffer.str is a pointer to data, buffer.used is a
   // number of printed characters (not including terminating zero).
   write( fd, buffer.str, buffer.used );

   // Free buffer.
   __kmp_str_buf_free( & buffer );

   // Alternatively, you can detach allocated memory from buffer:
   __kmp_str_buf_detach( & buffer );
   return buffer.str;    // That memory should be freed eventually.

   Notes:

   * Buffer users may use buffer.str and buffer.used. Users should not change
     any fields of buffer directly.
   * buffer.str is never NULL. If buffer is empty, buffer.str points to empty
     string ("").
   * For performance reasons, buffer uses stack memory (buffer.bulk) first. If
     stack memory is exhausted, buffer allocates memory on heap by malloc(), and
     reallocates it by realloc() as amount of used memory grows.
   * Buffer doubles amount of allocated memory each time it is exhausted.
*/

// TODO: __kmp_str_buf_print() can use thread local memory allocator.

#define KMP_STR_BUF_INVARIANT(b)                                               \
  {                                                                            \
    KMP_DEBUG_ASSERT((b)->str != NULL);                                        \
    KMP_DEBUG_ASSERT((b)->size >= sizeof((b)->bulk));                          \
    KMP_DEBUG_ASSERT((b)->size % sizeof((b)->bulk) == 0);                      \
    KMP_DEBUG_ASSERT((unsigned)(b)->used < (b)->size);                         \
    KMP_DEBUG_ASSERT(                                                          \
        (b)->size == sizeof((b)->bulk) ? (b)->str == &(b)->bulk[0] : 1);       \
    KMP_DEBUG_ASSERT((b)->size > sizeof((b)->bulk) ? (b)->str != &(b)->bulk[0] \
                                                   : 1);                       \
  }

void __kmp_str_buf_clear(kmp_str_buf_t *buffer) {
  KMP_STR_BUF_INVARIANT(buffer);
  if (buffer->used > 0) {
    buffer->used = 0;
    buffer->str[0] = 0;
  }
  KMP_STR_BUF_INVARIANT(buffer);
} // __kmp_str_buf_clear

void __kmp_str_buf_reserve(kmp_str_buf_t *buffer, int size) {
  KMP_STR_BUF_INVARIANT(buffer);
  KMP_DEBUG_ASSERT(size >= 0);

  if (buffer->size < (unsigned int)size) {
    // Calculate buffer size.
    do {
      buffer->size *= 2;
    } while (buffer->size < (unsigned int)size);

    // Enlarge buffer.
    if (buffer->str == &buffer->bulk[0]) {
      buffer->str = (char *)KMP_INTERNAL_MALLOC(buffer->size);
      if (buffer->str == NULL) {
        KMP_FATAL(MemoryAllocFailed);
      }
      KMP_MEMCPY_S(buffer->str, buffer->size, buffer->bulk, buffer->used + 1);
    } else {
      buffer->str = (char *)KMP_INTERNAL_REALLOC(buffer->str, buffer->size);
      if (buffer->str == NULL) {
        KMP_FATAL(MemoryAllocFailed);
      }
    }
  }

  KMP_DEBUG_ASSERT(buffer->size > 0);
  KMP_DEBUG_ASSERT(buffer->size >= (unsigned)size);
  KMP_STR_BUF_INVARIANT(buffer);
} // __kmp_str_buf_reserve

void __kmp_str_buf_detach(kmp_str_buf_t *buffer) {
  KMP_STR_BUF_INVARIANT(buffer);

  // If internal bulk is used, allocate memory and copy it.
  if (buffer->size <= sizeof(buffer->bulk)) {
    buffer->str = (char *)KMP_INTERNAL_MALLOC(buffer->size);
    if (buffer->str == NULL) {
      KMP_FATAL(MemoryAllocFailed);
    }
    KMP_MEMCPY_S(buffer->str, buffer->size, buffer->bulk, buffer->used + 1);
  }
} // __kmp_str_buf_detach

void __kmp_str_buf_free(kmp_str_buf_t *buffer) {
  KMP_STR_BUF_INVARIANT(buffer);
  if (buffer->size > sizeof(buffer->bulk)) {
    KMP_INTERNAL_FREE(buffer->str);
  }
  buffer->str = buffer->bulk;
  buffer->size = sizeof(buffer->bulk);
  buffer->used = 0;
  KMP_STR_BUF_INVARIANT(buffer);
} // __kmp_str_buf_free

void __kmp_str_buf_cat(kmp_str_buf_t *buffer, char const *str, int len) {
  KMP_STR_BUF_INVARIANT(buffer);
  KMP_DEBUG_ASSERT(str != NULL);
  KMP_DEBUG_ASSERT(len >= 0);
  __kmp_str_buf_reserve(buffer, buffer->used + len + 1);
  KMP_MEMCPY(buffer->str + buffer->used, str, len);
  buffer->str[buffer->used + len] = 0;
  buffer->used += len;
  KMP_STR_BUF_INVARIANT(buffer);
} // __kmp_str_buf_cat

void __kmp_str_buf_catbuf(kmp_str_buf_t *dest, const kmp_str_buf_t *src) {
  KMP_DEBUG_ASSERT(dest);
  KMP_DEBUG_ASSERT(src);
  KMP_STR_BUF_INVARIANT(dest);
  KMP_STR_BUF_INVARIANT(src);
  if (!src->str || !src->used)
    return;
  __kmp_str_buf_reserve(dest, dest->used + src->used + 1);
  KMP_MEMCPY(dest->str + dest->used, src->str, src->used);
  dest->str[dest->used + src->used] = 0;
  dest->used += src->used;
  KMP_STR_BUF_INVARIANT(dest);
} // __kmp_str_buf_catbuf

// Return the number of characters written
int __kmp_str_buf_vprint(kmp_str_buf_t *buffer, char const *format,
                         va_list args) {
  int rc;
  KMP_STR_BUF_INVARIANT(buffer);

  for (;;) {
    int const free = buffer->size - buffer->used;
    int size;

    // Try to format string.
    {
/* On Linux* OS Intel(R) 64, vsnprintf() modifies args argument, so vsnprintf()
   crashes if it is called for the second time with the same args. To prevent
   the crash, we have to pass a fresh intact copy of args to vsnprintf() on each
   iteration.

   Unfortunately, standard va_copy() macro is not available on Windows* OS.
   However, it seems vsnprintf() does not modify args argument on Windows* OS.
*/

#if !KMP_OS_WINDOWS
      va_list _args;
      va_copy(_args, args); // Make copy of args.
#define args _args // Substitute args with its copy, _args.
#endif // KMP_OS_WINDOWS
      rc = KMP_VSNPRINTF(buffer->str + buffer->used, free, format, args);
#if !KMP_OS_WINDOWS
#undef args // Remove substitution.
      va_end(_args);
#endif // KMP_OS_WINDOWS
    }

    // No errors, string has been formatted.
    if (rc >= 0 && rc < free) {
      buffer->used += rc;
      break;
    }

    // Error occurred, buffer is too small.
    if (rc >= 0) {
      // C99-conforming implementation of vsnprintf returns required buffer size
      size = buffer->used + rc + 1;
    } else {
      // Older implementations just return -1. Double buffer size.
      size = buffer->size * 2;
    }

    // Enlarge buffer.
    __kmp_str_buf_reserve(buffer, size);

    // And try again.
  }

  KMP_DEBUG_ASSERT(buffer->size > 0);
  KMP_STR_BUF_INVARIANT(buffer);
  return rc;
} // __kmp_str_buf_vprint

// Return the number of characters written
int __kmp_str_buf_print(kmp_str_buf_t *buffer, char const *format, ...) {
  int rc;
  va_list args;
  va_start(args, format);
  rc = __kmp_str_buf_vprint(buffer, format, args);
  va_end(args);
  return rc;
} // __kmp_str_buf_print

/* The function prints specified size to buffer. Size is expressed using biggest
   possible unit, for example 1024 is printed as "1k". */
void __kmp_str_buf_print_size(kmp_str_buf_t *buf, size_t size) {
  char const *names[] = {"", "k", "M", "G", "T", "P", "E", "Z", "Y"};
  int const units = sizeof(names) / sizeof(char const *);
  int u = 0;
  if (size > 0) {
    while ((size % 1024 == 0) && (u + 1 < units)) {
      size = size / 1024;
      ++u;
    }
  }

  __kmp_str_buf_print(buf, "%" KMP_SIZE_T_SPEC "%s", size, names[u]);
} // __kmp_str_buf_print_size

void __kmp_str_fname_init(kmp_str_fname_t *fname, char const *path) {
  fname->path = NULL;
  fname->dir = NULL;
  fname->base = NULL;

  if (path != NULL) {
    char *slash = NULL; // Pointer to the last character of dir.
    char *base = NULL; // Pointer to the beginning of basename.
    fname->path = __kmp_str_format("%s", path);
    // Original code used strdup() function to copy a string, but on Windows* OS
    // Intel(R) 64 it causes assertion id debug heap, so I had to replace
    // strdup with __kmp_str_format().
    if (KMP_OS_WINDOWS) {
      __kmp_str_replace(fname->path, '\\', '/');
    }
    fname->dir = __kmp_str_format("%s", fname->path);
    slash = strrchr(fname->dir, '/');
    if (KMP_OS_WINDOWS &&
        slash == NULL) { // On Windows* OS, if slash not found,
      char first = TOLOWER(fname->dir[0]); // look for drive.
      if ('a' <= first && first <= 'z' && fname->dir[1] == ':') {
        slash = &fname->dir[1];
      }
    }
    base = (slash == NULL ? fname->dir : slash + 1);
    fname->base = __kmp_str_format("%s", base); // Copy basename
    *base = 0; // and truncate dir.
  }

} // kmp_str_fname_init

void __kmp_str_fname_free(kmp_str_fname_t *fname) {
  __kmp_str_free(&fname->path);
  __kmp_str_free(&fname->dir);
  __kmp_str_free(&fname->base);
} // kmp_str_fname_free

int __kmp_str_fname_match(kmp_str_fname_t const *fname, char const *pattern) {
  int dir_match = 1;
  int base_match = 1;

  if (pattern != NULL) {
    kmp_str_fname_t ptrn;
    __kmp_str_fname_init(&ptrn, pattern);
    dir_match = strcmp(ptrn.dir, "*/") == 0 ||
                (fname->dir != NULL && __kmp_str_eqf(fname->dir, ptrn.dir));
    base_match = strcmp(ptrn.base, "*") == 0 ||
                 (fname->base != NULL && __kmp_str_eqf(fname->base, ptrn.base));
    __kmp_str_fname_free(&ptrn);
  }

  return dir_match && base_match;
} // __kmp_str_fname_match

kmp_str_loc_t __kmp_str_loc_init(char const *psource, int init_fname) {
  kmp_str_loc_t loc;

  loc._bulk = NULL;
  loc.file = NULL;
  loc.func = NULL;
  loc.line = 0;
  loc.col = 0;

  if (psource != NULL) {
    char *str = NULL;
    char *dummy = NULL;
    char *line = NULL;
    char *col = NULL;

    // Copy psource to keep it intact.
    loc._bulk = __kmp_str_format("%s", psource);

    // Parse psource string: ";file;func;line;col;;"
    str = loc._bulk;
    __kmp_str_split(str, ';', &dummy, &str);
    __kmp_str_split(str, ';', &loc.file, &str);
    __kmp_str_split(str, ';', &loc.func, &str);
    __kmp_str_split(str, ';', &line, &str);
    __kmp_str_split(str, ';', &col, &str);

    // Convert line and col into numberic values.
    if (line != NULL) {
      loc.line = atoi(line);
      if (loc.line < 0) {
        loc.line = 0;
      }
    }
    if (col != NULL) {
      loc.col = atoi(col);
      if (loc.col < 0) {
        loc.col = 0;
      }
    }
  }

  __kmp_str_fname_init(&loc.fname, init_fname ? loc.file : NULL);

  return loc;
} // kmp_str_loc_init

void __kmp_str_loc_free(kmp_str_loc_t *loc) {
  __kmp_str_fname_free(&loc->fname);
  __kmp_str_free(&(loc->_bulk));
  loc->file = NULL;
  loc->func = NULL;
} // kmp_str_loc_free

/* This function is intended to compare file names. On Windows* OS file names
   are case-insensitive, so functions performs case-insensitive comparison. On
   Linux* OS it performs case-sensitive comparison. Note: The function returns
   *true* if strings are *equal*. */
int __kmp_str_eqf( // True, if strings are equal, false otherwise.
    char const *lhs, // First string.
    char const *rhs // Second string.
    ) {
  int result;
#if KMP_OS_WINDOWS
  result = (_stricmp(lhs, rhs) == 0);
#else
  result = (strcmp(lhs, rhs) == 0);
#endif
  return result;
} // __kmp_str_eqf

/* This function is like sprintf, but it *allocates* new buffer, which must be
   freed eventually by __kmp_str_free(). The function is very convenient for
   constructing strings, it successfully replaces strdup(), strcat(), it frees
   programmer from buffer allocations and helps to avoid buffer overflows.
   Examples:

   str = __kmp_str_format("%s", orig); //strdup() doesn't care about buffer size
   __kmp_str_free( & str );
   str = __kmp_str_format( "%s%s", orig1, orig2 ); // strcat(), doesn't care
                                                   // about buffer size.
   __kmp_str_free( & str );
   str = __kmp_str_format( "%s/%s.txt", path, file ); // constructing string.
   __kmp_str_free( & str );

   Performance note:
   This function allocates memory with malloc() calls, so do not call it from
   performance-critical code. In performance-critical code consider using
   kmp_str_buf_t instead, since it uses stack-allocated buffer for short
   strings.

   Why does this function use malloc()?
   1. __kmp_allocate() returns cache-aligned memory allocated with malloc().
      There are no reasons in using __kmp_allocate() for strings due to extra
      overhead while cache-aligned memory is not necessary.
   2. __kmp_thread_malloc() cannot be used because it requires pointer to thread
      structure. We need to perform string operations during library startup
      (for example, in __kmp_register_library_startup()) when no thread
      structures are allocated yet.
   So standard malloc() is the only available option.
*/

char *__kmp_str_format( // Allocated string.
    char const *format, // Format string.
    ... // Other parameters.
    ) {
  va_list args;
  int size = 512;
  char *buffer = NULL;
  int rc;

  // Allocate buffer.
  buffer = (char *)KMP_INTERNAL_MALLOC(size);
  if (buffer == NULL) {
    KMP_FATAL(MemoryAllocFailed);
  }

  for (;;) {
    // Try to format string.
    va_start(args, format);
    rc = KMP_VSNPRINTF(buffer, size, format, args);
    va_end(args);

    // No errors, string has been formatted.
    if (rc >= 0 && rc < size) {
      break;
    }

    // Error occurred, buffer is too small.
    if (rc >= 0) {
      // C99-conforming implementation of vsnprintf returns required buffer
      // size.
      size = rc + 1;
    } else {
      // Older implementations just return -1.
      size = size * 2;
    }

    // Enlarge buffer and try again.
    buffer = (char *)KMP_INTERNAL_REALLOC(buffer, size);
    if (buffer == NULL) {
      KMP_FATAL(MemoryAllocFailed);
    }
  }

  return buffer;
} // func __kmp_str_format

void __kmp_str_free(char **str) {
  KMP_DEBUG_ASSERT(str != NULL);
  KMP_INTERNAL_FREE(*str);
  *str = NULL;
} // func __kmp_str_free

/* If len is zero, returns true iff target and data have exact case-insensitive
   match. If len is negative, returns true iff target is a case-insensitive
   substring of data. If len is positive, returns true iff target is a
   case-insensitive substring of data or vice versa, and neither is shorter than
   len. */
int __kmp_str_match(char const *target, int len, char const *data) {
  int i;
  if (target == NULL || data == NULL) {
    return FALSE;
  }
  for (i = 0; target[i] && data[i]; ++i) {
    if (TOLOWER(target[i]) != TOLOWER(data[i])) {
      return FALSE;
    }
  }
  return ((len > 0) ? i >= len : (!target[i] && (len || !data[i])));
} // __kmp_str_match

int __kmp_str_match_false(char const *data) {
  int result =
      __kmp_str_match("false", 1, data) || __kmp_str_match("off", 2, data) ||
      __kmp_str_match("0", 1, data) || __kmp_str_match(".false.", 2, data) ||
      __kmp_str_match(".f.", 2, data) || __kmp_str_match("no", 1, data) ||
      __kmp_str_match("disabled", 0, data);
  return result;
} // __kmp_str_match_false

int __kmp_str_match_true(char const *data) {
  int result =
      __kmp_str_match("true", 1, data) || __kmp_str_match("on", 2, data) ||
      __kmp_str_match("1", 1, data) || __kmp_str_match(".true.", 2, data) ||
      __kmp_str_match(".t.", 2, data) || __kmp_str_match("yes", 1, data) ||
      __kmp_str_match("enabled", 0, data);
  return result;
} // __kmp_str_match_true

void __kmp_str_replace(char *str, char search_for, char replace_with) {
  char *found = NULL;

  found = strchr(str, search_for);
  while (found) {
    *found = replace_with;
    found = strchr(found + 1, search_for);
  }
} // __kmp_str_replace

void __kmp_str_split(char *str, // I: String to split.
                     char delim, // I: Character to split on.
                     char **head, // O: Pointer to head (may be NULL).
                     char **tail // O: Pointer to tail (may be NULL).
                     ) {
  char *h = str;
  char *t = NULL;
  if (str != NULL) {
    char *ptr = strchr(str, delim);
    if (ptr != NULL) {
      *ptr = 0;
      t = ptr + 1;
    }
  }
  if (head != NULL) {
    *head = h;
  }
  if (tail != NULL) {
    *tail = t;
  }
} // __kmp_str_split

/* strtok_r() is not available on Windows* OS. This function reimplements
   strtok_r(). */
char *__kmp_str_token(
    char *str, // String to split into tokens. Note: String *is* modified!
    char const *delim, // Delimiters.
    char **buf // Internal buffer.
    ) {
  char *token = NULL;
#if KMP_OS_WINDOWS
  // On Windows* OS there is no strtok_r() function. Let us implement it.
  if (str != NULL) {
    *buf = str; // First call, initialize buf.
  }
  *buf += strspn(*buf, delim); // Skip leading delimiters.
  if (**buf != 0) { // Rest of the string is not yet empty.
    token = *buf; // Use it as result.
    *buf += strcspn(*buf, delim); // Skip non-delimiters.
    if (**buf != 0) { // Rest of the string is not yet empty.
      **buf = 0; // Terminate token here.
      *buf += 1; // Advance buf to start with the next token next time.
    }
  }
#else
  // On Linux* OS and OS X*, strtok_r() is available. Let us use it.
  token = strtok_r(str, delim, buf);
#endif
  return token;
} // __kmp_str_token

int __kmp_str_to_int(char const *str, char sentinel) {
  int result, factor;
  char const *t;

  result = 0;

  for (t = str; *t != '\0'; ++t) {
    if (*t < '0' || *t > '9')
      break;
    result = (result * 10) + (*t - '0');
  }

  switch (*t) {
  case '\0': /* the current default for no suffix is bytes */
    factor = 1;
    break;
  case 'b':
  case 'B': /* bytes */
    ++t;
    factor = 1;
    break;
  case 'k':
  case 'K': /* kilo-bytes */
    ++t;
    factor = 1024;
    break;
  case 'm':
  case 'M': /* mega-bytes */
    ++t;
    factor = (1024 * 1024);
    break;
  default:
    if (*t != sentinel)
      return (-1);
    t = "";
    factor = 1;
  }

  if (result > (INT_MAX / factor))
    result = INT_MAX;
  else
    result *= factor;

  return (*t != 0 ? 0 : result);
} // __kmp_str_to_int

/* The routine parses input string. It is expected it is a unsigned integer with
   optional unit. Units are: "b" for bytes, "kb" or just "k" for kilobytes, "mb"
   or "m" for megabytes, ..., "yb" or "y" for yottabytes. :-) Unit name is
   case-insensitive. The routine returns 0 if everything is ok, or error code:
   -1 in case of overflow, -2 in case of unknown unit. *size is set to parsed
   value. In case of overflow *size is set to KMP_SIZE_T_MAX, in case of unknown
   unit *size is set to zero. */
void __kmp_str_to_size( // R: Error code.
    char const *str, // I: String of characters, unsigned number and unit ("b",
    // "kb", etc).
    size_t *out, // O: Parsed number.
    size_t dfactor, // I: The factor if none of the letters specified.
    char const **error // O: Null if everything is ok, error message otherwise.
    ) {

  size_t value = 0;
  size_t factor = 0;
  int overflow = 0;
  int i = 0;
  int digit;

  KMP_DEBUG_ASSERT(str != NULL);

  // Skip spaces.
  while (str[i] == ' ' || str[i] == '\t') {
    ++i;
  }

  // Parse number.
  if (str[i] < '0' || str[i] > '9') {
    *error = KMP_I18N_STR(NotANumber);
    return;
  }
  do {
    digit = str[i] - '0';
    overflow = overflow || (value > (KMP_SIZE_T_MAX - digit) / 10);
    value = (value * 10) + digit;
    ++i;
  } while (str[i] >= '0' && str[i] <= '9');

  // Skip spaces.
  while (str[i] == ' ' || str[i] == '\t') {
    ++i;
  }

// Parse unit.
#define _case(ch, exp)                                                         \
  case ch:                                                                     \
  case ch - ('a' - 'A'): {                                                     \
    size_t shift = (exp)*10;                                                   \
    ++i;                                                                       \
    if (shift < sizeof(size_t) * 8) {                                          \
      factor = (size_t)(1) << shift;                                           \
    } else {                                                                   \
      overflow = 1;                                                            \
    }                                                                          \
  } break;
  switch (str[i]) {
    _case('k', 1); // Kilo
    _case('m', 2); // Mega
    _case('g', 3); // Giga
    _case('t', 4); // Tera
    _case('p', 5); // Peta
    _case('e', 6); // Exa
    _case('z', 7); // Zetta
    _case('y', 8); // Yotta
    // Oops. No more units...
  }
#undef _case
  if (str[i] == 'b' || str[i] == 'B') { // Skip optional "b".
    if (factor == 0) {
      factor = 1;
    }
    ++i;
  }
  if (!(str[i] == ' ' || str[i] == '\t' || str[i] == 0)) { // Bad unit
    *error = KMP_I18N_STR(BadUnit);
    return;
  }

  if (factor == 0) {
    factor = dfactor;
  }

  // Apply factor.
  overflow = overflow || (value > (KMP_SIZE_T_MAX / factor));
  value *= factor;

  // Skip spaces.
  while (str[i] == ' ' || str[i] == '\t') {
    ++i;
  }

  if (str[i] != 0) {
    *error = KMP_I18N_STR(IllegalCharacters);
    return;
  }

  if (overflow) {
    *error = KMP_I18N_STR(ValueTooLarge);
    *out = KMP_SIZE_T_MAX;
    return;
  }

  *error = NULL;
  *out = value;
} // __kmp_str_to_size

void __kmp_str_to_uint( // R: Error code.
    char const *str, // I: String of characters, unsigned number.
    kmp_uint64 *out, // O: Parsed number.
    char const **error // O: Null if everything is ok, error message otherwise.
    ) {
  size_t value = 0;
  int overflow = 0;
  int i = 0;
  int digit;

  KMP_DEBUG_ASSERT(str != NULL);

  // Skip spaces.
  while (str[i] == ' ' || str[i] == '\t') {
    ++i;
  }

  // Parse number.
  if (str[i] < '0' || str[i] > '9') {
    *error = KMP_I18N_STR(NotANumber);
    return;
  }
  do {
    digit = str[i] - '0';
    overflow = overflow || (value > (KMP_SIZE_T_MAX - digit) / 10);
    value = (value * 10) + digit;
    ++i;
  } while (str[i] >= '0' && str[i] <= '9');

  // Skip spaces.
  while (str[i] == ' ' || str[i] == '\t') {
    ++i;
  }

  if (str[i] != 0) {
    *error = KMP_I18N_STR(IllegalCharacters);
    return;
  }

  if (overflow) {
    *error = KMP_I18N_STR(ValueTooLarge);
    *out = (kmp_uint64)-1;
    return;
  }

  *error = NULL;
  *out = value;
} // __kmp_str_to_unit

// end of file //