├── .gitignore ├── LICENSE ├── README.md ├── alexer.h ├── example.c ├── nob.c └── nob.h /.gitignore: -------------------------------------------------------------------------------- 1 | main 2 | example 3 | nob 4 | nob.old 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Alexer 2 | 3 | Very basic lexer for very basic cases. Don't expect it to be fast or super flexible. 4 | 5 | ## Quick Start 6 | 7 | ```c 8 | #define ALEXER_IMPLEMENTATION 9 | #include "alexer.h" 10 | 11 | typedef enum { 12 | PUNCT_PLUS, 13 | PUNCT_MULT, 14 | PUNCT_EQUALS, 15 | PUNCT_OPAREN, 16 | PUNCT_CPAREN, 17 | PUNCT_OCURLY, 18 | PUNCT_CCURLY, 19 | PUNCT_SEMICOLON, 20 | COUNT_PUNCTS, 21 | } Punct_Index; 22 | 23 | static_assert(COUNT_PUNCTS == 8, "Amount of puncts have changed"); 24 | const char *puncts[COUNT_PUNCTS] = { 25 | [PUNCT_PLUS] = "+", 26 | [PUNCT_MULT] = "*", 27 | [PUNCT_OPAREN] = "(", 28 | [PUNCT_CPAREN] = ")", 29 | [PUNCT_OCURLY] = "{", 30 | [PUNCT_CCURLY] = "}", 31 | [PUNCT_EQUALS] = "==", 32 | [PUNCT_SEMICOLON] = ";", 33 | }; 34 | 35 | typedef enum { 36 | KEYWORD_IF, 37 | KEYWORD_RETURN, 38 | COUNT_KEYWORDS, 39 | } Keyword_Index; 40 | 41 | static_assert(COUNT_KEYWORDS == 2, "Amount of keywords have changed"); 42 | const char *keywords[COUNT_KEYWORDS] = { 43 | [KEYWORD_IF] = "if", 44 | [KEYWORD_RETURN] = "return", 45 | }; 46 | 47 | const char *sl_comments[] = { 48 | "//", 49 | "#", 50 | }; 51 | 52 | Alexer_ML_Comments ml_comments[] = { 53 | {"/*", "*/"}, 54 | }; 55 | 56 | int main() 57 | { 58 | const char *file_path = "example/path"; // The file path is only needed for diagnostic message 59 | const char *content = 60 | "#include \n" 61 | "if (a == 17*2 + 35) { // single line comment\n" 62 | " /* multi\n" 63 | " * line\n" 64 | " * comment\n" 65 | " */\n" 66 | " return b;\n" 67 | "}\n"; 68 | Alexer l = alexer_create(file_path, content, strlen(content)); 69 | l.puncts = puncts; 70 | l.puncts_count = ALEXER_ARRAY_LEN(puncts); 71 | l.keywords = keywords; 72 | l.keywords_count = ALEXER_ARRAY_LEN(keywords); 73 | l.sl_comments = sl_comments; 74 | l.sl_comments_count = ALEXER_ARRAY_LEN(sl_comments); 75 | l.ml_comments = ml_comments; 76 | l.ml_comments_count = ALEXER_ARRAY_LEN(ml_comments); 77 | Alexer_Token t = {0}; 78 | while (alexer_get_token(&l, &t)) { 79 | l.diagf(t.loc, "INFO", "%s: %.*s", alexer_kind_name(ALEXER_KIND(t.id)), t.end - t.begin, t.begin); 80 | } 81 | if (!alexer_expect_id(&l, t, ALEXER_END)) return 1; 82 | return 0; 83 | } 84 | ``` 85 | 86 | ```console 87 | $ cc -o nob nob.c 88 | $ ./nob 89 | [INFO] CMD: cc -Wall -Wextra -Wswitch-enum -ggdb -o example example.c 90 | [INFO] CMD: ./example 91 | example/path:2:1: INFO: KEYWORD: if 92 | example/path:2:4: INFO: PUNCT: ( 93 | example/path:2:5: INFO: SYMBOL: a 94 | example/path:2:7: INFO: PUNCT: == 95 | example/path:2:10: INFO: INT: 17 96 | example/path:2:12: INFO: PUNCT: * 97 | example/path:2:13: INFO: INT: 2 98 | example/path:2:15: INFO: PUNCT: + 99 | example/path:2:17: INFO: INT: 35 100 | example/path:2:19: INFO: PUNCT: ) 101 | example/path:2:21: INFO: PUNCT: { 102 | example/path:7:5: INFO: KEYWORD: return 103 | example/path:7:12: INFO: SYMBOL: b 104 | example/path:7:13: INFO: PUNCT: ; 105 | example/path:8:1: INFO: PUNCT: } 106 | ``` 107 | -------------------------------------------------------------------------------- /alexer.h: -------------------------------------------------------------------------------- 1 | #ifndef ALEXER_H_ 2 | #define ALEXER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define ALEXER_ARRAY_LEN(xs) (sizeof(xs)/(sizeof((xs)[0]))) 14 | #define alexer_return_defer(value) do { result = (value); goto defer; } while(0) 15 | 16 | typedef struct { 17 | char *items; 18 | size_t count; 19 | size_t capacity; 20 | } Alexer_String_Builder; 21 | 22 | #ifndef ALEXER_ASSERT 23 | #define ALEXER_ASSERT assert 24 | #endif // ALEXER_ASSERT 25 | #ifndef ALEXER_REALLOC 26 | #define ALEXER_REALLOC realloc 27 | #endif // ALEXER_REALLOC 28 | #ifndef ALEXER_FREE 29 | #define ALEXER_FREE free 30 | #endif // ALEXER_FREE 31 | 32 | // Initial capacity of a dynamic array 33 | #ifndef ALEXER_DA_INIT_CAP 34 | #define ALEXER_DA_INIT_CAP 256 35 | #endif 36 | 37 | // Append several items to a dynamic array 38 | #define alexer_da_append_many(da, new_items, new_items_count) \ 39 | do { \ 40 | if ((da)->count + (new_items_count) > (da)->capacity) { \ 41 | if ((da)->capacity == 0) { \ 42 | (da)->capacity = ALEXER_DA_INIT_CAP; \ 43 | } \ 44 | while ((da)->count + (new_items_count) > (da)->capacity) { \ 45 | (da)->capacity *= 2; \ 46 | } \ 47 | (da)->items = ALEXER_REALLOC((da)->items, (da)->capacity*sizeof(*(da)->items)); \ 48 | ALEXER_ASSERT((da)->items != NULL && "Buy more RAM lol"); \ 49 | } \ 50 | memcpy((da)->items + (da)->count, (new_items), (new_items_count)*sizeof(*(da)->items)); \ 51 | (da)->count += (new_items_count); \ 52 | } while (0) 53 | 54 | // Append a NULL-terminated string to a string builder 55 | #define alexer_sb_append_cstr(sb, cstr) \ 56 | do { \ 57 | const char *s = (cstr); \ 58 | size_t n = strlen(s); \ 59 | alexer_da_append_many(sb, s, n); \ 60 | } while (0) 61 | 62 | // Append a single NULL character at the end of a string builder. So then you can 63 | // use it a NULL-terminated C string 64 | #define alexer_sb_append_null(sb) alexer_da_append_many(sb, "", 1) 65 | 66 | // TODO: support for utf-8 67 | 68 | typedef struct { 69 | const char *file_path; 70 | size_t row; 71 | size_t col; 72 | } Alexer_Loc; 73 | 74 | #define Alexer_Loc_Fmt "%s:%zu:%zu" 75 | #define Alexer_Loc_Arg(loc) (loc).file_path, (loc).row, (loc).col 76 | 77 | #define ALEXER_LOW32(x) (((uint64_t)(x))&0xFFFFFFFF) 78 | #define ALEXER_ID(kind, index) ((ALEXER_LOW32(index)<<32)|(ALEXER_LOW32(kind))) 79 | #define ALEXER_KIND(id) ALEXER_LOW32(id) 80 | #define ALEXER_INDEX(id) ALEXER_LOW32((id)>>32) 81 | 82 | typedef enum { 83 | ALEXER_INVALID, 84 | ALEXER_END, 85 | ALEXER_INT, 86 | ALEXER_SYMBOL, 87 | ALEXER_KEYWORD, 88 | ALEXER_PUNCT, 89 | // TODO: Add support for strings of different customizable types 90 | ALEXER_STRING, 91 | ALEXER_COUNT_KINDS, 92 | } Alexer_Kind; 93 | 94 | static_assert(ALEXER_COUNT_KINDS == 7, "Amount of kinds have changed"); 95 | const char *alexer_kind_names[ALEXER_COUNT_KINDS] = { 96 | [ALEXER_INVALID] = "INVALID", 97 | [ALEXER_END] = "END", 98 | [ALEXER_INT] = "INT", 99 | [ALEXER_SYMBOL] = "SYMBOL", 100 | [ALEXER_KEYWORD] = "KEYWORD", 101 | [ALEXER_PUNCT] = "PUNCT", 102 | [ALEXER_STRING] = "STRING", 103 | }; 104 | #define alexer_kind_name(kind) (ALEXER_ASSERT((uint64_t)kind < ALEXER_COUNT_KINDS), alexer_kind_names[(uint64_t)kind]) 105 | 106 | typedef struct { 107 | uint64_t id; 108 | Alexer_Loc loc; 109 | const char *begin; 110 | const char *end; 111 | long int_value; 112 | } Alexer_Token; 113 | 114 | bool alexer_token_text_equal(Alexer_Token a, Alexer_Token b); 115 | bool alexer_token_text_equal_cstr(Alexer_Token a, const char *b); 116 | 117 | #define Alexer_Token_Fmt "%.*s" 118 | #define Alexer_Token_Arg(t) (int)((t).end - (t).begin), (t).begin 119 | 120 | typedef struct { 121 | const char *opening; 122 | const char *closing; 123 | } Alexer_ML_Comments; 124 | 125 | typedef struct { 126 | size_t cur; 127 | size_t bol; 128 | size_t row; 129 | } Alexer_State; 130 | 131 | typedef struct { 132 | const char *file_path; 133 | const char *content; 134 | size_t size; 135 | 136 | size_t cur; 137 | size_t bol; 138 | size_t row; 139 | 140 | // TODO: Document properly which order puncts should be in. 141 | // If one of the puncts is a prefix of another one, the longer one should come first. 142 | // Maybe we can sort them for the user like that automatically somehow? 143 | const char **puncts; 144 | size_t puncts_count; 145 | const char **keywords; 146 | size_t keywords_count; 147 | const char **sl_comments; 148 | size_t sl_comments_count; 149 | Alexer_ML_Comments *ml_comments; 150 | size_t ml_comments_count; 151 | bool (*is_symbol_start)(char x); 152 | bool (*is_symbol)(char x); 153 | void (*diagf)(Alexer_Loc loc, const char *level, const char *fmt, ...); 154 | } Alexer; 155 | 156 | Alexer alexer_create(const char *file_path, const char *content, size_t size); 157 | // alexer_get_token() 158 | // Gets the next token. Returns false on END or INVALID. Returns true on any other kind of token. 159 | bool alexer_get_token(Alexer *l, Alexer_Token *t); 160 | Alexer_State alexer_save(Alexer *l); 161 | void alexer_rewind(Alexer *l, Alexer_State s); 162 | bool alexer_chop_char(Alexer *l); 163 | void alexer_chop_chars(Alexer *l, size_t n); 164 | void alexer_trim_left_ws(Alexer *l); 165 | void alexer_drop_until_endline(Alexer *l); 166 | Alexer_Loc alexer_loc(Alexer *l); 167 | bool alexer_default_is_symbol(char x); 168 | bool alexer_default_is_symbol_start(char x); 169 | void alexer_default_diagf(Alexer_Loc loc, const char *level, const char *fmt, ...); 170 | void alexer_ignore_diagf(Alexer_Loc loc, const char *level, const char *fmt, ...); 171 | bool alexer_expect_id(Alexer *l, Alexer_Token t, uint64_t id); 172 | bool alexer_expect_one_of_ids(Alexer *l, Alexer_Token t, uint64_t *ids, size_t ids_count); 173 | 174 | #endif // ALEXER_H_ 175 | 176 | #ifdef ALEXER_IMPLEMENTATION 177 | 178 | Alexer alexer_create(const char *file_path, const char *content, size_t size) 179 | { 180 | return (Alexer) { 181 | .file_path = file_path, 182 | .content = content, 183 | .size = size, 184 | .diagf = alexer_default_diagf, 185 | .is_symbol = alexer_default_is_symbol, 186 | .is_symbol_start = alexer_default_is_symbol_start, 187 | }; 188 | } 189 | 190 | bool alexer_chop_char(Alexer *l) 191 | { 192 | if (l->cur < l->size) { 193 | char x = l->content[l->cur]; 194 | l->cur++; 195 | if (x == '\n') { 196 | l->bol = l->cur; 197 | l->row += 1; 198 | } 199 | return true; 200 | } 201 | return false; 202 | } 203 | 204 | void alexer_chop_chars(Alexer *l, size_t n) 205 | { 206 | while (n --> 0 && alexer_chop_char(l)); 207 | } 208 | 209 | void alexer_trim_left_ws(Alexer *l) 210 | { 211 | // TODO: configurable isspace() 212 | while (l->cur < l->size && isspace(l->content[l->cur])) { 213 | alexer_chop_char(l); 214 | } 215 | } 216 | 217 | Alexer_Loc alexer_loc(Alexer *l) 218 | { 219 | return (Alexer_Loc) { 220 | .file_path = l->file_path, 221 | .row = l->row + 1, 222 | .col = l->cur - l->bol + 1, 223 | }; 224 | } 225 | 226 | bool alexer_default_is_symbol(char x) 227 | { 228 | return isalnum(x) || x == '_'; 229 | } 230 | 231 | bool alexer_default_is_symbol_start(char x) 232 | { 233 | return isalpha(x) || x == '_'; 234 | } 235 | 236 | bool alexer_starts_with_cstr(Alexer *l, const char *prefix) 237 | { 238 | for (size_t i = 0; l->cur + i < l->size && prefix[i] != '\0'; ++i) { 239 | if (l->content[l->cur + i] != prefix[i]) { 240 | return false; 241 | } 242 | } 243 | return true; 244 | } 245 | 246 | void alexer_drop_until_endline(Alexer *l) 247 | { 248 | while (l->cur < l->size) { 249 | char x = l->content[l->cur]; 250 | alexer_chop_char(l); 251 | if (x == '\n') break; 252 | } 253 | } 254 | 255 | // TODO: multiline comments are not nestable 256 | void alexer_chop_until_prefix(Alexer *l, const char *prefix) 257 | { 258 | while (l->cur < l->size && !alexer_starts_with_cstr(l, prefix)) { 259 | alexer_chop_char(l); 260 | } 261 | } 262 | 263 | bool alexer_get_token(Alexer *l, Alexer_Token *t) 264 | { 265 | another_trim_round: 266 | while (l->cur < l->size) { 267 | alexer_trim_left_ws(l); 268 | 269 | for (size_t i = 0; i < l->sl_comments_count; ++i) { 270 | if (alexer_starts_with_cstr(l, l->sl_comments[i])) { 271 | alexer_drop_until_endline(l); 272 | goto another_trim_round; 273 | } 274 | } 275 | 276 | for (size_t i = 0; i < l->ml_comments_count; ++i) { 277 | const char *opening = l->ml_comments[i].opening; 278 | const char *closing = l->ml_comments[i].closing; 279 | if (alexer_starts_with_cstr(l, opening)) { 280 | alexer_chop_chars(l, strlen(opening)); 281 | alexer_chop_until_prefix(l, closing); 282 | alexer_chop_chars(l, strlen(closing)); 283 | goto another_trim_round; 284 | } 285 | } 286 | 287 | break; // trimmed everything we could 288 | } 289 | 290 | memset(t, 0, sizeof(*t)); 291 | t->loc = alexer_loc(l); 292 | t->begin = &l->content[l->cur]; 293 | t->end = &l->content[l->cur]; 294 | 295 | if (l->cur >= l->size) { 296 | t->id = ALEXER_END; 297 | return false; 298 | } 299 | 300 | // Puncts 301 | for (size_t i = 0; i < l->puncts_count; ++i) { 302 | if (alexer_starts_with_cstr(l, l->puncts[i])) { 303 | size_t n = strlen(l->puncts[i]); 304 | t->id = ALEXER_ID(ALEXER_PUNCT, i); 305 | t->end += n; 306 | alexer_chop_chars(l, n); 307 | return true; 308 | } 309 | } 310 | 311 | // Int 312 | if (isdigit(l->content[l->cur])) { 313 | t->id = ALEXER_INT; 314 | while (l->cur < l->size && isdigit(l->content[l->cur])) { 315 | t->int_value = t->int_value*10 + l->content[l->cur] - '0'; 316 | t->end += 1; 317 | alexer_chop_char(l); 318 | } 319 | return true; 320 | } 321 | 322 | // Symbol 323 | if (l->is_symbol_start(l->content[l->cur])) { 324 | t->id = ALEXER_SYMBOL; 325 | while (l->cur < l->size && l->is_symbol(l->content[l->cur])) { 326 | t->end += 1; 327 | alexer_chop_char(l); 328 | } 329 | 330 | // Keyword 331 | for (size_t i = 0; i < l->keywords_count; ++i) { 332 | size_t n = strlen(l->keywords[i]); 333 | if (n == (size_t)(t->end - t->begin) && memcmp(l->keywords[i], t->begin, n) == 0) { 334 | t->id = ALEXER_ID(ALEXER_KEYWORD, i); 335 | break; 336 | } 337 | } 338 | 339 | return true; 340 | } 341 | 342 | alexer_chop_char(l); 343 | t->end += 1; 344 | return false; 345 | } 346 | 347 | void alexer_sb_append_id_display(Alexer_String_Builder *sb, Alexer *l, uint64_t id) 348 | { 349 | uint64_t kind = ALEXER_KIND(id); 350 | uint64_t index = ALEXER_INDEX(id); 351 | switch (kind) { 352 | case ALEXER_INVALID: 353 | case ALEXER_END: 354 | case ALEXER_STRING: 355 | case ALEXER_INT: 356 | case ALEXER_SYMBOL: 357 | alexer_sb_append_cstr(sb, alexer_kind_name(kind)); 358 | break; 359 | case ALEXER_KEYWORD: 360 | alexer_sb_append_cstr(sb, alexer_kind_name(kind)); 361 | alexer_sb_append_cstr(sb, " `"); 362 | alexer_sb_append_cstr(sb, (ALEXER_ASSERT(index < l->keywords_count), l->keywords[index])); 363 | alexer_sb_append_cstr(sb, "`"); 364 | break; 365 | case ALEXER_PUNCT: 366 | alexer_sb_append_cstr(sb, alexer_kind_name(kind)); 367 | alexer_sb_append_cstr(sb, " `"); 368 | alexer_sb_append_cstr(sb, (ALEXER_ASSERT(index < l->puncts_count), l->puncts[index])); 369 | alexer_sb_append_cstr(sb, "`"); 370 | break; 371 | case ALEXER_COUNT_KINDS: 372 | default: ALEXER_ASSERT(0 && "unreachable"); 373 | } 374 | } 375 | 376 | void alexer_sb_append_token_display(Alexer_String_Builder *sb, Alexer *l, Alexer_Token t) 377 | { 378 | uint64_t kind = ALEXER_KIND(t.id); 379 | uint64_t index = ALEXER_INDEX(t.id); 380 | switch (kind) { 381 | case ALEXER_INVALID: 382 | case ALEXER_END: 383 | case ALEXER_STRING: 384 | case ALEXER_INT: 385 | case ALEXER_SYMBOL: 386 | alexer_sb_append_cstr(sb, alexer_kind_name(kind)); 387 | alexer_sb_append_cstr(sb, " `"); 388 | alexer_da_append_many(sb, t.begin, t.end - t.begin); 389 | alexer_sb_append_cstr(sb, "`"); 390 | break; 391 | case ALEXER_KEYWORD: 392 | alexer_sb_append_cstr(sb, alexer_kind_name(kind)); 393 | alexer_sb_append_cstr(sb, " `"); 394 | alexer_sb_append_cstr(sb, (ALEXER_ASSERT(index < l->keywords_count), l->keywords[index])); 395 | alexer_sb_append_cstr(sb, "`"); 396 | break; 397 | case ALEXER_PUNCT: 398 | alexer_sb_append_cstr(sb, alexer_kind_name(kind)); 399 | alexer_sb_append_cstr(sb, " `"); 400 | alexer_sb_append_cstr(sb, (ALEXER_ASSERT(index < l->puncts_count), l->puncts[index])); 401 | alexer_sb_append_cstr(sb, "`"); 402 | break; 403 | case ALEXER_COUNT_KINDS: 404 | default: ALEXER_ASSERT(0 && "unreachable"); 405 | } 406 | } 407 | 408 | bool alexer_expect_id(Alexer *l, Alexer_Token t, uint64_t id) 409 | { 410 | return alexer_expect_one_of_ids(l, t, &id, 1); 411 | } 412 | 413 | // TODO: Reserve a special index value of the id as "any index". Potentially 0xFFFFFFFF. 414 | bool alexer_expect_one_of_ids(Alexer *l, Alexer_Token t, uint64_t *ids, size_t ids_count) 415 | { 416 | bool result = false; 417 | Alexer_String_Builder sb = {0}; 418 | 419 | for (size_t i = 0; i < ids_count; ++i) { 420 | if (t.id == ids[i]) { 421 | alexer_return_defer(true); 422 | } 423 | } 424 | 425 | alexer_sb_append_cstr(&sb, "Expected "); 426 | for (size_t i = 0; i < ids_count; ++i) { 427 | if (i > 0) alexer_sb_append_cstr(&sb, ", "); 428 | alexer_sb_append_id_display(&sb, l, ids[i]); 429 | } 430 | alexer_sb_append_cstr(&sb, " but got "); 431 | alexer_sb_append_token_display(&sb, l, t); 432 | alexer_sb_append_null(&sb); 433 | 434 | l->diagf(t.loc, "ERROR", "%s", sb.items); 435 | 436 | defer: 437 | free(sb.items); 438 | return result; 439 | } 440 | 441 | void alexer_default_diagf(Alexer_Loc loc, const char *level, const char *fmt, ...) 442 | { 443 | va_list args; 444 | va_start(args, fmt); 445 | fprintf(stderr, Alexer_Loc_Fmt": %s: ", Alexer_Loc_Arg(loc), level); 446 | vfprintf(stderr, fmt, args); 447 | fprintf(stderr, "\n"); 448 | va_end(args); 449 | } 450 | 451 | void alexer_ignore_diagf(Alexer_Loc loc, const char *level, const char *fmt, ...) 452 | { 453 | (void) loc; 454 | (void) level; 455 | (void) fmt; 456 | } 457 | 458 | bool alexer_token_text_equal(Alexer_Token a, Alexer_Token b) 459 | { 460 | size_t na = a.end - a.begin; 461 | size_t nb = b.end - b.begin; 462 | if (na != nb) return false; 463 | return memcmp(a.begin, b.begin, na) == 0; 464 | } 465 | 466 | bool alexer_token_text_equal_cstr(Alexer_Token a, const char *b) 467 | { 468 | size_t na = a.end - a.begin; 469 | size_t nb = strlen(b); 470 | if (na != nb) return false; 471 | return memcmp(a.begin, b, na) == 0; 472 | } 473 | 474 | Alexer_State alexer_save(Alexer *l) 475 | { 476 | return (Alexer_State) { 477 | .cur = l->cur, 478 | .bol = l->bol, 479 | .row = l->row, 480 | }; 481 | } 482 | 483 | void alexer_rewind(Alexer *l, Alexer_State s) 484 | { 485 | l->cur = s.cur; 486 | l->bol = s.bol; 487 | l->row = s.row; 488 | } 489 | 490 | #endif // ALEXER_IMPLEMENTATION 491 | -------------------------------------------------------------------------------- /example.c: -------------------------------------------------------------------------------- 1 | #define ALEXER_IMPLEMENTATION 2 | #include "alexer.h" 3 | 4 | typedef enum { 5 | PUNCT_PLUS, 6 | PUNCT_MULT, 7 | PUNCT_EQUALS, 8 | PUNCT_OPAREN, 9 | PUNCT_CPAREN, 10 | PUNCT_OCURLY, 11 | PUNCT_CCURLY, 12 | PUNCT_SEMICOLON, 13 | COUNT_PUNCTS, 14 | } Punct_Index; 15 | 16 | static_assert(COUNT_PUNCTS == 8, "Amount of puncts have changed"); 17 | const char *puncts[COUNT_PUNCTS] = { 18 | [PUNCT_PLUS] = "+", 19 | [PUNCT_MULT] = "*", 20 | [PUNCT_OPAREN] = "(", 21 | [PUNCT_CPAREN] = ")", 22 | [PUNCT_OCURLY] = "{", 23 | [PUNCT_CCURLY] = "}", 24 | [PUNCT_EQUALS] = "==", 25 | [PUNCT_SEMICOLON] = ";", 26 | }; 27 | 28 | typedef enum { 29 | KEYWORD_IF, 30 | KEYWORD_RETURN, 31 | COUNT_KEYWORDS, 32 | } Keyword_Index; 33 | 34 | static_assert(COUNT_KEYWORDS == 2, "Amount of keywords have changed"); 35 | const char *keywords[COUNT_KEYWORDS] = { 36 | [KEYWORD_IF] = "if", 37 | [KEYWORD_RETURN] = "return", 38 | }; 39 | 40 | const char *sl_comments[] = { 41 | "//", 42 | "#", 43 | }; 44 | 45 | Alexer_ML_Comments ml_comments[] = { 46 | {"/*", "*/"}, 47 | }; 48 | 49 | int main() 50 | { 51 | const char *file_path = "example/path"; // The file path is only needed for diagnostic message 52 | const char *content = 53 | "#include \n" 54 | "if (a == 17*2 + 35) { // single line comment\n" 55 | " /* multi\n" 56 | " * line\n" 57 | " * comment\n" 58 | " */\n" 59 | " return b;\n" 60 | "}\n"; 61 | Alexer l = alexer_create(file_path, content, strlen(content)); 62 | l.puncts = puncts; 63 | l.puncts_count = ALEXER_ARRAY_LEN(puncts); 64 | l.keywords = keywords; 65 | l.keywords_count = ALEXER_ARRAY_LEN(keywords); 66 | l.sl_comments = sl_comments; 67 | l.sl_comments_count = ALEXER_ARRAY_LEN(sl_comments); 68 | l.ml_comments = ml_comments; 69 | l.ml_comments_count = ALEXER_ARRAY_LEN(ml_comments); 70 | Alexer_Token t = {0}; 71 | while (alexer_get_token(&l, &t)) { 72 | l.diagf(t.loc, "INFO", "%s: %.*s", alexer_kind_name(ALEXER_KIND(t.id)), t.end - t.begin, t.begin); 73 | } 74 | if (!alexer_expect_id(&l, t, ALEXER_END)) return 1; 75 | return 0; 76 | } 77 | -------------------------------------------------------------------------------- /nob.c: -------------------------------------------------------------------------------- 1 | #define NOB_IMPLEMENTATION 2 | #define NOB_STRIP_PREFIX 3 | #include "nob.h" 4 | 5 | int main(int argc, char **argv) 6 | { 7 | NOB_GO_REBUILD_URSELF(argc, argv); 8 | Cmd cmd = {0}; 9 | cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-Wswitch-enum", "-ggdb", "-o", "example", "example.c"); 10 | if (!cmd_run_sync_and_reset(&cmd)) return 1; 11 | cmd_append(&cmd, "./example"); 12 | if (!cmd_run_sync_and_reset(&cmd)) return 1; 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /nob.h: -------------------------------------------------------------------------------- 1 | /* nob - v1.8.0 - Public Domain - https://github.com/tsoding/nob 2 | 3 | This library is the next generation of the [NoBuild](https://github.com/tsoding/nobuild) idea. 4 | 5 | # Quick Example 6 | 7 | ```c 8 | // nob.c 9 | #define NOB_IMPLEMENTATION 10 | #include "nob.h" 11 | 12 | int main(int argc, char **argv) 13 | { 14 | NOB_GO_REBUILD_URSELF(argc, argv); 15 | Nob_Cmd cmd = {0}; 16 | nob_cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c"); 17 | if (!nob_cmd_run_sync(cmd)) return 1; 18 | return 0; 19 | } 20 | ``` 21 | 22 | ```console 23 | $ cc -o nob nob.c 24 | $ ./nob 25 | ``` 26 | 27 | The `nob` automatically rebuilds itself if `nob.c` is modified thanks to 28 | the `NOB_GO_REBUILD_URSELF` macro (don't forget to check out how it works below) 29 | 30 | # Stripping off `nob_` Prefixes 31 | 32 | Since Pure C does not have any namespaces we prefix each name of the API with the `nob_` to avoid any 33 | potential conflicts with any other names in your code. But sometimes it is very annoying and makes 34 | the code noisy. If you know that none of the names from nob.h conflict with anything in your code 35 | you can enable NOB_STRIP_PREFIX macro and just drop all the prefixes: 36 | 37 | ```c 38 | // nob.c 39 | #define NOB_IMPLEMENTATION 40 | #define NOB_STRIP_PREFIX 41 | #include "nob.h" 42 | 43 | int main(int argc, char **argv) 44 | { 45 | NOB_GO_REBUILD_URSELF(argc, argv); 46 | Cmd cmd = {0}; 47 | cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c"); 48 | if (!cmd_run_sync(cmd)) return 1; 49 | return 0; 50 | } 51 | ``` 52 | 53 | Not all the names have strippable prefixes. All the redefinable names like `NOB_GO_REBUILD_URSELF` 54 | for instance will retain their prefix even if NOB_STRIP_PREFIX is enabled. Notable exception is the 55 | nob_log() function. Stripping away the prefix results in log() which was historically always referring 56 | to the natural logarithmic function that is already defined in math.h. So there is no reason to strip 57 | off the prefix for nob_log(). 58 | 59 | The prefixes are stripped off only on the level of preprocessor. The names of the functions in the 60 | compiled object file will still retain the `nob_` prefix. Keep that in mind when you FFI with nob.h 61 | from other languages (for whatever reason). 62 | 63 | If only few specific names create conflicts for you, you can just #undef those names after the 64 | `#include ` since they are macros anyway. 65 | */ 66 | 67 | #ifndef NOB_H_ 68 | #define NOB_H_ 69 | 70 | #define NOB_ASSERT assert 71 | #define NOB_REALLOC realloc 72 | #define NOB_FREE free 73 | 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | #include 80 | #include 81 | #include 82 | #include 83 | 84 | #ifdef _WIN32 85 | # define WIN32_LEAN_AND_MEAN 86 | # define _WINUSER_ 87 | # define _WINGDI_ 88 | # define _IMM_ 89 | # define _WINCON_ 90 | # include 91 | # include 92 | # include 93 | #else 94 | # include 95 | # include 96 | # include 97 | # include 98 | # include 99 | #endif 100 | 101 | #ifdef _WIN32 102 | # define NOB_LINE_END "\r\n" 103 | #else 104 | # define NOB_LINE_END "\n" 105 | #endif 106 | 107 | #define NOB_UNUSED(value) (void)(value) 108 | #define NOB_TODO(message) do { fprintf(stderr, "%s:%d: TODO: %s\n", __FILE__, __LINE__, message); abort(); } while(0) 109 | #define NOB_UNREACHABLE(message) do { fprintf(stderr, "%s:%d: UNREACHABLE: %s\n", __FILE__, __LINE__, message); abort(); } while(0) 110 | 111 | #define NOB_ARRAY_LEN(array) (sizeof(array)/sizeof(array[0])) 112 | #define NOB_ARRAY_GET(array, index) \ 113 | (NOB_ASSERT((size_t)index < NOB_ARRAY_LEN(array)), array[(size_t)index]) 114 | 115 | typedef enum { 116 | NOB_INFO, 117 | NOB_WARNING, 118 | NOB_ERROR, 119 | NOB_NO_LOGS, 120 | } Nob_Log_Level; 121 | 122 | // Any messages with the level below nob_minimal_log_level are going to be suppressed. 123 | extern Nob_Log_Level nob_minimal_log_level; 124 | 125 | void nob_log(Nob_Log_Level level, const char *fmt, ...); 126 | 127 | // It is an equivalent of shift command from bash. It basically pops an element from 128 | // the beginning of a sized array. 129 | #define nob_shift(xs, xs_sz) (NOB_ASSERT((xs_sz) > 0), (xs_sz)--, *(xs)++) 130 | // NOTE: nob_shift_args() is an alias for an old variant of nob_shift that only worked with 131 | // the command line arguments passed to the main() function. nob_shift() is more generic. 132 | // So nob_shift_args() is semi-deprecated, but I don't see much reason to urgently 133 | // remove it. This alias does not hurt anybody. 134 | #define nob_shift_args(argc, argv) nob_shift(*argv, *argc) 135 | 136 | typedef struct { 137 | const char **items; 138 | size_t count; 139 | size_t capacity; 140 | } Nob_File_Paths; 141 | 142 | typedef enum { 143 | NOB_FILE_REGULAR = 0, 144 | NOB_FILE_DIRECTORY, 145 | NOB_FILE_SYMLINK, 146 | NOB_FILE_OTHER, 147 | } Nob_File_Type; 148 | 149 | bool nob_mkdir_if_not_exists(const char *path); 150 | bool nob_copy_file(const char *src_path, const char *dst_path); 151 | bool nob_copy_directory_recursively(const char *src_path, const char *dst_path); 152 | bool nob_read_entire_dir(const char *parent, Nob_File_Paths *children); 153 | bool nob_write_entire_file(const char *path, const void *data, size_t size); 154 | Nob_File_Type nob_get_file_type(const char *path); 155 | 156 | #define nob_return_defer(value) do { result = (value); goto defer; } while(0) 157 | 158 | // Initial capacity of a dynamic array 159 | #ifndef NOB_DA_INIT_CAP 160 | #define NOB_DA_INIT_CAP 256 161 | #endif 162 | 163 | // Append an item to a dynamic array 164 | #define nob_da_append(da, item) \ 165 | do { \ 166 | if ((da)->count >= (da)->capacity) { \ 167 | (da)->capacity = (da)->capacity == 0 ? NOB_DA_INIT_CAP : (da)->capacity*2; \ 168 | (da)->items = NOB_REALLOC((da)->items, (da)->capacity*sizeof(*(da)->items)); \ 169 | NOB_ASSERT((da)->items != NULL && "Buy more RAM lol"); \ 170 | } \ 171 | \ 172 | (da)->items[(da)->count++] = (item); \ 173 | } while (0) 174 | 175 | #define nob_da_free(da) NOB_FREE((da).items) 176 | 177 | // Append several items to a dynamic array 178 | #define nob_da_append_many(da, new_items, new_items_count) \ 179 | do { \ 180 | if ((da)->count + (new_items_count) > (da)->capacity) { \ 181 | if ((da)->capacity == 0) { \ 182 | (da)->capacity = NOB_DA_INIT_CAP; \ 183 | } \ 184 | while ((da)->count + (new_items_count) > (da)->capacity) { \ 185 | (da)->capacity *= 2; \ 186 | } \ 187 | (da)->items = NOB_REALLOC((da)->items, (da)->capacity*sizeof(*(da)->items)); \ 188 | NOB_ASSERT((da)->items != NULL && "Buy more RAM lol"); \ 189 | } \ 190 | memcpy((da)->items + (da)->count, (new_items), (new_items_count)*sizeof(*(da)->items)); \ 191 | (da)->count += (new_items_count); \ 192 | } while (0) 193 | 194 | typedef struct { 195 | char *items; 196 | size_t count; 197 | size_t capacity; 198 | } Nob_String_Builder; 199 | 200 | bool nob_read_entire_file(const char *path, Nob_String_Builder *sb); 201 | 202 | // Append a sized buffer to a string builder 203 | #define nob_sb_append_buf(sb, buf, size) nob_da_append_many(sb, buf, size) 204 | 205 | // Append a NULL-terminated string to a string builder 206 | #define nob_sb_append_cstr(sb, cstr) \ 207 | do { \ 208 | const char *s = (cstr); \ 209 | size_t n = strlen(s); \ 210 | nob_da_append_many(sb, s, n); \ 211 | } while (0) 212 | 213 | // Append a single NULL character at the end of a string builder. So then you can 214 | // use it a NULL-terminated C string 215 | #define nob_sb_append_null(sb) nob_da_append_many(sb, "", 1) 216 | 217 | // Free the memory allocated by a string builder 218 | #define nob_sb_free(sb) NOB_FREE((sb).items) 219 | 220 | // Process handle 221 | #ifdef _WIN32 222 | typedef HANDLE Nob_Proc; 223 | #define NOB_INVALID_PROC INVALID_HANDLE_VALUE 224 | #else 225 | typedef int Nob_Proc; 226 | #define NOB_INVALID_PROC (-1) 227 | #endif // _WIN32 228 | 229 | typedef struct { 230 | Nob_Proc *items; 231 | size_t count; 232 | size_t capacity; 233 | } Nob_Procs; 234 | 235 | bool nob_procs_wait(Nob_Procs procs); 236 | bool nob_procs_wait_and_reset(Nob_Procs *procs); 237 | 238 | // Wait until the process has finished 239 | bool nob_proc_wait(Nob_Proc proc); 240 | 241 | // A command - the main workhorse of Nob. Nob is all about building commands an running them 242 | typedef struct { 243 | const char **items; 244 | size_t count; 245 | size_t capacity; 246 | } Nob_Cmd; 247 | 248 | // Render a string representation of a command into a string builder. Keep in mind the the 249 | // string builder is not NULL-terminated by default. Use nob_sb_append_null if you plan to 250 | // use it as a C string. 251 | void nob_cmd_render(Nob_Cmd cmd, Nob_String_Builder *render); 252 | 253 | #define nob_cmd_append(cmd, ...) \ 254 | nob_da_append_many(cmd, \ 255 | ((const char*[]){__VA_ARGS__}), \ 256 | (sizeof((const char*[]){__VA_ARGS__})/sizeof(const char*))) 257 | 258 | #define nob_cmd_extend(cmd, other_cmd) \ 259 | nob_da_append_many(cmd, (other_cmd)->items, (other_cmd)->count) 260 | 261 | // Free all the memory allocated by command arguments 262 | #define nob_cmd_free(cmd) NOB_FREE(cmd.items) 263 | 264 | // Run command asynchronously 265 | Nob_Proc nob_cmd_run_async(Nob_Cmd cmd); 266 | // NOTE: nob_cmd_run_async_and_reset() is just like nob_cmd_run_async() except it also resets cmd.count to 0 267 | // so the Nob_Cmd instance can be seamlessly used several times in a row 268 | Nob_Proc nob_cmd_run_async_and_reset(Nob_Cmd *cmd); 269 | 270 | // Run command synchronously 271 | bool nob_cmd_run_sync(Nob_Cmd cmd); 272 | // NOTE: nob_cmd_run_sync_and_reset() is just like nob_cmd_run_sync() except it also resets cmd.count to 0 273 | // so the Nob_Cmd instance can be seamlessly used several times in a row 274 | bool nob_cmd_run_sync_and_reset(Nob_Cmd *cmd); 275 | 276 | #ifndef NOB_TEMP_CAPACITY 277 | #define NOB_TEMP_CAPACITY (8*1024*1024) 278 | #endif // NOB_TEMP_CAPACITY 279 | char *nob_temp_strdup(const char *cstr); 280 | void *nob_temp_alloc(size_t size); 281 | char *nob_temp_sprintf(const char *format, ...); 282 | void nob_temp_reset(void); 283 | size_t nob_temp_save(void); 284 | void nob_temp_rewind(size_t checkpoint); 285 | 286 | bool nob_rename(const char *old_path, const char *new_path); 287 | int nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count); 288 | int nob_needs_rebuild1(const char *output_path, const char *input_path); 289 | int nob_file_exists(const char *file_path); 290 | const char *nob_get_current_dir_temp(void); 291 | bool nob_set_current_dir(const char *path); 292 | 293 | // TODO: add MinGW support for Go Rebuild Urself™ Technology 294 | #ifndef NOB_REBUILD_URSELF 295 | # if _WIN32 296 | # if defined(__GNUC__) 297 | # define NOB_REBUILD_URSELF(binary_path, source_path) "gcc", "-o", binary_path, source_path 298 | # elif defined(__clang__) 299 | # define NOB_REBUILD_URSELF(binary_path, source_path) "clang", "-o", binary_path, source_path 300 | # elif defined(_MSC_VER) 301 | # define NOB_REBUILD_URSELF(binary_path, source_path) "cl.exe", nob_temp_sprintf("/Fe:%s", (binary_path)), source_path 302 | # endif 303 | # else 304 | # define NOB_REBUILD_URSELF(binary_path, source_path) "cc", "-o", binary_path, source_path 305 | # endif 306 | #endif 307 | 308 | // Go Rebuild Urself™ Technology 309 | // 310 | // How to use it: 311 | // int main(int argc, char** argv) { 312 | // NOB_GO_REBUILD_URSELF(argc, argv); 313 | // // actual work 314 | // return 0; 315 | // } 316 | // 317 | // After your added this macro every time you run ./nob it will detect 318 | // that you modified its original source code and will try to rebuild itself 319 | // before doing any actual work. So you only need to bootstrap your build system 320 | // once. 321 | // 322 | // The modification is detected by comparing the last modified times of the executable 323 | // and its source code. The same way the make utility usually does it. 324 | // 325 | // The rebuilding is done by using the NOB_REBUILD_URSELF macro which you can redefine 326 | // if you need a special way of bootstraping your build system. (which I personally 327 | // do not recommend since the whole idea of NoBuild is to keep the process of bootstrapping 328 | // as simple as possible and doing all of the actual work inside of ./nob) 329 | // 330 | void nob__go_rebuild_urself(const char *source_path, int argc, char **argv); 331 | #define NOB_GO_REBUILD_URSELF(argc, argv) nob__go_rebuild_urself(__FILE__, argc, argv) 332 | 333 | typedef struct { 334 | size_t count; 335 | const char *data; 336 | } Nob_String_View; 337 | 338 | const char *nob_temp_sv_to_cstr(Nob_String_View sv); 339 | 340 | Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim); 341 | Nob_String_View nob_sv_trim(Nob_String_View sv); 342 | Nob_String_View nob_sv_trim_left(Nob_String_View sv); 343 | Nob_String_View nob_sv_trim_right(Nob_String_View sv); 344 | bool nob_sv_eq(Nob_String_View a, Nob_String_View b); 345 | bool nob_sv_end_with(Nob_String_View sv, const char *cstr); 346 | Nob_String_View nob_sv_from_cstr(const char *cstr); 347 | Nob_String_View nob_sv_from_parts(const char *data, size_t count); 348 | // nob_sb_to_sv() enables you to just view Nob_String_Builder as Nob_String_View 349 | #define nob_sb_to_sv(sb) nob_sv_from_parts((sb).items, (sb).count) 350 | 351 | // printf macros for String_View 352 | #ifndef SV_Fmt 353 | #define SV_Fmt "%.*s" 354 | #endif // SV_Fmt 355 | #ifndef SV_Arg 356 | #define SV_Arg(sv) (int) (sv).count, (sv).data 357 | #endif // SV_Arg 358 | // USAGE: 359 | // String_View name = ...; 360 | // printf("Name: "SV_Fmt"\n", SV_Arg(name)); 361 | 362 | 363 | // minirent.h HEADER BEGIN //////////////////////////////////////// 364 | // Copyright 2021 Alexey Kutepov 365 | // 366 | // Permission is hereby granted, free of charge, to any person obtaining 367 | // a copy of this software and associated documentation files (the 368 | // "Software"), to deal in the Software without restriction, including 369 | // without limitation the rights to use, copy, modify, merge, publish, 370 | // distribute, sublicense, and/or sell copies of the Software, and to 371 | // permit persons to whom the Software is furnished to do so, subject to 372 | // the following conditions: 373 | // 374 | // The above copyright notice and this permission notice shall be 375 | // included in all copies or substantial portions of the Software. 376 | // 377 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 378 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 379 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 380 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 381 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 382 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 383 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 384 | // 385 | // ============================================================ 386 | // 387 | // minirent — 0.0.1 — A subset of dirent interface for Windows. 388 | // 389 | // https://github.com/tsoding/minirent 390 | // 391 | // ============================================================ 392 | // 393 | // ChangeLog (https://semver.org/ is implied) 394 | // 395 | // 0.0.2 Automatically include dirent.h on non-Windows 396 | // platforms 397 | // 0.0.1 First Official Release 398 | 399 | #ifndef _WIN32 400 | #include 401 | #else // _WIN32 402 | 403 | #define WIN32_LEAN_AND_MEAN 404 | #include "windows.h" 405 | 406 | struct dirent 407 | { 408 | char d_name[MAX_PATH+1]; 409 | }; 410 | 411 | typedef struct DIR DIR; 412 | 413 | static DIR *opendir(const char *dirpath); 414 | static struct dirent *readdir(DIR *dirp); 415 | static int closedir(DIR *dirp); 416 | 417 | #endif // _WIN32 418 | // minirent.h HEADER END //////////////////////////////////////// 419 | 420 | #ifdef _WIN32 421 | 422 | char *nob_win32_error_message(DWORD err); 423 | 424 | #endif // _WIN32 425 | 426 | #endif // NOB_H_ 427 | 428 | #ifdef NOB_IMPLEMENTATION 429 | 430 | // Any messages with the level below nob_minimal_log_level are going to be suppressed. 431 | Nob_Log_Level nob_minimal_log_level = NOB_INFO; 432 | 433 | #ifdef _WIN32 434 | 435 | // Base on https://stackoverflow.com/a/75644008 436 | // > .NET Core uses 4096 * sizeof(WCHAR) buffer on stack for FormatMessageW call. And...thats it. 437 | // > 438 | // > https://github.com/dotnet/runtime/blob/3b63eb1346f1ddbc921374a5108d025662fb5ffd/src/coreclr/utilcode/posterror.cpp#L264-L265 439 | #ifndef NOB_WIN32_ERR_MSG_SIZE 440 | #define NOB_WIN32_ERR_MSG_SIZE (4 * 1024) 441 | #endif // NOB_WIN32_ERR_MSG_SIZE 442 | 443 | char *nob_win32_error_message(DWORD err) { 444 | static char win32ErrMsg[NOB_WIN32_ERR_MSG_SIZE] = {0}; 445 | DWORD errMsgSize = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, LANG_USER_DEFAULT, win32ErrMsg, 446 | NOB_WIN32_ERR_MSG_SIZE, NULL); 447 | 448 | if (errMsgSize == 0) { 449 | if (GetLastError() != ERROR_MR_MID_NOT_FOUND) { 450 | if (sprintf(win32ErrMsg, "Could not get error message for 0x%lX", err) > 0) { 451 | return (char *)&win32ErrMsg; 452 | } else { 453 | return NULL; 454 | } 455 | } else { 456 | if (sprintf(win32ErrMsg, "Invalid Windows Error code (0x%lX)", err) > 0) { 457 | return (char *)&win32ErrMsg; 458 | } else { 459 | return NULL; 460 | } 461 | } 462 | } 463 | 464 | while (errMsgSize > 1 && isspace(win32ErrMsg[errMsgSize - 1])) { 465 | win32ErrMsg[--errMsgSize] = '\0'; 466 | } 467 | 468 | return win32ErrMsg; 469 | } 470 | 471 | #endif // _WIN32 472 | 473 | // The implementation idea is stolen from https://github.com/zhiayang/nabs 474 | void nob__go_rebuild_urself(const char *source_path, int argc, char **argv) 475 | { 476 | const char *binary_path = nob_shift(argv, argc); 477 | #ifdef _WIN32 478 | // On Windows executables almost always invoked without extension, so 479 | // it's ./nob, not ./nob.exe. For renaming the extension is a must. 480 | if (!nob_sv_end_with(nob_sv_from_cstr(binary_path), ".exe")) { 481 | binary_path = nob_temp_sprintf("%s.exe", binary_path); 482 | } 483 | #endif 484 | 485 | int rebuild_is_needed = nob_needs_rebuild1(binary_path, source_path); 486 | if (rebuild_is_needed < 0) exit(1); // error 487 | if (!rebuild_is_needed) return; // no rebuild is needed 488 | 489 | Nob_Cmd cmd = {0}; 490 | 491 | const char *old_binary_path = nob_temp_sprintf("%s.old", binary_path); 492 | 493 | if (!nob_rename(binary_path, old_binary_path)) exit(1); 494 | nob_cmd_append(&cmd, NOB_REBUILD_URSELF(binary_path, source_path)); 495 | if (!nob_cmd_run_sync_and_reset(&cmd)) { 496 | nob_rename(old_binary_path, binary_path); 497 | exit(1); 498 | } 499 | 500 | nob_cmd_append(&cmd, binary_path); 501 | nob_da_append_many(&cmd, argv, argc); 502 | if (!nob_cmd_run_sync_and_reset(&cmd)) exit(1); 503 | exit(0); 504 | } 505 | 506 | static size_t nob_temp_size = 0; 507 | static char nob_temp[NOB_TEMP_CAPACITY] = {0}; 508 | 509 | bool nob_mkdir_if_not_exists(const char *path) 510 | { 511 | #ifdef _WIN32 512 | int result = mkdir(path); 513 | #else 514 | int result = mkdir(path, 0755); 515 | #endif 516 | if (result < 0) { 517 | if (errno == EEXIST) { 518 | nob_log(NOB_INFO, "directory `%s` already exists", path); 519 | return true; 520 | } 521 | nob_log(NOB_ERROR, "could not create directory `%s`: %s", path, strerror(errno)); 522 | return false; 523 | } 524 | 525 | nob_log(NOB_INFO, "created directory `%s`", path); 526 | return true; 527 | } 528 | 529 | bool nob_copy_file(const char *src_path, const char *dst_path) 530 | { 531 | nob_log(NOB_INFO, "copying %s -> %s", src_path, dst_path); 532 | #ifdef _WIN32 533 | if (!CopyFile(src_path, dst_path, FALSE)) { 534 | nob_log(NOB_ERROR, "Could not copy file: %s", nob_win32_error_message(GetLastError())); 535 | return false; 536 | } 537 | return true; 538 | #else 539 | int src_fd = -1; 540 | int dst_fd = -1; 541 | size_t buf_size = 32*1024; 542 | char *buf = NOB_REALLOC(NULL, buf_size); 543 | NOB_ASSERT(buf != NULL && "Buy more RAM lol!!"); 544 | bool result = true; 545 | 546 | src_fd = open(src_path, O_RDONLY); 547 | if (src_fd < 0) { 548 | nob_log(NOB_ERROR, "Could not open file %s: %s", src_path, strerror(errno)); 549 | nob_return_defer(false); 550 | } 551 | 552 | struct stat src_stat; 553 | if (fstat(src_fd, &src_stat) < 0) { 554 | nob_log(NOB_ERROR, "Could not get mode of file %s: %s", src_path, strerror(errno)); 555 | nob_return_defer(false); 556 | } 557 | 558 | dst_fd = open(dst_path, O_CREAT | O_TRUNC | O_WRONLY, src_stat.st_mode); 559 | if (dst_fd < 0) { 560 | nob_log(NOB_ERROR, "Could not create file %s: %s", dst_path, strerror(errno)); 561 | nob_return_defer(false); 562 | } 563 | 564 | for (;;) { 565 | ssize_t n = read(src_fd, buf, buf_size); 566 | if (n == 0) break; 567 | if (n < 0) { 568 | nob_log(NOB_ERROR, "Could not read from file %s: %s", src_path, strerror(errno)); 569 | nob_return_defer(false); 570 | } 571 | char *buf2 = buf; 572 | while (n > 0) { 573 | ssize_t m = write(dst_fd, buf2, n); 574 | if (m < 0) { 575 | nob_log(NOB_ERROR, "Could not write to file %s: %s", dst_path, strerror(errno)); 576 | nob_return_defer(false); 577 | } 578 | n -= m; 579 | buf2 += m; 580 | } 581 | } 582 | 583 | defer: 584 | free(buf); 585 | close(src_fd); 586 | close(dst_fd); 587 | return result; 588 | #endif 589 | } 590 | 591 | void nob_cmd_render(Nob_Cmd cmd, Nob_String_Builder *render) 592 | { 593 | for (size_t i = 0; i < cmd.count; ++i) { 594 | const char *arg = cmd.items[i]; 595 | if (arg == NULL) break; 596 | if (i > 0) nob_sb_append_cstr(render, " "); 597 | if (!strchr(arg, ' ')) { 598 | nob_sb_append_cstr(render, arg); 599 | } else { 600 | nob_da_append(render, '\''); 601 | nob_sb_append_cstr(render, arg); 602 | nob_da_append(render, '\''); 603 | } 604 | } 605 | } 606 | 607 | Nob_Proc nob_cmd_run_async(Nob_Cmd cmd) 608 | { 609 | if (cmd.count < 1) { 610 | nob_log(NOB_ERROR, "Could not run empty command"); 611 | return NOB_INVALID_PROC; 612 | } 613 | 614 | Nob_String_Builder sb = {0}; 615 | nob_cmd_render(cmd, &sb); 616 | nob_sb_append_null(&sb); 617 | nob_log(NOB_INFO, "CMD: %s", sb.items); 618 | nob_sb_free(sb); 619 | memset(&sb, 0, sizeof(sb)); 620 | 621 | #ifdef _WIN32 622 | // https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output 623 | 624 | STARTUPINFO siStartInfo; 625 | ZeroMemory(&siStartInfo, sizeof(siStartInfo)); 626 | siStartInfo.cb = sizeof(STARTUPINFO); 627 | // NOTE: theoretically setting NULL to std handles should not be a problem 628 | // https://docs.microsoft.com/en-us/windows/console/getstdhandle?redirectedfrom=MSDN#attachdetach-behavior 629 | // TODO: check for errors in GetStdHandle 630 | siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); 631 | siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); 632 | siStartInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); 633 | siStartInfo.dwFlags |= STARTF_USESTDHANDLES; 634 | 635 | PROCESS_INFORMATION piProcInfo; 636 | ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); 637 | 638 | // TODO: use a more reliable rendering of the command instead of cmd_render 639 | // cmd_render is for logging primarily 640 | nob_cmd_render(cmd, &sb); 641 | nob_sb_append_null(&sb); 642 | BOOL bSuccess = CreateProcessA(NULL, sb.items, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo); 643 | nob_sb_free(sb); 644 | 645 | if (!bSuccess) { 646 | nob_log(NOB_ERROR, "Could not create child process: %s", nob_win32_error_message(GetLastError())); 647 | return NOB_INVALID_PROC; 648 | } 649 | 650 | CloseHandle(piProcInfo.hThread); 651 | 652 | return piProcInfo.hProcess; 653 | #else 654 | pid_t cpid = fork(); 655 | if (cpid < 0) { 656 | nob_log(NOB_ERROR, "Could not fork child process: %s", strerror(errno)); 657 | return NOB_INVALID_PROC; 658 | } 659 | 660 | if (cpid == 0) { 661 | // NOTE: This leaks a bit of memory in the child process. 662 | // But do we actually care? It's a one off leak anyway... 663 | Nob_Cmd cmd_null = {0}; 664 | nob_da_append_many(&cmd_null, cmd.items, cmd.count); 665 | nob_cmd_append(&cmd_null, NULL); 666 | 667 | if (execvp(cmd.items[0], (char * const*) cmd_null.items) < 0) { 668 | nob_log(NOB_ERROR, "Could not exec child process: %s", strerror(errno)); 669 | exit(1); 670 | } 671 | NOB_UNREACHABLE("nob_cmd_run_async"); 672 | } 673 | 674 | return cpid; 675 | #endif 676 | } 677 | 678 | Nob_Proc nob_cmd_run_async_and_reset(Nob_Cmd *cmd) 679 | { 680 | Nob_Proc proc = nob_cmd_run_async(*cmd); 681 | cmd->count = 0; 682 | return proc; 683 | } 684 | 685 | bool nob_procs_wait(Nob_Procs procs) 686 | { 687 | bool success = true; 688 | for (size_t i = 0; i < procs.count; ++i) { 689 | success = nob_proc_wait(procs.items[i]) && success; 690 | } 691 | return success; 692 | } 693 | 694 | bool nob_procs_wait_and_reset(Nob_Procs *procs) 695 | { 696 | bool success = nob_procs_wait(*procs); 697 | procs->count = 0; 698 | return success; 699 | } 700 | 701 | bool nob_proc_wait(Nob_Proc proc) 702 | { 703 | if (proc == NOB_INVALID_PROC) return false; 704 | 705 | #ifdef _WIN32 706 | DWORD result = WaitForSingleObject( 707 | proc, // HANDLE hHandle, 708 | INFINITE // DWORD dwMilliseconds 709 | ); 710 | 711 | if (result == WAIT_FAILED) { 712 | nob_log(NOB_ERROR, "could not wait on child process: %s", nob_win32_error_message(GetLastError())); 713 | return false; 714 | } 715 | 716 | DWORD exit_status; 717 | if (!GetExitCodeProcess(proc, &exit_status)) { 718 | nob_log(NOB_ERROR, "could not get process exit code: %s", nob_win32_error_message(GetLastError())); 719 | return false; 720 | } 721 | 722 | if (exit_status != 0) { 723 | nob_log(NOB_ERROR, "command exited with exit code %lu", exit_status); 724 | return false; 725 | } 726 | 727 | CloseHandle(proc); 728 | 729 | return true; 730 | #else 731 | for (;;) { 732 | int wstatus = 0; 733 | if (waitpid(proc, &wstatus, 0) < 0) { 734 | nob_log(NOB_ERROR, "could not wait on command (pid %d): %s", proc, strerror(errno)); 735 | return false; 736 | } 737 | 738 | if (WIFEXITED(wstatus)) { 739 | int exit_status = WEXITSTATUS(wstatus); 740 | if (exit_status != 0) { 741 | nob_log(NOB_ERROR, "command exited with exit code %d", exit_status); 742 | return false; 743 | } 744 | 745 | break; 746 | } 747 | 748 | if (WIFSIGNALED(wstatus)) { 749 | nob_log(NOB_ERROR, "command process was terminated by %s", strsignal(WTERMSIG(wstatus))); 750 | return false; 751 | } 752 | } 753 | 754 | return true; 755 | #endif 756 | } 757 | 758 | bool nob_cmd_run_sync(Nob_Cmd cmd) 759 | { 760 | Nob_Proc p = nob_cmd_run_async(cmd); 761 | if (p == NOB_INVALID_PROC) return false; 762 | return nob_proc_wait(p); 763 | } 764 | 765 | bool nob_cmd_run_sync_and_reset(Nob_Cmd *cmd) 766 | { 767 | bool p = nob_cmd_run_sync(*cmd); 768 | cmd->count = 0; 769 | return p; 770 | } 771 | 772 | void nob_log(Nob_Log_Level level, const char *fmt, ...) 773 | { 774 | if (level < nob_minimal_log_level) return; 775 | 776 | switch (level) { 777 | case NOB_INFO: 778 | fprintf(stderr, "[INFO] "); 779 | break; 780 | case NOB_WARNING: 781 | fprintf(stderr, "[WARNING] "); 782 | break; 783 | case NOB_ERROR: 784 | fprintf(stderr, "[ERROR] "); 785 | break; 786 | case NOB_NO_LOGS: return; 787 | default: 788 | NOB_UNREACHABLE("nob_log"); 789 | } 790 | 791 | va_list args; 792 | va_start(args, fmt); 793 | vfprintf(stderr, fmt, args); 794 | va_end(args); 795 | fprintf(stderr, "\n"); 796 | } 797 | 798 | bool nob_read_entire_dir(const char *parent, Nob_File_Paths *children) 799 | { 800 | bool result = true; 801 | DIR *dir = NULL; 802 | 803 | dir = opendir(parent); 804 | if (dir == NULL) { 805 | #ifdef _WIN32 806 | nob_log(NOB_ERROR, "Could not open directory %s: %s", parent, nob_win32_error_message(GetLastError())); 807 | #else 808 | nob_log(NOB_ERROR, "Could not open directory %s: %s", parent, strerror(errno)); 809 | #endif // _WIN32 810 | nob_return_defer(false); 811 | } 812 | 813 | errno = 0; 814 | struct dirent *ent = readdir(dir); 815 | while (ent != NULL) { 816 | nob_da_append(children, nob_temp_strdup(ent->d_name)); 817 | ent = readdir(dir); 818 | } 819 | 820 | if (errno != 0) { 821 | #ifdef _WIN32 822 | nob_log(NOB_ERROR, "Could not read directory %s: %s", parent, nob_win32_error_message(GetLastError())); 823 | #else 824 | nob_log(NOB_ERROR, "Could not read directory %s: %s", parent, strerror(errno)); 825 | #endif // _WIN32 826 | nob_return_defer(false); 827 | } 828 | 829 | defer: 830 | if (dir) closedir(dir); 831 | return result; 832 | } 833 | 834 | bool nob_write_entire_file(const char *path, const void *data, size_t size) 835 | { 836 | bool result = true; 837 | 838 | FILE *f = fopen(path, "wb"); 839 | if (f == NULL) { 840 | nob_log(NOB_ERROR, "Could not open file %s for writing: %s\n", path, strerror(errno)); 841 | nob_return_defer(false); 842 | } 843 | 844 | // len 845 | // v 846 | // aaaaaaaaaa 847 | // ^ 848 | // data 849 | 850 | const char *buf = data; 851 | while (size > 0) { 852 | size_t n = fwrite(buf, 1, size, f); 853 | if (ferror(f)) { 854 | nob_log(NOB_ERROR, "Could not write into file %s: %s\n", path, strerror(errno)); 855 | nob_return_defer(false); 856 | } 857 | size -= n; 858 | buf += n; 859 | } 860 | 861 | defer: 862 | if (f) fclose(f); 863 | return result; 864 | } 865 | 866 | Nob_File_Type nob_get_file_type(const char *path) 867 | { 868 | #ifdef _WIN32 869 | DWORD attr = GetFileAttributesA(path); 870 | if (attr == INVALID_FILE_ATTRIBUTES) { 871 | nob_log(NOB_ERROR, "Could not get file attributes of %s: %s", path, nob_win32_error_message(GetLastError())); 872 | return -1; 873 | } 874 | 875 | if (attr & FILE_ATTRIBUTE_DIRECTORY) return NOB_FILE_DIRECTORY; 876 | // TODO: detect symlinks on Windows (whatever that means on Windows anyway) 877 | return NOB_FILE_REGULAR; 878 | #else // _WIN32 879 | struct stat statbuf; 880 | if (stat(path, &statbuf) < 0) { 881 | nob_log(NOB_ERROR, "Could not get stat of %s: %s", path, strerror(errno)); 882 | return -1; 883 | } 884 | 885 | switch (statbuf.st_mode & S_IFMT) { 886 | case S_IFDIR: return NOB_FILE_DIRECTORY; 887 | case S_IFREG: return NOB_FILE_REGULAR; 888 | case S_IFLNK: return NOB_FILE_SYMLINK; 889 | default: return NOB_FILE_OTHER; 890 | } 891 | #endif // _WIN32 892 | } 893 | 894 | bool nob_copy_directory_recursively(const char *src_path, const char *dst_path) 895 | { 896 | bool result = true; 897 | Nob_File_Paths children = {0}; 898 | Nob_String_Builder src_sb = {0}; 899 | Nob_String_Builder dst_sb = {0}; 900 | size_t temp_checkpoint = nob_temp_save(); 901 | 902 | Nob_File_Type type = nob_get_file_type(src_path); 903 | if (type < 0) return false; 904 | 905 | switch (type) { 906 | case NOB_FILE_DIRECTORY: { 907 | if (!nob_mkdir_if_not_exists(dst_path)) nob_return_defer(false); 908 | if (!nob_read_entire_dir(src_path, &children)) nob_return_defer(false); 909 | 910 | for (size_t i = 0; i < children.count; ++i) { 911 | if (strcmp(children.items[i], ".") == 0) continue; 912 | if (strcmp(children.items[i], "..") == 0) continue; 913 | 914 | src_sb.count = 0; 915 | nob_sb_append_cstr(&src_sb, src_path); 916 | nob_sb_append_cstr(&src_sb, "/"); 917 | nob_sb_append_cstr(&src_sb, children.items[i]); 918 | nob_sb_append_null(&src_sb); 919 | 920 | dst_sb.count = 0; 921 | nob_sb_append_cstr(&dst_sb, dst_path); 922 | nob_sb_append_cstr(&dst_sb, "/"); 923 | nob_sb_append_cstr(&dst_sb, children.items[i]); 924 | nob_sb_append_null(&dst_sb); 925 | 926 | if (!nob_copy_directory_recursively(src_sb.items, dst_sb.items)) { 927 | nob_return_defer(false); 928 | } 929 | } 930 | } break; 931 | 932 | case NOB_FILE_REGULAR: { 933 | if (!nob_copy_file(src_path, dst_path)) { 934 | nob_return_defer(false); 935 | } 936 | } break; 937 | 938 | case NOB_FILE_SYMLINK: { 939 | nob_log(NOB_WARNING, "TODO: Copying symlinks is not supported yet"); 940 | } break; 941 | 942 | case NOB_FILE_OTHER: { 943 | nob_log(NOB_ERROR, "Unsupported type of file %s", src_path); 944 | nob_return_defer(false); 945 | } break; 946 | 947 | default: NOB_UNREACHABLE("nob_copy_directory_recursively"); 948 | } 949 | 950 | defer: 951 | nob_temp_rewind(temp_checkpoint); 952 | nob_da_free(src_sb); 953 | nob_da_free(dst_sb); 954 | nob_da_free(children); 955 | return result; 956 | } 957 | 958 | char *nob_temp_strdup(const char *cstr) 959 | { 960 | size_t n = strlen(cstr); 961 | char *result = nob_temp_alloc(n + 1); 962 | NOB_ASSERT(result != NULL && "Increase NOB_TEMP_CAPACITY"); 963 | memcpy(result, cstr, n); 964 | result[n] = '\0'; 965 | return result; 966 | } 967 | 968 | void *nob_temp_alloc(size_t size) 969 | { 970 | if (nob_temp_size + size > NOB_TEMP_CAPACITY) return NULL; 971 | void *result = &nob_temp[nob_temp_size]; 972 | nob_temp_size += size; 973 | return result; 974 | } 975 | 976 | char *nob_temp_sprintf(const char *format, ...) 977 | { 978 | va_list args; 979 | va_start(args, format); 980 | int n = vsnprintf(NULL, 0, format, args); 981 | va_end(args); 982 | 983 | NOB_ASSERT(n >= 0); 984 | char *result = nob_temp_alloc(n + 1); 985 | NOB_ASSERT(result != NULL && "Extend the size of the temporary allocator"); 986 | // TODO: use proper arenas for the temporary allocator; 987 | va_start(args, format); 988 | vsnprintf(result, n + 1, format, args); 989 | va_end(args); 990 | 991 | return result; 992 | } 993 | 994 | void nob_temp_reset(void) 995 | { 996 | nob_temp_size = 0; 997 | } 998 | 999 | size_t nob_temp_save(void) 1000 | { 1001 | return nob_temp_size; 1002 | } 1003 | 1004 | void nob_temp_rewind(size_t checkpoint) 1005 | { 1006 | nob_temp_size = checkpoint; 1007 | } 1008 | 1009 | const char *nob_temp_sv_to_cstr(Nob_String_View sv) 1010 | { 1011 | char *result = nob_temp_alloc(sv.count + 1); 1012 | NOB_ASSERT(result != NULL && "Extend the size of the temporary allocator"); 1013 | memcpy(result, sv.data, sv.count); 1014 | result[sv.count] = '\0'; 1015 | return result; 1016 | } 1017 | 1018 | int nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count) 1019 | { 1020 | #ifdef _WIN32 1021 | BOOL bSuccess; 1022 | 1023 | HANDLE output_path_fd = CreateFile(output_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); 1024 | if (output_path_fd == INVALID_HANDLE_VALUE) { 1025 | // NOTE: if output does not exist it 100% must be rebuilt 1026 | if (GetLastError() == ERROR_FILE_NOT_FOUND) return 1; 1027 | nob_log(NOB_ERROR, "Could not open file %s: %s", output_path, nob_win32_error_message(GetLastError())); 1028 | return -1; 1029 | } 1030 | FILETIME output_path_time; 1031 | bSuccess = GetFileTime(output_path_fd, NULL, NULL, &output_path_time); 1032 | CloseHandle(output_path_fd); 1033 | if (!bSuccess) { 1034 | nob_log(NOB_ERROR, "Could not get time of %s: %s", output_path, nob_win32_error_message(GetLastError())); 1035 | return -1; 1036 | } 1037 | 1038 | for (size_t i = 0; i < input_paths_count; ++i) { 1039 | const char *input_path = input_paths[i]; 1040 | HANDLE input_path_fd = CreateFile(input_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); 1041 | if (input_path_fd == INVALID_HANDLE_VALUE) { 1042 | // NOTE: non-existing input is an error cause it is needed for building in the first place 1043 | nob_log(NOB_ERROR, "Could not open file %s: %s", input_path, nob_win32_error_message(GetLastError())); 1044 | return -1; 1045 | } 1046 | FILETIME input_path_time; 1047 | bSuccess = GetFileTime(input_path_fd, NULL, NULL, &input_path_time); 1048 | CloseHandle(input_path_fd); 1049 | if (!bSuccess) { 1050 | nob_log(NOB_ERROR, "Could not get time of %s: %s", input_path, nob_win32_error_message(GetLastError())); 1051 | return -1; 1052 | } 1053 | 1054 | // NOTE: if even a single input_path is fresher than output_path that's 100% rebuild 1055 | if (CompareFileTime(&input_path_time, &output_path_time) == 1) return 1; 1056 | } 1057 | 1058 | return 0; 1059 | #else 1060 | struct stat statbuf = {0}; 1061 | 1062 | if (stat(output_path, &statbuf) < 0) { 1063 | // NOTE: if output does not exist it 100% must be rebuilt 1064 | if (errno == ENOENT) return 1; 1065 | nob_log(NOB_ERROR, "could not stat %s: %s", output_path, strerror(errno)); 1066 | return -1; 1067 | } 1068 | int output_path_time = statbuf.st_mtime; 1069 | 1070 | for (size_t i = 0; i < input_paths_count; ++i) { 1071 | const char *input_path = input_paths[i]; 1072 | if (stat(input_path, &statbuf) < 0) { 1073 | // NOTE: non-existing input is an error cause it is needed for building in the first place 1074 | nob_log(NOB_ERROR, "could not stat %s: %s", input_path, strerror(errno)); 1075 | return -1; 1076 | } 1077 | int input_path_time = statbuf.st_mtime; 1078 | // NOTE: if even a single input_path is fresher than output_path that's 100% rebuild 1079 | if (input_path_time > output_path_time) return 1; 1080 | } 1081 | 1082 | return 0; 1083 | #endif 1084 | } 1085 | 1086 | int nob_needs_rebuild1(const char *output_path, const char *input_path) 1087 | { 1088 | return nob_needs_rebuild(output_path, &input_path, 1); 1089 | } 1090 | 1091 | bool nob_rename(const char *old_path, const char *new_path) 1092 | { 1093 | nob_log(NOB_INFO, "renaming %s -> %s", old_path, new_path); 1094 | #ifdef _WIN32 1095 | if (!MoveFileEx(old_path, new_path, MOVEFILE_REPLACE_EXISTING)) { 1096 | nob_log(NOB_ERROR, "could not rename %s to %s: %s", old_path, new_path, nob_win32_error_message(GetLastError())); 1097 | return false; 1098 | } 1099 | #else 1100 | if (rename(old_path, new_path) < 0) { 1101 | nob_log(NOB_ERROR, "could not rename %s to %s: %s", old_path, new_path, strerror(errno)); 1102 | return false; 1103 | } 1104 | #endif // _WIN32 1105 | return true; 1106 | } 1107 | 1108 | bool nob_read_entire_file(const char *path, Nob_String_Builder *sb) 1109 | { 1110 | bool result = true; 1111 | 1112 | FILE *f = fopen(path, "rb"); 1113 | if (f == NULL) nob_return_defer(false); 1114 | if (fseek(f, 0, SEEK_END) < 0) nob_return_defer(false); 1115 | long m = ftell(f); 1116 | if (m < 0) nob_return_defer(false); 1117 | if (fseek(f, 0, SEEK_SET) < 0) nob_return_defer(false); 1118 | 1119 | size_t new_count = sb->count + m; 1120 | if (new_count > sb->capacity) { 1121 | sb->items = realloc(sb->items, new_count); 1122 | NOB_ASSERT(sb->items != NULL && "Buy more RAM lool!!"); 1123 | sb->capacity = new_count; 1124 | } 1125 | 1126 | fread(sb->items + sb->count, m, 1, f); 1127 | if (ferror(f)) { 1128 | // TODO: Afaik, ferror does not set errno. So the error reporting in defer is not correct in this case. 1129 | nob_return_defer(false); 1130 | } 1131 | sb->count = new_count; 1132 | 1133 | defer: 1134 | if (!result) nob_log(NOB_ERROR, "Could not read file %s: %s", path, strerror(errno)); 1135 | if (f) fclose(f); 1136 | return result; 1137 | } 1138 | 1139 | Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim) 1140 | { 1141 | size_t i = 0; 1142 | while (i < sv->count && sv->data[i] != delim) { 1143 | i += 1; 1144 | } 1145 | 1146 | Nob_String_View result = nob_sv_from_parts(sv->data, i); 1147 | 1148 | if (i < sv->count) { 1149 | sv->count -= i + 1; 1150 | sv->data += i + 1; 1151 | } else { 1152 | sv->count -= i; 1153 | sv->data += i; 1154 | } 1155 | 1156 | return result; 1157 | } 1158 | 1159 | Nob_String_View nob_sv_from_parts(const char *data, size_t count) 1160 | { 1161 | Nob_String_View sv; 1162 | sv.count = count; 1163 | sv.data = data; 1164 | return sv; 1165 | } 1166 | 1167 | Nob_String_View nob_sv_trim_left(Nob_String_View sv) 1168 | { 1169 | size_t i = 0; 1170 | while (i < sv.count && isspace(sv.data[i])) { 1171 | i += 1; 1172 | } 1173 | 1174 | return nob_sv_from_parts(sv.data + i, sv.count - i); 1175 | } 1176 | 1177 | Nob_String_View nob_sv_trim_right(Nob_String_View sv) 1178 | { 1179 | size_t i = 0; 1180 | while (i < sv.count && isspace(sv.data[sv.count - 1 - i])) { 1181 | i += 1; 1182 | } 1183 | 1184 | return nob_sv_from_parts(sv.data, sv.count - i); 1185 | } 1186 | 1187 | Nob_String_View nob_sv_trim(Nob_String_View sv) 1188 | { 1189 | return nob_sv_trim_right(nob_sv_trim_left(sv)); 1190 | } 1191 | 1192 | Nob_String_View nob_sv_from_cstr(const char *cstr) 1193 | { 1194 | return nob_sv_from_parts(cstr, strlen(cstr)); 1195 | } 1196 | 1197 | bool nob_sv_eq(Nob_String_View a, Nob_String_View b) 1198 | { 1199 | if (a.count != b.count) { 1200 | return false; 1201 | } else { 1202 | return memcmp(a.data, b.data, a.count) == 0; 1203 | } 1204 | } 1205 | 1206 | bool nob_sv_end_with(Nob_String_View sv, const char *cstr) 1207 | { 1208 | size_t cstr_count = strlen(cstr); 1209 | if (sv.count >= cstr_count) { 1210 | size_t ending_start = sv.count - cstr_count; 1211 | Nob_String_View sv_ending = nob_sv_from_parts(sv.data + ending_start, cstr_count); 1212 | return nob_sv_eq(sv_ending, nob_sv_from_cstr(cstr)); 1213 | } 1214 | return false; 1215 | } 1216 | 1217 | // RETURNS: 1218 | // 0 - file does not exists 1219 | // 1 - file exists 1220 | // -1 - error while checking if file exists. The error is logged 1221 | int nob_file_exists(const char *file_path) 1222 | { 1223 | #if _WIN32 1224 | // TODO: distinguish between "does not exists" and other errors 1225 | DWORD dwAttrib = GetFileAttributesA(file_path); 1226 | return dwAttrib != INVALID_FILE_ATTRIBUTES; 1227 | #else 1228 | struct stat statbuf; 1229 | if (stat(file_path, &statbuf) < 0) { 1230 | if (errno == ENOENT) return 0; 1231 | nob_log(NOB_ERROR, "Could not check if file %s exists: %s", file_path, strerror(errno)); 1232 | return -1; 1233 | } 1234 | return 1; 1235 | #endif 1236 | } 1237 | 1238 | const char *nob_get_current_dir_temp() 1239 | { 1240 | #ifdef _WIN32 1241 | DWORD nBufferLength = GetCurrentDirectory(0, NULL); 1242 | if (nBufferLength == 0) { 1243 | nob_log(NOB_ERROR, "could not get current directory: %s", nob_win32_error_message(GetLastError())); 1244 | return NULL; 1245 | } 1246 | 1247 | char *buffer = (char*) nob_temp_alloc(nBufferLength); 1248 | if (GetCurrentDirectory(nBufferLength, buffer) == 0) { 1249 | nob_log(NOB_ERROR, "could not get current directory: %s", nob_win32_error_message(GetLastError())); 1250 | return NULL; 1251 | } 1252 | 1253 | return buffer; 1254 | #else 1255 | char *buffer = (char*) nob_temp_alloc(PATH_MAX); 1256 | if (getcwd(buffer, PATH_MAX) == NULL) { 1257 | nob_log(NOB_ERROR, "could not get current directory: %s", strerror(errno)); 1258 | return NULL; 1259 | } 1260 | 1261 | return buffer; 1262 | #endif // _WIN32 1263 | } 1264 | 1265 | bool nob_set_current_dir(const char *path) 1266 | { 1267 | #ifdef _WIN32 1268 | if (!SetCurrentDirectory(path)) { 1269 | nob_log(NOB_ERROR, "could not set current directory to %s: %s", path, nob_win32_error_message(GetLastError())); 1270 | return false; 1271 | } 1272 | return true; 1273 | #else 1274 | if (chdir(path) < 0) { 1275 | nob_log(NOB_ERROR, "could not set current directory to %s: %s", path, strerror(errno)); 1276 | return false; 1277 | } 1278 | return true; 1279 | #endif // _WIN32 1280 | } 1281 | 1282 | // minirent.h SOURCE BEGIN //////////////////////////////////////// 1283 | #ifdef _WIN32 1284 | struct DIR 1285 | { 1286 | HANDLE hFind; 1287 | WIN32_FIND_DATA data; 1288 | struct dirent *dirent; 1289 | }; 1290 | 1291 | DIR *opendir(const char *dirpath) 1292 | { 1293 | assert(dirpath); 1294 | 1295 | char buffer[MAX_PATH]; 1296 | snprintf(buffer, MAX_PATH, "%s\\*", dirpath); 1297 | 1298 | DIR *dir = (DIR*)calloc(1, sizeof(DIR)); 1299 | 1300 | dir->hFind = FindFirstFile(buffer, &dir->data); 1301 | if (dir->hFind == INVALID_HANDLE_VALUE) { 1302 | // TODO: opendir should set errno accordingly on FindFirstFile fail 1303 | // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror 1304 | errno = ENOSYS; 1305 | goto fail; 1306 | } 1307 | 1308 | return dir; 1309 | 1310 | fail: 1311 | if (dir) { 1312 | free(dir); 1313 | } 1314 | 1315 | return NULL; 1316 | } 1317 | 1318 | struct dirent *readdir(DIR *dirp) 1319 | { 1320 | assert(dirp); 1321 | 1322 | if (dirp->dirent == NULL) { 1323 | dirp->dirent = (struct dirent*)calloc(1, sizeof(struct dirent)); 1324 | } else { 1325 | if(!FindNextFile(dirp->hFind, &dirp->data)) { 1326 | if (GetLastError() != ERROR_NO_MORE_FILES) { 1327 | // TODO: readdir should set errno accordingly on FindNextFile fail 1328 | // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror 1329 | errno = ENOSYS; 1330 | } 1331 | 1332 | return NULL; 1333 | } 1334 | } 1335 | 1336 | memset(dirp->dirent->d_name, 0, sizeof(dirp->dirent->d_name)); 1337 | 1338 | strncpy( 1339 | dirp->dirent->d_name, 1340 | dirp->data.cFileName, 1341 | sizeof(dirp->dirent->d_name) - 1); 1342 | 1343 | return dirp->dirent; 1344 | } 1345 | 1346 | int closedir(DIR *dirp) 1347 | { 1348 | assert(dirp); 1349 | 1350 | if(!FindClose(dirp->hFind)) { 1351 | // TODO: closedir should set errno accordingly on FindClose fail 1352 | // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror 1353 | errno = ENOSYS; 1354 | return -1; 1355 | } 1356 | 1357 | if (dirp->dirent) { 1358 | free(dirp->dirent); 1359 | } 1360 | free(dirp); 1361 | 1362 | return 0; 1363 | } 1364 | #endif // _WIN32 1365 | // minirent.h SOURCE END //////////////////////////////////////// 1366 | 1367 | #endif // NOB_IMPLEMENTATION 1368 | 1369 | #ifndef NOB_STRIP_PREFIX_GUARD_ 1370 | #define NOB_STRIP_PREFIX_GUARD_ 1371 | // NOTE: The name stripping should be part of the header so it's not accidentally included 1372 | // several times. At the same time, it should be at the end of the file so to not create any 1373 | // potential conflicts in the NOB_IMPLEMENTATION. The header obviously cannot be at the end 1374 | // of the file because NOB_IMPLEMENTATION needs the forward declarations from there. So the 1375 | // solution is to split the header into two parts where the name stripping part is at the 1376 | // end of the file after the NOB_IMPLEMENTATION. 1377 | #ifdef NOB_STRIP_PREFIX 1378 | #define TODO NOB_TODO 1379 | #define UNREACHABLE NOB_UNREACHABLE 1380 | #define UNUSED NOB_UNUSED 1381 | #define ARRAY_LEN NOB_ARRAY_LEN 1382 | #define ARRAY_GET NOB_ARRAY_GET 1383 | #define INFO NOB_INFO 1384 | #define WARNING NOB_WARNING 1385 | #define ERROR NOB_ERROR 1386 | #define NO_LOGS NOB_NO_LOGS 1387 | #define Log_Level Nob_Log_Level 1388 | #define minimal_log_level nob_minimal_log_level 1389 | // NOTE: Name log is already defined in math.h and historically always was the natural logarithmic function. 1390 | // So there should be no reason to strip the `nob_` prefix in this specific case. 1391 | // #define log nob_log 1392 | #define shift nob_shift 1393 | #define shift_args nob_shift_args 1394 | #define File_Paths Nob_File_Paths 1395 | #define FILE_REGULAR NOB_FILE_REGULAR 1396 | #define FILE_DIRECTORY NOB_FILE_DIRECTORY 1397 | #define FILE_SYMLINK NOB_FILE_SYMLINK 1398 | #define FILE_OTHER NOB_FILE_OTHER 1399 | #define File_Type Nob_File_Type 1400 | #define mkdir_if_not_exists nob_mkdir_if_not_exists 1401 | #define copy_file nob_copy_file 1402 | #define copy_directory_recursively nob_copy_directory_recursively 1403 | #define read_entire_dir nob_read_entire_dir 1404 | #define write_entire_file nob_write_entire_file 1405 | #define get_file_type nob_get_file_type 1406 | #define return_defer nob_return_defer 1407 | #define da_append nob_da_append 1408 | #define da_free nob_da_free 1409 | #define da_append_many nob_da_append_many 1410 | #define String_Builder Nob_String_Builder 1411 | #define read_entire_file nob_read_entire_file 1412 | #define sb_append_buf nob_sb_append_buf 1413 | #define sb_append_cstr nob_sb_append_cstr 1414 | #define sb_append_null nob_sb_append_null 1415 | #define sb_free nob_sb_free 1416 | #define Proc Nob_Proc 1417 | #define INVALID_PROC NOB_INVALID_PROC 1418 | #define Procs Nob_Procs 1419 | #define procs_wait nob_procs_wait 1420 | #define procs_wait_and_reset nob_procs_wait_and_reset 1421 | #define proc_wait nob_proc_wait 1422 | #define Cmd Nob_Cmd 1423 | #define cmd_render nob_cmd_render 1424 | #define cmd_append nob_cmd_append 1425 | #define cmd_extend nob_cmd_extend 1426 | #define cmd_free nob_cmd_free 1427 | #define cmd_run_async nob_cmd_run_async 1428 | #define cmd_run_async_and_reset nob_cmd_run_async_and_reset 1429 | #define cmd_run_sync nob_cmd_run_sync 1430 | #define cmd_run_sync_and_reset nob_cmd_run_sync_and_reset 1431 | #define temp_strdup nob_temp_strdup 1432 | #define temp_alloc nob_temp_alloc 1433 | #define temp_sprintf nob_temp_sprintf 1434 | #define temp_reset nob_temp_reset 1435 | #define temp_save nob_temp_save 1436 | #define temp_rewind nob_temp_rewind 1437 | #define rename nob_rename 1438 | #define needs_rebuild nob_needs_rebuild 1439 | #define needs_rebuild1 nob_needs_rebuild1 1440 | #define file_exists nob_file_exists 1441 | #define get_current_dir_temp nob_get_current_dir_temp 1442 | #define set_current_dir nob_set_current_dir 1443 | #define String_View Nob_String_View 1444 | #define temp_sv_to_cstr nob_temp_sv_to_cstr 1445 | #define sv_chop_by_delim nob_sv_chop_by_delim 1446 | #define sv_trim nob_sv_trim 1447 | #define sv_trim_left nob_sv_trim_left 1448 | #define sv_trim_right nob_sv_trim_right 1449 | #define sv_eq nob_sv_eq 1450 | #define sv_end_with nob_sv_end_with 1451 | #define sv_from_cstr nob_sv_from_cstr 1452 | #define sv_from_parts nob_sv_from_parts 1453 | #define sb_to_sv nob_sb_to_sv 1454 | #define win32_error_message nob_win32_error_message 1455 | #endif // NOB_STRIP_PREFIX 1456 | #endif // NOB_STRIP_PREFIX_GUARD_ 1457 | 1458 | /* 1459 | Revision history: 1460 | 1461 | 1.8.0 (2024-11-03) Add nob_cmd_extend() (By @0dminnimda) 1462 | 1.7.0 (2024-11-03) Add nob_win32_error_message and NOB_WIN32_ERR_MSG_SIZE (By @KillerxDBr) 1463 | 1.6.0 (2024-10-27) Add nob_cmd_run_sync_and_reset() 1464 | Add nob_sb_to_sv() 1465 | Add nob_procs_wait_and_reset() 1466 | 1.5.1 (2024-10-25) Include limits.h for Linux musl libc (by @pgalkin) 1467 | 1.5.0 (2024-10-23) Add nob_get_current_dir_temp() 1468 | Add nob_set_current_dir() 1469 | 1.4.0 (2024-10-21) Fix UX issues with NOB_GO_REBUILD_URSELF on Windows when you call nob without the .exe extension (By @pgalkin) 1470 | Add nob_sv_end_with (By @pgalkin) 1471 | 1.3.2 (2024-10-21) Fix unreachable error in nob_log on passing NOB_NO_LOGS 1472 | 1.3.1 (2024-10-21) Fix redeclaration error for minimal_log_level (By @KillerxDBr) 1473 | 1.3.0 (2024-10-17) Add NOB_UNREACHABLE 1474 | 1.2.2 (2024-10-16) Fix compilation of nob_cmd_run_sync_and_reset on Windows (By @KillerxDBr) 1475 | 1.2.1 (2024-10-16) Add a separate include guard for NOB_STRIP_PREFIX. 1476 | 1.2.0 (2024-10-15) Make NOB_DA_INIT_CAP redefinable 1477 | Add NOB_STRIP_PREFIX which strips off nob_* prefix from all the user facing names 1478 | Add NOB_UNUSED macro 1479 | Add NOB_TODO macro 1480 | Add nob_sv_trim_left and nob_sv_trim_right declarations to the header part 1481 | 1.1.1 (2024-10-15) Remove forward declaration for is_path1_modified_after_path2 1482 | 1.1.0 (2024-10-15) nob_minimal_log_level 1483 | nob_cmd_run_sync_and_reset 1484 | 1.0.0 (2024-10-15) first release based on https://github.com/tsoding/musializer/blob/4ac7cce9874bc19e02d8c160c8c6229de8919401/nob.h 1485 | */ 1486 | 1487 | /* 1488 | Version Conventions: 1489 | 1490 | We are following https://semver.org/ so the version has a format MAJOR.MINOR.PATCH: 1491 | - Modifying comments does not update the version. 1492 | - PATCH is incremented in case of a bug fix or refactoring without touching the API. 1493 | - MINOR is incremented when new functions and/or types are added in a way that does 1494 | not break any existing user code. We want to do this in the majority of the situation. 1495 | If we want to delete a certain function or type in favor of another one we should 1496 | just add the new function/type and deprecate the old one in a backward compatible way 1497 | and let them co-exist for a while. 1498 | - MAJOR update should be just a periodic cleanup of the deprecated functions and types 1499 | without really modifying any existing functionality. 1500 | 1501 | Naming Conventions: 1502 | 1503 | - All the user facing names should be prefixed with `nob_` or `NOB_` depending on the case. 1504 | - The prefixes of non-redefinable names should be strippable with NOB_STRIP_PREFIX (unless 1505 | explicitly stated otherwise like in case of nob_log). 1506 | - Internal functions should be prefixed with `nob__` (double underscore). 1507 | */ 1508 | 1509 | /* 1510 | ------------------------------------------------------------------------------ 1511 | This software is available under 2 licenses -- choose whichever you prefer. 1512 | ------------------------------------------------------------------------------ 1513 | ALTERNATIVE A - MIT License 1514 | Copyright (c) 2024 Alexey Kutepov 1515 | Permission is hereby granted, free of charge, to any person obtaining a copy of 1516 | this software and associated documentation files (the "Software"), to deal in 1517 | the Software without restriction, including without limitation the rights to 1518 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 1519 | of the Software, and to permit persons to whom the Software is furnished to do 1520 | so, subject to the following conditions: 1521 | The above copyright notice and this permission notice shall be included in all 1522 | copies or substantial portions of the Software. 1523 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1524 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1525 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1526 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1527 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1528 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 1529 | SOFTWARE. 1530 | ------------------------------------------------------------------------------ 1531 | ALTERNATIVE B - Public Domain (www.unlicense.org) 1532 | This is free and unencumbered software released into the public domain. 1533 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 1534 | software, either in source code form or as a compiled binary, for any purpose, 1535 | commercial or non-commercial, and by any means. 1536 | In jurisdictions that recognize copyright laws, the author or authors of this 1537 | software dedicate any and all copyright interest in the software to the public 1538 | domain. We make this dedication for the benefit of the public at large and to 1539 | the detriment of our heirs and successors. We intend this dedication to be an 1540 | overt act of relinquishment in perpetuity of all present and future rights to 1541 | this software under copyright law. 1542 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1543 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1544 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1545 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 1546 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 1547 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1548 | ------------------------------------------------------------------------------ 1549 | */ 1550 | --------------------------------------------------------------------------------