iodine 0.4.14 → 0.4.15

Sign up to get free protection for your applications and to get access to all the features.

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
@@ -1,81 +0,0 @@
1
- /*
2
- Copyright: Boaz segev, 2017
3
- License: MIT
4
-
5
- Feel free to copy, use and enjoy according to the license provided.
6
- */
7
-
8
- /* *****************************************************************************
9
- This file handles deallocation concerns.
10
-
11
- Since Hash and Array objects will need to deallocate their children, the logic
12
- regareding object type recorgnition and deallocation was centrelized here.
13
- ***************************************************************************** */
14
- #include "fiobj_types.h"
15
-
16
- /* *****************************************************************************
17
- Object Deallocation
18
- ***************************************************************************** */
19
-
20
- void fiobj_dealloc(fiobj_s *obj) {
21
- if (!obj)
22
- return;
23
- if (OBJ2HEAD(obj).ref == 0) {
24
- fprintf(stderr,
25
- "ERROR: attempting to free an object that isn't a fiobj or already "
26
- "freed (%p)\n",
27
- (void *)obj);
28
- kill(0, SIGABRT);
29
- }
30
- if (spn_sub(&OBJ2HEAD(obj).ref, 1))
31
- return;
32
- OBJ2HEAD(obj).vtable->free(obj);
33
- }
34
-
35
- /* *****************************************************************************
36
- Deep Allocation (`fiobj_dup`)
37
- ***************************************************************************** */
38
-
39
- /* simply increrase the reference count for each object. */
40
- static int dup_task_callback(fiobj_s *obj, void *arg) {
41
- if (!obj)
42
- return 0;
43
- spn_add(&OBJ2HEAD(obj).ref, 1);
44
- // if (obj->type == FIOBJ_T_COUPLET)
45
- // spn_add(&OBJ2HEAD(obj2couplet(obj)->obj).ref, 1);
46
- return 0;
47
- (void)arg;
48
- }
49
-
50
- /** Increases an object's reference count. */
51
- fiobj_s *fiobj_dup(fiobj_s *obj) {
52
- fiobj_each2(obj, dup_task_callback, NULL);
53
- return obj;
54
- }
55
-
56
- /* *****************************************************************************
57
- Deep Deallocation (`fiobj_free`)
58
- ***************************************************************************** */
59
-
60
- static int dealloc_task_callback(fiobj_s *obj, void *arg) {
61
- // if (!obj)
62
- // return 0;
63
- // if (obj->type == FIOBJ_T_COUPLET)
64
- // fiobj_dealloc(obj2couplet(obj)->obj);
65
- fiobj_dealloc(obj);
66
- return 0;
67
- (void)arg;
68
- }
69
- /**
70
- * Decreases an object's reference count, releasing memory and
71
- * resources.
72
- *
73
- * This function affects nested objects, meaning that when an Array or
74
- * a Hashe object is passed along, it's children (nested objects) are
75
- * also freed.
76
- */
77
- void fiobj_free(fiobj_s *obj) {
78
- if (!obj)
79
- return;
80
- fiobj_each2(obj, dealloc_task_callback, NULL);
81
- }
@@ -1,260 +0,0 @@
1
- /*
2
- Copyright: Boaz segev, 2017
3
- License: MIT
4
-
5
- Feel free to copy, use and enjoy according to the license provided.
6
- */
7
-
8
- #include "fiobj_types.h"
9
-
10
- /* *****************************************************************************
11
- No-op for vtable
12
- ***************************************************************************** */
13
-
14
- fio_cstr_s fiobj_noop_str(fiobj_s *obj) {
15
- return (fio_cstr_s){.data = NULL};
16
- (void)obj;
17
- }
18
- int64_t fiobj_noop_i(fiobj_s *obj) {
19
- return 0;
20
- (void)obj;
21
- }
22
- double fiobj_noop_f(fiobj_s *obj) {
23
- return 0;
24
- (void)obj;
25
- }
26
-
27
- size_t fiobj_noop_count(fiobj_s *obj) {
28
- return 0;
29
- (void)obj;
30
- }
31
-
32
- size_t fiobj_noop_each1(fiobj_s *obj, size_t start_at,
33
- int (*task)(fiobj_s *obj, void *arg), void *arg) {
34
- return 0;
35
- (void)obj;
36
- (void)start_at;
37
- (void)task;
38
- (void)arg;
39
- }
40
-
41
- void fiobj_simple_dealloc(fiobj_s *o) { free(&OBJ2HEAD(o)); }
42
-
43
- /* *****************************************************************************
44
- Generic Object API
45
- ***************************************************************************** */
46
-
47
- /**
48
- * Returns a Number's value.
49
- *
50
- * If a String or Symbol are passed to the function, they will be
51
- * parsed assuming base 10 numerical data.
52
- *
53
- * A type error results in 0.
54
- */
55
- int64_t fiobj_obj2num(fiobj_s *obj) {
56
- if (!obj)
57
- return 0;
58
- return OBJ2HEAD(obj).vtable->to_i(obj);
59
- }
60
-
61
- /**
62
- * Returns a Float's value.
63
- *
64
- * If a String or Symbol are passed to the function, they will be
65
- * parsed assuming base 10 numerical data.
66
- *
67
- * A type error results in 0.
68
- */
69
- double fiobj_obj2float(fiobj_s *obj) {
70
- if (!obj)
71
- return 0;
72
- return OBJ2HEAD(obj).vtable->to_f(obj);
73
- }
74
-
75
- /**
76
- * Returns a C String (NUL terminated) using the `fio_cstr_s` data type.
77
- */
78
- fio_cstr_s fiobj_obj2cstr(fiobj_s *obj) {
79
- if (!obj)
80
- return (fio_cstr_s){.buffer = NULL, .len = 0};
81
- return OBJ2HEAD(obj).vtable->to_str(obj);
82
- }
83
-
84
- /**
85
- * Single layer iteration using a callback for each nested fio object.
86
- */
87
- size_t fiobj_each1(fiobj_s *obj, size_t start_at,
88
- int (*task)(fiobj_s *obj, void *arg), void *arg) {
89
- return OBJ2HEAD(obj).vtable->each1(obj, start_at, task, arg);
90
- }
91
-
92
- /* *****************************************************************************
93
- Object Iteration (`fiobj_each2`)
94
- ***************************************************************************** */
95
-
96
- static __thread fiobj_s *fiobj_cyclic_protection = NULL;
97
- fiobj_s *fiobj_each_get_cyclic(void) { return fiobj_cyclic_protection; }
98
-
99
- static uint8_t already_processed(fiobj_s *nested, fiobj_s *obj) {
100
- #if FIOBJ_NESTING_PROTECTION
101
- size_t end = obj2ary(nested)->end;
102
- for (size_t i = obj2ary(nested)->start; i < end; i++) {
103
- if (obj2ary(nested)->arry[i] == obj)
104
- return 1;
105
- }
106
- #endif
107
- OBJREF_ADD(obj);
108
- fiobj_ary_push(nested, obj);
109
- return 0;
110
- }
111
-
112
- struct fiobj_each2_task_s {
113
- fiobj_s *nested;
114
- int (*task)(fiobj_s *obj, void *arg);
115
- void *arg;
116
- fiobj_s *child;
117
- };
118
-
119
- int fiobj_each2_task_wrapper(fiobj_s *obj, void *data_) {
120
- struct fiobj_each2_task_s *data = data_;
121
- if (!obj || !OBJ2HEAD(obj).vtable->count(obj))
122
- return data->task(obj, data->arg);
123
-
124
- data->child = obj->type == FIOBJ_T_COUPLET ? obj2couplet(obj)->obj : obj;
125
- if (already_processed(data->nested, data->child)) {
126
- data->child = NULL;
127
- fiobj_cyclic_protection = obj;
128
- int ret = data->task(NULL, data->arg);
129
- fiobj_cyclic_protection = NULL;
130
- return ret;
131
- }
132
- data->task(obj, data->arg);
133
- return -1;
134
- }
135
- /**
136
- * Deep itteration using a callback for each fio object, including the parent.
137
- * If the callback returns -1, the loop is broken. Any other value is ignored.
138
- */
139
- void fiobj_each2(fiobj_s *obj, int (*task)(fiobj_s *obj, void *arg),
140
- void *arg) {
141
- /* optimize simple items */
142
- if (!obj || OBJ2HEAD(obj).vtable->count(obj) == 0) {
143
- task(obj, arg);
144
- return;
145
- }
146
- /* Prepare for layerd iteration */
147
- uintptr_t count = 0;
148
- fiobj_s *nested = fiobj_ary_new2(64);
149
- fiobj_s *state = fiobj_ary_new2(128);
150
- struct fiobj_each2_task_s data = {.task = task, .arg = arg, .nested = nested};
151
- OBJREF_ADD(obj);
152
- fiobj_ary_push(nested, obj);
153
- if (task(obj, arg) == -1)
154
- goto finish;
155
- rebase:
156
- /* resume ? */
157
- data.child = NULL;
158
- count =
159
- OBJ2HEAD(obj).vtable->each1(obj, count, fiobj_each2_task_wrapper, &data);
160
- if (data.child) {
161
- fiobj_ary_push(state, obj);
162
- fiobj_ary_push(state, (void *)count);
163
- count = 0;
164
- obj = data.child;
165
- goto rebase;
166
- }
167
-
168
- /* any more nested layers left to handle? */
169
- if (fiobj_ary_count(state)) {
170
- count = (uintptr_t)fiobj_ary_pop(state);
171
- obj = fiobj_ary_pop(state);
172
- goto rebase;
173
- }
174
-
175
- finish:
176
- while ((obj = fiobj_ary_pop(nested)))
177
- fiobj_dealloc(obj);
178
- fiobj_dealloc(nested);
179
- fiobj_dealloc(state);
180
- return;
181
- }
182
-
183
- /* *****************************************************************************
184
- Object Comparison (`fiobj_iseq`)
185
- ***************************************************************************** */
186
-
187
- static inline int fiobj_iseq_check(fiobj_s *obj1, fiobj_s *obj2) {
188
- if (obj1 == obj2)
189
- return 1;
190
- if (!obj1 || !obj2)
191
- return 0;
192
- return OBJ2HEAD(obj1).vtable->is_eq(obj1, obj2);
193
- }
194
-
195
- struct fiobj_iseq_data_s {
196
- fiobj_s *root;
197
- fiobj_s *nested;
198
- fiobj_s *parallel;
199
- uintptr_t count;
200
- uint8_t is_hash;
201
- uint8_t err;
202
- };
203
-
204
- int fiobj_iseq_task(fiobj_s *obj, void *data_) {
205
- struct fiobj_iseq_data_s *data = data_;
206
- if (fiobj_cyclic_protection)
207
- obj = fiobj_cyclic_protection;
208
-
209
- if (data->root == obj)
210
- return 0;
211
-
212
- if (data->count >= OBJ2HEAD(data->root).vtable->count(data->root)) {
213
- data->count = (uintptr_t)fiobj_ary_pop(data->nested);
214
- data->parallel = fiobj_ary_pop(data->nested);
215
- data->root = fiobj_ary_pop(data->nested);
216
- data->is_hash = (data->root->type == FIOBJ_T_HASH);
217
- }
218
-
219
- fiobj_s *obj2;
220
- if (data->is_hash) {
221
- obj2 = fiobj_hash_get(data->parallel, fiobj_couplet2key(obj));
222
- obj = fiobj_couplet2obj(obj);
223
- } else {
224
- obj2 = fiobj_ary_entry(data->parallel, data->count);
225
- }
226
- data->count++;
227
- if (!fiobj_iseq_check(obj, obj2)) {
228
- data->err = 1;
229
- return -1;
230
- }
231
- if (!fiobj_cyclic_protection && OBJ2HEAD(obj).vtable->count(obj)) {
232
- fiobj_ary_push(data->nested, data->root);
233
- fiobj_ary_push(data->nested, data->parallel);
234
- fiobj_ary_push(data->nested, (fiobj_s *)data->count);
235
- data->count = 0;
236
- data->root = obj;
237
- data->parallel = obj2;
238
- data->is_hash = (obj->type == FIOBJ_T_HASH);
239
- }
240
- return 0;
241
- }
242
-
243
- int fiobj_iseq(fiobj_s *obj1, fiobj_s *obj2) {
244
- if (OBJ2HEAD(obj1).vtable->count(obj1) == 0)
245
- return fiobj_iseq_check(obj1, obj2);
246
- if (!fiobj_iseq_check(obj1, obj2))
247
- return 0;
248
- struct fiobj_iseq_data_s data = {
249
- .root = obj1,
250
- .nested = fiobj_ary_new2(128),
251
- .is_hash = obj1->type == FIOBJ_T_HASH,
252
- // .count = OBJ2HEAD(obj1).vtable->count(obj1),
253
- .parallel = obj2,
254
- };
255
- // fiobj_ary_push(data.nested, obj2);
256
- // fiobj_ary_push(data.nested, (fiobj_s *)OBJ2HEAD(obj1).vtable->count(obj1));
257
- fiobj_each2(obj1, fiobj_iseq_task, &data);
258
- fiobj_dealloc(data.nested);
259
- return data.err == 0;
260
- }
@@ -1,58 +0,0 @@
1
- /*
2
- Copyright: Boaz segev, 2017
3
- License: MIT
4
-
5
- Feel free to copy, use and enjoy according to the license provided.
6
- */
7
- #include "fiobj_types.h"
8
-
9
- /* *****************************************************************************
10
- IO VTable
11
- ***************************************************************************** */
12
-
13
- static int64_t fio_io2i(fiobj_s *io) { return (int64_t)obj2io(io)->fd; }
14
-
15
- static int fiobj_io_is_eq(fiobj_s *self, fiobj_s *other) {
16
- if (!other || other->type != self->type ||
17
- obj2io(self)->fd != obj2io(other)->fd)
18
- return 0;
19
- return 1;
20
- }
21
-
22
- static struct fiobj_vtable_s FIOBJ_VTABLE_IO = {
23
- .free = fiobj_simple_dealloc,
24
- .to_i = fio_io2i,
25
- .to_f = fiobj_noop_f,
26
- .to_str = fiobj_noop_str,
27
- .is_eq = fiobj_io_is_eq,
28
- .count = fiobj_noop_count,
29
- .each1 = fiobj_noop_each1,
30
- };
31
-
32
- /* *****************************************************************************
33
- IO API
34
- ***************************************************************************** */
35
-
36
- /** Wrapps a file descriptor in an IO object. Use `fiobj_free` to close. */
37
- fiobj_s *fio_io_wrap(intptr_t fd) {
38
- fiobj_head_s *head;
39
- head = malloc(sizeof(*head) + sizeof(fio_io_s));
40
- if (!head)
41
- perror("ERROR: fiobj IO couldn't allocate memory"), exit(errno);
42
- *head = (fiobj_head_s){
43
- .ref = 1, .vtable = &FIOBJ_VTABLE_IO,
44
- };
45
- *obj2io(HEAD2OBJ(head)) = (fio_io_s){.type = FIOBJ_T_IO, .fd = fd};
46
- return HEAD2OBJ(head);
47
- }
48
-
49
- /**
50
- * Return an IO's fd.
51
- *
52
- * A type error results in -1.
53
- */
54
- intptr_t fiobj_io_fd(fiobj_s *obj) {
55
- if (obj->type != FIOBJ_T_IO)
56
- return -1;
57
- return ((fio_io_s *)obj)->fd;
58
- }
@@ -1,213 +0,0 @@
1
- /*
2
- Copyright: Boaz segev, 2017
3
- License: MIT
4
-
5
- Feel free to copy, use and enjoy according to the license provided.
6
- */
7
- #include "fiobj_types.h"
8
-
9
- /* *****************************************************************************
10
- Number and Float Helpers
11
- ***************************************************************************** */
12
- static const char hex_notation[] = {'0', '1', '2', '3', '4', '5', '6', '7',
13
- '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
14
-
15
- /**
16
- * A helper function that converts between String data to a signed int64_t.
17
- *
18
- * Numbers are assumed to be in base 10. `0x##` (or `x##`) and `0b##` (or
19
- * `b##`) are recognized as base 16 and base 2 (binary MSB first)
20
- * respectively.
21
- */
22
- int64_t fio_atol(char **pstr) {
23
- char *str = *pstr;
24
- uint64_t result = 0;
25
- uint8_t invert = 0;
26
- while (str[0] == '0')
27
- str++;
28
- while (str[0] == '-') {
29
- invert ^= 1;
30
- str++;
31
- }
32
- while (str[0] == '0')
33
- str++;
34
- if (str[0] == 'b' || str[0] == 'B') {
35
- /* base 2 */
36
- str++;
37
- while (str[0] == '0' || str[0] == '1') {
38
- result = (result << 1) | (str[0] == '1');
39
- str++;
40
- }
41
- } else if (str[0] == 'x' || str[0] == 'X') {
42
- /* base 16 */
43
- uint8_t tmp;
44
- str++;
45
- while (1) {
46
- if (str[0] >= '0' && str[0] <= '9')
47
- tmp = str[0] - '0';
48
- else if (str[0] >= 'A' && str[0] <= 'F')
49
- tmp = str[0] - ('A' - 10);
50
- else if (str[0] >= 'a' && str[0] <= 'f')
51
- tmp = str[0] - ('a' - 10);
52
- else
53
- goto finish;
54
- result = (result << 4) | tmp;
55
- str++;
56
- }
57
- } else {
58
- /* base 10 */
59
- const char *end = str;
60
- while (end[0] >= '0' && end[0] <= '9' && (uintptr_t)(end - str) < 22)
61
- end++;
62
- if ((uintptr_t)(end - str) > 21) /* too large for a number */
63
- return 0;
64
-
65
- while (str < end) {
66
- result = (result * 10) + (str[0] - '0');
67
- str++;
68
- }
69
- }
70
- finish:
71
- if (invert)
72
- result = 0 - result;
73
- *pstr = str;
74
- return (int64_t)result;
75
- }
76
-
77
- /** A helper function that convers between String data to a signed double. */
78
- double fio_atof(char **pstr) { return strtold(*pstr, pstr); }
79
-
80
- /* *****************************************************************************
81
- String Helpers
82
- ***************************************************************************** */
83
-
84
- /**
85
- * A helper function that convers between a signed int64_t to a string.
86
- *
87
- * No overflow guard is provided, make sure there's at least 66 bytes
88
- * available (for base 2).
89
- *
90
- * Supports base 2, base 10 and base 16. An unsupported base will silently
91
- * default to base 10. Prefixes aren't added (i.e., no "0x" or "0b" at the
92
- * beginning of the string).
93
- *
94
- * Returns the number of bytes actually written (excluding the NUL
95
- * terminator).
96
- */
97
- size_t fio_ltoa(char *dest, int64_t num, uint8_t base) {
98
- if (!num) {
99
- *(dest++) = '0';
100
- *(dest++) = 0;
101
- return 1;
102
- }
103
-
104
- size_t len = 0;
105
-
106
- if (base == 2) {
107
- uint64_t n = num; /* avoid bit shifting inconsistencies with signed bit */
108
- uint8_t i = 0; /* counting bits */
109
-
110
- while ((i < 64) && (n & 0x8000000000000000) == 0) {
111
- n = n << 1;
112
- i++;
113
- }
114
- /* make sure the Binary representation doesn't appear signed. */
115
- if (i) {
116
- dest[len++] = '0';
117
- }
118
- /* write to dest. */
119
- while (i < 64) {
120
- dest[len++] = ((n & 0x8000000000000000) ? '1' : '0');
121
- n = n << 1;
122
- i++;
123
- }
124
- dest[len] = 0;
125
- return len;
126
-
127
- } else if (base == 16) {
128
- uint64_t n = num; /* avoid bit shifting inconsistencies with signed bit */
129
- uint8_t i = 0; /* counting bytes */
130
- uint8_t tmp = 0;
131
- while (i < 8 && (n & 0xFF00000000000000) == 0) {
132
- n = n << 8;
133
- i++;
134
- }
135
- /* make sure the Hex representation doesn't appear signed. */
136
- if (i && (n & 0x8000000000000000)) {
137
- dest[len++] = '0';
138
- dest[len++] = '0';
139
- }
140
- /* write the damn thing */
141
- while (i < 8) {
142
- tmp = (n & 0xF000000000000000) >> 60;
143
- dest[len++] = hex_notation[tmp];
144
- tmp = (n & 0x0F00000000000000) >> 56;
145
- dest[len++] = hex_notation[tmp];
146
- i++;
147
- n = n << 8;
148
- }
149
- dest[len] = 0;
150
- return len;
151
- }
152
-
153
- /* fallback to base 10 */
154
- uint64_t rem = 0;
155
- uint64_t factor = 1;
156
- if (num < 0) {
157
- dest[len++] = '-';
158
- num = 0 - num;
159
- }
160
-
161
- while (num / factor)
162
- factor *= 10;
163
-
164
- while (factor > 1) {
165
- factor = factor / 10;
166
- rem = (rem * 10);
167
- dest[len++] = '0' + ((num / factor) - rem);
168
- rem += ((num / factor) - rem);
169
- }
170
- dest[len] = 0;
171
- return len;
172
- }
173
-
174
- /**
175
- * A helper function that convers between a double to a string.
176
- *
177
- * No overflow guard is provided, make sure there's at least 130 bytes
178
- * available (for base 2).
179
- *
180
- * Supports base 2, base 10 and base 16. An unsupported base will silently
181
- * default to base 10. Prefixes aren't added (i.e., no "0x" or "0b" at the
182
- * beginning of the string).
183
- *
184
- * Returns the number of bytes actually written (excluding the NUL
185
- * terminator).
186
- */
187
- size_t fio_ftoa(char *dest, double num, uint8_t base) {
188
- if (base == 2 || base == 16) {
189
- /* handle the binary / Hex representation the same as if it were an
190
- * int64_t
191
- */
192
- int64_t *i = (void *)&num;
193
- return fio_ltoa(dest, *i, base);
194
- }
195
-
196
- size_t written = sprintf(dest, "%g", num);
197
- uint8_t need_zero = 1;
198
- char *start = dest;
199
- while (*start) {
200
- if (*start == ',') // locale issues?
201
- *start = '.';
202
- if (*start == '.' || *start == 'e') {
203
- need_zero = 0;
204
- break;
205
- }
206
- start++;
207
- }
208
- if (need_zero) {
209
- dest[written++] = '.';
210
- dest[written++] = '0';
211
- }
212
- return written;
213
- }