├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── imhttp.h ├── main.c ├── sv.c └── sv.h /.gitignore: -------------------------------------------------------------------------------- 1 | main -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Alexey Kutepov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-Wall -Wextra -std=c11 -pedantic -ggdb 2 | 3 | main: main.c imhttp.h 4 | $(CC) $(CFLAGS) -o main main.c sv.c 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImHTTP 2 | 3 | Immediate Mode HTTP Client Library. 4 | 5 | ## Quick Start 6 | 7 | ```console 8 | $ make -B 9 | $ ./main 10 | ``` 11 | -------------------------------------------------------------------------------- /imhttp.h: -------------------------------------------------------------------------------- 1 | #ifndef IMHTTP_H_ 2 | #define IMHTTP_H_ 3 | 4 | #include 5 | // TODO: imhttp depends on a separate translation unit which makes it no longer header-only 6 | #include "./sv.h" 7 | 8 | typedef void* ImHTTP_Socket; 9 | typedef ssize_t (*ImHTTP_Write)(ImHTTP_Socket socket, const void *buf, size_t count); 10 | typedef ssize_t (*ImHTTP_Read)(ImHTTP_Socket socket, void *buf, size_t count); 11 | 12 | // TODO: not all methods are supported 13 | typedef enum { 14 | IMHTTP_GET, 15 | IMHTTP_POST, 16 | } ImHTTP_Method; 17 | 18 | #define IMHTTP_ROLLIN_BUFFER_CAPACITY (8 * 1024) 19 | #define IMHTTP_USER_BUFFER_CAPACITY IMHTTP_ROLLIN_BUFFER_CAPACITY 20 | 21 | static_assert(IMHTTP_ROLLIN_BUFFER_CAPACITY <= IMHTTP_USER_BUFFER_CAPACITY, 22 | "The user buffer should be at least as big as the rolling buffer " 23 | "because sometimes you may wanna put the whole rollin content into " 24 | "the user buffer."); 25 | 26 | typedef struct { 27 | ImHTTP_Socket socket; 28 | ImHTTP_Write write; 29 | ImHTTP_Read read; 30 | 31 | char rollin_buffer[IMHTTP_ROLLIN_BUFFER_CAPACITY]; 32 | size_t rollin_buffer_size; 33 | 34 | char user_buffer[IMHTTP_USER_BUFFER_CAPACITY]; 35 | 36 | int content_length; 37 | bool chunked; 38 | size_t chunk_length; 39 | bool chunked_done; 40 | } ImHTTP; 41 | 42 | void imhttp_req_begin(ImHTTP *imhttp, ImHTTP_Method method, const char *resource); 43 | void imhttp_req_header(ImHTTP *imhttp, const char *header_name, const char *header_value); 44 | void imhttp_req_headers_end(ImHTTP *imhttp); 45 | void imhttp_req_body_chunk(ImHTTP *imhttp, const char *chunk_cstr); 46 | void imhttp_req_body_chunk_sized(ImHTTP *imhttp, const char *chunk, size_t chunk_size); 47 | void imhttp_req_end(ImHTTP *imhttp); 48 | 49 | void imhttp_res_begin(ImHTTP *imhttp); 50 | uint64_t imhttp_res_status_code(ImHTTP *imhttp); 51 | bool imhttp_res_next_header(ImHTTP *imhttp, String_View *name, String_View *value); 52 | bool imhttp_res_next_body_chunk(ImHTTP *imhttp, String_View *chunk); 53 | void imhttp_res_end(ImHTTP *imhttp); 54 | 55 | #endif // IMHTTP_H_ 56 | 57 | #ifdef IMHTTP_IMPLEMENTATION 58 | 59 | static void imhttp_drop_rollin_buffer(ImHTTP *imhttp, size_t n) 60 | { 61 | assert(n <= imhttp->rollin_buffer_size); 62 | memmove(imhttp->rollin_buffer, imhttp->rollin_buffer + n, n); 63 | imhttp->rollin_buffer_size -= n; 64 | } 65 | 66 | static String_View imhttp_shift_rollin_buffer(ImHTTP *imhttp, const char *end) 67 | { 68 | // Boundary check 69 | assert(imhttp->rollin_buffer <= end); 70 | size_t n = end - imhttp->rollin_buffer; 71 | assert(n <= imhttp->rollin_buffer_size); 72 | 73 | // Copy chunk to user buffer 74 | assert(n <= IMHTTP_USER_BUFFER_CAPACITY); 75 | memcpy(imhttp->user_buffer, imhttp->rollin_buffer, n); 76 | 77 | // Shifting the rollin buffer 78 | imhttp->rollin_buffer_size -= n; 79 | memmove(imhttp->rollin_buffer, end, imhttp->rollin_buffer_size); 80 | 81 | return (String_View) { 82 | .data = imhttp->user_buffer, 83 | .count = n, 84 | }; 85 | } 86 | 87 | // TODO: document that imhttp_top_rollin_buffer does not perform any reads until *everything* inside of rollin_buffer is processed 88 | static void imhttp_top_rollin_buffer(ImHTTP *imhttp) 89 | { 90 | if (imhttp->rollin_buffer_size == 0) { 91 | ssize_t n = imhttp->read( 92 | imhttp->socket, 93 | imhttp->rollin_buffer + imhttp->rollin_buffer_size, 94 | IMHTTP_ROLLIN_BUFFER_CAPACITY - imhttp->rollin_buffer_size); 95 | // TODO: imhttp_top_rollin_buffer() does not handle read errors 96 | assert(n > 0); 97 | imhttp->rollin_buffer_size += n; 98 | } 99 | } 100 | 101 | static String_View imhttp_rollin_buffer_as_sv(ImHTTP *imhttp) 102 | { 103 | return (String_View) { 104 | .data = imhttp->rollin_buffer, 105 | .count = imhttp->rollin_buffer_size, 106 | }; 107 | } 108 | 109 | static const char *imhttp_method_as_cstr(ImHTTP_Method method) 110 | { 111 | switch (method) { 112 | case IMHTTP_GET: 113 | return "GET"; 114 | case IMHTTP_POST: 115 | return "POST"; 116 | default: 117 | assert(0 && "imhttp_method_as_cstr: unreachable"); 118 | } 119 | } 120 | 121 | static void imhttp_write_cstr(ImHTTP *imhttp, const char *cstr) 122 | { 123 | size_t cstr_size = strlen(cstr); 124 | // TODO: imhttp_write_cstr does not handle ImHTTP_Write errors 125 | imhttp->write(imhttp->socket, cstr, cstr_size); 126 | } 127 | 128 | void imhttp_req_begin(ImHTTP *imhttp, ImHTTP_Method method, const char *resource) 129 | { 130 | imhttp_write_cstr(imhttp, imhttp_method_as_cstr(method)); 131 | imhttp_write_cstr(imhttp, " "); 132 | // TODO: it is easy to make the resource malformed in imhttp_req_begin 133 | imhttp_write_cstr(imhttp, resource); 134 | imhttp_write_cstr(imhttp, " HTTP/1.1\r\n"); 135 | } 136 | 137 | void imhttp_req_header(ImHTTP *imhttp, const char *header_name, const char *header_value) 138 | { 139 | imhttp_write_cstr(imhttp, header_name); 140 | imhttp_write_cstr(imhttp, ": "); 141 | imhttp_write_cstr(imhttp, header_value); 142 | imhttp_write_cstr(imhttp, "\r\n"); 143 | } 144 | 145 | void imhttp_req_headers_end(ImHTTP *imhttp) 146 | { 147 | imhttp_write_cstr(imhttp, "\r\n"); 148 | } 149 | 150 | void imhttp_req_body_chunk(ImHTTP *imhttp, const char *chunk_cstr) 151 | { 152 | imhttp_write_cstr(imhttp, chunk_cstr); 153 | } 154 | 155 | void imhttp_req_body_chunk_sized(ImHTTP *imhttp, const char *chunk, size_t chunk_size) 156 | { 157 | imhttp->write(imhttp->socket, chunk, chunk_size); 158 | } 159 | 160 | void imhttp_req_end(ImHTTP *imhttp) 161 | { 162 | (void) imhttp; 163 | } 164 | 165 | void imhttp_res_begin(ImHTTP *imhttp) 166 | { 167 | imhttp->content_length = -1; 168 | imhttp->chunked = false; 169 | imhttp->chunk_length = 0; 170 | imhttp->chunked_done = false; 171 | } 172 | 173 | uint64_t imhttp_res_status_code(ImHTTP *imhttp) 174 | { 175 | imhttp_top_rollin_buffer(imhttp); 176 | String_View rollin = imhttp_rollin_buffer_as_sv(imhttp); 177 | String_View status_line = sv_chop_by_delim(&rollin, '\n'); 178 | assert( 179 | sv_ends_with(status_line, SV("\r")) && 180 | "The rolling buffer is so small that it could not fit the whole status line. " 181 | "Or maybe the status line was not fully read after the imhttp_top_rollin_buffer() " 182 | "above"); 183 | status_line = imhttp_shift_rollin_buffer(imhttp, rollin.data); 184 | // TODO: the HTTP version is skipped in imhttp_res_status_code() 185 | sv_chop_by_delim(&status_line, ' '); 186 | String_View code_sv = sv_chop_by_delim(&status_line, ' '); 187 | return sv_to_u64(code_sv); 188 | } 189 | 190 | // TODO: Document that imhttp_res_next_header() invalidate name and value on the consequent imhttp_* calls 191 | bool imhttp_res_next_header(ImHTTP *imhttp, String_View *name, String_View *value) 192 | { 193 | imhttp_top_rollin_buffer(imhttp); 194 | String_View rollin = imhttp_rollin_buffer_as_sv(imhttp); 195 | String_View header_line = sv_chop_by_delim(&rollin, '\n'); 196 | assert( 197 | sv_ends_with(header_line, SV("\r")) && 198 | "The rolling buffer is so small that it could not fit the whole header line. " 199 | "Or maybe the header line was not fully read after the imhttp_top_rollin_buffer() " 200 | "above"); 201 | // Transfer the ownership of header_line from rollin_buffer to user_buffer 202 | header_line = imhttp_shift_rollin_buffer(imhttp, rollin.data); 203 | 204 | if (!sv_eq(header_line, SV("\r\n"))) { 205 | // TODO: don't set name/value if the user set them to NULL in imhttp_res_next_header 206 | *name = sv_chop_by_delim(&header_line, ':'); 207 | *value = sv_trim(header_line); 208 | 209 | // TODO: are header case-sensitive? 210 | if (sv_eq(*name, SV("Content-Length"))) { 211 | // TODO: content_length overflow 212 | imhttp->content_length = sv_to_u64(*value); 213 | } else if (sv_eq(*name, SV("Transfer-Encoding"))) { 214 | String_View encoding_list = *value; 215 | while (encoding_list.count > 0) { 216 | String_View encoding = sv_trim(sv_chop_by_delim(&encoding_list, ',')); 217 | if (sv_eq(encoding, SV("chunked"))) { 218 | imhttp->chunked = true; 219 | } 220 | } 221 | } 222 | return true; 223 | } 224 | 225 | return false; 226 | } 227 | 228 | // TODO: document that the chunk is always invalidated after each imhttp_res_next_body_chunk() call 229 | bool imhttp_res_next_body_chunk(ImHTTP *imhttp, String_View *chunk) 230 | { 231 | if (imhttp->chunked) { 232 | if (!imhttp->chunked_done) { 233 | imhttp_top_rollin_buffer(imhttp); 234 | 235 | if (imhttp->chunk_length == 0) { 236 | String_View rollin = imhttp_rollin_buffer_as_sv(imhttp); 237 | String_View chunk_length_sv = sv_chop_by_delim(&rollin, '\n'); 238 | assert(sv_ends_with(chunk_length_sv, SV("\r")) && 239 | "The rolling buffer is so small that it could not fit the whole chunk length. " 240 | "Or maybe the chunk length was not fully read after the imhttp_top_rollin_buffer() " 241 | "above"); 242 | imhttp->chunk_length = sv_hex_to_u64(sv_trim(chunk_length_sv)); 243 | imhttp_shift_rollin_buffer(imhttp, rollin.data); 244 | } 245 | 246 | if (imhttp->chunk_length == 0) { 247 | imhttp->chunked_done = true; 248 | return false; 249 | } 250 | 251 | { 252 | size_t n = imhttp->chunk_length; 253 | if (n > imhttp->rollin_buffer_size) { 254 | n = imhttp->rollin_buffer_size; 255 | } 256 | 257 | String_View data = imhttp_shift_rollin_buffer(imhttp, imhttp->rollin_buffer + n); 258 | imhttp->chunk_length -= n; 259 | 260 | if (imhttp->chunk_length == 0) { 261 | String_View rollin = imhttp_rollin_buffer_as_sv(imhttp); 262 | assert(sv_starts_with(rollin, SV("\r\n"))); 263 | imhttp_drop_rollin_buffer(imhttp, 2); 264 | } 265 | 266 | if (chunk) { 267 | *chunk = data; 268 | } 269 | } 270 | 271 | return true; 272 | } 273 | } else { 274 | // TODO: ImHTTP can't handle the responses that do not set Content-Length 275 | assert(imhttp->content_length >= 0); 276 | 277 | if (imhttp->content_length > 0) { 278 | imhttp_top_rollin_buffer(imhttp); 279 | String_View rollin = imhttp_rollin_buffer_as_sv(imhttp); 280 | // TODO: ImHTTP does not handle the situation when the server responded with more data than it claimed with Content-Length 281 | assert(rollin.count <= (size_t) imhttp->content_length); 282 | 283 | String_View result = imhttp_shift_rollin_buffer( 284 | imhttp, 285 | imhttp->rollin_buffer + imhttp->rollin_buffer_size); 286 | 287 | if (chunk) { 288 | *chunk = result; 289 | } 290 | 291 | imhttp->content_length -= result.count; 292 | 293 | return true; 294 | } 295 | } 296 | 297 | return false; 298 | } 299 | 300 | void imhttp_res_end(ImHTTP *imhttp) 301 | { 302 | (void) imhttp; 303 | } 304 | 305 | #endif // IMHTTP_IMPLEMENTATION 306 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #define _POSIX_C_SOURCE 200112L 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define IMHTTP_IMPLEMENTATION 16 | #include "./imhttp.h" 17 | 18 | // TODO: Sometimes http://anglesharp.azurewebsites.net/Chunked fires the asserts 19 | #define HOST "anglesharp.azurewebsites.net" 20 | #define PORT "80" 21 | 22 | ssize_t imhttp_write(ImHTTP_Socket socket, const void *buf, size_t count) 23 | { 24 | return write((int) (int64_t) socket, buf, count); 25 | } 26 | 27 | ssize_t imhttp_read(ImHTTP_Socket socket, void *buf, size_t count) 28 | { 29 | return read((int) (int64_t) socket, buf, count); 30 | } 31 | 32 | int main() 33 | { 34 | struct addrinfo hints = {0}; 35 | hints.ai_family = AF_INET; 36 | hints.ai_socktype = SOCK_STREAM; 37 | hints.ai_protocol = IPPROTO_TCP; 38 | 39 | struct addrinfo *addrs; 40 | if (getaddrinfo(HOST, PORT, &hints, &addrs) < 0) { 41 | fprintf(stderr, "Could not get address of `"HOST"`: %s\n", 42 | strerror(errno)); 43 | exit(1); 44 | } 45 | 46 | int sd = 0; 47 | for (struct addrinfo *addr = addrs; addr != NULL; addr = addr->ai_next) { 48 | sd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); 49 | 50 | if (sd == -1) break; 51 | if (connect(sd, addr->ai_addr, addr->ai_addrlen) == 0) break; 52 | 53 | close(sd); 54 | sd = -1; 55 | } 56 | freeaddrinfo(addrs); 57 | 58 | if (sd == -1) { 59 | fprintf(stderr, "Could not connect to "HOST":"PORT": %s\n", 60 | strerror(errno)); 61 | exit(1); 62 | } 63 | 64 | static ImHTTP imhttp = { 65 | .write = imhttp_write, 66 | .read = imhttp_read, 67 | }; 68 | imhttp.socket = (void*) (int64_t) sd, 69 | 70 | imhttp_req_begin(&imhttp, IMHTTP_GET, "/Chunked"); 71 | { 72 | imhttp_req_header(&imhttp, "Host", HOST); 73 | imhttp_req_header(&imhttp, "Foo", "Bar"); 74 | imhttp_req_header(&imhttp, "Hello", "World"); 75 | imhttp_req_headers_end(&imhttp); 76 | imhttp_req_body_chunk(&imhttp, "Hello, World\n"); 77 | imhttp_req_body_chunk(&imhttp, "Test, test, test\n"); 78 | } 79 | imhttp_req_end(&imhttp); 80 | 81 | imhttp_res_begin(&imhttp); 82 | { 83 | // Status Code 84 | { 85 | uint64_t code = imhttp_res_status_code(&imhttp); 86 | printf("Status Code: %lu\n", code); 87 | } 88 | 89 | // Headers 90 | { 91 | String_View name, value; 92 | while (imhttp_res_next_header(&imhttp, &name, &value)) { 93 | printf("------------------------------\n"); 94 | printf("Header Name: "SV_Fmt"\n", SV_Arg(name)); 95 | printf("Header Value: "SV_Fmt"\n", SV_Arg(value)); 96 | } 97 | printf("------------------------------\n"); 98 | } 99 | 100 | // Body 101 | { 102 | String_View chunk; 103 | while (imhttp_res_next_body_chunk(&imhttp, &chunk)) { 104 | printf(SV_Fmt, SV_Arg(chunk)); 105 | } 106 | } 107 | } 108 | imhttp_res_end(&imhttp); 109 | 110 | close(sd); 111 | 112 | return 0; 113 | } 114 | -------------------------------------------------------------------------------- /sv.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "./sv.h" 6 | 7 | String_View sv_from_cstr(const char *cstr) 8 | { 9 | return (String_View) { 10 | .count = strlen(cstr), 11 | .data = cstr, 12 | }; 13 | } 14 | 15 | String_View sv_trim_left(String_View sv) 16 | { 17 | size_t i = 0; 18 | while (i < sv.count && isspace(sv.data[i])) { 19 | i += 1; 20 | } 21 | 22 | return (String_View) { 23 | .count = sv.count - i, 24 | .data = sv.data + i, 25 | }; 26 | } 27 | 28 | String_View sv_trim_right(String_View sv) 29 | { 30 | size_t i = 0; 31 | while (i < sv.count && isspace(sv.data[sv.count - 1 - i])) { 32 | i += 1; 33 | } 34 | 35 | return (String_View) { 36 | .count = sv.count - i, 37 | .data = sv.data 38 | }; 39 | } 40 | 41 | String_View sv_trim(String_View sv) 42 | { 43 | return sv_trim_right(sv_trim_left(sv)); 44 | } 45 | 46 | String_View sv_chop_left(String_View *sv, size_t n) 47 | { 48 | if (n > sv->count) { 49 | n = sv->count; 50 | } 51 | 52 | String_View result = { 53 | .data = sv->data, 54 | .count = n, 55 | }; 56 | 57 | sv->data += n; 58 | sv->count -= n; 59 | 60 | return result; 61 | } 62 | 63 | String_View sv_chop_right(String_View *sv, size_t n) 64 | { 65 | if (n > sv->count) { 66 | n = sv->count; 67 | } 68 | 69 | String_View result = { 70 | .data = sv->data + sv->count - n, 71 | .count = n 72 | }; 73 | 74 | sv->count -= n; 75 | 76 | return result; 77 | } 78 | 79 | bool sv_index_of(String_View sv, char c, size_t *index) 80 | { 81 | size_t i = 0; 82 | while (i < sv.count && sv.data[i] != c) { 83 | i += 1; 84 | } 85 | 86 | if (i < sv.count) { 87 | *index = i; 88 | return true; 89 | } else { 90 | return false; 91 | } 92 | } 93 | 94 | String_View sv_chop_by_delim(String_View *sv, char delim) 95 | { 96 | size_t i = 0; 97 | while (i < sv->count && sv->data[i] != delim) { 98 | i += 1; 99 | } 100 | 101 | String_View result = { 102 | .count = i, 103 | .data = sv->data, 104 | }; 105 | 106 | if (i < sv->count) { 107 | sv->count -= i + 1; 108 | sv->data += i + 1; 109 | } else { 110 | sv->count -= i; 111 | sv->data += i; 112 | } 113 | 114 | return result; 115 | } 116 | 117 | bool sv_starts_with(String_View sv, String_View expected_prefix) 118 | { 119 | if (expected_prefix.count <= sv.count) { 120 | String_View actual_prefix = { 121 | .data = sv.data, 122 | .count = expected_prefix.count, 123 | }; 124 | 125 | return sv_eq(expected_prefix, actual_prefix); 126 | } 127 | 128 | return false; 129 | } 130 | 131 | bool sv_ends_with(String_View sv, String_View expected_suffix) 132 | { 133 | if (expected_suffix.count <= sv.count) { 134 | String_View actual_suffix = { 135 | .data = sv.data + sv.count - expected_suffix.count, 136 | .count = expected_suffix.count 137 | }; 138 | 139 | return sv_eq(expected_suffix, actual_suffix); 140 | } 141 | 142 | return false; 143 | } 144 | 145 | bool sv_eq(String_View a, String_View b) 146 | { 147 | if (a.count != b.count) { 148 | return false; 149 | } else { 150 | return memcmp(a.data, b.data, a.count) == 0; 151 | } 152 | } 153 | 154 | uint64_t sv_to_u64(String_View sv) 155 | { 156 | uint64_t result = 0; 157 | 158 | for (size_t i = 0; i < sv.count && isdigit(sv.data[i]); ++i) { 159 | result = result * 10 + (uint64_t) sv.data[i] - '0'; 160 | } 161 | 162 | return result; 163 | } 164 | 165 | uint64_t sv_hex_to_u64(String_View sv) 166 | { 167 | uint64_t result = 0; 168 | 169 | for (size_t i = 0; i < sv.count; ++i) { 170 | const char x = sv.data[i]; 171 | if ('0' <= x && x <= '9') { 172 | result = result * 16 + x - '0'; 173 | } else if ('a' <= x && x <= 'z') { 174 | result = result * 16 + x - 'a' + 10; 175 | } else if ('A' <= x && x <= 'Z') { 176 | result = result * 16 + x - 'A' + 10; 177 | } else { 178 | assert(false); 179 | } 180 | } 181 | 182 | return result; 183 | } 184 | 185 | String_View sv_chop_left_while(String_View *sv, bool (*predicate)(char x)) 186 | { 187 | size_t i = 0; 188 | while (i < sv->count && predicate(sv->data[i])) { 189 | i += 1; 190 | } 191 | return sv_chop_left(sv, i); 192 | } 193 | 194 | -------------------------------------------------------------------------------- /sv.h: -------------------------------------------------------------------------------- 1 | #ifndef SV_H_ 2 | #define SV_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct { 9 | size_t count; 10 | const char *data; 11 | } String_View; 12 | 13 | #define SV(cstr_lit) \ 14 | ((String_View) { \ 15 | .count = sizeof(cstr_lit) - 1, \ 16 | .data = (cstr_lit) \ 17 | }) 18 | 19 | #define SV_NULL (String_View) {0} 20 | 21 | // printf macros for String_View 22 | #define SV_Fmt "%.*s" 23 | #define SV_Arg(sv) (int) (sv).count, (sv).data 24 | // USAGE: 25 | // String_View name = ...; 26 | // printf("Name: "SV_Fmt"\n", SV_Arg(name)); 27 | 28 | String_View sv_from_cstr(const char *cstr); 29 | String_View sv_trim_left(String_View sv); 30 | String_View sv_trim_right(String_View sv); 31 | String_View sv_trim(String_View sv); 32 | String_View sv_chop_by_delim(String_View *sv, char delim); 33 | String_View sv_chop_left(String_View *sv, size_t n); 34 | String_View sv_chop_right(String_View *sv, size_t n); 35 | String_View sv_chop_left_while(String_View *sv, bool (*predicate)(char x)); 36 | bool sv_index_of(String_View sv, char c, size_t *index); 37 | bool sv_eq(String_View a, String_View b); 38 | bool sv_starts_with(String_View sv, String_View prefix); 39 | bool sv_ends_with(String_View sv, String_View suffix); 40 | uint64_t sv_to_u64(String_View sv); 41 | uint64_t sv_hex_to_u64(String_View sv); 42 | 43 | #endif // SV_H_ 44 | --------------------------------------------------------------------------------