iodine 0.4.14 → 0.4.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of iodine might be problematic. Click here for more details.

Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +61 -33
  3. data/README.md +7 -5
  4. data/ext/iodine/base64.c +12 -0
  5. data/ext/iodine/defer.c +211 -108
  6. data/ext/iodine/defer.h +7 -0
  7. data/ext/iodine/facil.c +5 -1
  8. data/ext/iodine/facil.h +1 -1
  9. data/ext/iodine/fio2resp.c +19 -30
  10. data/ext/iodine/fio2resp.h +2 -1
  11. data/ext/iodine/fio_cli_helper.c +2 -2
  12. data/ext/iodine/fiobj.h +11 -624
  13. data/ext/iodine/fiobj_ary.c +65 -26
  14. data/ext/iodine/fiobj_ary.h +106 -0
  15. data/ext/iodine/fiobj_hash.c +175 -115
  16. data/ext/iodine/fiobj_hash.h +128 -0
  17. data/ext/iodine/fiobj_internal.c +189 -0
  18. data/ext/iodine/{fiobj_types.h → fiobj_internal.h} +126 -136
  19. data/ext/iodine/fiobj_json.c +161 -207
  20. data/ext/iodine/fiobj_json.h +43 -0
  21. data/ext/iodine/fiobj_numbers.c +53 -35
  22. data/ext/iodine/fiobj_numbers.h +49 -0
  23. data/ext/iodine/fiobj_primitives.c +103 -70
  24. data/ext/iodine/fiobj_primitives.h +55 -0
  25. data/ext/iodine/fiobj_str.c +171 -59
  26. data/ext/iodine/fiobj_str.h +113 -0
  27. data/ext/iodine/fiobj_sym.c +46 -124
  28. data/ext/iodine/fiobj_sym.h +60 -0
  29. data/ext/iodine/fiobject.c +589 -0
  30. data/ext/iodine/fiobject.h +276 -0
  31. data/ext/iodine/pubsub.h +1 -1
  32. data/ext/iodine/resp.c +2 -2
  33. data/ext/iodine/siphash.c +11 -0
  34. data/ext/iodine/spnlock.inc +7 -5
  35. data/ext/iodine/websocket_parser.h +44 -7
  36. data/lib/iodine/version.rb +1 -1
  37. metadata +13 -8
  38. data/ext/iodine/fiobj_alloc.c +0 -81
  39. data/ext/iodine/fiobj_generic.c +0 -260
  40. data/ext/iodine/fiobj_io.c +0 -58
  41. data/ext/iodine/fiobj_misc.c +0 -213
  42. data/ext/iodine/fiobj_tests.c +0 -474
@@ -5,154 +5,70 @@ License: MIT
5
5
  Feel free to copy, use and enjoy according to the license provided.
6
6
  */
7
7
 
8
- #include "fiobj_types.h"
8
+ #include "fiobj_internal.h"
9
9
 
10
10
  /* *****************************************************************************
11
- Hashing (SipHash copy)
11
+ Symbol Type
12
12
  ***************************************************************************** */
13
13
 
14
- #if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) && \
15
- __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
16
- /* the algorithm was designed as little endian... so, byte swap 64 bit. */
17
- #define sip_local64(i) \
18
- (((i)&0xFFULL) << 56) | (((i)&0xFF00ULL) << 40) | \
19
- (((i)&0xFF0000ULL) << 24) | (((i)&0xFF000000ULL) << 8) | \
20
- (((i)&0xFF00000000ULL) >> 8) | (((i)&0xFF0000000000ULL) >> 24) | \
21
- (((i)&0xFF000000000000ULL) >> 40) | (((i)&0xFF00000000000000ULL) >> 56)
22
- #else
23
- /* no need */
24
- #define sip_local64(i) (i)
25
- #endif
26
-
27
- /* 64Bit left rotation, inlined. */
28
- #define lrot64(i, bits) \
29
- (((uint64_t)(i) << (bits)) | ((uint64_t)(i) >> (64 - (bits))))
30
-
31
- uint64_t fiobj_sym_hash(const void *data, size_t len) {
32
- /* initialize the 4 words */
33
- uint64_t v0 = (0x0706050403020100ULL ^ 0x736f6d6570736575ULL);
34
- uint64_t v1 = (0x0f0e0d0c0b0a0908ULL ^ 0x646f72616e646f6dULL);
35
- uint64_t v2 = (0x0706050403020100ULL ^ 0x6c7967656e657261ULL);
36
- uint64_t v3 = (0x0f0e0d0c0b0a0908ULL ^ 0x7465646279746573ULL);
37
- const uint64_t *w64 = data;
38
- uint8_t len_mod = len & 255;
39
- union {
40
- uint64_t i;
41
- uint8_t str[8];
42
- } word;
43
-
44
- #define hash_map_SipRound \
45
- do { \
46
- v2 += v3; \
47
- v3 = lrot64(v3, 16) ^ v2; \
48
- v0 += v1; \
49
- v1 = lrot64(v1, 13) ^ v0; \
50
- v0 = lrot64(v0, 32); \
51
- v2 += v1; \
52
- v0 += v3; \
53
- v1 = lrot64(v1, 17) ^ v2; \
54
- v3 = lrot64(v3, 21) ^ v0; \
55
- v2 = lrot64(v2, 32); \
56
- } while (0);
57
-
58
- while (len >= 8) {
59
- word.i = sip_local64(*w64);
60
- v3 ^= word.i;
61
- /* Sip Rounds */
62
- hash_map_SipRound;
63
- hash_map_SipRound;
64
- v0 ^= word.i;
65
- w64 += 1;
66
- len -= 8;
67
- }
68
- word.i = 0;
69
- uint8_t *pos = word.str;
70
- uint8_t *w8 = (void *)w64;
71
- switch (len) { /* fallthrough is intentional */
72
- case 7:
73
- pos[6] = w8[6];
74
- case 6:
75
- pos[5] = w8[5];
76
- case 5:
77
- pos[4] = w8[4];
78
- case 4:
79
- pos[3] = w8[3];
80
- case 3:
81
- pos[2] = w8[2];
82
- case 2:
83
- pos[1] = w8[1];
84
- case 1:
85
- pos[0] = w8[0];
86
- }
87
- word.str[7] = len_mod;
88
-
89
- /* last round */
90
- v3 ^= word.i;
91
- hash_map_SipRound;
92
- hash_map_SipRound;
93
- v0 ^= word.i;
94
- /* Finalization */
95
- v2 ^= 0xff;
96
- /* d iterations of SipRound */
97
- hash_map_SipRound;
98
- hash_map_SipRound;
99
- hash_map_SipRound;
100
- hash_map_SipRound;
101
- /* XOR it all together */
102
- v0 ^= v1 ^ v2 ^ v3;
103
- #undef hash_map_SipRound
104
- return v0;
105
- }
14
+ typedef struct {
15
+ struct fiobj_vtable_s *vtable;
16
+ uintptr_t hash;
17
+ uint64_t len;
18
+ char str[];
19
+ } fiobj_sym_s;
20
+
21
+ #define obj2sym(o) ((fiobj_sym_s *)(o))
106
22
 
107
23
  /* *****************************************************************************
108
24
  Symbol VTable
109
25
  ***************************************************************************** */
110
26
 
111
- static int fiobj_sym_is_eq(fiobj_s *self, fiobj_s *other) {
112
- if (!other || other->type != self->type)
27
+ static int fiobj_sym_is_eq(const fiobj_s *self, const fiobj_s *other) {
28
+ if (other->type != self->type)
113
29
  return 0;
114
30
  return obj2sym(self)->hash == obj2sym(other)->hash;
115
31
  }
116
32
 
117
- static fio_cstr_s fio_sym2str(fiobj_s *o) {
33
+ static fio_cstr_s fio_sym2str(const fiobj_s *o) {
118
34
  return (fio_cstr_s){.buffer = obj2sym(o)->str, .len = obj2sym(o)->len};
119
35
  }
120
- static int64_t fio_sym2i(fiobj_s *o) {
121
- char *s = obj2str(o)->str;
36
+ static int64_t fio_sym2i(const fiobj_s *o) {
37
+ char *s = obj2sym(o)->str;
122
38
  return fio_atol(&s);
123
39
  }
124
- static double fio_sym2f(fiobj_s *o) {
40
+ static double fio_sym2f(const fiobj_s *o) {
125
41
  char *s = obj2sym(o)->str;
126
42
  return fio_atof(&s);
127
43
  }
128
44
 
129
45
  static struct fiobj_vtable_s FIOBJ_VTABLE_SYMBOL = {
46
+ .name = "Symbol",
130
47
  .free = fiobj_simple_dealloc,
131
48
  .to_i = fio_sym2i,
132
49
  .to_f = fio_sym2f,
133
50
  .to_str = fio_sym2str,
134
51
  .is_eq = fiobj_sym_is_eq,
52
+ .is_true = fiobj_noop_true,
135
53
  .count = fiobj_noop_count,
54
+ .unwrap = fiobj_noop_unwrap,
136
55
  .each1 = fiobj_noop_each1,
137
56
  };
138
57
 
58
+ const uintptr_t FIOBJ_T_SYMBOL = (uintptr_t)(&FIOBJ_VTABLE_SYMBOL);
59
+
139
60
  /* *****************************************************************************
140
61
  Symbol API
141
62
  ***************************************************************************** */
142
63
 
143
64
  static inline fiobj_s *fiobj_sym_alloc(size_t len) {
144
- fiobj_head_s *head;
145
- head = malloc(sizeof(*head) + sizeof(fio_sym_s) + len + 1);
146
- if (!head)
65
+ fiobj_s *o = fiobj_alloc(sizeof(fiobj_sym_s) + len + 1);
66
+ if (!o)
147
67
  perror("ERROR: fiobj symbol couldn't allocate memory"), exit(errno);
148
- *head = (fiobj_head_s){
149
- .ref = 1, .vtable = &FIOBJ_VTABLE_SYMBOL,
68
+ *obj2sym(o) = (fiobj_sym_s){
69
+ .vtable = &FIOBJ_VTABLE_SYMBOL, .len = len,
150
70
  };
151
- *obj2sym(HEAD2OBJ(head)) = (fio_sym_s){
152
- .type = FIOBJ_T_SYMBOL, .len = len,
153
- };
154
- obj2sym(HEAD2OBJ(head))->str[len] = 0;
155
- return HEAD2OBJ(head);
71
+ return o;
156
72
  }
157
73
 
158
74
  /** Creates a Symbol object. Use `fiobj_free`. */
@@ -160,10 +76,24 @@ fiobj_s *fiobj_sym_new(const char *str, size_t len) {
160
76
  fiobj_s *s = fiobj_sym_alloc(len);
161
77
  if (str)
162
78
  memcpy(obj2sym(s)->str, str, len);
79
+ obj2sym(s)->str[len] = 0;
163
80
  obj2sym(s)->hash = (uintptr_t)fiobj_sym_hash(str, len);
164
81
  return s;
165
82
  }
166
83
 
84
+ /** Finalizes a pre-allocated Symbol buffer to set it's final length and
85
+ * calculate it's final hashing value. */
86
+ fiobj_s *fiobj_sym_reinitialize(fiobj_s *s, const size_t len) {
87
+ if (obj2sym(s)->len < len)
88
+ fprintf(stderr,
89
+ "FATAL ERROR: facil.io Symbol object reinitialization error.\n"),
90
+ exit(-1);
91
+ obj2sym(s)->len = len;
92
+ obj2sym(s)->str[len] = 0;
93
+ obj2sym(s)->hash = (uintptr_t)fiobj_sym_hash(obj2sym(s)->str, len);
94
+ return s;
95
+ }
96
+
167
97
  /** Creates a Symbol object using a printf like interface. */
168
98
  __attribute__((format(printf, 1, 0))) fiobj_s *
169
99
  fiobj_symvprintf(const char *format, va_list argv) {
@@ -172,16 +102,15 @@ fiobj_symvprintf(const char *format, va_list argv) {
172
102
  va_copy(argv_cpy, argv);
173
103
  int len = vsnprintf(NULL, 0, format, argv_cpy);
174
104
  va_end(argv_cpy);
175
- if (len == 0) {
105
+ if (len <= 0) {
176
106
  sym = fiobj_sym_alloc(0);
177
- ((fio_sym_s *)(sym))->hash = fiobj_sym_hash(NULL, 0);
178
- }
179
- if (len <= 0)
107
+ obj2sym(sym)->hash = fiobj_sym_hash(NULL, 0);
180
108
  return sym;
109
+ }
181
110
  sym = fiobj_sym_alloc(len); /* adds 1 to len, for NUL */
182
- vsnprintf(((fio_sym_s *)(sym))->str, len + 1, format, argv);
183
- ((fio_sym_s *)(sym))->hash =
184
- (uintptr_t)fiobj_sym_hash(((fio_sym_s *)(sym))->str, len);
111
+ vsnprintf(obj2sym(sym)->str, len + 1, format, argv);
112
+ obj2sym(sym)->str[len] = 0; /* enforce NUL */
113
+ obj2sym(sym)->hash = (uintptr_t)fiobj_sym_hash(obj2sym(sym)->str, len);
185
114
  return sym;
186
115
  }
187
116
  __attribute__((format(printf, 1, 2))) fiobj_s *
@@ -193,13 +122,6 @@ fiobj_symprintf(const char *format, ...) {
193
122
  return sym;
194
123
  }
195
124
 
196
- /** Returns 1 if both Symbols are equal and 0 if not. */
197
- int fiobj_sym_iseql(fiobj_s *sym1, fiobj_s *sym2) {
198
- if (sym1->type != FIOBJ_T_SYMBOL || sym2->type != FIOBJ_T_SYMBOL)
199
- return 0;
200
- return (((fio_sym_s *)sym1)->hash == ((fio_sym_s *)sym2)->hash);
201
- }
202
-
203
125
  /**
204
126
  * Returns a symbol's identifier.
205
127
  *
@@ -0,0 +1,60 @@
1
+ #ifndef H_FIOBJ_SYMBOL_H
2
+ /*
3
+ Copyright: Boaz Segev, 2017
4
+ License: MIT
5
+ */
6
+
7
+ /**
8
+ */
9
+ #define H_FIOBJ_SYMBOL_H
10
+
11
+ #include "fiobject.h"
12
+
13
+ #ifdef __cplusplus
14
+ extern "C" {
15
+ #endif
16
+
17
+ /** Symbol type identifier */
18
+ extern const uintptr_t FIOBJ_T_SYMBOL;
19
+
20
+ /* *****************************************************************************
21
+ Symbol API
22
+ ***************************************************************************** */
23
+
24
+ /** Creates a Symbol object. Use `fiobj_free`. */
25
+ fiobj_s *fiobj_sym_new(const char *str, size_t len);
26
+
27
+ /** Creates a Symbol object using a printf like interface. */
28
+ __attribute__((format(printf, 1, 0))) fiobj_s *
29
+ fiobj_symvprintf(const char *format, va_list argv);
30
+
31
+ /** Creates a Symbol object using a printf like interface. */
32
+ __attribute__((format(printf, 1, 2))) fiobj_s *
33
+ fiobj_symprintf(const char *format, ...);
34
+
35
+ /**
36
+ * Returns a symbol's identifier.
37
+ *
38
+ * The unique identifier is calculated using SipHash and is equal for all Symbol
39
+ * objects that were created using the same data.
40
+ */
41
+ uintptr_t fiobj_sym_id(fiobj_s *sym);
42
+
43
+ /* *****************************************************************************
44
+ Risky Symbol API
45
+ ***************************************************************************** */
46
+
47
+ /**
48
+ * Reinitializes a pre-allocated Symbol buffer to set it's final length and
49
+ * calculate it's final hashing value.
50
+ *
51
+ * NEVER use this on a symbol that was already used in other objects, such as a
52
+ * Hash.
53
+ */
54
+ fiobj_s *fiobj_sym_reinitialize(fiobj_s *s, const size_t len);
55
+
56
+ #ifdef __cplusplus
57
+ } /* extern "C" */
58
+ #endif
59
+
60
+ #endif
@@ -0,0 +1,589 @@
1
+ /*
2
+ Copyright: Boaz Segev, 2017
3
+ License: MIT
4
+ */
5
+
6
+ /**
7
+ This facil.io core library provides wrappers around complex and (or) dynamic
8
+ types, abstracting some complexity and making dynamic type related tasks easier.
9
+
10
+
11
+ The library offers a rudementry protection against cyclic references using the
12
+ `FIOBJ_NESTING_PROTECTION` flag (i.e., nesting an Array within itself)...
13
+ however, this isn't fully tested and the performance price is high.
14
+ */
15
+ #include "fiobj_internal.h"
16
+ #include "fiobj_primitives.h"
17
+
18
+ #include <stdarg.h>
19
+ #include <stdint.h>
20
+ #include <stdio.h>
21
+ #include <stdlib.h>
22
+
23
+ /* *****************************************************************************
24
+ Cyclic Protection helpers & API
25
+ ***************************************************************************** */
26
+
27
+ static __thread fiobj_s *fiobj_cyclic_protection = NULL;
28
+ fiobj_s *fiobj_each_get_cyclic(void) { return fiobj_cyclic_protection; }
29
+
30
+ static inline fiobj_s *protected_pop_obj(fio_ls_s *queue, fio_ls_s *history) {
31
+ #if FIOBJ_NESTING_PROTECTION
32
+ fiobj_cyclic_protection = NULL;
33
+
34
+ fiobj_s *obj = fio_ls_pop(queue);
35
+ if (!obj)
36
+ return NULL;
37
+ fiobj_s *child = OBJVTBL(obj)->unwrap(obj);
38
+ if (!child)
39
+ return obj;
40
+ if (OBJVTBL(child)->count(child) == 0)
41
+ return obj;
42
+ fio_ls_s *pos = history->next;
43
+ while (pos != history) {
44
+ if (child == pos->obj) {
45
+ fiobj_cyclic_protection = obj;
46
+ return NULL;
47
+ }
48
+ pos = pos->next;
49
+ }
50
+ return obj;
51
+ #else
52
+ return fio_ls_pop(queue);
53
+ (void)history;
54
+ #endif
55
+ }
56
+
57
+ static inline void protected_push_obj(const fiobj_s *obj, fio_ls_s *history) {
58
+ #if FIOBJ_NESTING_PROTECTION
59
+ fio_ls_push(history, OBJVTBL(obj)->unwrap(obj));
60
+ #else
61
+ (void)obj;
62
+ (void)history;
63
+ #endif
64
+ }
65
+
66
+ /* *****************************************************************************
67
+ Generic Object API
68
+ ***************************************************************************** */
69
+
70
+ /** Returns a C string naming the objects dynamic type. */
71
+ const char *fiobj_type_name(const fiobj_s *obj) { return OBJVTBL(obj)->name; }
72
+
73
+ /**
74
+ * Copy by reference(!) - increases an object's (and any nested object's)
75
+ * reference count.
76
+ *
77
+ * Always returns the value passed along.
78
+ *
79
+ * Future implementations might provide `fiobj_dup2` providing a deep copy.
80
+ *
81
+ * We don't need this feature just yet, so I'm not working on it.
82
+ */
83
+ fiobj_s *fiobj_dup(fiobj_s *obj) {
84
+ OBJREF_ADD(obj);
85
+ return obj;
86
+ }
87
+
88
+ static int fiobj_free_or_mark(fiobj_s *o, void *arg) {
89
+ if (!o)
90
+ return 0;
91
+ #if FIOBJ_NESTING_PROTECTION
92
+ if (OBJ2HEAD(o)->ref == 0) /* maybe a nested returning... */
93
+ return 0;
94
+ #elif DEBUG
95
+ if (OBJ2HEAD(o)->ref == 0) {
96
+ fprintf(stderr,
97
+ "ERROR: attempting to free an object that isn't a fiobj or already "
98
+ "freed (%p)\n",
99
+ (void *)o);
100
+ kill(0, SIGABRT);
101
+ }
102
+ #endif
103
+
104
+ if (OBJREF_REM(o))
105
+ return 0;
106
+
107
+ /* reference count is zero: free memory or add to queue */
108
+
109
+ /* test for wrapped object (i.e., Hash Couplet) */
110
+ fiobj_s *child = OBJVTBL(o)->unwrap(o);
111
+
112
+ if (child != o) {
113
+ if (!child || OBJREF_REM(child)) {
114
+ OBJVTBL(o)->free(o);
115
+ return 0;
116
+ }
117
+ OBJVTBL(o)->free(o);
118
+ o = child;
119
+ }
120
+
121
+ if (OBJVTBL(o)->count(o)) {
122
+ fio_ls_push(arg, o);
123
+ } else
124
+ OBJVTBL(o)->free(o);
125
+
126
+ /* handle nesting / wrapping (i.e., Array, Hash, Couplets ) */
127
+ return 0;
128
+ }
129
+
130
+ /**
131
+ * Decreases an object's reference count, releasing memory and
132
+ * resources.
133
+ *
134
+ * This function affects nested objects, meaning that when an Array or
135
+ * a Hash object is passed along, it's children (nested objects) are
136
+ * also freed.
137
+ */
138
+ void fiobj_free(fiobj_s *o) {
139
+ #if DEBUG
140
+ if (!o)
141
+ return;
142
+ if (OBJ2HEAD(o)->ref == 0) {
143
+ fprintf(stderr,
144
+ "ERROR: attempting to free an object that isn't a fiobj or already "
145
+ "freed (%p)\n",
146
+ (void *)o);
147
+ kill(0, SIGABRT);
148
+ }
149
+ #endif
150
+ if (!o || OBJREF_REM(o))
151
+ return;
152
+
153
+ /* handle wrapping */
154
+ {
155
+ fiobj_s *child = OBJVTBL(o)->unwrap(o);
156
+ if (child != o) {
157
+ OBJVTBL(o)->free(o);
158
+ if (OBJREF_REM(child))
159
+ return;
160
+ o = child;
161
+ }
162
+ }
163
+ if (OBJVTBL(o)->count(o) == 0) {
164
+ OBJVTBL(o)->free(o);
165
+ return;
166
+ }
167
+ /* nested free */
168
+ fio_ls_s queue = FIO_LS_INIT(queue);
169
+ fio_ls_s history = FIO_LS_INIT(history);
170
+ while (o) {
171
+ /* the queue always contains valid enumerable objects that are unwrapped. */
172
+ OBJVTBL(o)->each1(o, 0, fiobj_free_or_mark, &queue);
173
+ fio_ls_push(&history, o);
174
+ o = protected_pop_obj(&queue, &history);
175
+ }
176
+ /* clean up and free enumerables */
177
+ while ((o = fio_ls_pop(&history)))
178
+ OBJVTBL(o)->free(o);
179
+ return;
180
+ }
181
+
182
+ /**
183
+ * Attempts to return the object's current reference count.
184
+ *
185
+ * This is mostly for testing rather than normal library operations.
186
+ */
187
+ uintptr_t fiobj_reference_count(const fiobj_s *o) { return OBJ2HEAD(o)->ref; }
188
+
189
+ /**
190
+ * Tests if an object evaluates as TRUE.
191
+ *
192
+ * This is object type specific. For example, empty strings might evaluate as
193
+ * FALSE, even though they aren't a boolean type.
194
+ */
195
+ int fiobj_is_true(const fiobj_s *o) { return (o && OBJVTBL(o)->is_true(o)); }
196
+
197
+ /**
198
+ * Returns an Object's numerical value.
199
+ *
200
+ * If a String or Symbol are passed to the function, they will be
201
+ * parsed assuming base 10 numerical data.
202
+ *
203
+ * Hashes and Arrays return their object count.
204
+ *
205
+ * IO and File objects return their underlying file descriptor.
206
+ *
207
+ * A type error results in 0.
208
+ */
209
+ int64_t fiobj_obj2num(const fiobj_s *o) { return o ? OBJVTBL(o)->to_i(o) : 0; }
210
+
211
+ /**
212
+ * Returns a Float's value.
213
+ *
214
+ * If a String or Symbol are passed to the function, they will be
215
+ * parsed assuming base 10 numerical data.
216
+ *
217
+ * Hashes and Arrays return their object count.
218
+ *
219
+ * IO and File objects return their underlying file descriptor.
220
+ *
221
+ * A type error results in 0.
222
+ */
223
+ double fiobj_obj2float(const fiobj_s *o) { return o ? OBJVTBL(o)->to_f(o) : 0; }
224
+
225
+ /**
226
+ * Returns a C String (NUL terminated) using the `fio_cstr_s` data type.
227
+ *
228
+ * The Sting in binary safe and might contain NUL bytes in the middle as well as
229
+ * a terminating NUL.
230
+ *
231
+ * If a Symbol, a Number or a Float are passed to the function, they
232
+ * will be parsed as a *temporary*, thread-safe, String.
233
+ *
234
+ * Numbers will be represented in base 10 numerical data.
235
+ *
236
+ * A type error results in NULL (i.e. object isn't a String).
237
+ */
238
+ fio_cstr_s fiobj_obj2cstr(const fiobj_s *o) {
239
+ return o ? OBJVTBL(o)->to_str(o) : fiobj_noop_str(NULL);
240
+ }
241
+
242
+ /**
243
+ * Single layer iteration using a callback for each nested fio object.
244
+ *
245
+ * Accepts any `fiobj_s *` type but only collections (Arrays and Hashes) are
246
+ * processed. The container itself (the Array or the Hash) is **not** processed
247
+ * (unlike `fiobj_each2`).
248
+ *
249
+ * The callback task function must accept an object and an opaque user pointer.
250
+ *
251
+ * Hash objects pass along a `FIOBJ_T_COUPLET` object, containing
252
+ * references for both the key (Symbol) and the object (any object).
253
+ *
254
+ * If the callback returns -1, the loop is broken. Any other value is ignored.
255
+ *
256
+ * Returns the "stop" position, i.e., the number of items processed + the
257
+ * starting point.
258
+ */
259
+ size_t fiobj_each1(fiobj_s *o, size_t start_at,
260
+ int (*task)(fiobj_s *obj, void *arg), void *arg) {
261
+ return o ? OBJVTBL(o)->each1(o, start_at, task, arg) : 0;
262
+ }
263
+
264
+ /* *****************************************************************************
265
+ Nested concern (each2, is_eq)
266
+ ***************************************************************************** */
267
+
268
+ static int each2_add_to_queue(fiobj_s *obj, void *arg) {
269
+ fio_ls_s *const queue = arg;
270
+ fio_ls_unshift(queue, obj);
271
+ return 0;
272
+ }
273
+
274
+ /**
275
+ * Deep iteration using a callback for each fio object, including the parent.
276
+ *
277
+ *
278
+ * Notice that when passing collections to the function, the collection itself
279
+ * is sent to the callback followed by it's children (if any). This is true also
280
+ * for nested collections (a nested Hash will be sent first, followed by the
281
+ * nested Hash's children and then followed by the rest of it's siblings.
282
+ *
283
+ * If the callback returns -1, the loop is broken. Any other value is ignored.
284
+ */
285
+ void fiobj_each2(fiobj_s *obj, int (*task)(fiobj_s *obj, void *arg),
286
+ void *arg) {
287
+ if (!obj)
288
+ goto single;
289
+ size_t count = OBJVTBL(obj)->count(obj);
290
+ if (!count)
291
+ goto single;
292
+
293
+ fio_ls_s queue = FIO_LS_INIT(queue), history = FIO_LS_INIT(history);
294
+ while (obj || queue.next != &queue) {
295
+ int i = task(obj, arg);
296
+ if (i == -1)
297
+ goto finish;
298
+ if (obj && OBJVTBL(obj)->count(obj)) {
299
+ protected_push_obj(obj, &history);
300
+ OBJVTBL(obj)->each1(obj, 0, each2_add_to_queue, queue.next);
301
+ }
302
+ obj = protected_pop_obj(&queue, &history);
303
+ }
304
+ finish:
305
+ while (fio_ls_pop(&history))
306
+ ;
307
+ while (fio_ls_pop(&queue))
308
+ ;
309
+ return;
310
+ single:
311
+ task(obj, arg);
312
+ return;
313
+ }
314
+
315
+ /**
316
+ * Deeply compare two objects. No hashing is involved.
317
+ *
318
+ * KNOWN ISSUES:
319
+ *
320
+ * * Cyclic nesting might cause this function to hang (much like `fiobj_each2`).
321
+ *
322
+ * * `FIOBJ_NESTING_PROTECTION` might be ignored when testing nested objects.
323
+ *
324
+ * * Hash order might be ignored when comapring Hashes, which means that equal
325
+ * Hases might behave differently during iteration.
326
+ *
327
+ */
328
+ int fiobj_iseq(const fiobj_s *self, const fiobj_s *other) {
329
+ if (self == other)
330
+ return 1;
331
+ if (!self)
332
+ return other->type == FIOBJ_T_NULL;
333
+ if (!other)
334
+ return self->type == FIOBJ_T_NULL;
335
+
336
+ if (!OBJVTBL(self)->is_eq(self, other))
337
+ return 0;
338
+ if (!OBJVTBL(self)->count(self))
339
+ return 1;
340
+
341
+ uint8_t eq = 0;
342
+ fio_ls_s self_queue = FIO_LS_INIT(self_queue);
343
+ fio_ls_s self_history = FIO_LS_INIT(self_history);
344
+ fio_ls_s other_queue = FIO_LS_INIT(other_queue);
345
+ fio_ls_s other_history = FIO_LS_INIT(other_history);
346
+
347
+ while (self) {
348
+ protected_push_obj(self, &self_history);
349
+ protected_push_obj(other, &other_history);
350
+ OBJVTBL(self)->each1((fiobj_s *)self, 0, each2_add_to_queue,
351
+ self_queue.next);
352
+ OBJVTBL(other)->each1((fiobj_s *)other, 0, each2_add_to_queue,
353
+ other_queue.next);
354
+ while (self_queue.next != &self_queue || self) {
355
+ self = protected_pop_obj(&self_queue, &self_history);
356
+ other = protected_pop_obj(&other_queue, &other_history);
357
+ if (self == other)
358
+ continue;
359
+ if (!self && other->type != FIOBJ_T_NULL)
360
+ goto finish;
361
+ if (!other && self->type != FIOBJ_T_NULL)
362
+ goto finish;
363
+ if (OBJVTBL(self)->count(self))
364
+ break;
365
+ if (!OBJVTBL(self)->is_eq(self, other))
366
+ goto finish;
367
+ }
368
+ if (self && !OBJVTBL(self)->is_eq(self, other))
369
+ goto finish;
370
+ }
371
+ eq = 1;
372
+
373
+ finish:
374
+ while (fio_ls_pop(&self_history))
375
+ ;
376
+ while (fio_ls_pop(&self_queue))
377
+ ;
378
+ while (fio_ls_pop(&other_history))
379
+ ;
380
+ while (fio_ls_pop(&other_queue))
381
+ ;
382
+ return eq;
383
+ }
384
+
385
+ /* *****************************************************************************
386
+ Number and Float Helpers
387
+ ***************************************************************************** */
388
+ static const char hex_notation[] = {'0', '1', '2', '3', '4', '5', '6', '7',
389
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
390
+
391
+ /**
392
+ * A helper function that converts between String data to a signed int64_t.
393
+ *
394
+ * Numbers are assumed to be in base 10. `0x##` (or `x##`) and `0b##` (or
395
+ * `b##`) are recognized as base 16 and base 2 (binary MSB first)
396
+ * respectively.
397
+ */
398
+ int64_t fio_atol(char **pstr) {
399
+ char *str = *pstr;
400
+ uint64_t result = 0;
401
+ uint8_t invert = 0;
402
+ while (str[0] == '0')
403
+ str++;
404
+ while (str[0] == '-') {
405
+ invert ^= 1;
406
+ str++;
407
+ }
408
+ while (str[0] == '0')
409
+ str++;
410
+ if (str[0] == 'b' || str[0] == 'B') {
411
+ /* base 2 */
412
+ str++;
413
+ while (str[0] == '0' || str[0] == '1') {
414
+ result = (result << 1) | (str[0] == '1');
415
+ str++;
416
+ }
417
+ } else if (str[0] == 'x' || str[0] == 'X') {
418
+ /* base 16 */
419
+ uint8_t tmp;
420
+ str++;
421
+ while (1) {
422
+ if (str[0] >= '0' && str[0] <= '9')
423
+ tmp = str[0] - '0';
424
+ else if (str[0] >= 'A' && str[0] <= 'F')
425
+ tmp = str[0] - ('A' - 10);
426
+ else if (str[0] >= 'a' && str[0] <= 'f')
427
+ tmp = str[0] - ('a' - 10);
428
+ else
429
+ goto finish;
430
+ result = (result << 4) | tmp;
431
+ str++;
432
+ }
433
+ } else {
434
+ /* base 10 */
435
+ const char *end = str;
436
+ while (end[0] >= '0' && end[0] <= '9' && (uintptr_t)(end - str) < 22)
437
+ end++;
438
+ if ((uintptr_t)(end - str) > 21) /* too large for a number */
439
+ return 0;
440
+
441
+ while (str < end) {
442
+ result = (result * 10) + (str[0] - '0');
443
+ str++;
444
+ }
445
+ }
446
+ finish:
447
+ if (invert)
448
+ result = 0 - result;
449
+ *pstr = str;
450
+ return (int64_t)result;
451
+ }
452
+
453
+ /** A helper function that convers between String data to a signed double. */
454
+ double fio_atof(char **pstr) { return strtold(*pstr, pstr); }
455
+
456
+ /* *****************************************************************************
457
+ String Helpers
458
+ ***************************************************************************** */
459
+
460
+ /**
461
+ * A helper function that convers between a signed int64_t to a string.
462
+ *
463
+ * No overflow guard is provided, make sure there's at least 66 bytes
464
+ * available (for base 2).
465
+ *
466
+ * Supports base 2, base 10 and base 16. An unsupported base will silently
467
+ * default to base 10. Prefixes aren't added (i.e., no "0x" or "0b" at the
468
+ * beginning of the string).
469
+ *
470
+ * Returns the number of bytes actually written (excluding the NUL
471
+ * terminator).
472
+ */
473
+ size_t fio_ltoa(char *dest, int64_t num, uint8_t base) {
474
+ if (!num) {
475
+ *(dest++) = '0';
476
+ *(dest++) = 0;
477
+ return 1;
478
+ }
479
+
480
+ size_t len = 0;
481
+
482
+ if (base == 2) {
483
+ uint64_t n = num; /* avoid bit shifting inconsistencies with signed bit */
484
+ uint8_t i = 0; /* counting bits */
485
+
486
+ while ((i < 64) && (n & 0x8000000000000000) == 0) {
487
+ n = n << 1;
488
+ i++;
489
+ }
490
+ /* make sure the Binary representation doesn't appear signed. */
491
+ if (i) {
492
+ dest[len++] = '0';
493
+ }
494
+ /* write to dest. */
495
+ while (i < 64) {
496
+ dest[len++] = ((n & 0x8000000000000000) ? '1' : '0');
497
+ n = n << 1;
498
+ i++;
499
+ }
500
+ dest[len] = 0;
501
+ return len;
502
+
503
+ } else if (base == 16) {
504
+ uint64_t n = num; /* avoid bit shifting inconsistencies with signed bit */
505
+ uint8_t i = 0; /* counting bytes */
506
+ uint8_t tmp = 0;
507
+ while (i < 8 && (n & 0xFF00000000000000) == 0) {
508
+ n = n << 8;
509
+ i++;
510
+ }
511
+ /* make sure the Hex representation doesn't appear signed. */
512
+ if (i && (n & 0x8000000000000000)) {
513
+ dest[len++] = '0';
514
+ dest[len++] = '0';
515
+ }
516
+ /* write the damn thing */
517
+ while (i < 8) {
518
+ tmp = (n & 0xF000000000000000) >> 60;
519
+ dest[len++] = hex_notation[tmp];
520
+ tmp = (n & 0x0F00000000000000) >> 56;
521
+ dest[len++] = hex_notation[tmp];
522
+ i++;
523
+ n = n << 8;
524
+ }
525
+ dest[len] = 0;
526
+ return len;
527
+ }
528
+
529
+ /* fallback to base 10 */
530
+ uint64_t rem = 0;
531
+ uint64_t factor = 1;
532
+ if (num < 0) {
533
+ dest[len++] = '-';
534
+ num = 0 - num;
535
+ }
536
+
537
+ while (num / factor)
538
+ factor *= 10;
539
+
540
+ while (factor > 1) {
541
+ factor = factor / 10;
542
+ rem = (rem * 10);
543
+ dest[len++] = '0' + ((num / factor) - rem);
544
+ rem += ((num / factor) - rem);
545
+ }
546
+ dest[len] = 0;
547
+ return len;
548
+ }
549
+
550
+ /**
551
+ * A helper function that convers between a double to a string.
552
+ *
553
+ * No overflow guard is provided, make sure there's at least 130 bytes
554
+ * available (for base 2).
555
+ *
556
+ * Supports base 2, base 10 and base 16. An unsupported base will silently
557
+ * default to base 10. Prefixes aren't added (i.e., no "0x" or "0b" at the
558
+ * beginning of the string).
559
+ *
560
+ * Returns the number of bytes actually written (excluding the NUL
561
+ * terminator).
562
+ */
563
+ size_t fio_ftoa(char *dest, double num, uint8_t base) {
564
+ if (base == 2 || base == 16) {
565
+ /* handle the binary / Hex representation the same as if it were an
566
+ * int64_t
567
+ */
568
+ int64_t *i = (void *)&num;
569
+ return fio_ltoa(dest, *i, base);
570
+ }
571
+
572
+ size_t written = sprintf(dest, "%g", num);
573
+ uint8_t need_zero = 1;
574
+ char *start = dest;
575
+ while (*start) {
576
+ if (*start == ',') // locale issues?
577
+ *start = '.';
578
+ if (*start == '.' || *start == 'e') {
579
+ need_zero = 0;
580
+ break;
581
+ }
582
+ start++;
583
+ }
584
+ if (need_zero) {
585
+ dest[written++] = '.';
586
+ dest[written++] = '0';
587
+ }
588
+ return written;
589
+ }