iodine 0.6.5 → 0.7.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +4 -4
- data/SPEC-Websocket-Draft.md +3 -6
- data/bin/mustache.rb +128 -0
- data/examples/test_template.mustache +16 -0
- data/ext/iodine/fio.c +9397 -0
- data/ext/iodine/fio.h +4723 -0
- data/ext/iodine/fio_ary.h +353 -54
- data/ext/iodine/fio_cli.c +351 -361
- data/ext/iodine/fio_cli.h +84 -105
- data/ext/iodine/fio_hashmap.h +70 -16
- data/ext/iodine/fio_json_parser.h +35 -24
- data/ext/iodine/fio_siphash.c +104 -4
- data/ext/iodine/fio_siphash.h +18 -2
- data/ext/iodine/fio_str.h +1218 -0
- data/ext/iodine/fio_tmpfile.h +1 -1
- data/ext/iodine/fiobj.h +13 -8
- data/ext/iodine/fiobj4sock.h +6 -8
- data/ext/iodine/fiobj_ary.c +107 -17
- data/ext/iodine/fiobj_ary.h +36 -4
- data/ext/iodine/fiobj_data.c +146 -127
- data/ext/iodine/fiobj_data.h +25 -23
- data/ext/iodine/fiobj_hash.c +7 -7
- data/ext/iodine/fiobj_hash.h +6 -5
- data/ext/iodine/fiobj_json.c +20 -17
- data/ext/iodine/fiobj_json.h +5 -5
- data/ext/iodine/fiobj_mem.h +71 -0
- data/ext/iodine/fiobj_mustache.c +310 -0
- data/ext/iodine/fiobj_mustache.h +40 -0
- data/ext/iodine/fiobj_numbers.c +199 -94
- data/ext/iodine/fiobj_numbers.h +7 -7
- data/ext/iodine/fiobj_str.c +142 -333
- data/ext/iodine/fiobj_str.h +65 -55
- data/ext/iodine/fiobject.c +49 -11
- data/ext/iodine/fiobject.h +40 -39
- data/ext/iodine/http.c +382 -190
- data/ext/iodine/http.h +124 -80
- data/ext/iodine/http1.c +99 -127
- data/ext/iodine/http1.h +5 -5
- data/ext/iodine/http1_parser.c +3 -2
- data/ext/iodine/http1_parser.h +2 -2
- data/ext/iodine/http_internal.c +14 -12
- data/ext/iodine/http_internal.h +25 -19
- data/ext/iodine/iodine.c +37 -18
- data/ext/iodine/iodine.h +4 -0
- data/ext/iodine/iodine_caller.c +9 -2
- data/ext/iodine/iodine_caller.h +2 -0
- data/ext/iodine/iodine_connection.c +82 -117
- data/ext/iodine/iodine_defer.c +57 -50
- data/ext/iodine/iodine_defer.h +0 -1
- data/ext/iodine/iodine_fiobj2rb.h +4 -2
- data/ext/iodine/iodine_helpers.c +4 -4
- data/ext/iodine/iodine_http.c +25 -32
- data/ext/iodine/iodine_json.c +2 -1
- data/ext/iodine/iodine_mustache.c +423 -0
- data/ext/iodine/iodine_mustache.h +6 -0
- data/ext/iodine/iodine_pubsub.c +48 -153
- data/ext/iodine/iodine_pubsub.h +5 -4
- data/ext/iodine/iodine_rack_io.c +7 -5
- data/ext/iodine/iodine_store.c +16 -13
- data/ext/iodine/iodine_tcp.c +26 -34
- data/ext/iodine/mustache_parser.h +1085 -0
- data/ext/iodine/redis_engine.c +740 -646
- data/ext/iodine/redis_engine.h +13 -15
- data/ext/iodine/resp_parser.h +11 -5
- data/ext/iodine/websocket_parser.h +13 -13
- data/ext/iodine/websockets.c +240 -393
- data/ext/iodine/websockets.h +52 -113
- data/lib/iodine.rb +1 -1
- data/lib/iodine/mustache.rb +140 -0
- data/lib/iodine/version.rb +1 -1
- metadata +15 -28
- data/ext/iodine/defer.c +0 -566
- data/ext/iodine/defer.h +0 -148
- data/ext/iodine/evio.c +0 -26
- data/ext/iodine/evio.h +0 -161
- data/ext/iodine/evio_callbacks.c +0 -26
- data/ext/iodine/evio_epoll.c +0 -251
- data/ext/iodine/evio_kqueue.c +0 -194
- data/ext/iodine/facil.c +0 -2325
- data/ext/iodine/facil.h +0 -616
- data/ext/iodine/fio_base64.c +0 -277
- data/ext/iodine/fio_base64.h +0 -71
- data/ext/iodine/fio_llist.h +0 -257
- data/ext/iodine/fio_mem.c +0 -675
- data/ext/iodine/fio_mem.h +0 -143
- data/ext/iodine/fio_random.c +0 -248
- data/ext/iodine/fio_random.h +0 -45
- data/ext/iodine/fio_sha1.c +0 -362
- data/ext/iodine/fio_sha1.h +0 -107
- data/ext/iodine/fio_sha2.c +0 -842
- data/ext/iodine/fio_sha2.h +0 -169
- data/ext/iodine/pubsub.c +0 -867
- data/ext/iodine/pubsub.h +0 -221
- data/ext/iodine/sock.c +0 -1366
- data/ext/iodine/sock.h +0 -566
- data/ext/iodine/spnlock.inc +0 -111
data/ext/iodine/fio_siphash.c
CHANGED
@@ -2,7 +2,18 @@
|
|
2
2
|
Copyright: Boaz Segev, 2017
|
3
3
|
License: MIT
|
4
4
|
*/
|
5
|
-
#include
|
5
|
+
#include <fio_siphash.h>
|
6
|
+
|
7
|
+
/* *****************************************************************************
|
8
|
+
|
9
|
+
NOTICE
|
10
|
+
|
11
|
+
This code won't be linked to the final application when using fio.h and fio.c.
|
12
|
+
|
13
|
+
The code is here only to allow the FIOBJ library to be extracted from the
|
14
|
+
facil.io framework library.
|
15
|
+
|
16
|
+
***************************************************************************** */
|
6
17
|
|
7
18
|
/* *****************************************************************************
|
8
19
|
Hashing (SipHash implementation)
|
@@ -25,7 +36,8 @@ Hashing (SipHash implementation)
|
|
25
36
|
#define lrot64(i, bits) \
|
26
37
|
(((uint64_t)(i) << (bits)) | ((uint64_t)(i) >> (64 - (bits))))
|
27
38
|
|
28
|
-
uint64_t
|
39
|
+
static inline uint64_t fio_siphash_xy(const void *data, size_t len, size_t x,
|
40
|
+
size_t y) {
|
29
41
|
/* initialize the 4 words */
|
30
42
|
uint64_t v0 = (0x0706050403020100ULL ^ 0x736f6d6570736575ULL);
|
31
43
|
uint64_t v1 = (0x0f0e0d0c0b0a0908ULL ^ 0x646f72616e646f6dULL);
|
@@ -56,8 +68,9 @@ uint64_t fio_siphash(const void *data, size_t len) {
|
|
56
68
|
word.i = sip_local64(*w64);
|
57
69
|
v3 ^= word.i;
|
58
70
|
/* Sip Rounds */
|
59
|
-
|
60
|
-
|
71
|
+
for (size_t i = 0; i < x; ++i) {
|
72
|
+
hash_map_SipRound;
|
73
|
+
}
|
61
74
|
v0 ^= word.i;
|
62
75
|
w64 += 1;
|
63
76
|
len -= 8;
|
@@ -97,6 +110,9 @@ uint64_t fio_siphash(const void *data, size_t len) {
|
|
97
110
|
/* Finalization */
|
98
111
|
v2 ^= 0xff;
|
99
112
|
/* d iterations of SipRound */
|
113
|
+
for (size_t i = 0; i < y; ++i) {
|
114
|
+
hash_map_SipRound;
|
115
|
+
}
|
100
116
|
hash_map_SipRound;
|
101
117
|
hash_map_SipRound;
|
102
118
|
hash_map_SipRound;
|
@@ -106,3 +122,87 @@ uint64_t fio_siphash(const void *data, size_t len) {
|
|
106
122
|
#undef hash_map_SipRound
|
107
123
|
return v0;
|
108
124
|
}
|
125
|
+
|
126
|
+
#pragma weak fio_siphash24
|
127
|
+
uint64_t __attribute__((weak)) fio_siphash24(const void *data, size_t len) {
|
128
|
+
return fio_siphash_xy(data, len, 2, 4);
|
129
|
+
}
|
130
|
+
|
131
|
+
#pragma weak fio_siphash13
|
132
|
+
uint64_t __attribute__((weak)) fio_siphash13(const void *data, size_t len) {
|
133
|
+
return fio_siphash_xy(data, len, 1, 3);
|
134
|
+
}
|
135
|
+
|
136
|
+
#if defined(DEBUG) && DEBUG == 1
|
137
|
+
#include <stdio.h>
|
138
|
+
#include <string.h>
|
139
|
+
#include <time.h>
|
140
|
+
|
141
|
+
#if 0
|
142
|
+
static void fio_siphash_speed_test(void) {
|
143
|
+
/* test based on code from BearSSL with credit to Thomas Pornin */
|
144
|
+
uint8_t buffer[8192];
|
145
|
+
memset(buffer, 'T', sizeof(buffer));
|
146
|
+
/* warmup */
|
147
|
+
uint64_t hash = 0;
|
148
|
+
for (size_t i = 0; i < 4; i++) {
|
149
|
+
hash += fio_siphash(buffer, sizeof(buffer));
|
150
|
+
memcpy(buffer, &hash, sizeof(hash));
|
151
|
+
}
|
152
|
+
/* loop until test runs for more than 2 seconds */
|
153
|
+
for (uint64_t cycles = 8192;;) {
|
154
|
+
clock_t start, end;
|
155
|
+
start = clock();
|
156
|
+
for (size_t i = cycles; i > 0; i--) {
|
157
|
+
hash += fio_siphash(buffer, sizeof(buffer));
|
158
|
+
__asm__ volatile("" ::: "memory");
|
159
|
+
}
|
160
|
+
end = clock();
|
161
|
+
memcpy(buffer, &hash, sizeof(hash));
|
162
|
+
if ((end - start) >= (2 * CLOCKS_PER_SEC) ||
|
163
|
+
cycles >= ((uint64_t)1 << 62)) {
|
164
|
+
fprintf(stderr, "%-20s %8.2f MB/s\n", "fio SipHash24",
|
165
|
+
(double)(sizeof(buffer) * cycles) /
|
166
|
+
(((end - start) * 1000000.0 / CLOCKS_PER_SEC)));
|
167
|
+
break;
|
168
|
+
}
|
169
|
+
cycles <<= 2;
|
170
|
+
}
|
171
|
+
/* loop until test runs for more than 2 seconds */
|
172
|
+
for (uint64_t cycles = 8192;;) {
|
173
|
+
clock_t start, end;
|
174
|
+
start = clock();
|
175
|
+
for (size_t i = cycles; i > 0; i--) {
|
176
|
+
hash += fio_siphash13(buffer, sizeof(buffer));
|
177
|
+
__asm__ volatile("" ::: "memory");
|
178
|
+
}
|
179
|
+
end = clock();
|
180
|
+
memcpy(buffer, &hash, sizeof(hash));
|
181
|
+
if ((end - start) >= (2 * CLOCKS_PER_SEC) ||
|
182
|
+
cycles >= ((uint64_t)1 << 62)) {
|
183
|
+
fprintf(stderr, "%-20s %8.2f MB/s\n", "fio SipHash13",
|
184
|
+
(double)(sizeof(buffer) * cycles) /
|
185
|
+
(((end - start) * 1000000.0 / CLOCKS_PER_SEC)));
|
186
|
+
break;
|
187
|
+
}
|
188
|
+
cycles <<= 2;
|
189
|
+
}
|
190
|
+
}
|
191
|
+
|
192
|
+
#endif
|
193
|
+
|
194
|
+
void fiobj_siphash_test(void) {
|
195
|
+
fprintf(stderr, "===================================\n");
|
196
|
+
// fio_siphash_speed_test();
|
197
|
+
uint64_t result = 0;
|
198
|
+
clock_t start;
|
199
|
+
start = clock();
|
200
|
+
for (size_t i = 0; i < 100000; i++) {
|
201
|
+
char *data = "The quick brown fox jumps over the lazy dog ";
|
202
|
+
__asm__ volatile("" ::: "memory");
|
203
|
+
result += fio_siphash_xy(data, 43, 1, 3);
|
204
|
+
}
|
205
|
+
fprintf(stderr, "fio 100K SipHash: %lf\n",
|
206
|
+
(double)(clock() - start) / CLOCKS_PER_SEC);
|
207
|
+
}
|
208
|
+
#endif
|
data/ext/iodine/fio_siphash.h
CHANGED
@@ -8,11 +8,27 @@
|
|
8
8
|
#include <stdint.h>
|
9
9
|
#include <sys/types.h>
|
10
10
|
|
11
|
+
/**
|
12
|
+
* A SipHash variation (2-4).
|
13
|
+
*/
|
14
|
+
uint64_t fio_siphash24(const void *data, size_t len);
|
15
|
+
|
16
|
+
/**
|
17
|
+
* A SipHash 1-3 variation.
|
18
|
+
*/
|
19
|
+
uint64_t fio_siphash13(const void *data, size_t len);
|
20
|
+
|
11
21
|
/**
|
12
22
|
* The Hashing function used by dynamic facil.io objects.
|
13
23
|
*
|
14
|
-
* Currently implemented using SipHash.
|
24
|
+
* Currently implemented using SipHash 1-3.
|
15
25
|
*/
|
16
|
-
|
26
|
+
#define fio_siphash(data, length) fio_siphash13((data), (length))
|
17
27
|
|
28
|
+
#if defined(DEBUG) && DEBUG
|
29
|
+
void fiobj_siphash_test(void);
|
30
|
+
#else
|
31
|
+
#define fiobj_siphash_test()
|
18
32
|
#endif
|
33
|
+
|
34
|
+
#endif /* H_FIO_SIPHASH_H */
|
@@ -0,0 +1,1218 @@
|
|
1
|
+
#ifndef H_FIO_STRING_H
|
2
|
+
/*
|
3
|
+
Copyright: Boaz Segev, 2018
|
4
|
+
License: MIT
|
5
|
+
*/
|
6
|
+
|
7
|
+
/**
|
8
|
+
* A Dynamic String C library for ease of use and binary strings.
|
9
|
+
*
|
10
|
+
* This is different from the fio.h library in the sense that it does NOT
|
11
|
+
* include a built-in reference counter.
|
12
|
+
*
|
13
|
+
* The string is a simple byte string which is compatible with binary data (NUL
|
14
|
+
* is a valid byte).
|
15
|
+
*
|
16
|
+
* Example use:
|
17
|
+
*
|
18
|
+
* fio_str_s str = FIO_STR_INIT; // container on the stack.
|
19
|
+
* fio_str_write(&str, "hello", 5); // add / remove / read data...
|
20
|
+
* printf("String: %s", fio_str_data(&str)); // print data
|
21
|
+
* fio_str_free(&str) // free the data - NOT the container.
|
22
|
+
*
|
23
|
+
* Should work with both 32bit and 64bit architectures.
|
24
|
+
*/
|
25
|
+
#define H_FIO_STRING_H
|
26
|
+
|
27
|
+
#ifndef _GNU_SOURCE
|
28
|
+
#define _GNU_SOURCE
|
29
|
+
#endif
|
30
|
+
|
31
|
+
#if defined(__unix__) || defined(__APPLE__) || defined(__linux__)
|
32
|
+
#include <fcntl.h>
|
33
|
+
#include <sys/stat.h>
|
34
|
+
#include <unistd.h>
|
35
|
+
#endif
|
36
|
+
|
37
|
+
#include <errno.h>
|
38
|
+
#include <stdarg.h>
|
39
|
+
#include <stdint.h>
|
40
|
+
#include <stdio.h>
|
41
|
+
#include <stdlib.h>
|
42
|
+
#include <string.h>
|
43
|
+
#include <strings.h>
|
44
|
+
|
45
|
+
#ifndef FIO_FUNC
|
46
|
+
#define FIO_FUNC static __attribute__((unused))
|
47
|
+
#endif
|
48
|
+
|
49
|
+
#ifndef FIO_ASSERT_ALLOC
|
50
|
+
/** Tests for an allocation failure. The behavior can be overridden. */
|
51
|
+
#define FIO_ASSERT_ALLOC(ptr) \
|
52
|
+
if (!(ptr)) { \
|
53
|
+
perror("FATAL ERROR: no memory (for string allocation)"); \
|
54
|
+
exit(errno); \
|
55
|
+
}
|
56
|
+
#endif
|
57
|
+
|
58
|
+
/* *****************************************************************************
|
59
|
+
String API - Initialization and Destruction
|
60
|
+
***************************************************************************** */
|
61
|
+
|
62
|
+
/**
|
63
|
+
* The `fio_str_s` type should be considered opaque.
|
64
|
+
*
|
65
|
+
* The type's attributes should be accessed ONLY through the accessor functions:
|
66
|
+
* `fio_str_state`, `fio_str_len`, `fio_str_data`, `fio_str_capa`, etc'.
|
67
|
+
*
|
68
|
+
* Note: when the `small` flag is present, the structure is ignored and used as
|
69
|
+
* raw memory for a small String (no additional allocation). This changes the
|
70
|
+
* String's behavior drastically and requires that the accessor functions be
|
71
|
+
* used.
|
72
|
+
*/
|
73
|
+
typedef struct {
|
74
|
+
uint8_t small; /* Flag indicating the String is small and self-contained */
|
75
|
+
uint8_t frozen; /* Flag indicating the String is frozen (don't edit) */
|
76
|
+
uint8_t reserved[sizeof(size_t) - (sizeof(uint8_t) * 2)]; /* padding */
|
77
|
+
size_t capa; /* Known capacity for longer Strings */
|
78
|
+
size_t len; /* String length for longer Strings */
|
79
|
+
char *data; /* Data for longer Strings */
|
80
|
+
} fio_str_s;
|
81
|
+
|
82
|
+
/**
|
83
|
+
* This value should be used for initialization. For example:
|
84
|
+
*
|
85
|
+
* // on the stack
|
86
|
+
* fio_str_s str = FIO_STR_INIT;
|
87
|
+
*
|
88
|
+
* // or on the heap
|
89
|
+
* fio_str_s *str = malloc(sizeof(*str);
|
90
|
+
* *str = FIO_STR_INIT;
|
91
|
+
*
|
92
|
+
* Remember to cleanup:
|
93
|
+
*
|
94
|
+
* // on the stack
|
95
|
+
* fio_str_free(&str);
|
96
|
+
*
|
97
|
+
* // or on the heap
|
98
|
+
* fio_str_free(str);
|
99
|
+
* free(str);
|
100
|
+
*/
|
101
|
+
#define FIO_STR_INIT ((fio_str_s){.data = NULL, .small = 1})
|
102
|
+
|
103
|
+
/**
|
104
|
+
* This macro allows the container to be initialized with existing data, as long
|
105
|
+
* as it's memory was allocated using `malloc`.
|
106
|
+
*
|
107
|
+
* The `capacity` value should exclude the NUL character (if exists).
|
108
|
+
*/
|
109
|
+
#define FIO_STR_INIT_EXISTING(buffer, length, capacity) \
|
110
|
+
((fio_str_s){.data = (buffer), .len = (length), .capa = (capacity)})
|
111
|
+
|
112
|
+
/**
|
113
|
+
* Frees the String's resources and _reinitializes the container_.
|
114
|
+
*
|
115
|
+
* Note: if the container isn't allocated on the stack, it should be freed
|
116
|
+
* separately using `free(s)`.
|
117
|
+
*/
|
118
|
+
inline FIO_FUNC void fio_str_free(fio_str_s *s);
|
119
|
+
|
120
|
+
/* *****************************************************************************
|
121
|
+
String API - String state (data pointers, length, capacity, etc')
|
122
|
+
***************************************************************************** */
|
123
|
+
|
124
|
+
#ifndef FIO_STR_INFO_TYPE
|
125
|
+
/** A string information type, reports information about a C string. */
|
126
|
+
typedef struct fio_str_info_s {
|
127
|
+
size_t capa; /* Buffer capacity, if the string is writable. */
|
128
|
+
size_t len; /* String length. */
|
129
|
+
char *data; /* String's first byte. */
|
130
|
+
} fio_str_info_s;
|
131
|
+
#define FIO_STR_INFO_TYPE
|
132
|
+
#endif
|
133
|
+
|
134
|
+
/** Returns the String's complete state (capacity, length and pointer). */
|
135
|
+
inline FIO_FUNC fio_str_info_s fio_str_state(const fio_str_s *s);
|
136
|
+
|
137
|
+
/** Returns the String's length in bytes. */
|
138
|
+
inline FIO_FUNC size_t fio_str_len(fio_str_s *s);
|
139
|
+
|
140
|
+
/** Returns a pointer (`char *`) to the String's content. */
|
141
|
+
inline FIO_FUNC char *fio_str_data(fio_str_s *s);
|
142
|
+
|
143
|
+
/** Returns a byte pointer (`uint8_t *`) to the String's unsigned content. */
|
144
|
+
#define fio_str_bytes(s) ((uint8_t *)fio_str_data((s)))
|
145
|
+
|
146
|
+
/** Returns the String's existing capacity (total used & available memory). */
|
147
|
+
inline FIO_FUNC size_t fio_str_capa(fio_str_s *s);
|
148
|
+
|
149
|
+
/**
|
150
|
+
* Sets the new String size without reallocating any memory (limited by
|
151
|
+
* existing capacity).
|
152
|
+
*
|
153
|
+
* Returns the updated state of the String.
|
154
|
+
*
|
155
|
+
* Note: When shrinking, any existing data beyond the new size may be corrupted.
|
156
|
+
*/
|
157
|
+
inline FIO_FUNC fio_str_info_s fio_str_resize(fio_str_s *s, size_t size);
|
158
|
+
|
159
|
+
/**
|
160
|
+
* Clears the string (retaining the existing capacity).
|
161
|
+
*/
|
162
|
+
#define fio_str_clear(s) fio_str_resize((s), 0)
|
163
|
+
|
164
|
+
/* *****************************************************************************
|
165
|
+
String API - Memory management
|
166
|
+
***************************************************************************** */
|
167
|
+
|
168
|
+
/**
|
169
|
+
* Performs a best attempt at minimizing memory consumption.
|
170
|
+
*
|
171
|
+
* Actual effects depend on the underlying memory allocator and it's
|
172
|
+
* implementation. Not all allocators will free any memory.
|
173
|
+
*/
|
174
|
+
inline FIO_FUNC void fio_str_compact(fio_str_s *s);
|
175
|
+
|
176
|
+
/**
|
177
|
+
* Requires the String to have at least `needed` capacity. Returns the current
|
178
|
+
* state of the String.
|
179
|
+
*/
|
180
|
+
FIO_FUNC fio_str_info_s fio_str_capa_assert(fio_str_s *s, size_t needed);
|
181
|
+
|
182
|
+
/* *****************************************************************************
|
183
|
+
String API - UTF-8 State
|
184
|
+
***************************************************************************** */
|
185
|
+
|
186
|
+
/** Returns 1 if the String is UTF-8 valid and 0 if not. */
|
187
|
+
inline FIO_FUNC size_t fio_str_utf8_valid(fio_str_s *s);
|
188
|
+
|
189
|
+
/** Returns the String's length in UTF-8 characters. */
|
190
|
+
FIO_FUNC size_t fio_str_utf8_len(fio_str_s *s);
|
191
|
+
|
192
|
+
/**
|
193
|
+
* Takes a UTF-8 character selection information (UTF-8 position and length) and
|
194
|
+
* updates the same variables so they reference the raw byte slice information.
|
195
|
+
*
|
196
|
+
* If the String isn't UTF-8 valid up to the requested selection, than `pos`
|
197
|
+
* will be updated to `-1` otherwise values are always positive.
|
198
|
+
*
|
199
|
+
* The returned `len` value may be shorter than the original if there wasn't
|
200
|
+
* enough data left to accomodate the requested length. When a `len` value of
|
201
|
+
* `0` is returned, this means that `pos` marks the end of the String.
|
202
|
+
*
|
203
|
+
* Returns -1 on error and 0 on success.
|
204
|
+
*/
|
205
|
+
FIO_FUNC int fio_str_utf8_select(fio_str_s *s, intptr_t *pos, size_t *len);
|
206
|
+
|
207
|
+
/**
|
208
|
+
* Advances the `ptr` by one utf-8 character, placing the value of the UTF-8
|
209
|
+
* character into the i32 variable (which must be a signed integer with 32bits
|
210
|
+
* or more). On error, `i32` will be equal to `-1` and `ptr` will not step
|
211
|
+
* forwards.
|
212
|
+
*
|
213
|
+
* The `end` value is only used for overflow protection.
|
214
|
+
*
|
215
|
+
* This helper macro is used internally but left exposed for external use.
|
216
|
+
*/
|
217
|
+
#define FIO_STR_UTF8_CODE_POINT(ptr, end, i32)
|
218
|
+
|
219
|
+
/* *****************************************************************************
|
220
|
+
String API - Content Manipulation and Review
|
221
|
+
***************************************************************************** */
|
222
|
+
|
223
|
+
/**
|
224
|
+
* Writes data at the end of the String (similar to `fio_str_insert` with the
|
225
|
+
* argument `pos == -1`).
|
226
|
+
*/
|
227
|
+
inline FIO_FUNC fio_str_info_s fio_str_write(fio_str_s *s, const void *src,
|
228
|
+
size_t src_len);
|
229
|
+
|
230
|
+
/**
|
231
|
+
* Writes a number at the end of the String using normal base 10 notation.
|
232
|
+
*/
|
233
|
+
inline FIO_FUNC fio_str_info_s fio_str_write_i(fio_str_s *s, int64_t num);
|
234
|
+
|
235
|
+
/**
|
236
|
+
* Appens the `src` String to the end of the `dest` String.
|
237
|
+
*
|
238
|
+
* If `src` is empty, the resulting Strings will be equal.
|
239
|
+
*/
|
240
|
+
inline FIO_FUNC fio_str_info_s fio_str_concat(fio_str_s *dest,
|
241
|
+
fio_str_s const *src);
|
242
|
+
/** Alias for fio_str_concat */
|
243
|
+
#define fio_str_join(dest, src) fio_str_concat((dest), (src))
|
244
|
+
|
245
|
+
/**
|
246
|
+
* Replaces the data in the String - replacing `old_len` bytes starting at
|
247
|
+
* `start_pos`, with the data at `src` (`src_len` bytes long).
|
248
|
+
*
|
249
|
+
* Negative `start_pos` values are calculated backwards, `-1` == end of String.
|
250
|
+
*
|
251
|
+
* When `old_len` is zero, the function will insert the data at `start_pos`.
|
252
|
+
*
|
253
|
+
* If `src_len == 0` than `src` will be ignored and the data marked for
|
254
|
+
* replacement will be erased.
|
255
|
+
*/
|
256
|
+
inline FIO_FUNC fio_str_info_s fio_str_replace(fio_str_s *s, intptr_t start_pos,
|
257
|
+
size_t old_len, const void *src,
|
258
|
+
size_t src_len);
|
259
|
+
|
260
|
+
/**
|
261
|
+
* Writes to the String using a vprintf like interface.
|
262
|
+
*
|
263
|
+
* Data is written to the end of the String.
|
264
|
+
*/
|
265
|
+
FIO_FUNC fio_str_info_s fio_str_vprintf(fio_str_s *s, const char *format,
|
266
|
+
va_list argv);
|
267
|
+
|
268
|
+
/**
|
269
|
+
* Writes to the String using a printf like interface.
|
270
|
+
*
|
271
|
+
* Data is written to the end of the String.
|
272
|
+
*/
|
273
|
+
FIO_FUNC fio_str_info_s fio_str_printf(fio_str_s *s, const char *format, ...);
|
274
|
+
|
275
|
+
/**
|
276
|
+
* Opens the file `filename` and pastes it's contents (or a slice ot it) at the
|
277
|
+
* end of the String. If `limit == 0`, than the data will be read until EOF.
|
278
|
+
*
|
279
|
+
* If the file can't be located, opened or read, or if `start_at` is beyond
|
280
|
+
* the EOF position, NULL is returned in the state's `data` field.
|
281
|
+
*
|
282
|
+
* Works on POSIX only.
|
283
|
+
*/
|
284
|
+
inline FIO_FUNC fio_str_info_s fio_str_readfile(fio_str_s *s,
|
285
|
+
const char *filename,
|
286
|
+
intptr_t start_at,
|
287
|
+
intptr_t limit);
|
288
|
+
|
289
|
+
/**
|
290
|
+
* Prevents further manipulations to the String's content.
|
291
|
+
*/
|
292
|
+
inline FIO_FUNC void fio_str_freeze(fio_str_s *s);
|
293
|
+
|
294
|
+
/**
|
295
|
+
* Binary comparison returns `1` if both strings are equal and `0` if not.
|
296
|
+
*/
|
297
|
+
inline FIO_FUNC int fio_str_iseq(const fio_str_s *str1, const fio_str_s *str2);
|
298
|
+
|
299
|
+
/* *****************************************************************************
|
300
|
+
|
301
|
+
|
302
|
+
IMPLEMENTATION
|
303
|
+
|
304
|
+
|
305
|
+
***************************************************************************** */
|
306
|
+
|
307
|
+
/* *****************************************************************************
|
308
|
+
Implementation - String state (data pointers, length, capacity, etc')
|
309
|
+
***************************************************************************** */
|
310
|
+
|
311
|
+
/* the capacity when the string is stored in the container itself */
|
312
|
+
#define FIO_STR_SMALL_CAPA \
|
313
|
+
(sizeof(fio_str_s) - (size_t)(&((fio_str_s *)0)->reserved))
|
314
|
+
|
315
|
+
typedef struct {
|
316
|
+
uint8_t small;
|
317
|
+
uint8_t frozen;
|
318
|
+
char data[1];
|
319
|
+
} fio_str__small_s;
|
320
|
+
|
321
|
+
/** Returns the String's state (capacity, length and pointer). */
|
322
|
+
inline FIO_FUNC fio_str_info_s fio_str_state(const fio_str_s *s) {
|
323
|
+
if (!s)
|
324
|
+
return (fio_str_info_s){.capa = 0};
|
325
|
+
return (s->small || !s->data)
|
326
|
+
? (fio_str_info_s){.capa =
|
327
|
+
(s->frozen ? 0 : (FIO_STR_SMALL_CAPA - 1)),
|
328
|
+
.len = (size_t)(s->small >> 1),
|
329
|
+
.data = ((fio_str__small_s *)s)->data}
|
330
|
+
: (fio_str_info_s){.capa = (s->frozen ? 0 : s->capa),
|
331
|
+
.len = s->len,
|
332
|
+
.data = s->data};
|
333
|
+
}
|
334
|
+
|
335
|
+
/**
|
336
|
+
* Frees the String's resources and reinitializes the container.
|
337
|
+
*
|
338
|
+
* Note: if the container isn't allocated on the stack, it should be freed
|
339
|
+
* separately using `free(s)`.
|
340
|
+
*/
|
341
|
+
inline FIO_FUNC void fio_str_free(fio_str_s *s) {
|
342
|
+
if (!s->small)
|
343
|
+
free(s->data);
|
344
|
+
*s = FIO_STR_INIT;
|
345
|
+
}
|
346
|
+
|
347
|
+
/** Returns the String's length in bytes. */
|
348
|
+
inline FIO_FUNC size_t fio_str_len(fio_str_s *s) {
|
349
|
+
return (s->small || !s->data) ? (s->small >> 1) : s->len;
|
350
|
+
}
|
351
|
+
|
352
|
+
/** Returns a pointer (`char *`) to the String's content. */
|
353
|
+
inline FIO_FUNC char *fio_str_data(fio_str_s *s) {
|
354
|
+
return (s->small || !s->data) ? (((fio_str__small_s *)s)->data) : s->data;
|
355
|
+
}
|
356
|
+
|
357
|
+
/** Returns the String's existing capacity (allocated memory). */
|
358
|
+
inline FIO_FUNC size_t fio_str_capa(fio_str_s *s) {
|
359
|
+
return (s->small || !s->data) ? (FIO_STR_SMALL_CAPA - 1) : s->capa;
|
360
|
+
}
|
361
|
+
|
362
|
+
/**
|
363
|
+
* Sets the new String size without reallocating any memory (limited by
|
364
|
+
* existing capacity).
|
365
|
+
*
|
366
|
+
* Returns the updated state of the String.
|
367
|
+
*
|
368
|
+
* Note: When shrinking, any existing data beyond the new size may be corrupted.
|
369
|
+
*/
|
370
|
+
inline FIO_FUNC fio_str_info_s fio_str_resize(fio_str_s *s, size_t size) {
|
371
|
+
if (!s || s->frozen) {
|
372
|
+
return fio_str_state(s);
|
373
|
+
}
|
374
|
+
fio_str_capa_assert(s, size);
|
375
|
+
if (s->small || !s->data) {
|
376
|
+
s->small = (uint8_t)(((size << 1) | 1) & 0xFF);
|
377
|
+
((fio_str__small_s *)s)->data[size] = 0;
|
378
|
+
return (fio_str_info_s){.capa = (FIO_STR_SMALL_CAPA - 1),
|
379
|
+
.len = size,
|
380
|
+
.data = ((fio_str__small_s *)s)->data};
|
381
|
+
}
|
382
|
+
s->len = size;
|
383
|
+
s->data[size] = 0;
|
384
|
+
return (fio_str_info_s){.capa = s->capa, .len = size, .data = s->data};
|
385
|
+
}
|
386
|
+
|
387
|
+
/* *****************************************************************************
|
388
|
+
Implementation - Memory management
|
389
|
+
***************************************************************************** */
|
390
|
+
|
391
|
+
/**
|
392
|
+
* Rounds up allocated capacity to the closest 2 words byte boundary (leaving 1
|
393
|
+
* byte space for the NUL byte).
|
394
|
+
*
|
395
|
+
* This shouldn't effect actual allocation size and should only minimize the
|
396
|
+
* effects of the memory allocator's alignment rounding scheme.
|
397
|
+
*
|
398
|
+
* To clarify:
|
399
|
+
*
|
400
|
+
* Memory allocators are required to allocate memory on the minimal alignment
|
401
|
+
* required by the largest type (`long double`), which usually results in memory
|
402
|
+
* allocations using this alignment as a minimal spacing.
|
403
|
+
*
|
404
|
+
* For example, on 64 bit architectures, it's likely that `malloc(18)` will
|
405
|
+
* allocate the same amount of memory as `malloc(32)` due to alignment.
|
406
|
+
*
|
407
|
+
* In fact, on some allocators (i.e., jemalloc), spacing increases for larger
|
408
|
+
* allocations - meaning the allocator will round up to more than 16 bytes, as
|
409
|
+
* noted here: http://jemalloc.net/jemalloc.3.html#size_classes
|
410
|
+
*
|
411
|
+
* Note that this increased spacing, doesn't occure with facil.io's `fio_mem.h`
|
412
|
+
* allocator, since it uses 16 byte alignment right up until allocations are
|
413
|
+
* routed directly to `mmap` (due to their size, usually over 12KB).
|
414
|
+
*/
|
415
|
+
#define ROUND_UP_CAPA_2WORDS(num) \
|
416
|
+
(((num + 1) & (sizeof(long double) - 1)) \
|
417
|
+
? ((num + 1) | (sizeof(long double) - 1)) \
|
418
|
+
: (num))
|
419
|
+
/**
|
420
|
+
* Requires the String to have at least `needed` capacity. Returns the current
|
421
|
+
* state of the String.
|
422
|
+
*/
|
423
|
+
FIO_FUNC fio_str_info_s fio_str_capa_assert(fio_str_s *s, size_t needed) {
|
424
|
+
if (!s)
|
425
|
+
return (fio_str_info_s){.capa = 0};
|
426
|
+
char *tmp;
|
427
|
+
if (s->small || !s->data) {
|
428
|
+
goto is_small;
|
429
|
+
}
|
430
|
+
if (needed > s->capa) {
|
431
|
+
needed = ROUND_UP_CAPA_2WORDS(needed);
|
432
|
+
tmp = (char *)realloc(s->data, needed + 1);
|
433
|
+
FIO_ASSERT_ALLOC(tmp);
|
434
|
+
s->capa = needed;
|
435
|
+
s->data = tmp;
|
436
|
+
s->data[needed] = 0;
|
437
|
+
}
|
438
|
+
return (fio_str_info_s){
|
439
|
+
.capa = (s->frozen ? 0 : s->capa), .len = s->len, .data = s->data};
|
440
|
+
|
441
|
+
is_small:
|
442
|
+
/* small string (string data is within the container) */
|
443
|
+
if (needed < FIO_STR_SMALL_CAPA) {
|
444
|
+
return (fio_str_info_s){.capa = (s->frozen ? 0 : (FIO_STR_SMALL_CAPA - 1)),
|
445
|
+
.len = (size_t)(s->small >> 1),
|
446
|
+
.data = ((fio_str__small_s *)s)->data};
|
447
|
+
}
|
448
|
+
needed = ROUND_UP_CAPA_2WORDS(needed);
|
449
|
+
tmp = (char *)malloc(needed + 1);
|
450
|
+
FIO_ASSERT_ALLOC(tmp);
|
451
|
+
const size_t existing_len = (size_t)((s->small >> 1) & 0xFF);
|
452
|
+
if (existing_len) {
|
453
|
+
memcpy(tmp, ((fio_str__small_s *)s)->data, existing_len + 1);
|
454
|
+
} else {
|
455
|
+
tmp[0] = 0;
|
456
|
+
}
|
457
|
+
*s = (fio_str_s){
|
458
|
+
.small = 0,
|
459
|
+
.capa = needed,
|
460
|
+
.len = existing_len,
|
461
|
+
.data = tmp,
|
462
|
+
};
|
463
|
+
return (fio_str_info_s){
|
464
|
+
.capa = (s->frozen ? 0 : needed), .len = existing_len, .data = s->data};
|
465
|
+
}
|
466
|
+
|
467
|
+
/** Performs a best attempt at minimizing memory consumption. */
|
468
|
+
inline FIO_FUNC void fio_str_compact(fio_str_s *s) {
|
469
|
+
if (!s || (s->small || !s->data))
|
470
|
+
return;
|
471
|
+
char *tmp;
|
472
|
+
if (s->len < FIO_STR_SMALL_CAPA)
|
473
|
+
goto shrink2small;
|
474
|
+
tmp = realloc(s->data, s->len + 1);
|
475
|
+
FIO_ASSERT_ALLOC(tmp);
|
476
|
+
s->data = tmp;
|
477
|
+
s->capa = s->len;
|
478
|
+
return;
|
479
|
+
|
480
|
+
shrink2small:
|
481
|
+
/* move the string into the container */
|
482
|
+
tmp = s->data;
|
483
|
+
size_t len = s->len;
|
484
|
+
*s = (fio_str_s){.small = (uint8_t)(((len << 1) | 1) & 0xFF),
|
485
|
+
.frozen = s->frozen};
|
486
|
+
if (len) {
|
487
|
+
memcpy(((fio_str__small_s *)s)->data, tmp, len + 1);
|
488
|
+
}
|
489
|
+
free(tmp);
|
490
|
+
}
|
491
|
+
|
492
|
+
/* *****************************************************************************
|
493
|
+
Implementation - UTF-8 State
|
494
|
+
***************************************************************************** */
|
495
|
+
|
496
|
+
/**
|
497
|
+
* Maps the last 5 bits in a byte (0b11111xxx) to a UTF-8 codepoint length.
|
498
|
+
*
|
499
|
+
* Codepoint length 0 == error.
|
500
|
+
*
|
501
|
+
* The first valid length can be any value between 1 to 4.
|
502
|
+
*
|
503
|
+
* An intermidiate (second, third or forth) valid length must be 5.
|
504
|
+
*
|
505
|
+
* To map was populated using the following Ruby script:
|
506
|
+
*
|
507
|
+
* map = []; 32.times { map << 0 }; (0..0b1111).each {|i| map[i] = 1} ;
|
508
|
+
* (0b10000..0b10111).each {|i| map[i] = 5} ;
|
509
|
+
* (0b11000..0b11011).each {|i| map[i] = 2} ;
|
510
|
+
* (0b11100..0b11101).each {|i| map[i] = 3} ;
|
511
|
+
* map[0b11110] = 4; map;
|
512
|
+
*/
|
513
|
+
static uint8_t fio_str_utf8_map[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
514
|
+
1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5,
|
515
|
+
5, 5, 2, 2, 2, 2, 3, 3, 4, 0};
|
516
|
+
|
517
|
+
#undef FIO_STR_UTF8_CODE_POINT
|
518
|
+
/**
|
519
|
+
* Advances the `ptr` by one utf-8 character, placing the value of the UTF-8
|
520
|
+
* character into the i32 variable (which must be a signed integer with 32bits
|
521
|
+
* or more). On error, `i32` will be equal to `-1` and `ptr` will not step
|
522
|
+
* forwards.
|
523
|
+
*
|
524
|
+
* The `end` value is only used for overflow protection.
|
525
|
+
*/
|
526
|
+
#define FIO_STR_UTF8_CODE_POINT(ptr, end, i32) \
|
527
|
+
do { \
|
528
|
+
switch (fio_str_utf8_map[((uint8_t *)(ptr))[0] >> 3]) { \
|
529
|
+
case 1: \
|
530
|
+
(i32) = ((uint8_t *)(ptr))[0]; \
|
531
|
+
++(ptr); \
|
532
|
+
break; \
|
533
|
+
case 2: \
|
534
|
+
if (((ptr) + 2 > (end)) || \
|
535
|
+
fio_str_utf8_map[((uint8_t *)(ptr))[1] >> 3] != 5) { \
|
536
|
+
(i32) = -1; \
|
537
|
+
break; \
|
538
|
+
} \
|
539
|
+
(i32) = \
|
540
|
+
((((uint8_t *)(ptr))[0] & 31) << 6) | (((uint8_t *)(ptr))[1] & 63); \
|
541
|
+
(ptr) += 2; \
|
542
|
+
break; \
|
543
|
+
case 3: \
|
544
|
+
if (((ptr) + 3 > (end)) || \
|
545
|
+
fio_str_utf8_map[((uint8_t *)(ptr))[1] >> 3] != 5 || \
|
546
|
+
fio_str_utf8_map[((uint8_t *)(ptr))[2] >> 3] != 5) { \
|
547
|
+
(i32) = -1; \
|
548
|
+
break; \
|
549
|
+
} \
|
550
|
+
(i32) = ((((uint8_t *)(ptr))[0] & 15) << 12) | \
|
551
|
+
((((uint8_t *)(ptr))[1] & 63) << 6) | \
|
552
|
+
(((uint8_t *)(ptr))[2] & 63); \
|
553
|
+
(ptr) += 3; \
|
554
|
+
break; \
|
555
|
+
case 4: \
|
556
|
+
if (((ptr) + 4 > (end)) || \
|
557
|
+
fio_str_utf8_map[((uint8_t *)(ptr))[1] >> 3] != 5 || \
|
558
|
+
fio_str_utf8_map[((uint8_t *)(ptr))[2] >> 3] != 5 || \
|
559
|
+
fio_str_utf8_map[((uint8_t *)(ptr))[3] >> 3] != 5) { \
|
560
|
+
(i32) = -1; \
|
561
|
+
break; \
|
562
|
+
} \
|
563
|
+
(i32) = ((((uint8_t *)(ptr))[0] & 7) << 18) | \
|
564
|
+
((((uint8_t *)(ptr))[1] & 63) << 12) | \
|
565
|
+
((((uint8_t *)(ptr))[2] & 63) << 6) | \
|
566
|
+
(((uint8_t *)(ptr))[3] & 63); \
|
567
|
+
(ptr) += 4; \
|
568
|
+
break; \
|
569
|
+
default: \
|
570
|
+
(i32) = -1; \
|
571
|
+
break; \
|
572
|
+
} \
|
573
|
+
} while (0);
|
574
|
+
|
575
|
+
/** Returns 1 if the String is UTF-8 valid and 0 if not. */
|
576
|
+
inline FIO_FUNC size_t fio_str_utf8_valid(fio_str_s *s) {
|
577
|
+
if (!s)
|
578
|
+
return 0;
|
579
|
+
fio_str_info_s state = fio_str_state(s);
|
580
|
+
if (!state.len)
|
581
|
+
return 1;
|
582
|
+
char *const end = state.data + state.len;
|
583
|
+
int32_t c = 0;
|
584
|
+
do {
|
585
|
+
FIO_STR_UTF8_CODE_POINT(state.data, end, c);
|
586
|
+
} while (c > 0 && state.data < end);
|
587
|
+
return state.data == end && c >= 0;
|
588
|
+
}
|
589
|
+
|
590
|
+
/** Returns the String's length in UTF-8 characters. */
|
591
|
+
FIO_FUNC size_t fio_str_utf8_len(fio_str_s *s) {
|
592
|
+
fio_str_info_s state = fio_str_state(s);
|
593
|
+
if (!state.len)
|
594
|
+
return 0;
|
595
|
+
char *end = state.data + state.len;
|
596
|
+
size_t utf8len = 0;
|
597
|
+
int32_t c = 0;
|
598
|
+
do {
|
599
|
+
++utf8len;
|
600
|
+
FIO_STR_UTF8_CODE_POINT(state.data, end, c);
|
601
|
+
} while (c > 0 && state.data < end);
|
602
|
+
if (state.data != end || c == -1) {
|
603
|
+
/* invalid */
|
604
|
+
return 0;
|
605
|
+
}
|
606
|
+
return utf8len;
|
607
|
+
}
|
608
|
+
|
609
|
+
/**
|
610
|
+
* Takes a UTF-8 character selection information (UTF-8 position and length) and
|
611
|
+
* updates the same variables so they reference the raw byte slice information.
|
612
|
+
*
|
613
|
+
* If the String isn't UTF-8 valid up to the requested selection, than `pos`
|
614
|
+
* will be updated to `-1` otherwise values are always positive.
|
615
|
+
*
|
616
|
+
* The returned `len` value may be shorter than the original if there wasn't
|
617
|
+
* enough data left to accomodate the requested length. When a `len` value of
|
618
|
+
* `0` is returned, this means that `pos` marks the end of the String.
|
619
|
+
*
|
620
|
+
* Returns -1 on error and 0 on success.
|
621
|
+
*/
|
622
|
+
FIO_FUNC int fio_str_utf8_select(fio_str_s *s, intptr_t *pos, size_t *len) {
|
623
|
+
fio_str_info_s state = fio_str_state(s);
|
624
|
+
if (!state.data)
|
625
|
+
goto error;
|
626
|
+
if (!state.len || *pos == -1)
|
627
|
+
goto at_end;
|
628
|
+
|
629
|
+
int32_t c = 0;
|
630
|
+
char *p = state.data;
|
631
|
+
char *const end = state.data + state.len;
|
632
|
+
size_t start;
|
633
|
+
|
634
|
+
if (*pos) {
|
635
|
+
if ((*pos) > 0) {
|
636
|
+
start = *pos;
|
637
|
+
while (start && p < end && c >= 0) {
|
638
|
+
FIO_STR_UTF8_CODE_POINT(p, end, c);
|
639
|
+
--start;
|
640
|
+
}
|
641
|
+
if (c == -1)
|
642
|
+
goto error;
|
643
|
+
if (start || p >= end)
|
644
|
+
goto at_end;
|
645
|
+
*pos = p - state.data;
|
646
|
+
} else {
|
647
|
+
/* walk backwards */
|
648
|
+
p = state.data + state.len - 1;
|
649
|
+
c = 0;
|
650
|
+
++*pos;
|
651
|
+
do {
|
652
|
+
switch (fio_str_utf8_map[((uint8_t *)p)[0] >> 3]) {
|
653
|
+
case 5:
|
654
|
+
++c;
|
655
|
+
break;
|
656
|
+
case 4:
|
657
|
+
if (c != 3)
|
658
|
+
goto error;
|
659
|
+
c = 0;
|
660
|
+
++(*pos);
|
661
|
+
break;
|
662
|
+
case 3:
|
663
|
+
if (c != 2)
|
664
|
+
goto error;
|
665
|
+
c = 0;
|
666
|
+
++(*pos);
|
667
|
+
break;
|
668
|
+
case 2:
|
669
|
+
if (c != 1)
|
670
|
+
goto error;
|
671
|
+
c = 0;
|
672
|
+
++(*pos);
|
673
|
+
break;
|
674
|
+
case 1:
|
675
|
+
if (c)
|
676
|
+
goto error;
|
677
|
+
++(*pos);
|
678
|
+
break;
|
679
|
+
default:
|
680
|
+
goto error;
|
681
|
+
}
|
682
|
+
--p;
|
683
|
+
} while (p > state.data && *pos);
|
684
|
+
if (c)
|
685
|
+
goto error;
|
686
|
+
++p; /* There's always an extra back-step */
|
687
|
+
*pos = (p - state.data);
|
688
|
+
}
|
689
|
+
}
|
690
|
+
|
691
|
+
/* find end */
|
692
|
+
start = *len;
|
693
|
+
while (start && p < end && c >= 0) {
|
694
|
+
FIO_STR_UTF8_CODE_POINT(p, end, c);
|
695
|
+
--start;
|
696
|
+
}
|
697
|
+
if (c == -1 || p > end)
|
698
|
+
goto error;
|
699
|
+
*len = p - (state.data + (*pos));
|
700
|
+
return 0;
|
701
|
+
|
702
|
+
at_end:
|
703
|
+
*pos = state.len;
|
704
|
+
*len = 0;
|
705
|
+
return 0;
|
706
|
+
error:
|
707
|
+
*pos = -1;
|
708
|
+
*len = 0;
|
709
|
+
return -1;
|
710
|
+
}
|
711
|
+
|
712
|
+
/* *****************************************************************************
|
713
|
+
Implementation - Content Manipulation and Review
|
714
|
+
***************************************************************************** */
|
715
|
+
|
716
|
+
/**
|
717
|
+
* Writes data at the end of the String (similar to `fio_str_insert` with the
|
718
|
+
* argument `pos == -1`).
|
719
|
+
*/
|
720
|
+
inline FIO_FUNC fio_str_info_s fio_str_write(fio_str_s *s, const void *src,
|
721
|
+
size_t src_len) {
|
722
|
+
if (!s || !src_len || !src || s->frozen)
|
723
|
+
return fio_str_state(s);
|
724
|
+
fio_str_info_s state = fio_str_resize(s, src_len + fio_str_len(s));
|
725
|
+
memcpy(state.data + (state.len - src_len), src, src_len);
|
726
|
+
return state;
|
727
|
+
}
|
728
|
+
|
729
|
+
/**
|
730
|
+
* Writes a number at the end of the String using normal base 10 notation.
|
731
|
+
*/
|
732
|
+
inline FIO_FUNC fio_str_info_s fio_str_write_i(fio_str_s *s, int64_t num) {
|
733
|
+
if (!s || s->frozen)
|
734
|
+
return fio_str_state(s);
|
735
|
+
char buf[22];
|
736
|
+
fio_str_info_s i;
|
737
|
+
if (!num)
|
738
|
+
goto zero;
|
739
|
+
uint64_t l = 0;
|
740
|
+
uint8_t neg;
|
741
|
+
if ((neg = (num < 0))) {
|
742
|
+
num = 0 - num;
|
743
|
+
neg = 1;
|
744
|
+
}
|
745
|
+
while (num) {
|
746
|
+
uint64_t t = num / 10;
|
747
|
+
buf[l++] = '0' + (num - (t * 10));
|
748
|
+
num = t;
|
749
|
+
}
|
750
|
+
if (neg) {
|
751
|
+
buf[l++] = '-';
|
752
|
+
}
|
753
|
+
i = fio_str_resize(s, fio_str_len(s) + l);
|
754
|
+
|
755
|
+
while (l) {
|
756
|
+
--l;
|
757
|
+
i.data[i.len - (l + 1)] = buf[l];
|
758
|
+
}
|
759
|
+
return i;
|
760
|
+
|
761
|
+
zero:
|
762
|
+
i = fio_str_resize(s, fio_str_len(s) + 1);
|
763
|
+
i.data[i.len - 1] = '0';
|
764
|
+
return i;
|
765
|
+
}
|
766
|
+
/**
|
767
|
+
* Appens the `src` String to the end of the `dest` String.
|
768
|
+
*/
|
769
|
+
inline FIO_FUNC fio_str_info_s fio_str_concat(fio_str_s *dest,
|
770
|
+
fio_str_s const *src) {
|
771
|
+
if (!dest || !src || dest->frozen)
|
772
|
+
return fio_str_state(dest);
|
773
|
+
fio_str_info_s src_state = fio_str_state(src);
|
774
|
+
if (!src_state.len)
|
775
|
+
return fio_str_state(dest);
|
776
|
+
fio_str_info_s state =
|
777
|
+
fio_str_resize(dest, src_state.len + fio_str_len(dest));
|
778
|
+
memcpy(state.data + state.len - src_state.len, src_state.data, src_state.len);
|
779
|
+
return state;
|
780
|
+
}
|
781
|
+
|
782
|
+
/**
|
783
|
+
* Replaces the data in the String - replacing `old_len` bytes starting at
|
784
|
+
* `start_pos`, with the data at `src` (`src_len` bytes long).
|
785
|
+
*
|
786
|
+
* Negative `start_pos` values are calculated backwards, `-1` == end of String.
|
787
|
+
*
|
788
|
+
* When `old_len` is zero, the function will insert the data at `start_pos`.
|
789
|
+
*
|
790
|
+
* If `src_len == 0` than `src` will be ignored and the data marked for
|
791
|
+
* replacement will be erased.
|
792
|
+
*/
|
793
|
+
inline FIO_FUNC fio_str_info_s fio_str_replace(fio_str_s *s, intptr_t start_pos,
|
794
|
+
size_t old_len, const void *src,
|
795
|
+
size_t src_len) {
|
796
|
+
fio_str_info_s state = fio_str_state(s);
|
797
|
+
if (!s || s->frozen || (!old_len && !src_len))
|
798
|
+
return state;
|
799
|
+
|
800
|
+
if (start_pos < 0) {
|
801
|
+
/* backwards position indexing */
|
802
|
+
start_pos += s->len + 1;
|
803
|
+
if (start_pos < 0)
|
804
|
+
start_pos = 0;
|
805
|
+
}
|
806
|
+
|
807
|
+
if (start_pos + old_len >= state.len) {
|
808
|
+
/* old_len overflows the end of the String */
|
809
|
+
if (s->small || !s->data) {
|
810
|
+
s->small = 1 | ((size_t)((start_pos << 1) & 0xFF));
|
811
|
+
} else {
|
812
|
+
s->len = start_pos;
|
813
|
+
}
|
814
|
+
return fio_str_write(s, src, src_len);
|
815
|
+
}
|
816
|
+
|
817
|
+
/* data replacement is now always in the middle (or start) of the String */
|
818
|
+
const size_t new_size = state.len + (src_len - old_len);
|
819
|
+
|
820
|
+
if (old_len != src_len) {
|
821
|
+
/* there's an offset requiring an adjustment */
|
822
|
+
if (old_len < src_len) {
|
823
|
+
/* make room for new data */
|
824
|
+
const size_t offset = src_len - old_len;
|
825
|
+
state = fio_str_resize(s, state.len + offset);
|
826
|
+
}
|
827
|
+
memmove(state.data + start_pos + src_len, state.data + start_pos + old_len,
|
828
|
+
(state.len - start_pos) - old_len);
|
829
|
+
}
|
830
|
+
if (src_len) {
|
831
|
+
memcpy(state.data + start_pos, src, src_len);
|
832
|
+
}
|
833
|
+
|
834
|
+
return fio_str_resize(s, new_size);
|
835
|
+
}
|
836
|
+
|
837
|
+
/** Writes to the String using a vprintf like interface. */
|
838
|
+
FIO_FUNC __attribute__((format(printf, 2, 0))) fio_str_info_s
|
839
|
+
fio_str_vprintf(fio_str_s *s, const char *format, va_list argv) {
|
840
|
+
va_list argv_cpy;
|
841
|
+
va_copy(argv_cpy, argv);
|
842
|
+
int len = vsnprintf(NULL, 0, format, argv_cpy);
|
843
|
+
va_end(argv_cpy);
|
844
|
+
if (len <= 0)
|
845
|
+
return fio_str_state(s);
|
846
|
+
fio_str_info_s state = fio_str_resize(s, len + fio_str_len(s));
|
847
|
+
vsnprintf(state.data + (state.len - len), len + 1, format, argv);
|
848
|
+
return state;
|
849
|
+
}
|
850
|
+
|
851
|
+
/** Writes to the String using a printf like interface. */
|
852
|
+
FIO_FUNC __attribute__((format(printf, 2, 3))) fio_str_info_s
|
853
|
+
fio_str_printf(fio_str_s *s, const char *format, ...) {
|
854
|
+
va_list argv;
|
855
|
+
va_start(argv, format);
|
856
|
+
fio_str_info_s state = fio_str_vprintf(s, format, argv);
|
857
|
+
va_end(argv);
|
858
|
+
return state;
|
859
|
+
}
|
860
|
+
|
861
|
+
/**
|
862
|
+
* Opens the file `filename` and pastes it's contents (or a slice ot it) at the
|
863
|
+
* end of the String. If `limit == 0`, than the data will be read until EOF.
|
864
|
+
*
|
865
|
+
* If the file can't be located, opened or read, or if `start_at` is beyond
|
866
|
+
* the EOF position, NULL is returned in the state's `data` field.
|
867
|
+
*/
|
868
|
+
inline FIO_FUNC fio_str_info_s fio_str_readfile(fio_str_s *s,
|
869
|
+
const char *filename,
|
870
|
+
intptr_t start_at,
|
871
|
+
intptr_t limit) {
|
872
|
+
fio_str_info_s state = {.data = NULL};
|
873
|
+
#if defined(__unix__) || defined(__linux__) || defined(__APPLE__)
|
874
|
+
/* POSIX implementations. */
|
875
|
+
if (filename == NULL)
|
876
|
+
return state;
|
877
|
+
struct stat f_data;
|
878
|
+
int file = -1;
|
879
|
+
char *path = NULL;
|
880
|
+
size_t path_len = 0;
|
881
|
+
|
882
|
+
if (filename[0] == '~' && (filename[1] == '/' || filename[1] == '\\')) {
|
883
|
+
char *home = getenv("HOME");
|
884
|
+
if (home) {
|
885
|
+
size_t filename_len = strlen(filename);
|
886
|
+
size_t home_len = strlen(home);
|
887
|
+
if ((home_len + filename_len) >= (1 << 16)) {
|
888
|
+
/* too long */
|
889
|
+
return state;
|
890
|
+
}
|
891
|
+
if (home[home_len - 1] == '/' || home[home_len - 1] == '\\')
|
892
|
+
--home_len;
|
893
|
+
path_len = home_len + filename_len - 1;
|
894
|
+
path = malloc(path_len + 1);
|
895
|
+
FIO_ASSERT_ALLOC(path);
|
896
|
+
memcpy(path, home, home_len);
|
897
|
+
memcpy(path + home_len, filename + 1, filename_len);
|
898
|
+
path[path_len] = 0;
|
899
|
+
filename = path;
|
900
|
+
}
|
901
|
+
}
|
902
|
+
|
903
|
+
if (stat(filename, &f_data)) {
|
904
|
+
goto finish;
|
905
|
+
}
|
906
|
+
|
907
|
+
if (f_data.st_size <= 0 || start_at >= f_data.st_size) {
|
908
|
+
state = fio_str_state(s);
|
909
|
+
goto finish;
|
910
|
+
}
|
911
|
+
|
912
|
+
file = open(filename, O_RDONLY);
|
913
|
+
if (-1 == file)
|
914
|
+
goto finish;
|
915
|
+
|
916
|
+
if (start_at < 0) {
|
917
|
+
start_at = f_data.st_size + start_at;
|
918
|
+
if (start_at < 0)
|
919
|
+
start_at = 0;
|
920
|
+
}
|
921
|
+
|
922
|
+
if (limit <= 0 || f_data.st_size < (limit + start_at))
|
923
|
+
limit = f_data.st_size - start_at;
|
924
|
+
|
925
|
+
const size_t org_len = fio_str_len(s);
|
926
|
+
state = fio_str_resize(s, org_len + limit);
|
927
|
+
if (pread(file, state.data + org_len, limit, start_at) != (ssize_t)limit) {
|
928
|
+
close(file);
|
929
|
+
fio_str_resize(s, org_len);
|
930
|
+
state.data = NULL;
|
931
|
+
state.len = state.capa = 0;
|
932
|
+
goto finish;
|
933
|
+
}
|
934
|
+
close(file);
|
935
|
+
finish:
|
936
|
+
free(path);
|
937
|
+
return state;
|
938
|
+
#else
|
939
|
+
/* TODO: consider adding non POSIX implementations. */
|
940
|
+
return state;
|
941
|
+
#endif
|
942
|
+
}
|
943
|
+
|
944
|
+
/**
|
945
|
+
* Prevents further manipulations to the String's content.
|
946
|
+
*/
|
947
|
+
inline FIO_FUNC void fio_str_freeze(fio_str_s *s) {
|
948
|
+
if (!s)
|
949
|
+
return;
|
950
|
+
s->frozen = 1;
|
951
|
+
}
|
952
|
+
|
953
|
+
/**
|
954
|
+
* Binary comparison returns `1` if both strings are equal and `0` if not.
|
955
|
+
*/
|
956
|
+
inline FIO_FUNC int fio_str_iseq(const fio_str_s *str1, const fio_str_s *str2) {
|
957
|
+
if (str1 == str2)
|
958
|
+
return 1;
|
959
|
+
if (!str1 || !str2)
|
960
|
+
return 0;
|
961
|
+
fio_str_info_s s1 = fio_str_state(str1);
|
962
|
+
fio_str_info_s s2 = fio_str_state(str2);
|
963
|
+
return (s1.len == s2.len && !memcmp(s1.data, s2.data, s1.len));
|
964
|
+
}
|
965
|
+
|
966
|
+
/* *****************************************************************************
|
967
|
+
Testing
|
968
|
+
***************************************************************************** */
|
969
|
+
|
970
|
+
#if DEBUG
|
971
|
+
#include <stdio.h>
|
972
|
+
#define TEST_ASSERT(cond, ...) \
|
973
|
+
if (!(cond)) { \
|
974
|
+
fprintf(stderr, "* " __VA_ARGS__); \
|
975
|
+
fprintf(stderr, "\n !!! Testing failed !!!\n"); \
|
976
|
+
exit(-1); \
|
977
|
+
}
|
978
|
+
/**
|
979
|
+
* Removes any FIO_ARY_TYPE_INVALID *pointers* from an Array, keeping all other
|
980
|
+
* data in the array.
|
981
|
+
*
|
982
|
+
* This action is O(n) where n in the length of the array.
|
983
|
+
* It could get expensive.
|
984
|
+
*/
|
985
|
+
FIO_FUNC inline void fio_str_test(void) {
|
986
|
+
fprintf(stderr, "=== Testing Core String features (fio_str.h)\n");
|
987
|
+
fprintf(stderr, "* String container size: %zu\n", sizeof(fio_str_s));
|
988
|
+
fprintf(stderr,
|
989
|
+
"* Self-Contained String Capacity (FIO_STR_SMALL_CAPA): %zu\n",
|
990
|
+
FIO_STR_SMALL_CAPA);
|
991
|
+
fio_str_s str = {.small = 0}; /* test zeroed out memory */
|
992
|
+
TEST_ASSERT(fio_str_capa(&str) == FIO_STR_SMALL_CAPA - 1,
|
993
|
+
"Small String capacity reporting error!");
|
994
|
+
TEST_ASSERT(fio_str_len(&str) == 0, "Small String length reporting error!");
|
995
|
+
TEST_ASSERT(fio_str_data(&str) ==
|
996
|
+
(char *)((uintptr_t)(&str + 1) - FIO_STR_SMALL_CAPA),
|
997
|
+
"Small String pointer reporting error!");
|
998
|
+
fio_str_write(&str, "World", 4);
|
999
|
+
TEST_ASSERT(str.small,
|
1000
|
+
"Small String writing error - not small on small write!");
|
1001
|
+
TEST_ASSERT(fio_str_capa(&str) == FIO_STR_SMALL_CAPA - 1,
|
1002
|
+
"Small String capacity reporting error after write!");
|
1003
|
+
TEST_ASSERT(fio_str_len(&str) == 4,
|
1004
|
+
"Small String length reporting error after write!");
|
1005
|
+
TEST_ASSERT(fio_str_data(&str) ==
|
1006
|
+
(char *)((uintptr_t)(&str + 1) - FIO_STR_SMALL_CAPA),
|
1007
|
+
"Small String pointer reporting error after write!");
|
1008
|
+
TEST_ASSERT(strlen(fio_str_data(&str)) == 4,
|
1009
|
+
"Small String NUL missing after write (%zu)!",
|
1010
|
+
strlen(fio_str_data(&str)));
|
1011
|
+
TEST_ASSERT(!strcmp(fio_str_data(&str), "Worl"),
|
1012
|
+
"Small String write error (%s)!", fio_str_data(&str));
|
1013
|
+
|
1014
|
+
fio_str_capa_assert(&str, sizeof(fio_str_s) - 1);
|
1015
|
+
TEST_ASSERT(!str.small,
|
1016
|
+
"Long String reporting as small after capacity update!");
|
1017
|
+
TEST_ASSERT(fio_str_capa(&str) == sizeof(fio_str_s) - 1,
|
1018
|
+
"Long String capacity update error (%zu != %zu)!",
|
1019
|
+
fio_str_capa(&str), sizeof(fio_str_s));
|
1020
|
+
TEST_ASSERT(
|
1021
|
+
fio_str_len(&str) == 4,
|
1022
|
+
"Long String length changed during conversion from small string (%zu)!",
|
1023
|
+
fio_str_len(&str));
|
1024
|
+
TEST_ASSERT(fio_str_data(&str) == str.data,
|
1025
|
+
"Long String pointer reporting error after capacity update!");
|
1026
|
+
TEST_ASSERT(strlen(fio_str_data(&str)) == 4,
|
1027
|
+
"Long String NUL missing after capacity update (%zu)!",
|
1028
|
+
strlen(fio_str_data(&str)));
|
1029
|
+
TEST_ASSERT(!strcmp(fio_str_data(&str), "Worl"),
|
1030
|
+
"Long String value changed after capacity update (%s)!",
|
1031
|
+
fio_str_data(&str));
|
1032
|
+
|
1033
|
+
fio_str_write(&str, "d!", 2);
|
1034
|
+
TEST_ASSERT(!strcmp(fio_str_data(&str), "World!"),
|
1035
|
+
"Long String `write` error (%s)!", fio_str_data(&str));
|
1036
|
+
|
1037
|
+
fio_str_replace(&str, 0, 0, "Hello ", 6);
|
1038
|
+
TEST_ASSERT(!strcmp(fio_str_data(&str), "Hello World!"),
|
1039
|
+
"Long String `insert` error (%s)!", fio_str_data(&str));
|
1040
|
+
|
1041
|
+
fio_str_resize(&str, 6);
|
1042
|
+
TEST_ASSERT(!strcmp(fio_str_data(&str), "Hello "),
|
1043
|
+
"Long String `resize` clipping error (%s)!", fio_str_data(&str));
|
1044
|
+
|
1045
|
+
fio_str_replace(&str, 6, 0, "My World!", 9);
|
1046
|
+
TEST_ASSERT(!strcmp(fio_str_data(&str), "Hello My World!"),
|
1047
|
+
"Long String `replace` error when testing overflow (%s)!",
|
1048
|
+
fio_str_data(&str));
|
1049
|
+
|
1050
|
+
str.capa = str.len;
|
1051
|
+
fio_str_replace(&str, -10, 2, "Big", 3);
|
1052
|
+
TEST_ASSERT(!strcmp(fio_str_data(&str), "Hello Big World!"),
|
1053
|
+
"Long String `replace` error when testing splicing (%s)!",
|
1054
|
+
fio_str_data(&str));
|
1055
|
+
|
1056
|
+
TEST_ASSERT(
|
1057
|
+
fio_str_capa(&str) == ROUND_UP_CAPA_2WORDS(strlen("Hello Big World!")),
|
1058
|
+
"Long String `fio_str_replace` capacity update error (%zu != %zu)!",
|
1059
|
+
fio_str_capa(&str), ROUND_UP_CAPA_2WORDS(strlen("Hello Big World!")));
|
1060
|
+
|
1061
|
+
if (str.len < FIO_STR_SMALL_CAPA) {
|
1062
|
+
fio_str_compact(&str);
|
1063
|
+
TEST_ASSERT(str.small, "Compacting didn't change String to small!");
|
1064
|
+
TEST_ASSERT(fio_str_len(&str) == strlen("Hello Big World!"),
|
1065
|
+
"Compacting altered String length! (%zu != %zu)!",
|
1066
|
+
fio_str_len(&str), strlen("Hello Big World!"));
|
1067
|
+
TEST_ASSERT(!strcmp(fio_str_data(&str), "Hello Big World!"),
|
1068
|
+
"Compact data error (%s)!", fio_str_data(&str));
|
1069
|
+
TEST_ASSERT(fio_str_capa(&str) == FIO_STR_SMALL_CAPA - 1,
|
1070
|
+
"Compacted String capacity reporting error!");
|
1071
|
+
} else {
|
1072
|
+
fprintf(stderr, "* skipped `compact` test!\n");
|
1073
|
+
}
|
1074
|
+
|
1075
|
+
{
|
1076
|
+
fio_str_freeze(&str);
|
1077
|
+
fio_str_info_s old_state = fio_str_state(&str);
|
1078
|
+
fio_str_write(&str, "more data to be written here", 28);
|
1079
|
+
fio_str_replace(&str, 2, 1, "more data to be written here", 28);
|
1080
|
+
fio_str_info_s new_state = fio_str_state(&str);
|
1081
|
+
TEST_ASSERT(old_state.len == new_state.len,
|
1082
|
+
"Frozen String length changed!");
|
1083
|
+
TEST_ASSERT(old_state.data == new_state.data,
|
1084
|
+
"Frozen String pointer changed!");
|
1085
|
+
TEST_ASSERT(
|
1086
|
+
old_state.capa == new_state.capa,
|
1087
|
+
"Frozen String capacity changed (allowed, but shouldn't happen)!");
|
1088
|
+
str.frozen = 0;
|
1089
|
+
}
|
1090
|
+
fio_str_printf(&str, " %u", 42);
|
1091
|
+
TEST_ASSERT(!strcmp(fio_str_data(&str), "Hello Big World! 42"),
|
1092
|
+
"`fio_str_printf` data error (%s)!", fio_str_data(&str));
|
1093
|
+
|
1094
|
+
{
|
1095
|
+
fio_str_s str2 = FIO_STR_INIT;
|
1096
|
+
fio_str_concat(&str2, &str);
|
1097
|
+
TEST_ASSERT(fio_str_iseq(&str, &str2),
|
1098
|
+
"`fio_str_concat` error, strings not equal (%s != %s)!",
|
1099
|
+
fio_str_data(&str), fio_str_data(&str2));
|
1100
|
+
fio_str_write(&str2, ":extra data", 11);
|
1101
|
+
TEST_ASSERT(
|
1102
|
+
!fio_str_iseq(&str, &str2),
|
1103
|
+
"`fio_str_write` error after copy, strings equal ((%zu)%s == (%zu)%s)!",
|
1104
|
+
fio_str_len(&str), fio_str_data(&str), fio_str_len(&str2),
|
1105
|
+
fio_str_data(&str2));
|
1106
|
+
|
1107
|
+
fio_str_free(&str2);
|
1108
|
+
}
|
1109
|
+
|
1110
|
+
fio_str_free(&str);
|
1111
|
+
|
1112
|
+
{
|
1113
|
+
fio_str_info_s state = fio_str_readfile(&str, __FILE__, 0, 0);
|
1114
|
+
TEST_ASSERT(state.data,
|
1115
|
+
"`fio_str_readfile` error, no data was read for file %s!",
|
1116
|
+
__FILE__);
|
1117
|
+
TEST_ASSERT(!memcmp(state.data, "#ifndef H_FIO_STRING_H", 22),
|
1118
|
+
"`fio_str_readfile` content error, header mismatch!\n %s",
|
1119
|
+
state.data);
|
1120
|
+
TEST_ASSERT(
|
1121
|
+
fio_str_utf8_valid(&str),
|
1122
|
+
"`fio_str_utf8_valid` error, code in this file should be valid!");
|
1123
|
+
TEST_ASSERT(fio_str_utf8_len(&str) &&
|
1124
|
+
(fio_str_utf8_len(&str) <= fio_str_len(&str)) &&
|
1125
|
+
(fio_str_utf8_len(&str) >= (fio_str_len(&str)) >> 1),
|
1126
|
+
"`fio_str_utf8_len` error, invalid value (%zu / %zu!",
|
1127
|
+
fio_str_utf8_len(&str), fio_str_len(&str));
|
1128
|
+
{
|
1129
|
+
/* String content == whole file (this file) */
|
1130
|
+
intptr_t pos = -11;
|
1131
|
+
size_t len = 20;
|
1132
|
+
|
1133
|
+
TEST_ASSERT(
|
1134
|
+
fio_str_utf8_select(&str, &pos, &len) == 0,
|
1135
|
+
"`fio_str_utf8_select` returned error for negative pos! (%zd, %zu)",
|
1136
|
+
(ssize_t)pos, len);
|
1137
|
+
TEST_ASSERT(
|
1138
|
+
pos == (intptr_t)state.len - 10, /* no UTF-8 bytes in this file */
|
1139
|
+
"`fio_str_utf8_select` error, negative position invalid! (%zd)",
|
1140
|
+
(ssize_t)pos);
|
1141
|
+
TEST_ASSERT(
|
1142
|
+
len == 10,
|
1143
|
+
"`fio_str_utf8_select` error, trancated length invalid! (%zd)",
|
1144
|
+
(ssize_t)len);
|
1145
|
+
pos = 10;
|
1146
|
+
len = 20;
|
1147
|
+
TEST_ASSERT(fio_str_utf8_select(&str, &pos, &len) == 0,
|
1148
|
+
"`fio_str_utf8_select` returned error! (%zd, %zu)",
|
1149
|
+
(ssize_t)pos, len);
|
1150
|
+
TEST_ASSERT(pos == 10,
|
1151
|
+
"`fio_str_utf8_select` error, position invalid! (%zd)",
|
1152
|
+
(ssize_t)pos);
|
1153
|
+
TEST_ASSERT(len == 20,
|
1154
|
+
"`fio_str_utf8_select` error, length invalid! (%zd)",
|
1155
|
+
(ssize_t)len);
|
1156
|
+
}
|
1157
|
+
}
|
1158
|
+
fio_str_free(&str);
|
1159
|
+
{
|
1160
|
+
|
1161
|
+
const char *utf8_sample = /* three hearts, small-big-small*/
|
1162
|
+
"\xf0\x9f\x92\x95\xe2\x9d\xa4\xef\xb8\x8f\xf0\x9f\x92\x95";
|
1163
|
+
fio_str_write(&str, utf8_sample, strlen(utf8_sample));
|
1164
|
+
intptr_t pos = -2;
|
1165
|
+
size_t len = 2;
|
1166
|
+
TEST_ASSERT(fio_str_utf8_select(&str, &pos, &len) == 0,
|
1167
|
+
"`fio_str_utf8_select` returned error for negative pos on "
|
1168
|
+
"UTF-8 data! (%zd, %zu)",
|
1169
|
+
(ssize_t)pos, len);
|
1170
|
+
TEST_ASSERT(pos == (intptr_t)fio_str_len(&str) - 4, /* 4 byte emoji */
|
1171
|
+
"`fio_str_utf8_select` error, negative position invalid on "
|
1172
|
+
"UTF-8 data! (%zd)",
|
1173
|
+
(ssize_t)pos);
|
1174
|
+
TEST_ASSERT(len == 4, /* last utf-8 char is 4 byte long */
|
1175
|
+
"`fio_str_utf8_select` error, trancated length invalid on "
|
1176
|
+
"UTF-8 data! (%zd)",
|
1177
|
+
(ssize_t)len);
|
1178
|
+
pos = 1;
|
1179
|
+
len = 20;
|
1180
|
+
TEST_ASSERT(
|
1181
|
+
fio_str_utf8_select(&str, &pos, &len) == 0,
|
1182
|
+
"`fio_str_utf8_select` returned error on UTF-8 data! (%zd, %zu)",
|
1183
|
+
(ssize_t)pos, len);
|
1184
|
+
TEST_ASSERT(
|
1185
|
+
pos == 4,
|
1186
|
+
"`fio_str_utf8_select` error, position invalid on UTF-8 data! (%zd)",
|
1187
|
+
(ssize_t)pos);
|
1188
|
+
TEST_ASSERT(
|
1189
|
+
len == 10,
|
1190
|
+
"`fio_str_utf8_select` error, length invalid on UTF-8 data! (%zd)",
|
1191
|
+
(ssize_t)len);
|
1192
|
+
pos = 1;
|
1193
|
+
len = 3;
|
1194
|
+
TEST_ASSERT(
|
1195
|
+
fio_str_utf8_select(&str, &pos, &len) == 0,
|
1196
|
+
"`fio_str_utf8_select` returned error on UTF-8 data (2)! (%zd, %zu)",
|
1197
|
+
(ssize_t)pos, len);
|
1198
|
+
TEST_ASSERT(
|
1199
|
+
len == 10, /* 3 UTF-8 chars: 4 byte + 4 byte + 2 byte codes == 10 */
|
1200
|
+
"`fio_str_utf8_select` error, length invalid on UTF-8 data! (%zd)",
|
1201
|
+
(ssize_t)len);
|
1202
|
+
}
|
1203
|
+
fio_str_free(&str);
|
1204
|
+
fprintf(stderr, "* passed.\n");
|
1205
|
+
}
|
1206
|
+
#undef TEST_ASSERT
|
1207
|
+
#else
|
1208
|
+
#define fio_str_test()
|
1209
|
+
#endif
|
1210
|
+
|
1211
|
+
/* *****************************************************************************
|
1212
|
+
Done
|
1213
|
+
***************************************************************************** */
|
1214
|
+
|
1215
|
+
#undef FIO_FUNC
|
1216
|
+
#undef FIO_ASSERT_ALLOC
|
1217
|
+
#undef ROUND_UP_CAPA_2WORDS
|
1218
|
+
#endif
|