← Back to davo.co
aboutsummaryrefslogtreecommitdiffstats
path: root/strtoul.c
blob: 9306822eff774282c02ff555efa0808a15a32556 (plain)
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
#include "c23-fallback.h"
#include <errno.h>
//#include <limits.h>
#include <stdio.h>
//#include <stdlib.h>
//#include <string.h>
#include <assert.h>
/* Supposes that lowercase characters are contiguous. */
static_assert('z'-'a' == 25,
               "alphabetic characters not contiguous");
#include <ctype.h>
/* Converts an alphanumeric digit to an unsigned */
/* '0' ... '9'  =>  0 ..  9u */
/* 'A' ... 'Z'  => 10 .. 35u */
/* 'a' ... 'z'  => 10 .. 35u */
/* Other values =>   Greater */
unsigned hexatridecimal(int a) {
  if (isdigit(a)) {
    /* This is guaranteed to work: decimal digits
       are consecutive, and isdigit is not
       locale dependent. */
    return a - '0';
  } else {
    /* Leaves a unchanged if it is not lowercase */
    a = toupper(a);
    /* Returns value >= 36 if not Latin uppercase */
    return (isupper(a)) ? 10 + (a - 'A') : -1;
  }
}

unsigned long Strtoul_inner(char const s[static 1],
                            size_t i,
                            unsigned base) {
  unsigned long ret = 0;
  while (s[i]) {
    unsigned c = hexatridecimal(s[i]);
    if (c >= base) break;
    /* Maximal representable value for 64 bit is
       3w5e11264sgsf in base 36 */
    if (ULONG_MAX/base < ret) {
      ret = ULONG_MAX;
      errno = ERANGE;
      break;
    }
    ret *= base;
    ret += c;
    ++i;
  }
  return ret;
}

static unsigned find_prefix(char const s[static 1], size_t i, char const t[static 1]) {
  size_t ret = 0;
  while (s[i+ret] && (tolower(s[i+ret]) == tolower(t[ret]))) {
    ++ret;
  }
  return ret;
}

unsigned long Strtoul(char const s[static 1], unsigned base) {
  if (base > 36u) {             /* Tests if base          */
    errno = EINVAL;             /* Extends the specification */
    return ULONG_MAX;
  }
  size_t i = strspn(s, " \f\n\r\t\v"); /* Skips spaces    */
  bool switchsign = false;      /* Looks for a sign       */
  switch (s[i]) {
  case '-' :
    switchsign = true;
    [[fallthrough]];
  case '+' :
    ++i;
  }
  /* Test if there is anything left in the string. */
  if (!s[i]) return 0;
  if (!base || base == 16 || base == 2) { /* use prefix */
    size_t adj = find_prefix(s, i, "0x");
    switch (adj) {
    case 2:                   // There is a 0x or a 0X prefix
      if (!base || base == 16) base = 16;
      // If we are looking for another base, the x is the end
      else return 0;
      break;
    case 1:                    // There is a 0 prefix
      adj = find_prefix(s, i, "0b");
      switch (adj) {
      case 1:                  // There is only a 0 prefix
        if (!base) base = 8;
        break;
      default:                 // There is a 0b or a 0B prefix
        if (!base || base == 2) base = 2;
        // If we are looking for another base, the b is the end
        else return 0;
        break;
      }
      break;
    default:
      if (!base) base = 10;
      break;
    }
    i += adj;
  }
  /* Test again, maybe the prefix 0 was the only digit. */
  if (!s[i]) return 0;
  /* Now, starts the real conversion */
  unsigned long ret = Strtoul_inner(s, i, base);
  return (switchsign) ? -ret : ret;
}

char* char36(unsigned long n, unsigned base, char buf[static 256]) {
  if (base > 36) {
    strcpy(buf, "EINVAL");
  } else {
    unsigned i = 256;
    while (n) {
      --i;
      buf[i] = n % base;
      n /= base;
      if (buf[i] < 10) {
        buf[i] += '0';
      } else {
        buf[i] += 'a' - 10;
      }
    }
    memmove(buf, &buf[i], 256-i);
  }
  return buf;
}

int main(int argc, char* argv[argc+1]) {
  static unsigned const code[] = { 0, 8, 10, 16, 36, 37, };
  for (unsigned j = 0; j < sizeof code/ sizeof code[0]; ++j) {
    printf("%u:", code[j]);
    for (int i = 1; i < argc; ++i) {
      unsigned long res = Strtoul(argv[i], code[j]);
      if (res == ULONG_MAX && errno) {
        puts("");
        fprintf(stderr, "invalid conversion with base %u and \"%s\"\n", code[j], argv[i]);
        perror("error");
        errno = 0;
      } else {
        printf("\t%lu", res);
      }
    }
    puts("");
    if (code[j]) printf("%u:\tULONG_MAX %s\n", code[j], char36(ULONG_MAX, code[j], (char[256]){ }));
  }
  return EXIT_SUCCESS;
}