├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── arena.h ├── compiler.c ├── main.py ├── nob.h ├── one_plus_one.ssa └── stb_c_lexer.h /.gitignore: -------------------------------------------------------------------------------- 1 | compiler 2 | main 3 | main.s 4 | main.ssa 5 | one_plus_one.s 6 | one_plus_one 7 | -------------------------------------------------------------------------------- /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. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | main: main.s 2 | cc -o main main.s 3 | 4 | main.s: main.ssa 5 | qbe -o main.s main.ssa 6 | 7 | main.ssa: compiler main.py 8 | ./compiler main.py main.ssa 9 | 10 | compiler: compiler.c 11 | cc -ggdb -o compiler compiler.c 12 | 13 | one_plus_one: one_plus_one.s 14 | cc -o one_plus_one one_plus_one.s 15 | 16 | one_plus_one.s: one_plus_one.ssa 17 | qbe -o one_plus_one.s one_plus_one.ssa 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QBE experiment 2 | 3 | Notes from the [stream](https://www.youtube.com/watch?v=JTjNoejn4iA) where I was checking out [QBE](https://c9x.me/compile/) 4 | 5 | ## Quick Start 6 | 7 | ```console 8 | $ make 9 | $ ./main 10 | ``` 11 | -------------------------------------------------------------------------------- /arena.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022 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. 21 | 22 | #ifndef ARENA_H_ 23 | #define ARENA_H_ 24 | 25 | #include 26 | #include 27 | 28 | #ifndef ARENA_ASSERT 29 | #include 30 | #define ARENA_ASSERT assert 31 | #endif 32 | 33 | #define ARENA_BACKEND_LIBC_MALLOC 0 34 | #define ARENA_BACKEND_LINUX_MMAP 1 35 | #define ARENA_BACKEND_WIN32_VIRTUALALLOC 2 36 | #define ARENA_BACKEND_WASM_HEAPBASE 3 37 | 38 | #ifndef ARENA_BACKEND 39 | #define ARENA_BACKEND ARENA_BACKEND_LIBC_MALLOC 40 | #endif // ARENA_BACKEND 41 | 42 | typedef struct Region Region; 43 | 44 | struct Region { 45 | Region *next; 46 | size_t count; 47 | size_t capacity; 48 | uintptr_t data[]; 49 | }; 50 | 51 | typedef struct { 52 | Region *begin, *end; 53 | } Arena; 54 | 55 | #define REGION_DEFAULT_CAPACITY (8*1024) 56 | 57 | Region *new_region(size_t capacity); 58 | void free_region(Region *r); 59 | 60 | // TODO: snapshot/rewind capability for the arena 61 | // - Snapshot should be combination of a->end and a->end->count. 62 | // - Rewinding should be restoring a->end and a->end->count from the snapshot and 63 | // setting count-s of all the Region-s after the remembered a->end to 0. 64 | void *arena_alloc(Arena *a, size_t size_bytes); 65 | void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz); 66 | 67 | void arena_reset(Arena *a); 68 | void arena_free(Arena *a); 69 | 70 | #endif // ARENA_H_ 71 | 72 | #ifdef ARENA_IMPLEMENTATION 73 | 74 | #if ARENA_BACKEND == ARENA_BACKEND_LIBC_MALLOC 75 | #include 76 | 77 | // TODO: instead of accepting specific capacity new_region() should accept the size of the object we want to fit into the region 78 | // It should be up to new_region() to decide the actual capacity to allocate 79 | Region *new_region(size_t capacity) 80 | { 81 | size_t size_bytes = sizeof(Region) + sizeof(uintptr_t)*capacity; 82 | // TODO: it would be nice if we could guarantee that the regions are allocated by ARENA_BACKEND_LIBC_MALLOC are page aligned 83 | Region *r = malloc(size_bytes); 84 | ARENA_ASSERT(r); 85 | r->next = NULL; 86 | r->count = 0; 87 | r->capacity = capacity; 88 | return r; 89 | } 90 | 91 | void free_region(Region *r) 92 | { 93 | free(r); 94 | } 95 | #elif ARENA_BACKEND == ARENA_BACKEND_LINUX_MMAP 96 | # error "TODO: Linux mmap backend is not implemented yet" 97 | #elif ARENA_BACKEND == ARENA_BACKEND_WIN32_VIRTUALALLOC 98 | 99 | #if !defined(_WIN32) 100 | # error "Current platform is not Windows" 101 | #endif 102 | 103 | #define WIN32_LEAN_AND_MEAN 104 | #include 105 | 106 | #define INV_HANDLE(x) (((x) == NULL) || ((x) == INVALID_HANDLE_VALUE)) 107 | 108 | Region *new_region(size_t capacity) 109 | { 110 | SIZE_T size_bytes = sizeof(Region) + sizeof(uintptr_t) * capacity; 111 | Region *r = VirtualAllocEx( 112 | GetCurrentProcess(), /* Allocate in current process address space */ 113 | NULL, /* Unknown position */ 114 | size_bytes, /* Bytes to allocate */ 115 | MEM_COMMIT | MEM_RESERVE, /* Reserve and commit allocated page */ 116 | PAGE_READWRITE /* Permissions ( Read/Write )*/ 117 | ); 118 | if (INV_HANDLE(r)) 119 | ARENA_ASSERT(0 && "VirtualAllocEx() failed."); 120 | 121 | r->next = NULL; 122 | r->count = 0; 123 | r->capacity = capacity; 124 | return r; 125 | } 126 | 127 | void free_region(Region *r) 128 | { 129 | if (INV_HANDLE(r)) 130 | return; 131 | 132 | BOOL free_result = VirtualFreeEx( 133 | GetCurrentProcess(), /* Deallocate from current process address space */ 134 | (LPVOID)r, /* Address to deallocate */ 135 | 0, /* Bytes to deallocate ( Unknown, deallocate entire page ) */ 136 | MEM_RELEASE /* Release the page ( And implicitly decommit it ) */ 137 | ); 138 | 139 | if (FALSE == free_result) 140 | ARENA_ASSERT(0 && "VirtualFreeEx() failed."); 141 | } 142 | 143 | #elif ARENA_BACKEND == ARENA_BACKEND_WASM_HEAPBASE 144 | # error "TODO: WASM __heap_base backend is not implemented yet" 145 | #else 146 | # error "Unknown Arena backend" 147 | #endif 148 | 149 | // TODO: add debug statistic collection mode for arena 150 | // Should collect things like: 151 | // - How many times new_region was called 152 | // - How many times existing region was skipped 153 | // - How many times allocation exceeded REGION_DEFAULT_CAPACITY 154 | 155 | void *arena_alloc(Arena *a, size_t size_bytes) 156 | { 157 | size_t size = (size_bytes + sizeof(uintptr_t) - 1)/sizeof(uintptr_t); 158 | 159 | if (a->end == NULL) { 160 | ARENA_ASSERT(a->begin == NULL); 161 | size_t capacity = REGION_DEFAULT_CAPACITY; 162 | if (capacity < size) capacity = size; 163 | a->end = new_region(capacity); 164 | a->begin = a->end; 165 | } 166 | 167 | while (a->end->count + size > a->end->capacity && a->end->next != NULL) { 168 | a->end = a->end->next; 169 | } 170 | 171 | if (a->end->count + size > a->end->capacity) { 172 | ARENA_ASSERT(a->end->next == NULL); 173 | size_t capacity = REGION_DEFAULT_CAPACITY; 174 | if (capacity < size) capacity = size; 175 | a->end->next = new_region(capacity); 176 | a->end = a->end->next; 177 | } 178 | 179 | void *result = &a->end->data[a->end->count]; 180 | a->end->count += size; 181 | return result; 182 | } 183 | 184 | void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz) 185 | { 186 | if (newsz <= oldsz) return oldptr; 187 | void *newptr = arena_alloc(a, newsz); 188 | char *newptr_char = newptr; 189 | char *oldptr_char = oldptr; 190 | for (size_t i = 0; i < oldsz; ++i) { 191 | newptr_char[i] = oldptr_char[i]; 192 | } 193 | return newptr; 194 | } 195 | 196 | void arena_reset(Arena *a) 197 | { 198 | for (Region *r = a->begin; r != NULL; r = r->next) { 199 | r->count = 0; 200 | } 201 | 202 | a->end = a->begin; 203 | } 204 | 205 | void arena_free(Arena *a) 206 | { 207 | Region *r = a->begin; 208 | while (r) { 209 | Region *r0 = r; 210 | r = r->next; 211 | free_region(r0); 212 | } 213 | a->begin = NULL; 214 | a->end = NULL; 215 | } 216 | 217 | #endif // ARENA_IMPLEMENTATION 218 | -------------------------------------------------------------------------------- /compiler.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define NOB_IMPLEMENTATION 4 | #include "./nob.h" 5 | #define STB_C_LEXER_IMPLEMENTATION 6 | #include "./stb_c_lexer.h" 7 | #define ARENA_IMPLEMENTATION 8 | #include "./arena.h" 9 | 10 | static Arena parser_arena = {0}; 11 | 12 | typedef struct { 13 | long token; 14 | long int_number; 15 | char *string; 16 | int string_len; 17 | 18 | const char *path; 19 | size_t row, column; 20 | } Lexeme; 21 | 22 | typedef struct { 23 | Lexeme *items; 24 | size_t count; 25 | size_t capacity; 26 | 27 | size_t pos; 28 | } Lexemes; 29 | 30 | typedef struct Expr Expr; 31 | 32 | typedef struct { 33 | Expr **items; 34 | size_t count; 35 | size_t capacity; 36 | } Exprs; 37 | 38 | typedef enum { 39 | EXPR_NUMBER, 40 | EXPR_BINOP, 41 | EXPR_FUNCALL, 42 | } Expr_Kind; 43 | 44 | typedef enum { 45 | BINOP_PLUS, 46 | BINOP_MULT, 47 | } Binop_Kind; 48 | 49 | typedef struct { 50 | Binop_Kind kind; 51 | Expr *lhs; 52 | Expr *rhs; 53 | } Expr_Binop; 54 | 55 | typedef struct { 56 | Nob_String_View name; 57 | Exprs args; 58 | } Expr_Funcall; 59 | 60 | typedef union { 61 | int number; 62 | Expr_Binop binop; 63 | Expr_Funcall funcall; 64 | } Expr_As; 65 | 66 | struct Expr { 67 | Expr_Kind kind; 68 | Expr_As as; 69 | }; 70 | 71 | bool expect_token(Lexemes *lexemes, long token) 72 | { 73 | if (lexemes->pos >= lexemes->count) { 74 | fprintf(stderr, "ERROR: expected token %ld, but got the end of the file", token); 75 | return false; 76 | } 77 | 78 | Lexeme *lexeme = &lexemes->items[lexemes->pos]; 79 | if (lexeme->token != token) { 80 | fprintf(stderr, "%s:%d:%d: ERROR: expected token %ld, but got %ld", lexeme->path, lexeme->row, lexeme->column, token, lexeme->token); 81 | return false; 82 | } 83 | 84 | return true; 85 | } 86 | 87 | Expr *parse_expr(Lexemes *lexemes); 88 | 89 | bool parse_args(Lexemes *lexemes, Exprs *args) 90 | { 91 | if (!expect_token(lexemes, '(')) return false; 92 | lexemes->pos += 1; 93 | 94 | Expr *arg = parse_expr(lexemes); 95 | if (arg == NULL) return false; 96 | nob_da_append(args, arg); 97 | 98 | if (!expect_token(lexemes, ')')) return false; 99 | lexemes->pos += 1; 100 | 101 | return true; 102 | } 103 | 104 | Expr *parse_primary(Lexemes *lexemes) 105 | { 106 | if (lexemes->pos >= lexemes->count) { 107 | fprintf(stderr, "ERROR: expected number or identifier, but got the end of the file"); 108 | return NULL; 109 | } 110 | 111 | Lexeme *lexeme = &lexemes->items[lexemes->pos]; 112 | lexemes->pos += 1; 113 | switch (lexeme->token) { 114 | case CLEX_intlit: { 115 | Expr *num = arena_alloc(&parser_arena, sizeof(Expr)); 116 | num->kind = EXPR_NUMBER; 117 | num->as.number = lexeme->int_number; 118 | return num; 119 | } break; 120 | 121 | case CLEX_id: { 122 | Expr *funcall = arena_alloc(&parser_arena, sizeof(Expr)); 123 | funcall->kind = EXPR_FUNCALL; 124 | funcall->as.funcall.name.data = lexeme->string; 125 | funcall->as.funcall.name.count = lexeme->string_len; 126 | if (!parse_args(lexemes, &funcall->as.funcall.args)) return NULL; 127 | return funcall; 128 | } break; 129 | 130 | default: { 131 | fprintf(stderr, "%s:%d:%d: Unexpected token\n", lexeme->path, lexeme->row, lexeme->column); 132 | return NULL; 133 | } 134 | } 135 | 136 | return NULL; 137 | } 138 | 139 | Expr *parse_expr_mult(Lexemes *lexemes) 140 | { 141 | Expr *lhs = parse_primary(lexemes); 142 | if (!lhs) return NULL; 143 | if (lexemes->pos < lexemes->count && lexemes->items[lexemes->pos].token == '*') { 144 | lexemes->pos += 1; 145 | Expr *rhs = parse_primary(lexemes); 146 | if (!rhs) return NULL; 147 | Expr *mult = arena_alloc(&parser_arena, sizeof(Expr)); 148 | *mult = (Expr) { 149 | .kind = EXPR_BINOP, 150 | .as = { 151 | .binop = { 152 | .kind = BINOP_MULT, 153 | .lhs = lhs, 154 | .rhs = rhs, 155 | } 156 | } 157 | }; 158 | return mult; 159 | } 160 | return lhs; 161 | } 162 | 163 | Expr *parse_expr_plus(Lexemes *lexemes) 164 | { 165 | Expr *lhs = parse_expr_mult(lexemes); 166 | if (!lhs) return NULL; 167 | if (lexemes->pos < lexemes->count && lexemes->items[lexemes->pos].token == '+') { 168 | lexemes->pos += 1; 169 | Expr *rhs = parse_expr_mult(lexemes); 170 | if (!rhs) return NULL; 171 | Expr *plus = arena_alloc(&parser_arena, sizeof(Expr)); 172 | *plus = (Expr) { 173 | .kind = EXPR_BINOP, 174 | .as = { 175 | .binop = { 176 | .kind = BINOP_PLUS, 177 | .lhs = lhs, 178 | .rhs = rhs, 179 | } 180 | } 181 | }; 182 | return plus; 183 | } 184 | return lhs; 185 | } 186 | 187 | Expr *parse_expr(Lexemes *lexemes) 188 | { 189 | return parse_expr_plus(lexemes); 190 | } 191 | 192 | void compile_expr(FILE *out, Expr *expr, size_t *stack_size) 193 | { 194 | // stack_size = 5 195 | // 0 1 2 3 4 196 | 197 | switch (expr->kind) { 198 | case EXPR_NUMBER: { 199 | fprintf(out, " %%s%zu =w copy %d\n", *stack_size, expr->as.number); 200 | *stack_size += 1; 201 | } break; 202 | case EXPR_BINOP: { 203 | compile_expr(out, expr->as.binop.lhs, stack_size); 204 | compile_expr(out, expr->as.binop.rhs, stack_size); 205 | switch (expr->as.binop.kind) { 206 | case BINOP_PLUS: { 207 | assert(*stack_size >= 2); 208 | fprintf(out, " %%s%zu =w add %%s%zu, %%s%zu\n", *stack_size - 2, *stack_size - 2, *stack_size - 1); 209 | *stack_size -= 1; 210 | } break; 211 | 212 | case BINOP_MULT: { 213 | assert(*stack_size >= 2); 214 | fprintf(out, " %%s%zu =w mul %%s%zu, %%s%zu\n", *stack_size - 2, *stack_size - 2, *stack_size - 1); 215 | *stack_size -= 1; 216 | } break; 217 | } 218 | } break; 219 | case EXPR_FUNCALL: { 220 | assert(nob_sv_eq(expr->as.funcall.name, nob_sv_from_cstr("print"))); 221 | assert(expr->as.funcall.args.count == 1); 222 | compile_expr(out, expr->as.funcall.args.items[0], stack_size); 223 | fprintf(out, " call $printf(l $fmt_int, ..., w %%s%zu)\n", *stack_size - 1); 224 | *stack_size -= 1; 225 | } break; 226 | } 227 | } 228 | 229 | int main(int argc, char **argv) 230 | { 231 | const char *program = nob_shift_args(&argc, &argv); 232 | 233 | if (argc <= 0) { 234 | nob_log(NOB_ERROR, "Usage: %s ", program); 235 | nob_log(NOB_ERROR, "No input is not provided"); 236 | return 1; 237 | } 238 | const char *path = nob_shift_args(&argc, &argv); 239 | 240 | if (argc <= 0) { 241 | nob_log(NOB_ERROR, "Usage: %s ", program); 242 | nob_log(NOB_ERROR, "No output is not provided"); 243 | return 1; 244 | } 245 | const char *out_path = nob_shift_args(&argc, &argv); 246 | 247 | Nob_String_Builder sb = {0}; 248 | if (!nob_read_entire_file(path, &sb)) return 0; 249 | 250 | int store_length = 640*1024; 251 | char *string_store = malloc(store_length); 252 | assert(string_store != NULL); 253 | 254 | stb_lexer lexer = {0}; 255 | stb_c_lexer_init(&lexer, sb.items, sb.items + sb.count, string_store, store_length); 256 | 257 | FILE *out = fopen(out_path, "wb"); 258 | if (out == NULL) { 259 | nob_log(NOB_ERROR, "Could not open file %s for writing: %s", out_path, strerror(errno)); 260 | return 1; 261 | } 262 | 263 | Lexemes lexemes = {0}; 264 | while (stb_c_lexer_get_token(&lexer)) { 265 | stb_lex_location loc = {0}; 266 | stb_c_lexer_get_location(&lexer, lexer.where_firstchar, &loc); 267 | loc.line_offset += 1; 268 | Lexeme lexeme = { 269 | .token = lexer.token, 270 | .path = path, 271 | .row = loc.line_number, 272 | .column = loc.line_offset, 273 | }; 274 | switch (lexer.token) { 275 | case '+': case '(': case '*': case ')': {} break; 276 | case CLEX_id: { 277 | size_t string_len = strlen(lexer.string); 278 | lexeme.string = arena_alloc(&parser_arena, string_len); 279 | memcpy(lexeme.string, lexer.string, string_len); 280 | lexeme.string_len = string_len; 281 | } break; 282 | 283 | case CLEX_intlit: { 284 | lexeme.int_number = lexer.int_number; 285 | } break; 286 | 287 | default: { 288 | assert(0 && "Unexpected token"); 289 | } break; 290 | } 291 | nob_da_append(&lexemes, lexeme); 292 | } 293 | 294 | 295 | fprintf(out, "export function w $main() {\n"); 296 | fprintf(out, "@start\n"); 297 | 298 | size_t stack_size = 0; 299 | while (lexemes.pos < lexemes.count) { 300 | Expr *root = parse_expr(&lexemes); 301 | if (root == NULL) return 1; 302 | compile_expr(out, root, &stack_size); 303 | } 304 | 305 | fprintf(out, " ret 0\n"); 306 | fprintf(out, "}\n"); 307 | fprintf(out, "data $fmt_int = { b \"%%d\\n\", b 0 }\n"); 308 | 309 | fclose(out); 310 | 311 | return 0; 312 | } 313 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | print(35 + 17*2) 2 | print(200*2 + 10*2) 3 | -------------------------------------------------------------------------------- /nob.h: -------------------------------------------------------------------------------- 1 | // This is a complete backward incompatible rewrite of https://github.com/tsoding/nobuild 2 | // because I'm really unhappy with the direction it is going. It's gonna sit in this repo 3 | // until it's matured enough and then I'll probably extract it to its own repo. 4 | 5 | // Copyright 2023 Alexey Kutepov 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining 8 | // a copy of this software and associated documentation files (the 9 | // "Software"), to deal in the Software without restriction, including 10 | // without limitation the rights to use, copy, modify, merge, publish, 11 | // distribute, sublicense, and/or sell copies of the Software, and to 12 | // permit persons to whom the Software is furnished to do so, subject to 13 | // the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be 16 | // included in all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 22 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 24 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | 26 | #ifndef NOB_H_ 27 | #define NOB_H_ 28 | 29 | #define NOB_ASSERT assert 30 | #define NOB_REALLOC realloc 31 | #define NOB_FREE free 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #ifdef _WIN32 43 | # define WIN32_LEAN_AND_MEAN 44 | # define _WINUSER_ 45 | # define _WINGDI_ 46 | # define _IMM_ 47 | # define _WINCON_ 48 | # include 49 | # include 50 | # include 51 | #else 52 | # include 53 | # include 54 | # include 55 | # include 56 | # include 57 | #endif 58 | 59 | #ifdef _WIN32 60 | # define NOB_LINE_END "\r\n" 61 | #else 62 | # define NOB_LINE_END "\n" 63 | #endif 64 | 65 | #define NOB_ARRAY_LEN(array) (sizeof(array)/sizeof(array[0])) 66 | #define NOB_ARRAY_GET(array, index) \ 67 | (NOB_ASSERT(index >= 0), NOB_ASSERT(index < NOB_ARRAY_LEN(array)), array[index]) 68 | 69 | typedef enum { 70 | NOB_INFO, 71 | NOB_WARNING, 72 | NOB_ERROR, 73 | } Nob_Log_Level; 74 | 75 | void nob_log(Nob_Log_Level level, const char *fmt, ...); 76 | 77 | // It is an equivalent of shift command from bash. It basically pops a command line 78 | // argument from the beginning. 79 | char *nob_shift_args(int *argc, char ***argv); 80 | 81 | typedef struct { 82 | const char **items; 83 | size_t count; 84 | size_t capacity; 85 | } Nob_File_Paths; 86 | 87 | typedef enum { 88 | NOB_FILE_REGULAR = 0, 89 | NOB_FILE_DIRECTORY, 90 | NOB_FILE_SYMLINK, 91 | NOB_FILE_OTHER, 92 | } Nob_File_Type; 93 | 94 | bool nob_mkdir_if_not_exists(const char *path); 95 | bool nob_copy_file(const char *src_path, const char *dst_path); 96 | bool nob_copy_directory_recursively(const char *src_path, const char *dst_path); 97 | bool nob_read_entire_dir(const char *parent, Nob_File_Paths *children); 98 | bool nob_write_entire_file(const char *path, const void *data, size_t size); 99 | Nob_File_Type nob_get_file_type(const char *path); 100 | 101 | #define nob_return_defer(value) do { result = (value); goto defer; } while(0) 102 | 103 | // Initial capacity of a dynamic array 104 | #define NOB_DA_INIT_CAP 256 105 | 106 | // Append an item to a dynamic array 107 | #define nob_da_append(da, item) \ 108 | do { \ 109 | if ((da)->count >= (da)->capacity) { \ 110 | (da)->capacity = (da)->capacity == 0 ? NOB_DA_INIT_CAP : (da)->capacity*2; \ 111 | (da)->items = NOB_REALLOC((da)->items, (da)->capacity*sizeof(*(da)->items)); \ 112 | NOB_ASSERT((da)->items != NULL && "Buy more RAM lol"); \ 113 | } \ 114 | \ 115 | (da)->items[(da)->count++] = (item); \ 116 | } while (0) 117 | 118 | #define nob_da_free(da) NOB_FREE((da).items) 119 | 120 | // Append several items to a dynamic array 121 | #define nob_da_append_many(da, new_items, new_items_count) \ 122 | do { \ 123 | if ((da)->count + new_items_count > (da)->capacity) { \ 124 | if ((da)->capacity == 0) { \ 125 | (da)->capacity = NOB_DA_INIT_CAP; \ 126 | } \ 127 | while ((da)->count + new_items_count > (da)->capacity) { \ 128 | (da)->capacity *= 2; \ 129 | } \ 130 | (da)->items = NOB_REALLOC((da)->items, (da)->capacity*sizeof(*(da)->items)); \ 131 | NOB_ASSERT((da)->items != NULL && "Buy more RAM lol"); \ 132 | } \ 133 | memcpy((da)->items + (da)->count, new_items, new_items_count*sizeof(*(da)->items)); \ 134 | (da)->count += new_items_count; \ 135 | } while (0) 136 | 137 | typedef struct { 138 | char *items; 139 | size_t count; 140 | size_t capacity; 141 | } Nob_String_Builder; 142 | 143 | bool nob_read_entire_file(const char *path, Nob_String_Builder *sb); 144 | 145 | // Append a sized buffer to a string builder 146 | #define nob_sb_append_buf(sb, buf, size) nob_da_append_many(sb, buf, size) 147 | 148 | // Append a NULL-terminated string to a string builder 149 | #define nob_sb_append_cstr(sb, cstr) \ 150 | do { \ 151 | const char *s = (cstr); \ 152 | size_t n = strlen(s); \ 153 | nob_da_append_many(sb, s, n); \ 154 | } while (0) 155 | 156 | // Append a single NULL character at the end of a string builder. So then you can 157 | // use it a NULL-terminated C string 158 | #define nob_sb_append_null(sb) nob_da_append_many(sb, "", 1) 159 | 160 | // Free the memory allocated by a string builder 161 | #define nob_sb_free(sb) NOB_FREE((sb).items) 162 | 163 | // Process handle 164 | #ifdef _WIN32 165 | typedef HANDLE Nob_Proc; 166 | #define NOB_INVALID_PROC INVALID_HANDLE_VALUE 167 | #else 168 | typedef int Nob_Proc; 169 | #define NOB_INVALID_PROC (-1) 170 | #endif // _WIN32 171 | 172 | typedef struct { 173 | Nob_Proc *items; 174 | size_t count; 175 | size_t capacity; 176 | } Nob_Procs; 177 | 178 | bool nob_procs_wait(Nob_Procs procs); 179 | 180 | // Wait until the process has finished 181 | bool nob_proc_wait(Nob_Proc proc); 182 | 183 | // A command - the main workhorse of Nob. Nob is all about building commands an running them 184 | typedef struct { 185 | const char **items; 186 | size_t count; 187 | size_t capacity; 188 | } Nob_Cmd; 189 | 190 | // Render a string representation of a command into a string builder. Keep in mind the the 191 | // string builder is not NULL-terminated by default. Use nob_sb_append_null if you plan to 192 | // use it as a C string. 193 | void nob_cmd_render(Nob_Cmd cmd, Nob_String_Builder *render); 194 | 195 | #define nob_cmd_append(cmd, ...) \ 196 | nob_da_append_many(cmd, ((const char*[]){__VA_ARGS__}), (sizeof((const char*[]){__VA_ARGS__})/sizeof(const char*))) 197 | 198 | // Free all the memory allocated by command arguments 199 | #define nob_cmd_free(cmd) NOB_FREE(cmd.items) 200 | 201 | // Run command asynchronously 202 | Nob_Proc nob_cmd_run_async(Nob_Cmd cmd); 203 | 204 | // Run command synchronously 205 | bool nob_cmd_run_sync(Nob_Cmd cmd); 206 | 207 | #ifndef NOB_TEMP_CAPACITY 208 | #define NOB_TEMP_CAPACITY (8*1024*1024) 209 | #endif // NOB_TEMP_CAPACITY 210 | char *nob_temp_strdup(const char *cstr); 211 | void *nob_temp_alloc(size_t size); 212 | char *nob_temp_sprintf(const char *format, ...); 213 | void nob_temp_reset(void); 214 | size_t nob_temp_save(void); 215 | void nob_temp_rewind(size_t checkpoint); 216 | 217 | int is_path1_modified_after_path2(const char *path1, const char *path2); 218 | bool nob_rename(const char *old_path, const char *new_path); 219 | int nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count); 220 | int nob_needs_rebuild1(const char *output_path, const char *input_path); 221 | int nob_file_exists(const char *file_path); 222 | 223 | // TODO: add MinGW support for Go Rebuild Urself™ Technology 224 | #ifndef NOB_REBUILD_URSELF 225 | # if _WIN32 226 | # if defined(__GNUC__) 227 | # define NOB_REBUILD_URSELF(binary_path, source_path) "gcc", "-o", binary_path, source_path 228 | # elif defined(__clang__) 229 | # define NOB_REBUILD_URSELF(binary_path, source_path) "clang", "-o", binary_path, source_path 230 | # elif defined(_MSC_VER) 231 | # define NOB_REBUILD_URSELF(binary_path, source_path) "cl.exe", source_path 232 | # endif 233 | # else 234 | # define NOB_REBUILD_URSELF(binary_path, source_path) "cc", "-o", binary_path, source_path 235 | # endif 236 | #endif 237 | 238 | // Go Rebuild Urself™ Technology 239 | // 240 | // How to use it: 241 | // int main(int argc, char** argv) { 242 | // GO_REBUILD_URSELF(argc, argv); 243 | // // actual work 244 | // return 0; 245 | // } 246 | // 247 | // After your added this macro every time you run ./nobuild it will detect 248 | // that you modified its original source code and will try to rebuild itself 249 | // before doing any actual work. So you only need to bootstrap your build system 250 | // once. 251 | // 252 | // The modification is detected by comparing the last modified times of the executable 253 | // and its source code. The same way the make utility usually does it. 254 | // 255 | // The rebuilding is done by using the REBUILD_URSELF macro which you can redefine 256 | // if you need a special way of bootstraping your build system. (which I personally 257 | // do not recommend since the whole idea of nobuild is to keep the process of bootstrapping 258 | // as simple as possible and doing all of the actual work inside of the nobuild) 259 | // 260 | #define NOB_GO_REBUILD_URSELF(argc, argv) \ 261 | do { \ 262 | const char *source_path = __FILE__; \ 263 | assert(argc >= 1); \ 264 | const char *binary_path = argv[0]; \ 265 | \ 266 | int rebuild_is_needed = nob_needs_rebuild(binary_path, &source_path, 1); \ 267 | if (rebuild_is_needed < 0) exit(1); \ 268 | if (rebuild_is_needed) { \ 269 | Nob_String_Builder sb = {0}; \ 270 | nob_sb_append_cstr(&sb, binary_path); \ 271 | nob_sb_append_cstr(&sb, ".old"); \ 272 | nob_sb_append_null(&sb); \ 273 | \ 274 | if (!nob_rename(binary_path, sb.items)) exit(1); \ 275 | Nob_Cmd rebuild = {0}; \ 276 | nob_cmd_append(&rebuild, NOB_REBUILD_URSELF(binary_path, source_path)); \ 277 | bool rebuild_succeeded = nob_cmd_run_sync(rebuild); \ 278 | nob_cmd_free(rebuild); \ 279 | if (!rebuild_succeeded) { \ 280 | nob_rename(sb.items, binary_path); \ 281 | exit(1); \ 282 | } \ 283 | \ 284 | Nob_Cmd cmd = {0}; \ 285 | nob_da_append_many(&cmd, argv, argc); \ 286 | if (!nob_cmd_run_sync(cmd)) exit(1); \ 287 | exit(0); \ 288 | } \ 289 | } while(0) 290 | // The implementation idea is stolen from https://github.com/zhiayang/nabs 291 | 292 | typedef struct { 293 | size_t count; 294 | const char *data; 295 | } Nob_String_View; 296 | 297 | const char *nob_temp_sv_to_cstr(Nob_String_View sv); 298 | 299 | Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim); 300 | Nob_String_View nob_sv_trim(Nob_String_View sv); 301 | bool nob_sv_eq(Nob_String_View a, Nob_String_View b); 302 | Nob_String_View nob_sv_from_cstr(const char *cstr); 303 | Nob_String_View nob_sv_from_parts(const char *data, size_t count); 304 | 305 | // printf macros for String_View 306 | #ifndef SV_Fmt 307 | #define SV_Fmt "%.*s" 308 | #endif // SV_Fmt 309 | #ifndef SV_Arg 310 | #define SV_Arg(sv) (int) (sv).count, (sv).data 311 | #endif // SV_Arg 312 | // USAGE: 313 | // String_View name = ...; 314 | // printf("Name: "SV_Fmt"\n", SV_Arg(name)); 315 | 316 | 317 | // minirent.h HEADER BEGIN //////////////////////////////////////// 318 | // Copyright 2021 Alexey Kutepov 319 | // 320 | // Permission is hereby granted, free of charge, to any person obtaining 321 | // a copy of this software and associated documentation files (the 322 | // "Software"), to deal in the Software without restriction, including 323 | // without limitation the rights to use, copy, modify, merge, publish, 324 | // distribute, sublicense, and/or sell copies of the Software, and to 325 | // permit persons to whom the Software is furnished to do so, subject to 326 | // the following conditions: 327 | // 328 | // The above copyright notice and this permission notice shall be 329 | // included in all copies or substantial portions of the Software. 330 | // 331 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 332 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 333 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 334 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 335 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 336 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 337 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 338 | // 339 | // ============================================================ 340 | // 341 | // minirent — 0.0.1 — A subset of dirent interface for Windows. 342 | // 343 | // https://github.com/tsoding/minirent 344 | // 345 | // ============================================================ 346 | // 347 | // ChangeLog (https://semver.org/ is implied) 348 | // 349 | // 0.0.2 Automatically include dirent.h on non-Windows 350 | // platforms 351 | // 0.0.1 First Official Release 352 | 353 | #ifndef _WIN32 354 | #include 355 | #else // _WIN32 356 | 357 | #define WIN32_LEAN_AND_MEAN 358 | #include "windows.h" 359 | 360 | struct dirent 361 | { 362 | char d_name[MAX_PATH+1]; 363 | }; 364 | 365 | typedef struct DIR DIR; 366 | 367 | DIR *opendir(const char *dirpath); 368 | struct dirent *readdir(DIR *dirp); 369 | int closedir(DIR *dirp); 370 | #endif // _WIN32 371 | // minirent.h HEADER END //////////////////////////////////////// 372 | 373 | #endif // NOB_H_ 374 | 375 | #ifdef NOB_IMPLEMENTATION 376 | 377 | static size_t nob_temp_size = 0; 378 | static char nob_temp[NOB_TEMP_CAPACITY] = {0}; 379 | 380 | bool nob_mkdir_if_not_exists(const char *path) 381 | { 382 | #ifdef _WIN32 383 | int result = mkdir(path); 384 | #else 385 | int result = mkdir(path, 0755); 386 | #endif 387 | if (result < 0) { 388 | if (errno == EEXIST) { 389 | nob_log(NOB_INFO, "directory `%s` already exists", path); 390 | return true; 391 | } 392 | nob_log(NOB_ERROR, "could not create directory `%s`: %s", path, strerror(errno)); 393 | return false; 394 | } 395 | 396 | nob_log(NOB_INFO, "created directory `%s`", path); 397 | return true; 398 | } 399 | 400 | bool nob_copy_file(const char *src_path, const char *dst_path) 401 | { 402 | nob_log(NOB_INFO, "copying %s -> %s", src_path, dst_path); 403 | #ifdef _WIN32 404 | if (!CopyFile(src_path, dst_path, FALSE)) { 405 | nob_log(NOB_ERROR, "Could not copy file: %lu", GetLastError()); 406 | return false; 407 | } 408 | return true; 409 | #else 410 | int src_fd = -1; 411 | int dst_fd = -1; 412 | size_t buf_size = 32*1024; 413 | char *buf = NOB_REALLOC(NULL, buf_size); 414 | NOB_ASSERT(buf != NULL && "Buy more RAM lol!!"); 415 | bool result = true; 416 | 417 | src_fd = open(src_path, O_RDONLY); 418 | if (src_fd < 0) { 419 | nob_log(NOB_ERROR, "Could not open file %s: %s", src_path, strerror(errno)); 420 | nob_return_defer(false); 421 | } 422 | 423 | struct stat src_stat; 424 | if (fstat(src_fd, &src_stat) < 0) { 425 | nob_log(NOB_ERROR, "Could not get mode of file %s: %s", src_path, strerror(errno)); 426 | nob_return_defer(false); 427 | } 428 | 429 | dst_fd = open(dst_path, O_CREAT | O_TRUNC | O_WRONLY, src_stat.st_mode); 430 | if (dst_fd < 0) { 431 | nob_log(NOB_ERROR, "Could not create file %s: %s", dst_path, strerror(errno)); 432 | nob_return_defer(false); 433 | } 434 | 435 | for (;;) { 436 | ssize_t n = read(src_fd, buf, buf_size); 437 | if (n == 0) break; 438 | if (n < 0) { 439 | nob_log(NOB_ERROR, "Could not read from file %s: %s", src_path, strerror(errno)); 440 | nob_return_defer(false); 441 | } 442 | char *buf2 = buf; 443 | while (n > 0) { 444 | ssize_t m = write(dst_fd, buf2, n); 445 | if (m < 0) { 446 | nob_log(NOB_ERROR, "Could not write to file %s: %s", dst_path, strerror(errno)); 447 | nob_return_defer(false); 448 | } 449 | n -= m; 450 | buf2 += m; 451 | } 452 | } 453 | 454 | defer: 455 | free(buf); 456 | close(src_fd); 457 | close(dst_fd); 458 | return result; 459 | #endif 460 | } 461 | 462 | void nob_cmd_render(Nob_Cmd cmd, Nob_String_Builder *render) 463 | { 464 | for (size_t i = 0; i < cmd.count; ++i) { 465 | const char *arg = cmd.items[i]; 466 | if (arg == NULL) break; 467 | if (i > 0) nob_sb_append_cstr(render, " "); 468 | if (!strchr(arg, ' ')) { 469 | nob_sb_append_cstr(render, arg); 470 | } else { 471 | nob_da_append(render, '\''); 472 | nob_sb_append_cstr(render, arg); 473 | nob_da_append(render, '\''); 474 | } 475 | } 476 | } 477 | 478 | Nob_Proc nob_cmd_run_async(Nob_Cmd cmd) 479 | { 480 | if (cmd.count < 1) { 481 | nob_log(NOB_ERROR, "Could not run empty command"); 482 | return NOB_INVALID_PROC; 483 | } 484 | 485 | Nob_String_Builder sb = {0}; 486 | nob_cmd_render(cmd, &sb); 487 | nob_sb_append_null(&sb); 488 | nob_log(NOB_INFO, "CMD: %s", sb.items); 489 | nob_sb_free(sb); 490 | memset(&sb, 0, sizeof(sb)); 491 | 492 | #ifdef _WIN32 493 | // https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output 494 | 495 | STARTUPINFO siStartInfo; 496 | ZeroMemory(&siStartInfo, sizeof(siStartInfo)); 497 | siStartInfo.cb = sizeof(STARTUPINFO); 498 | // NOTE: theoretically setting NULL to std handles should not be a problem 499 | // https://docs.microsoft.com/en-us/windows/console/getstdhandle?redirectedfrom=MSDN#attachdetach-behavior 500 | // TODO: check for errors in GetStdHandle 501 | siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); 502 | siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); 503 | siStartInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); 504 | siStartInfo.dwFlags |= STARTF_USESTDHANDLES; 505 | 506 | PROCESS_INFORMATION piProcInfo; 507 | ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); 508 | 509 | // TODO: use a more reliable rendering of the command instead of cmd_render 510 | // cmd_render is for logging primarily 511 | nob_cmd_render(cmd, &sb); 512 | nob_sb_append_null(&sb); 513 | BOOL bSuccess = CreateProcessA(NULL, sb.items, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo); 514 | nob_sb_free(sb); 515 | 516 | if (!bSuccess) { 517 | nob_log(NOB_ERROR, "Could not create child process: %lu", GetLastError()); 518 | return NOB_INVALID_PROC; 519 | } 520 | 521 | CloseHandle(piProcInfo.hThread); 522 | 523 | return piProcInfo.hProcess; 524 | #else 525 | pid_t cpid = fork(); 526 | if (cpid < 0) { 527 | nob_log(NOB_ERROR, "Could not fork child process: %s", strerror(errno)); 528 | return NOB_INVALID_PROC; 529 | } 530 | 531 | if (cpid == 0) { 532 | // NOTE: This leaks a bit of memory in the child process. 533 | // But do we actually care? It's a one off leak anyway... 534 | Nob_Cmd cmd_null = {0}; 535 | nob_da_append_many(&cmd_null, cmd.items, cmd.count); 536 | nob_cmd_append(&cmd_null, NULL); 537 | 538 | if (execvp(cmd.items[0], (char * const*) cmd_null.items) < 0) { 539 | nob_log(NOB_ERROR, "Could not exec child process: %s", strerror(errno)); 540 | exit(1); 541 | } 542 | NOB_ASSERT(0 && "unreachable"); 543 | } 544 | 545 | return cpid; 546 | #endif 547 | } 548 | 549 | bool nob_procs_wait(Nob_Procs procs) 550 | { 551 | bool success = true; 552 | for (size_t i = 0; i < procs.count; ++i) { 553 | success = nob_proc_wait(procs.items[i]) && success; 554 | } 555 | return success; 556 | } 557 | 558 | bool nob_proc_wait(Nob_Proc proc) 559 | { 560 | if (proc == NOB_INVALID_PROC) return false; 561 | 562 | #ifdef _WIN32 563 | DWORD result = WaitForSingleObject( 564 | proc, // HANDLE hHandle, 565 | INFINITE // DWORD dwMilliseconds 566 | ); 567 | 568 | if (result == WAIT_FAILED) { 569 | nob_log(NOB_ERROR, "could not wait on child process: %lu", GetLastError()); 570 | return false; 571 | } 572 | 573 | DWORD exit_status; 574 | if (!GetExitCodeProcess(proc, &exit_status)) { 575 | nob_log(NOB_ERROR, "could not get process exit code: %lu", GetLastError()); 576 | return false; 577 | } 578 | 579 | if (exit_status != 0) { 580 | nob_log(NOB_ERROR, "command exited with exit code %lu", exit_status); 581 | return false; 582 | } 583 | 584 | CloseHandle(proc); 585 | 586 | return true; 587 | #else 588 | for (;;) { 589 | int wstatus = 0; 590 | if (waitpid(proc, &wstatus, 0) < 0) { 591 | nob_log(NOB_ERROR, "could not wait on command (pid %d): %s", proc, strerror(errno)); 592 | return false; 593 | } 594 | 595 | if (WIFEXITED(wstatus)) { 596 | int exit_status = WEXITSTATUS(wstatus); 597 | if (exit_status != 0) { 598 | nob_log(NOB_ERROR, "command exited with exit code %d", exit_status); 599 | return false; 600 | } 601 | 602 | break; 603 | } 604 | 605 | if (WIFSIGNALED(wstatus)) { 606 | nob_log(NOB_ERROR, "command process was terminated by %s", strsignal(WTERMSIG(wstatus))); 607 | return false; 608 | } 609 | } 610 | 611 | return true; 612 | #endif 613 | } 614 | 615 | bool nob_cmd_run_sync(Nob_Cmd cmd) 616 | { 617 | Nob_Proc p = nob_cmd_run_async(cmd); 618 | if (p == NOB_INVALID_PROC) return false; 619 | return nob_proc_wait(p); 620 | } 621 | 622 | char *nob_shift_args(int *argc, char ***argv) 623 | { 624 | NOB_ASSERT(*argc > 0); 625 | char *result = **argv; 626 | (*argv) += 1; 627 | (*argc) -= 1; 628 | return result; 629 | } 630 | 631 | void nob_log(Nob_Log_Level level, const char *fmt, ...) 632 | { 633 | switch (level) { 634 | case NOB_INFO: 635 | fprintf(stderr, "[INFO] "); 636 | break; 637 | case NOB_WARNING: 638 | fprintf(stderr, "[WARNING] "); 639 | break; 640 | case NOB_ERROR: 641 | fprintf(stderr, "[ERROR] "); 642 | break; 643 | default: 644 | NOB_ASSERT(0 && "unreachable"); 645 | } 646 | 647 | va_list args; 648 | va_start(args, fmt); 649 | vfprintf(stderr, fmt, args); 650 | va_end(args); 651 | fprintf(stderr, "\n"); 652 | } 653 | 654 | bool nob_read_entire_dir(const char *parent, Nob_File_Paths *children) 655 | { 656 | bool result = true; 657 | DIR *dir = NULL; 658 | 659 | dir = opendir(parent); 660 | if (dir == NULL) { 661 | nob_log(NOB_ERROR, "Could not open directory %s: %s", parent, strerror(errno)); 662 | nob_return_defer(false); 663 | } 664 | 665 | errno = 0; 666 | struct dirent *ent = readdir(dir); 667 | while (ent != NULL) { 668 | nob_da_append(children, nob_temp_strdup(ent->d_name)); 669 | ent = readdir(dir); 670 | } 671 | 672 | if (errno != 0) { 673 | nob_log(NOB_ERROR, "Could not read directory %s: %s", parent, strerror(errno)); 674 | nob_return_defer(false); 675 | } 676 | 677 | defer: 678 | if (dir) closedir(dir); 679 | return result; 680 | } 681 | 682 | bool nob_write_entire_file(const char *path, const void *data, size_t size) 683 | { 684 | bool result = true; 685 | 686 | FILE *f = fopen(path, "wb"); 687 | if (f == NULL) { 688 | nob_log(NOB_ERROR, "Could not open file %s for writing: %s\n", path, strerror(errno)); 689 | nob_return_defer(false); 690 | } 691 | 692 | // len 693 | // v 694 | // aaaaaaaaaa 695 | // ^ 696 | // data 697 | 698 | const char *buf = data; 699 | while (size > 0) { 700 | size_t n = fwrite(buf, 1, size, f); 701 | if (ferror(f)) { 702 | nob_log(NOB_ERROR, "Could not write into file %s: %s\n", path, strerror(errno)); 703 | nob_return_defer(false); 704 | } 705 | size -= n; 706 | buf += n; 707 | } 708 | 709 | defer: 710 | if (f) fclose(f); 711 | return result; 712 | } 713 | 714 | Nob_File_Type nob_get_file_type(const char *path) 715 | { 716 | #ifdef _WIN32 717 | DWORD attr = GetFileAttributesA(path); 718 | if (attr == INVALID_FILE_ATTRIBUTES) { 719 | nob_log(NOB_ERROR, "Could not get file attributes of %s: %lu", path, GetLastError()); 720 | return -1; 721 | } 722 | 723 | if (attr & FILE_ATTRIBUTE_DIRECTORY) return NOB_FILE_DIRECTORY; 724 | // TODO: detect symlinks on Windows (whatever that means on Windows anyway) 725 | return NOB_FILE_REGULAR; 726 | #else // _WIN32 727 | struct stat statbuf; 728 | if (stat(path, &statbuf) < 0) { 729 | nob_log(NOB_ERROR, "Could not get stat of %s: %s", path, strerror(errno)); 730 | return -1; 731 | } 732 | 733 | switch (statbuf.st_mode & S_IFMT) { 734 | case S_IFDIR: return NOB_FILE_DIRECTORY; 735 | case S_IFREG: return NOB_FILE_REGULAR; 736 | case S_IFLNK: return NOB_FILE_SYMLINK; 737 | default: return NOB_FILE_OTHER; 738 | } 739 | #endif // _WIN32 740 | } 741 | 742 | bool nob_copy_directory_recursively(const char *src_path, const char *dst_path) 743 | { 744 | bool result = true; 745 | Nob_File_Paths children = {0}; 746 | Nob_String_Builder src_sb = {0}; 747 | Nob_String_Builder dst_sb = {0}; 748 | size_t temp_checkpoint = nob_temp_save(); 749 | 750 | Nob_File_Type type = nob_get_file_type(src_path); 751 | if (type < 0) return false; 752 | 753 | switch (type) { 754 | case NOB_FILE_DIRECTORY: { 755 | if (!nob_mkdir_if_not_exists(dst_path)) nob_return_defer(false); 756 | if (!nob_read_entire_dir(src_path, &children)) nob_return_defer(false); 757 | 758 | for (size_t i = 0; i < children.count; ++i) { 759 | if (strcmp(children.items[i], ".") == 0) continue; 760 | if (strcmp(children.items[i], "..") == 0) continue; 761 | 762 | src_sb.count = 0; 763 | nob_sb_append_cstr(&src_sb, src_path); 764 | nob_sb_append_cstr(&src_sb, "/"); 765 | nob_sb_append_cstr(&src_sb, children.items[i]); 766 | nob_sb_append_null(&src_sb); 767 | 768 | dst_sb.count = 0; 769 | nob_sb_append_cstr(&dst_sb, dst_path); 770 | nob_sb_append_cstr(&dst_sb, "/"); 771 | nob_sb_append_cstr(&dst_sb, children.items[i]); 772 | nob_sb_append_null(&dst_sb); 773 | 774 | if (!nob_copy_directory_recursively(src_sb.items, dst_sb.items)) { 775 | nob_return_defer(false); 776 | } 777 | } 778 | } break; 779 | 780 | case NOB_FILE_REGULAR: { 781 | if (!nob_copy_file(src_path, dst_path)) { 782 | nob_return_defer(false); 783 | } 784 | } break; 785 | 786 | case NOB_FILE_SYMLINK: { 787 | nob_log(NOB_WARNING, "TODO: Copying symlinks is not supported yet"); 788 | } break; 789 | 790 | case NOB_FILE_OTHER: { 791 | nob_log(NOB_ERROR, "Unsupported type of file %s", src_path); 792 | nob_return_defer(false); 793 | } break; 794 | 795 | default: NOB_ASSERT(0 && "unreachable"); 796 | } 797 | 798 | defer: 799 | nob_temp_rewind(temp_checkpoint); 800 | nob_da_free(src_sb); 801 | nob_da_free(dst_sb); 802 | nob_da_free(children); 803 | return result; 804 | } 805 | 806 | char *nob_temp_strdup(const char *cstr) 807 | { 808 | size_t n = strlen(cstr); 809 | char *result = nob_temp_alloc(n + 1); 810 | NOB_ASSERT(result != NULL && "Increase NOB_TEMP_CAPACITY"); 811 | memcpy(result, cstr, n); 812 | result[n] = '\0'; 813 | return result; 814 | } 815 | 816 | void *nob_temp_alloc(size_t size) 817 | { 818 | if (nob_temp_size + size > NOB_TEMP_CAPACITY) return NULL; 819 | void *result = &nob_temp[nob_temp_size]; 820 | nob_temp_size += size; 821 | return result; 822 | } 823 | 824 | char *nob_temp_sprintf(const char *format, ...) 825 | { 826 | va_list args; 827 | va_start(args, format); 828 | int n = vsnprintf(NULL, 0, format, args); 829 | va_end(args); 830 | 831 | NOB_ASSERT(n >= 0); 832 | char *result = nob_temp_alloc(n + 1); 833 | NOB_ASSERT(result != NULL && "Extend the size of the temporary allocator"); 834 | // TODO: use proper arenas for the temporary allocator; 835 | va_start(args, format); 836 | vsnprintf(result, n + 1, format, args); 837 | va_end(args); 838 | 839 | return result; 840 | } 841 | 842 | void nob_temp_reset(void) 843 | { 844 | nob_temp_size = 0; 845 | } 846 | 847 | size_t nob_temp_save(void) 848 | { 849 | return nob_temp_size; 850 | } 851 | 852 | void nob_temp_rewind(size_t checkpoint) 853 | { 854 | nob_temp_size = checkpoint; 855 | } 856 | 857 | const char *nob_temp_sv_to_cstr(Nob_String_View sv) 858 | { 859 | char *result = nob_temp_alloc(sv.count + 1); 860 | NOB_ASSERT(result != NULL && "Extend the size of the temporary allocator"); 861 | memcpy(result, sv.data, sv.count); 862 | result[sv.count] = '\0'; 863 | return result; 864 | } 865 | 866 | int nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count) 867 | { 868 | #ifdef _WIN32 869 | BOOL bSuccess; 870 | 871 | HANDLE output_path_fd = CreateFile(output_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); 872 | if (output_path_fd == INVALID_HANDLE_VALUE) { 873 | // NOTE: if output does not exist it 100% must be rebuilt 874 | if (GetLastError() == ERROR_FILE_NOT_FOUND) return 1; 875 | nob_log(NOB_ERROR, "Could not open file %s: %lu", output_path, GetLastError()); 876 | return -1; 877 | } 878 | FILETIME output_path_time; 879 | bSuccess = GetFileTime(output_path_fd, NULL, NULL, &output_path_time); 880 | CloseHandle(output_path_fd); 881 | if (!bSuccess) { 882 | nob_log(NOB_ERROR, "Could not get time of %s: %lu", output_path, GetLastError()); 883 | return -1; 884 | } 885 | 886 | for (size_t i = 0; i < input_paths_count; ++i) { 887 | const char *input_path = input_paths[i]; 888 | HANDLE input_path_fd = CreateFile(input_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); 889 | if (input_path_fd == INVALID_HANDLE_VALUE) { 890 | // NOTE: non-existing input is an error cause it is needed for building in the first place 891 | nob_log(NOB_ERROR, "Could not open file %s: %lu", input_path, GetLastError()); 892 | return -1; 893 | } 894 | FILETIME input_path_time; 895 | bSuccess = GetFileTime(input_path_fd, NULL, NULL, &input_path_time); 896 | CloseHandle(input_path_fd); 897 | if (!bSuccess) { 898 | nob_log(NOB_ERROR, "Could not get time of %s: %lu", input_path, GetLastError()); 899 | return -1; 900 | } 901 | 902 | // NOTE: if even a single input_path is fresher than output_path that's 100% rebuild 903 | if (CompareFileTime(&input_path_time, &output_path_time) == 1) return 1; 904 | } 905 | 906 | return 0; 907 | #else 908 | struct stat statbuf = {0}; 909 | 910 | if (stat(output_path, &statbuf) < 0) { 911 | // NOTE: if output does not exist it 100% must be rebuilt 912 | if (errno == ENOENT) return 1; 913 | nob_log(NOB_ERROR, "could not stat %s: %s", output_path, strerror(errno)); 914 | return -1; 915 | } 916 | int output_path_time = statbuf.st_mtime; 917 | 918 | for (size_t i = 0; i < input_paths_count; ++i) { 919 | const char *input_path = input_paths[i]; 920 | if (stat(input_path, &statbuf) < 0) { 921 | // NOTE: non-existing input is an error cause it is needed for building in the first place 922 | nob_log(NOB_ERROR, "could not stat %s: %s", input_path, strerror(errno)); 923 | return -1; 924 | } 925 | int input_path_time = statbuf.st_mtime; 926 | // NOTE: if even a single input_path is fresher than output_path that's 100% rebuild 927 | if (input_path_time > output_path_time) return 1; 928 | } 929 | 930 | return 0; 931 | #endif 932 | } 933 | 934 | int nob_needs_rebuild1(const char *output_path, const char *input_path) 935 | { 936 | return nob_needs_rebuild(output_path, &input_path, 1); 937 | } 938 | 939 | bool nob_rename(const char *old_path, const char *new_path) 940 | { 941 | nob_log(NOB_INFO, "renaming %s -> %s", old_path, new_path); 942 | #ifdef _WIN32 943 | if (!MoveFileEx(old_path, new_path, MOVEFILE_REPLACE_EXISTING)) { 944 | nob_log(NOB_ERROR, "could not rename %s to %s: %lu", old_path, new_path, GetLastError()); 945 | return false; 946 | } 947 | #else 948 | if (rename(old_path, new_path) < 0) { 949 | nob_log(NOB_ERROR, "could not rename %s to %s: %s", old_path, new_path, strerror(errno)); 950 | return false; 951 | } 952 | #endif // _WIN32 953 | return true; 954 | } 955 | 956 | bool nob_read_entire_file(const char *path, Nob_String_Builder *sb) 957 | { 958 | bool result = true; 959 | 960 | FILE *f = fopen(path, "rb"); 961 | if (f == NULL) nob_return_defer(false); 962 | if (fseek(f, 0, SEEK_END) < 0) nob_return_defer(false); 963 | long m = ftell(f); 964 | if (m < 0) nob_return_defer(false); 965 | if (fseek(f, 0, SEEK_SET) < 0) nob_return_defer(false); 966 | 967 | size_t new_count = sb->count + m; 968 | if (new_count > sb->capacity) { 969 | sb->items = realloc(sb->items, new_count); 970 | NOB_ASSERT(sb->items != NULL && "Buy more RAM lool!!"); 971 | sb->capacity = new_count; 972 | } 973 | 974 | fread(sb->items + sb->count, m, 1, f); 975 | if (ferror(f)) { 976 | // TODO: Afaik, ferror does not set errno. So the error reporting in defer is not correct in this case. 977 | nob_return_defer(false); 978 | } 979 | sb->count = new_count; 980 | 981 | defer: 982 | if (!result) nob_log(NOB_ERROR, "Could not read file %s: %s", path, strerror(errno)); 983 | if (f) fclose(f); 984 | return result; 985 | } 986 | 987 | Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim) 988 | { 989 | size_t i = 0; 990 | while (i < sv->count && sv->data[i] != delim) { 991 | i += 1; 992 | } 993 | 994 | Nob_String_View result = nob_sv_from_parts(sv->data, i); 995 | 996 | if (i < sv->count) { 997 | sv->count -= i + 1; 998 | sv->data += i + 1; 999 | } else { 1000 | sv->count -= i; 1001 | sv->data += i; 1002 | } 1003 | 1004 | return result; 1005 | } 1006 | 1007 | Nob_String_View nob_sv_from_parts(const char *data, size_t count) 1008 | { 1009 | Nob_String_View sv; 1010 | sv.count = count; 1011 | sv.data = data; 1012 | return sv; 1013 | } 1014 | 1015 | Nob_String_View nob_sv_trim_left(Nob_String_View sv) 1016 | { 1017 | size_t i = 0; 1018 | while (i < sv.count && isspace(sv.data[i])) { 1019 | i += 1; 1020 | } 1021 | 1022 | return nob_sv_from_parts(sv.data + i, sv.count - i); 1023 | } 1024 | 1025 | Nob_String_View nob_sv_trim_right(Nob_String_View sv) 1026 | { 1027 | size_t i = 0; 1028 | while (i < sv.count && isspace(sv.data[sv.count - 1 - i])) { 1029 | i += 1; 1030 | } 1031 | 1032 | return nob_sv_from_parts(sv.data, sv.count - i); 1033 | } 1034 | 1035 | Nob_String_View nob_sv_trim(Nob_String_View sv) 1036 | { 1037 | return nob_sv_trim_right(nob_sv_trim_left(sv)); 1038 | } 1039 | 1040 | Nob_String_View nob_sv_from_cstr(const char *cstr) 1041 | { 1042 | return nob_sv_from_parts(cstr, strlen(cstr)); 1043 | } 1044 | 1045 | bool nob_sv_eq(Nob_String_View a, Nob_String_View b) 1046 | { 1047 | if (a.count != b.count) { 1048 | return false; 1049 | } else { 1050 | return memcmp(a.data, b.data, a.count) == 0; 1051 | } 1052 | } 1053 | 1054 | // RETURNS: 1055 | // 0 - file does not exists 1056 | // 1 - file exists 1057 | // -1 - error while checking if file exists. The error is logged 1058 | int nob_file_exists(const char *file_path) 1059 | { 1060 | #if _WIN32 1061 | // TODO: distinguish between "does not exists" and other errors 1062 | DWORD dwAttrib = GetFileAttributesA(file_path); 1063 | return dwAttrib != INVALID_FILE_ATTRIBUTES; 1064 | #else 1065 | struct stat statbuf; 1066 | if (stat(file_path, &statbuf) < 0) { 1067 | if (errno == ENOENT) return 0; 1068 | nob_log(NOB_ERROR, "Could not check if file %s exists: %s", file_path, strerror(errno)); 1069 | return -1; 1070 | } 1071 | return 1; 1072 | #endif 1073 | } 1074 | 1075 | // minirent.h SOURCE BEGIN //////////////////////////////////////// 1076 | #ifdef _WIN32 1077 | struct DIR 1078 | { 1079 | HANDLE hFind; 1080 | WIN32_FIND_DATA data; 1081 | struct dirent *dirent; 1082 | }; 1083 | 1084 | DIR *opendir(const char *dirpath) 1085 | { 1086 | assert(dirpath); 1087 | 1088 | char buffer[MAX_PATH]; 1089 | snprintf(buffer, MAX_PATH, "%s\\*", dirpath); 1090 | 1091 | DIR *dir = (DIR*)calloc(1, sizeof(DIR)); 1092 | 1093 | dir->hFind = FindFirstFile(buffer, &dir->data); 1094 | if (dir->hFind == INVALID_HANDLE_VALUE) { 1095 | // TODO: opendir should set errno accordingly on FindFirstFile fail 1096 | // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror 1097 | errno = ENOSYS; 1098 | goto fail; 1099 | } 1100 | 1101 | return dir; 1102 | 1103 | fail: 1104 | if (dir) { 1105 | free(dir); 1106 | } 1107 | 1108 | return NULL; 1109 | } 1110 | 1111 | struct dirent *readdir(DIR *dirp) 1112 | { 1113 | assert(dirp); 1114 | 1115 | if (dirp->dirent == NULL) { 1116 | dirp->dirent = (struct dirent*)calloc(1, sizeof(struct dirent)); 1117 | } else { 1118 | if(!FindNextFile(dirp->hFind, &dirp->data)) { 1119 | if (GetLastError() != ERROR_NO_MORE_FILES) { 1120 | // TODO: readdir should set errno accordingly on FindNextFile fail 1121 | // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror 1122 | errno = ENOSYS; 1123 | } 1124 | 1125 | return NULL; 1126 | } 1127 | } 1128 | 1129 | memset(dirp->dirent->d_name, 0, sizeof(dirp->dirent->d_name)); 1130 | 1131 | strncpy( 1132 | dirp->dirent->d_name, 1133 | dirp->data.cFileName, 1134 | sizeof(dirp->dirent->d_name) - 1); 1135 | 1136 | return dirp->dirent; 1137 | } 1138 | 1139 | int closedir(DIR *dirp) 1140 | { 1141 | assert(dirp); 1142 | 1143 | if(!FindClose(dirp->hFind)) { 1144 | // TODO: closedir should set errno accordingly on FindClose fail 1145 | // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror 1146 | errno = ENOSYS; 1147 | return -1; 1148 | } 1149 | 1150 | if (dirp->dirent) { 1151 | free(dirp->dirent); 1152 | } 1153 | free(dirp); 1154 | 1155 | return 0; 1156 | } 1157 | #endif // _WIN32 1158 | // minirent.h SOURCE END //////////////////////////////////////// 1159 | 1160 | #endif 1161 | -------------------------------------------------------------------------------- /one_plus_one.ssa: -------------------------------------------------------------------------------- 1 | function w $add(w %a, w %b) { 2 | @start 3 | %c =w add %a, %b 4 | ret %c 5 | } 6 | export function w $main() { 7 | @start 8 | %r =w call $add(w 1, w 1) 9 | call $printf(l $fmt, ..., w %r) 10 | ret 0 11 | } 12 | data $fmt = { b "One and one make %d!\n", b 0 } 13 | -------------------------------------------------------------------------------- /stb_c_lexer.h: -------------------------------------------------------------------------------- 1 | // stb_c_lexer.h - v0.12 - public domain Sean Barrett 2013 2 | // lexer for making little C-like languages with recursive-descent parsers 3 | // 4 | // This file provides both the interface and the implementation. 5 | // To instantiate the implementation, 6 | // #define STB_C_LEXER_IMPLEMENTATION 7 | // in *ONE* source file, before #including this file. 8 | // 9 | // The default configuration is fairly close to a C lexer, although 10 | // suffixes on integer constants are not handled (you can override this). 11 | // 12 | // History: 13 | // 0.12 fix compilation bug for NUL support; better support separate inclusion 14 | // 0.11 fix clang static analysis warning 15 | // 0.10 fix warnings 16 | // 0.09 hex floats, no-stdlib fixes 17 | // 0.08 fix bad pointer comparison 18 | // 0.07 fix mishandling of hexadecimal constants parsed by strtol 19 | // 0.06 fix missing next character after ending quote mark (Andreas Fredriksson) 20 | // 0.05 refixed get_location because github version had lost the fix 21 | // 0.04 fix octal parsing bug 22 | // 0.03 added STB_C_LEX_DISCARD_PREPROCESSOR option 23 | // refactor API to simplify (only one struct instead of two) 24 | // change literal enum names to have 'lit' at the end 25 | // 0.02 first public release 26 | // 27 | // Status: 28 | // - haven't tested compiling as C++ 29 | // - haven't tested the float parsing path 30 | // - haven't tested the non-default-config paths (e.g. non-stdlib) 31 | // - only tested default-config paths by eyeballing output of self-parse 32 | // 33 | // - haven't implemented multiline strings 34 | // - haven't implemented octal/hex character constants 35 | // - haven't implemented support for unicode CLEX_char 36 | // - need to expand error reporting so you don't just get "CLEX_parse_error" 37 | // 38 | // Contributors: 39 | // Arpad Goretity (bugfix) 40 | // Alan Hickman (hex floats) 41 | // 42 | // LICENSE 43 | // 44 | // See end of file for license information. 45 | 46 | #ifdef STB_C_LEXER_IMPLEMENTATION 47 | #ifndef STB_C_LEXER_DEFINITIONS 48 | // to change the default parsing rules, copy the following lines 49 | // into your C/C++ file *before* including this, and then replace 50 | // the Y's with N's for the ones you don't want. This needs to be 51 | // set to the same values for every place in your program where 52 | // stb_c_lexer.h is included. 53 | // --BEGIN-- 54 | 55 | #if defined(Y) || defined(N) 56 | #error "Can only use stb_c_lexer in contexts where the preprocessor symbols 'Y' and 'N' are not defined" 57 | #endif 58 | 59 | #define STB_C_LEX_C_DECIMAL_INTS Y // "0|[1-9][0-9]*" CLEX_intlit 60 | #define STB_C_LEX_C_HEX_INTS Y // "0x[0-9a-fA-F]+" CLEX_intlit 61 | #define STB_C_LEX_C_OCTAL_INTS Y // "[0-7]+" CLEX_intlit 62 | #define STB_C_LEX_C_DECIMAL_FLOATS Y // "[0-9]*(.[0-9]*([eE][-+]?[0-9]+)?) CLEX_floatlit 63 | #define STB_C_LEX_C99_HEX_FLOATS N // "0x{hex}+(.{hex}*)?[pP][-+]?{hex}+ CLEX_floatlit 64 | #define STB_C_LEX_C_IDENTIFIERS Y // "[_a-zA-Z][_a-zA-Z0-9]*" CLEX_id 65 | #define STB_C_LEX_C_DQ_STRINGS Y // double-quote-delimited strings with escapes CLEX_dqstring 66 | #define STB_C_LEX_C_SQ_STRINGS N // single-quote-delimited strings with escapes CLEX_ssstring 67 | #define STB_C_LEX_C_CHARS Y // single-quote-delimited character with escape CLEX_charlits 68 | #define STB_C_LEX_C_COMMENTS Y // "/* comment */" 69 | #define STB_C_LEX_CPP_COMMENTS Y // "// comment to end of line\n" 70 | #define STB_C_LEX_C_COMPARISONS Y // "==" CLEX_eq "!=" CLEX_noteq "<=" CLEX_lesseq ">=" CLEX_greatereq 71 | #define STB_C_LEX_C_LOGICAL Y // "&&" CLEX_andand "||" CLEX_oror 72 | #define STB_C_LEX_C_SHIFTS Y // "<<" CLEX_shl ">>" CLEX_shr 73 | #define STB_C_LEX_C_INCREMENTS Y // "++" CLEX_plusplus "--" CLEX_minusminus 74 | #define STB_C_LEX_C_ARROW Y // "->" CLEX_arrow 75 | #define STB_C_LEX_EQUAL_ARROW N // "=>" CLEX_eqarrow 76 | #define STB_C_LEX_C_BITWISEEQ Y // "&=" CLEX_andeq "|=" CLEX_oreq "^=" CLEX_xoreq 77 | #define STB_C_LEX_C_ARITHEQ Y // "+=" CLEX_pluseq "-=" CLEX_minuseq 78 | // "*=" CLEX_muleq "/=" CLEX_diveq "%=" CLEX_modeq 79 | // if both STB_C_LEX_SHIFTS & STB_C_LEX_ARITHEQ: 80 | // "<<=" CLEX_shleq ">>=" CLEX_shreq 81 | 82 | #define STB_C_LEX_PARSE_SUFFIXES N // letters after numbers are parsed as part of those numbers, and must be in suffix list below 83 | #define STB_C_LEX_DECIMAL_SUFFIXES "" // decimal integer suffixes e.g. "uUlL" -- these are returned as-is in string storage 84 | #define STB_C_LEX_HEX_SUFFIXES "" // e.g. "uUlL" 85 | #define STB_C_LEX_OCTAL_SUFFIXES "" // e.g. "uUlL" 86 | #define STB_C_LEX_FLOAT_SUFFIXES "" // 87 | 88 | #define STB_C_LEX_0_IS_EOF N // if Y, ends parsing at '\0'; if N, returns '\0' as token 89 | #define STB_C_LEX_INTEGERS_AS_DOUBLES N // parses integers as doubles so they can be larger than 'int', but only if STB_C_LEX_STDLIB==N 90 | #define STB_C_LEX_MULTILINE_DSTRINGS N // allow newlines in double-quoted strings 91 | #define STB_C_LEX_MULTILINE_SSTRINGS N // allow newlines in single-quoted strings 92 | #define STB_C_LEX_USE_STDLIB Y // use strtod,strtol for parsing #s; otherwise inaccurate hack 93 | #define STB_C_LEX_DOLLAR_IDENTIFIER Y // allow $ as an identifier character 94 | #define STB_C_LEX_FLOAT_NO_DECIMAL Y // allow floats that have no decimal point if they have an exponent 95 | 96 | #define STB_C_LEX_DEFINE_ALL_TOKEN_NAMES N // if Y, all CLEX_ token names are defined, even if never returned 97 | // leaving it as N should help you catch config bugs 98 | 99 | #define STB_C_LEX_DISCARD_PREPROCESSOR Y // discard C-preprocessor directives (e.g. after prepocess 100 | // still have #line, #pragma, etc) 101 | 102 | //#define STB_C_LEX_ISWHITE(str) ... // return length in bytes of whitespace characters if first char is whitespace 103 | 104 | #define STB_C_LEXER_DEFINITIONS // This line prevents the header file from replacing your definitions 105 | // --END-- 106 | #endif 107 | #endif 108 | 109 | #ifndef INCLUDE_STB_C_LEXER_H 110 | #define INCLUDE_STB_C_LEXER_H 111 | 112 | typedef struct 113 | { 114 | // lexer variables 115 | char *input_stream; 116 | char *eof; 117 | char *parse_point; 118 | char *string_storage; 119 | int string_storage_len; 120 | 121 | // lexer parse location for error messages 122 | char *where_firstchar; 123 | char *where_lastchar; 124 | 125 | // lexer token variables 126 | long token; 127 | double real_number; 128 | long int_number; 129 | char *string; 130 | int string_len; 131 | } stb_lexer; 132 | 133 | typedef struct 134 | { 135 | int line_number; 136 | int line_offset; 137 | } stb_lex_location; 138 | 139 | #ifdef __cplusplus 140 | extern "C" { 141 | #endif 142 | 143 | extern void stb_c_lexer_init(stb_lexer *lexer, const char *input_stream, const char *input_stream_end, char *string_store, int store_length); 144 | // this function initialize the 'lexer' structure 145 | // Input: 146 | // - input_stream points to the file to parse, loaded into memory 147 | // - input_stream_end points to the end of the file, or NULL if you use 0-for-EOF 148 | // - string_store is storage the lexer can use for storing parsed strings and identifiers 149 | // - store_length is the length of that storage 150 | 151 | extern int stb_c_lexer_get_token(stb_lexer *lexer); 152 | // this function returns non-zero if a token is parsed, or 0 if at EOF 153 | // Output: 154 | // - lexer->token is the token ID, which is unicode code point for a single-char token, < 0 for a multichar or eof or error 155 | // - lexer->real_number is a double constant value for CLEX_floatlit, or CLEX_intlit if STB_C_LEX_INTEGERS_AS_DOUBLES 156 | // - lexer->int_number is an integer constant for CLEX_intlit if !STB_C_LEX_INTEGERS_AS_DOUBLES, or character for CLEX_charlit 157 | // - lexer->string is a 0-terminated string for CLEX_dqstring or CLEX_sqstring or CLEX_identifier 158 | // - lexer->string_len is the byte length of lexer->string 159 | 160 | extern void stb_c_lexer_get_location(const stb_lexer *lexer, const char *where, stb_lex_location *loc); 161 | // this inefficient function returns the line number and character offset of a 162 | // given location in the file as returned by stb_lex_token. Because it's inefficient, 163 | // you should only call it for errors, not for every token. 164 | // For error messages of invalid tokens, you typically want the location of the start 165 | // of the token (which caused the token to be invalid). For bugs involving legit 166 | // tokens, you can report the first or the range. 167 | // Output: 168 | // - loc->line_number is the line number in the file, counting from 1, of the location 169 | // - loc->line_offset is the char-offset in the line, counting from 0, of the location 170 | 171 | 172 | #ifdef __cplusplus 173 | } 174 | #endif 175 | 176 | enum 177 | { 178 | CLEX_eof = 256, 179 | CLEX_parse_error, 180 | CLEX_intlit , 181 | CLEX_floatlit , 182 | CLEX_id , 183 | CLEX_dqstring , 184 | CLEX_sqstring , 185 | CLEX_charlit , 186 | CLEX_eq , 187 | CLEX_noteq , 188 | CLEX_lesseq , 189 | CLEX_greatereq , 190 | CLEX_andand , 191 | CLEX_oror , 192 | CLEX_shl , 193 | CLEX_shr , 194 | CLEX_plusplus , 195 | CLEX_minusminus , 196 | CLEX_pluseq , 197 | CLEX_minuseq , 198 | CLEX_muleq , 199 | CLEX_diveq , 200 | CLEX_modeq , 201 | CLEX_andeq , 202 | CLEX_oreq , 203 | CLEX_xoreq , 204 | CLEX_arrow , 205 | CLEX_eqarrow , 206 | CLEX_shleq, CLEX_shreq, 207 | 208 | CLEX_first_unused_token 209 | 210 | }; 211 | #endif // INCLUDE_STB_C_LEXER_H 212 | 213 | #ifdef STB_C_LEXER_IMPLEMENTATION 214 | 215 | // Hacky definitions so we can easily #if on them 216 | #define Y(x) 1 217 | #define N(x) 0 218 | 219 | #if STB_C_LEX_INTEGERS_AS_DOUBLES(x) 220 | typedef double stb__clex_int; 221 | #define intfield real_number 222 | #define STB__clex_int_as_double 223 | #else 224 | typedef long stb__clex_int; 225 | #define intfield int_number 226 | #endif 227 | 228 | // Convert these config options to simple conditional #defines so we can more 229 | // easily test them once we've change the meaning of Y/N 230 | 231 | #if STB_C_LEX_PARSE_SUFFIXES(x) 232 | #define STB__clex_parse_suffixes 233 | #endif 234 | 235 | #if STB_C_LEX_C99_HEX_FLOATS(x) 236 | #define STB__clex_hex_floats 237 | #endif 238 | 239 | #if STB_C_LEX_C_HEX_INTS(x) 240 | #define STB__clex_hex_ints 241 | #endif 242 | 243 | #if STB_C_LEX_C_DECIMAL_INTS(x) 244 | #define STB__clex_decimal_ints 245 | #endif 246 | 247 | #if STB_C_LEX_C_OCTAL_INTS(x) 248 | #define STB__clex_octal_ints 249 | #endif 250 | 251 | #if STB_C_LEX_C_DECIMAL_FLOATS(x) 252 | #define STB__clex_decimal_floats 253 | #endif 254 | 255 | #if STB_C_LEX_DISCARD_PREPROCESSOR(x) 256 | #define STB__clex_discard_preprocessor 257 | #endif 258 | 259 | #if STB_C_LEX_USE_STDLIB(x) && (!defined(STB__clex_hex_floats) || __STDC_VERSION__ >= 199901L) 260 | #define STB__CLEX_use_stdlib 261 | #include 262 | #endif 263 | 264 | // Now for the rest of the file we'll use the basic definition where 265 | // where Y expands to its contents and N expands to nothing 266 | #undef Y 267 | #define Y(a) a 268 | #undef N 269 | #define N(a) 270 | 271 | // API function 272 | void stb_c_lexer_init(stb_lexer *lexer, const char *input_stream, const char *input_stream_end, char *string_store, int store_length) 273 | { 274 | lexer->input_stream = (char *) input_stream; 275 | lexer->eof = (char *) input_stream_end; 276 | lexer->parse_point = (char *) input_stream; 277 | lexer->string_storage = string_store; 278 | lexer->string_storage_len = store_length; 279 | } 280 | 281 | // API function 282 | void stb_c_lexer_get_location(const stb_lexer *lexer, const char *where, stb_lex_location *loc) 283 | { 284 | char *p = lexer->input_stream; 285 | int line_number = 1; 286 | int char_offset = 0; 287 | while (*p && p < where) { 288 | if (*p == '\n' || *p == '\r') { 289 | p += (p[0]+p[1] == '\r'+'\n' ? 2 : 1); // skip newline 290 | line_number += 1; 291 | char_offset = 0; 292 | } else { 293 | ++p; 294 | ++char_offset; 295 | } 296 | } 297 | loc->line_number = line_number; 298 | loc->line_offset = char_offset; 299 | } 300 | 301 | // main helper function for returning a parsed token 302 | static int stb__clex_token(stb_lexer *lexer, int token, char *start, char *end) 303 | { 304 | lexer->token = token; 305 | lexer->where_firstchar = start; 306 | lexer->where_lastchar = end; 307 | lexer->parse_point = end+1; 308 | return 1; 309 | } 310 | 311 | // helper function for returning eof 312 | static int stb__clex_eof(stb_lexer *lexer) 313 | { 314 | lexer->token = CLEX_eof; 315 | return 0; 316 | } 317 | 318 | static int stb__clex_iswhite(int x) 319 | { 320 | return x == ' ' || x == '\t' || x == '\r' || x == '\n' || x == '\f'; 321 | } 322 | 323 | static const char *stb__strchr(const char *str, int ch) 324 | { 325 | for (; *str; ++str) 326 | if (*str == ch) 327 | return str; 328 | return 0; 329 | } 330 | 331 | // parse suffixes at the end of a number 332 | static int stb__clex_parse_suffixes(stb_lexer *lexer, long tokenid, char *start, char *cur, const char *suffixes) 333 | { 334 | #ifdef STB__clex_parse_suffixes 335 | lexer->string = lexer->string_storage; 336 | lexer->string_len = 0; 337 | 338 | while ((*cur >= 'a' && *cur <= 'z') || (*cur >= 'A' && *cur <= 'Z')) { 339 | if (stb__strchr(suffixes, *cur) == 0) 340 | return stb__clex_token(lexer, CLEX_parse_error, start, cur); 341 | if (lexer->string_len+1 >= lexer->string_storage_len) 342 | return stb__clex_token(lexer, CLEX_parse_error, start, cur); 343 | lexer->string[lexer->string_len++] = *cur++; 344 | } 345 | #else 346 | suffixes = suffixes; // attempt to suppress warnings 347 | #endif 348 | return stb__clex_token(lexer, tokenid, start, cur-1); 349 | } 350 | 351 | #ifndef STB__CLEX_use_stdlib 352 | static double stb__clex_pow(double base, unsigned int exponent) 353 | { 354 | double value=1; 355 | for ( ; exponent; exponent >>= 1) { 356 | if (exponent & 1) 357 | value *= base; 358 | base *= base; 359 | } 360 | return value; 361 | } 362 | 363 | static double stb__clex_parse_float(char *p, char **q) 364 | { 365 | char *s = p; 366 | double value=0; 367 | int base=10; 368 | int exponent=0; 369 | 370 | #ifdef STB__clex_hex_floats 371 | if (*p == '0') { 372 | if (p[1] == 'x' || p[1] == 'X') { 373 | base=16; 374 | p += 2; 375 | } 376 | } 377 | #endif 378 | 379 | for (;;) { 380 | if (*p >= '0' && *p <= '9') 381 | value = value*base + (*p++ - '0'); 382 | #ifdef STB__clex_hex_floats 383 | else if (base == 16 && *p >= 'a' && *p <= 'f') 384 | value = value*base + 10 + (*p++ - 'a'); 385 | else if (base == 16 && *p >= 'A' && *p <= 'F') 386 | value = value*base + 10 + (*p++ - 'A'); 387 | #endif 388 | else 389 | break; 390 | } 391 | 392 | if (*p == '.') { 393 | double pow, addend = 0; 394 | ++p; 395 | for (pow=1; ; pow*=base) { 396 | if (*p >= '0' && *p <= '9') 397 | addend = addend*base + (*p++ - '0'); 398 | #ifdef STB__clex_hex_floats 399 | else if (base == 16 && *p >= 'a' && *p <= 'f') 400 | addend = addend*base + 10 + (*p++ - 'a'); 401 | else if (base == 16 && *p >= 'A' && *p <= 'F') 402 | addend = addend*base + 10 + (*p++ - 'A'); 403 | #endif 404 | else 405 | break; 406 | } 407 | value += addend / pow; 408 | } 409 | #ifdef STB__clex_hex_floats 410 | if (base == 16) { 411 | // exponent required for hex float literal 412 | if (*p != 'p' && *p != 'P') { 413 | *q = s; 414 | return 0; 415 | } 416 | exponent = 1; 417 | } else 418 | #endif 419 | exponent = (*p == 'e' || *p == 'E'); 420 | 421 | if (exponent) { 422 | int sign = p[1] == '-'; 423 | unsigned int exponent=0; 424 | double power=1; 425 | ++p; 426 | if (*p == '-' || *p == '+') 427 | ++p; 428 | while (*p >= '0' && *p <= '9') 429 | exponent = exponent*10 + (*p++ - '0'); 430 | 431 | #ifdef STB__clex_hex_floats 432 | if (base == 16) 433 | power = stb__clex_pow(2, exponent); 434 | else 435 | #endif 436 | power = stb__clex_pow(10, exponent); 437 | if (sign) 438 | value /= power; 439 | else 440 | value *= power; 441 | } 442 | *q = p; 443 | return value; 444 | } 445 | #endif 446 | 447 | static int stb__clex_parse_char(char *p, char **q) 448 | { 449 | if (*p == '\\') { 450 | *q = p+2; // tentatively guess we'll parse two characters 451 | switch(p[1]) { 452 | case '\\': return '\\'; 453 | case '\'': return '\''; 454 | case '"': return '"'; 455 | case 't': return '\t'; 456 | case 'f': return '\f'; 457 | case 'n': return '\n'; 458 | case 'r': return '\r'; 459 | case '0': return '\0'; // @TODO ocatal constants 460 | case 'x': case 'X': return -1; // @TODO hex constants 461 | case 'u': return -1; // @TODO unicode constants 462 | } 463 | } 464 | *q = p+1; 465 | return (unsigned char) *p; 466 | } 467 | 468 | static int stb__clex_parse_string(stb_lexer *lexer, char *p, int type) 469 | { 470 | char *start = p; 471 | char delim = *p++; // grab the " or ' for later matching 472 | char *out = lexer->string_storage; 473 | char *outend = lexer->string_storage + lexer->string_storage_len; 474 | while (*p != delim) { 475 | int n; 476 | if (*p == '\\') { 477 | char *q; 478 | n = stb__clex_parse_char(p, &q); 479 | if (n < 0) 480 | return stb__clex_token(lexer, CLEX_parse_error, start, q); 481 | p = q; 482 | } else { 483 | // @OPTIMIZE: could speed this up by looping-while-not-backslash 484 | n = (unsigned char) *p++; 485 | } 486 | if (out+1 > outend) 487 | return stb__clex_token(lexer, CLEX_parse_error, start, p); 488 | // @TODO expand unicode escapes to UTF8 489 | *out++ = (char) n; 490 | } 491 | *out = 0; 492 | lexer->string = lexer->string_storage; 493 | lexer->string_len = (int) (out - lexer->string_storage); 494 | return stb__clex_token(lexer, type, start, p); 495 | } 496 | 497 | int stb_c_lexer_get_token(stb_lexer *lexer) 498 | { 499 | char *p = lexer->parse_point; 500 | 501 | // skip whitespace and comments 502 | for (;;) { 503 | #ifdef STB_C_LEX_ISWHITE 504 | while (p != lexer->stream_end) { 505 | int n; 506 | n = STB_C_LEX_ISWHITE(p); 507 | if (n == 0) break; 508 | if (lexer->eof && lexer->eof - lexer->parse_point < n) 509 | return stb__clex_token(tok, CLEX_parse_error, p,lexer->eof-1); 510 | p += n; 511 | } 512 | #else 513 | while (p != lexer->eof && stb__clex_iswhite(*p)) 514 | ++p; 515 | #endif 516 | 517 | STB_C_LEX_CPP_COMMENTS( 518 | if (p != lexer->eof && p[0] == '/' && p[1] == '/') { 519 | while (p != lexer->eof && *p != '\r' && *p != '\n') 520 | ++p; 521 | continue; 522 | } 523 | ) 524 | 525 | STB_C_LEX_C_COMMENTS( 526 | if (p != lexer->eof && p[0] == '/' && p[1] == '*') { 527 | char *start = p; 528 | p += 2; 529 | while (p != lexer->eof && (p[0] != '*' || p[1] != '/')) 530 | ++p; 531 | if (p == lexer->eof) 532 | return stb__clex_token(lexer, CLEX_parse_error, start, p-1); 533 | p += 2; 534 | continue; 535 | } 536 | ) 537 | 538 | #ifdef STB__clex_discard_preprocessor 539 | // @TODO this discards everything after a '#', regardless 540 | // of where in the line the # is, rather than requiring it 541 | // be at the start. (because this parser doesn't otherwise 542 | // check for line breaks!) 543 | if (p != lexer->eof && p[0] == '#') { 544 | while (p != lexer->eof && *p != '\r' && *p != '\n') 545 | ++p; 546 | continue; 547 | } 548 | #endif 549 | 550 | break; 551 | } 552 | 553 | if (p == lexer->eof) 554 | return stb__clex_eof(lexer); 555 | 556 | switch (*p) { 557 | default: 558 | if ( (*p >= 'a' && *p <= 'z') 559 | || (*p >= 'A' && *p <= 'Z') 560 | || *p == '_' || (unsigned char) *p >= 128 // >= 128 is UTF8 char 561 | STB_C_LEX_DOLLAR_IDENTIFIER( || *p == '$' ) ) 562 | { 563 | int n = 0; 564 | lexer->string = lexer->string_storage; 565 | lexer->string_len = n; 566 | do { 567 | if (n+1 >= lexer->string_storage_len) 568 | return stb__clex_token(lexer, CLEX_parse_error, p, p+n); 569 | lexer->string[n] = p[n]; 570 | ++n; 571 | } while ( 572 | (p[n] >= 'a' && p[n] <= 'z') 573 | || (p[n] >= 'A' && p[n] <= 'Z') 574 | || (p[n] >= '0' && p[n] <= '9') // allow digits in middle of identifier 575 | || p[n] == '_' || (unsigned char) p[n] >= 128 576 | STB_C_LEX_DOLLAR_IDENTIFIER( || p[n] == '$' ) 577 | ); 578 | lexer->string[n] = 0; 579 | return stb__clex_token(lexer, CLEX_id, p, p+n-1); 580 | } 581 | 582 | // check for EOF 583 | STB_C_LEX_0_IS_EOF( 584 | if (*p == 0) 585 | return stb__clex_eof(lexer); 586 | ) 587 | 588 | single_char: 589 | // not an identifier, return the character as itself 590 | return stb__clex_token(lexer, *p, p, p); 591 | 592 | case '+': 593 | if (p+1 != lexer->eof) { 594 | STB_C_LEX_C_INCREMENTS(if (p[1] == '+') return stb__clex_token(lexer, CLEX_plusplus, p,p+1);) 595 | STB_C_LEX_C_ARITHEQ( if (p[1] == '=') return stb__clex_token(lexer, CLEX_pluseq , p,p+1);) 596 | } 597 | goto single_char; 598 | case '-': 599 | if (p+1 != lexer->eof) { 600 | STB_C_LEX_C_INCREMENTS(if (p[1] == '-') return stb__clex_token(lexer, CLEX_minusminus, p,p+1);) 601 | STB_C_LEX_C_ARITHEQ( if (p[1] == '=') return stb__clex_token(lexer, CLEX_minuseq , p,p+1);) 602 | STB_C_LEX_C_ARROW( if (p[1] == '>') return stb__clex_token(lexer, CLEX_arrow , p,p+1);) 603 | } 604 | goto single_char; 605 | case '&': 606 | if (p+1 != lexer->eof) { 607 | STB_C_LEX_C_LOGICAL( if (p[1] == '&') return stb__clex_token(lexer, CLEX_andand, p,p+1);) 608 | STB_C_LEX_C_BITWISEEQ(if (p[1] == '=') return stb__clex_token(lexer, CLEX_andeq , p,p+1);) 609 | } 610 | goto single_char; 611 | case '|': 612 | if (p+1 != lexer->eof) { 613 | STB_C_LEX_C_LOGICAL( if (p[1] == '|') return stb__clex_token(lexer, CLEX_oror, p,p+1);) 614 | STB_C_LEX_C_BITWISEEQ(if (p[1] == '=') return stb__clex_token(lexer, CLEX_oreq, p,p+1);) 615 | } 616 | goto single_char; 617 | case '=': 618 | if (p+1 != lexer->eof) { 619 | STB_C_LEX_C_COMPARISONS(if (p[1] == '=') return stb__clex_token(lexer, CLEX_eq, p,p+1);) 620 | STB_C_LEX_EQUAL_ARROW( if (p[1] == '>') return stb__clex_token(lexer, CLEX_eqarrow, p,p+1);) 621 | } 622 | goto single_char; 623 | case '!': 624 | STB_C_LEX_C_COMPARISONS(if (p+1 != lexer->eof && p[1] == '=') return stb__clex_token(lexer, CLEX_noteq, p,p+1);) 625 | goto single_char; 626 | case '^': 627 | STB_C_LEX_C_BITWISEEQ(if (p+1 != lexer->eof && p[1] == '=') return stb__clex_token(lexer, CLEX_xoreq, p,p+1)); 628 | goto single_char; 629 | case '%': 630 | STB_C_LEX_C_ARITHEQ(if (p+1 != lexer->eof && p[1] == '=') return stb__clex_token(lexer, CLEX_modeq, p,p+1)); 631 | goto single_char; 632 | case '*': 633 | STB_C_LEX_C_ARITHEQ(if (p+1 != lexer->eof && p[1] == '=') return stb__clex_token(lexer, CLEX_muleq, p,p+1)); 634 | goto single_char; 635 | case '/': 636 | STB_C_LEX_C_ARITHEQ(if (p+1 != lexer->eof && p[1] == '=') return stb__clex_token(lexer, CLEX_diveq, p,p+1)); 637 | goto single_char; 638 | case '<': 639 | if (p+1 != lexer->eof) { 640 | STB_C_LEX_C_COMPARISONS(if (p[1] == '=') return stb__clex_token(lexer, CLEX_lesseq, p,p+1);) 641 | STB_C_LEX_C_SHIFTS( if (p[1] == '<') { 642 | STB_C_LEX_C_ARITHEQ(if (p+2 != lexer->eof && p[2] == '=') 643 | return stb__clex_token(lexer, CLEX_shleq, p,p+2);) 644 | return stb__clex_token(lexer, CLEX_shl, p,p+1); 645 | } 646 | ) 647 | } 648 | goto single_char; 649 | case '>': 650 | if (p+1 != lexer->eof) { 651 | STB_C_LEX_C_COMPARISONS(if (p[1] == '=') return stb__clex_token(lexer, CLEX_greatereq, p,p+1);) 652 | STB_C_LEX_C_SHIFTS( if (p[1] == '>') { 653 | STB_C_LEX_C_ARITHEQ(if (p+2 != lexer->eof && p[2] == '=') 654 | return stb__clex_token(lexer, CLEX_shreq, p,p+2);) 655 | return stb__clex_token(lexer, CLEX_shr, p,p+1); 656 | } 657 | ) 658 | } 659 | goto single_char; 660 | 661 | case '"': 662 | STB_C_LEX_C_DQ_STRINGS(return stb__clex_parse_string(lexer, p, CLEX_dqstring);) 663 | goto single_char; 664 | case '\'': 665 | STB_C_LEX_C_SQ_STRINGS(return stb__clex_parse_string(lexer, p, CLEX_sqstring);) 666 | STB_C_LEX_C_CHARS( 667 | { 668 | char *start = p; 669 | lexer->int_number = stb__clex_parse_char(p+1, &p); 670 | if (lexer->int_number < 0) 671 | return stb__clex_token(lexer, CLEX_parse_error, start,start); 672 | if (p == lexer->eof || *p != '\'') 673 | return stb__clex_token(lexer, CLEX_parse_error, start,p); 674 | return stb__clex_token(lexer, CLEX_charlit, start, p+1); 675 | }) 676 | goto single_char; 677 | 678 | case '0': 679 | #if defined(STB__clex_hex_ints) || defined(STB__clex_hex_floats) 680 | if (p+1 != lexer->eof) { 681 | if (p[1] == 'x' || p[1] == 'X') { 682 | char *q; 683 | 684 | #ifdef STB__clex_hex_floats 685 | for (q=p+2; 686 | q != lexer->eof && ((*q >= '0' && *q <= '9') || (*q >= 'a' && *q <= 'f') || (*q >= 'A' && *q <= 'F')); 687 | ++q); 688 | if (q != lexer->eof) { 689 | if (*q == '.' STB_C_LEX_FLOAT_NO_DECIMAL(|| *q == 'p' || *q == 'P')) { 690 | #ifdef STB__CLEX_use_stdlib 691 | lexer->real_number = strtod((char *) p, (char**) &q); 692 | #else 693 | lexer->real_number = stb__clex_parse_float(p, &q); 694 | #endif 695 | 696 | if (p == q) 697 | return stb__clex_token(lexer, CLEX_parse_error, p,q); 698 | return stb__clex_parse_suffixes(lexer, CLEX_floatlit, p,q, STB_C_LEX_FLOAT_SUFFIXES); 699 | 700 | } 701 | } 702 | #endif // STB__CLEX_hex_floats 703 | 704 | #ifdef STB__clex_hex_ints 705 | #ifdef STB__CLEX_use_stdlib 706 | lexer->int_number = strtol((char *) p, (char **) &q, 16); 707 | #else 708 | { 709 | stb__clex_int n=0; 710 | for (q=p+2; q != lexer->eof; ++q) { 711 | if (*q >= '0' && *q <= '9') 712 | n = n*16 + (*q - '0'); 713 | else if (*q >= 'a' && *q <= 'f') 714 | n = n*16 + (*q - 'a') + 10; 715 | else if (*q >= 'A' && *q <= 'F') 716 | n = n*16 + (*q - 'A') + 10; 717 | else 718 | break; 719 | } 720 | lexer->int_number = n; 721 | } 722 | #endif 723 | if (q == p+2) 724 | return stb__clex_token(lexer, CLEX_parse_error, p-2,p-1); 725 | return stb__clex_parse_suffixes(lexer, CLEX_intlit, p,q, STB_C_LEX_HEX_SUFFIXES); 726 | #endif 727 | } 728 | } 729 | #endif // defined(STB__clex_hex_ints) || defined(STB__clex_hex_floats) 730 | // can't test for octal because we might parse '0.0' as float or as '0' '.' '0', 731 | // so have to do float first 732 | 733 | /* FALL THROUGH */ 734 | case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': 735 | 736 | #ifdef STB__clex_decimal_floats 737 | { 738 | char *q = p; 739 | while (q != lexer->eof && (*q >= '0' && *q <= '9')) 740 | ++q; 741 | if (q != lexer->eof) { 742 | if (*q == '.' STB_C_LEX_FLOAT_NO_DECIMAL(|| *q == 'e' || *q == 'E')) { 743 | #ifdef STB__CLEX_use_stdlib 744 | lexer->real_number = strtod((char *) p, (char**) &q); 745 | #else 746 | lexer->real_number = stb__clex_parse_float(p, &q); 747 | #endif 748 | 749 | return stb__clex_parse_suffixes(lexer, CLEX_floatlit, p,q, STB_C_LEX_FLOAT_SUFFIXES); 750 | 751 | } 752 | } 753 | } 754 | #endif // STB__clex_decimal_floats 755 | 756 | #ifdef STB__clex_octal_ints 757 | if (p[0] == '0') { 758 | char *q = p; 759 | #ifdef STB__CLEX_use_stdlib 760 | lexer->int_number = strtol((char *) p, (char **) &q, 8); 761 | #else 762 | stb__clex_int n=0; 763 | while (q != lexer->eof) { 764 | if (*q >= '0' && *q <= '7') 765 | n = n*8 + (*q - '0'); 766 | else 767 | break; 768 | ++q; 769 | } 770 | if (q != lexer->eof && (*q == '8' || *q=='9')) 771 | return stb__clex_token(lexer, CLEX_parse_error, p, q); 772 | lexer->int_number = n; 773 | #endif 774 | return stb__clex_parse_suffixes(lexer, CLEX_intlit, p,q, STB_C_LEX_OCTAL_SUFFIXES); 775 | } 776 | #endif // STB__clex_octal_ints 777 | 778 | #ifdef STB__clex_decimal_ints 779 | { 780 | char *q = p; 781 | #ifdef STB__CLEX_use_stdlib 782 | lexer->int_number = strtol((char *) p, (char **) &q, 10); 783 | #else 784 | stb__clex_int n=0; 785 | while (q != lexer->eof) { 786 | if (*q >= '0' && *q <= '9') 787 | n = n*10 + (*q - '0'); 788 | else 789 | break; 790 | ++q; 791 | } 792 | lexer->int_number = n; 793 | #endif 794 | return stb__clex_parse_suffixes(lexer, CLEX_intlit, p,q, STB_C_LEX_OCTAL_SUFFIXES); 795 | } 796 | #endif // STB__clex_decimal_ints 797 | goto single_char; 798 | } 799 | } 800 | #endif // STB_C_LEXER_IMPLEMENTATION 801 | 802 | #ifdef STB_C_LEXER_SELF_TEST 803 | #define _CRT_SECURE_NO_WARNINGS 804 | #include 805 | #include 806 | 807 | static void print_token(stb_lexer *lexer) 808 | { 809 | switch (lexer->token) { 810 | case CLEX_id : printf("_%s", lexer->string); break; 811 | case CLEX_eq : printf("=="); break; 812 | case CLEX_noteq : printf("!="); break; 813 | case CLEX_lesseq : printf("<="); break; 814 | case CLEX_greatereq : printf(">="); break; 815 | case CLEX_andand : printf("&&"); break; 816 | case CLEX_oror : printf("||"); break; 817 | case CLEX_shl : printf("<<"); break; 818 | case CLEX_shr : printf(">>"); break; 819 | case CLEX_plusplus : printf("++"); break; 820 | case CLEX_minusminus: printf("--"); break; 821 | case CLEX_arrow : printf("->"); break; 822 | case CLEX_andeq : printf("&="); break; 823 | case CLEX_oreq : printf("|="); break; 824 | case CLEX_xoreq : printf("^="); break; 825 | case CLEX_pluseq : printf("+="); break; 826 | case CLEX_minuseq : printf("-="); break; 827 | case CLEX_muleq : printf("*="); break; 828 | case CLEX_diveq : printf("/="); break; 829 | case CLEX_modeq : printf("%%="); break; 830 | case CLEX_shleq : printf("<<="); break; 831 | case CLEX_shreq : printf(">>="); break; 832 | case CLEX_eqarrow : printf("=>"); break; 833 | case CLEX_dqstring : printf("\"%s\"", lexer->string); break; 834 | case CLEX_sqstring : printf("'\"%s\"'", lexer->string); break; 835 | case CLEX_charlit : printf("'%s'", lexer->string); break; 836 | #if defined(STB__clex_int_as_double) && !defined(STB__CLEX_use_stdlib) 837 | case CLEX_intlit : printf("#%g", lexer->real_number); break; 838 | #else 839 | case CLEX_intlit : printf("#%ld", lexer->int_number); break; 840 | #endif 841 | case CLEX_floatlit : printf("%g", lexer->real_number); break; 842 | default: 843 | if (lexer->token >= 0 && lexer->token < 256) 844 | printf("%c", (int) lexer->token); 845 | else { 846 | printf("<<>>\n", lexer->token); 847 | } 848 | break; 849 | } 850 | } 851 | 852 | /* Force a test 853 | of parsing 854 | multiline comments */ 855 | 856 | /*/ comment /*/ 857 | /**/ extern /**/ 858 | 859 | void dummy(void) 860 | { 861 | double some_floats[] = { 862 | 1.0501, -10.4e12, 5E+10, 863 | #if 0 // not supported in C++ or C-pre-99, so don't try to compile it, but let our parser test it 864 | 0x1.0p+24, 0xff.FP-8, 0x1p-23, 865 | #endif 866 | 4. 867 | }; 868 | (void) sizeof(some_floats); 869 | (void) some_floats[1]; 870 | 871 | printf("test %d",1); // https://github.com/nothings/stb/issues/13 872 | } 873 | 874 | int main(int argc, char **argv) 875 | { 876 | FILE *f = fopen("stb_c_lexer.h","rb"); 877 | char *text = (char *) malloc(1 << 20); 878 | int len = f ? (int) fread(text, 1, 1<<20, f) : -1; 879 | stb_lexer lex; 880 | if (len < 0) { 881 | fprintf(stderr, "Error opening file\n"); 882 | free(text); 883 | fclose(f); 884 | return 1; 885 | } 886 | fclose(f); 887 | 888 | stb_c_lexer_init(&lex, text, text+len, (char *) malloc(0x10000), 0x10000); 889 | while (stb_c_lexer_get_token(&lex)) { 890 | if (lex.token == CLEX_parse_error) { 891 | printf("\n<<>>\n"); 892 | break; 893 | } 894 | print_token(&lex); 895 | printf(" "); 896 | } 897 | return 0; 898 | } 899 | #endif 900 | /* 901 | ------------------------------------------------------------------------------ 902 | This software is available under 2 licenses -- choose whichever you prefer. 903 | ------------------------------------------------------------------------------ 904 | ALTERNATIVE A - MIT License 905 | Copyright (c) 2017 Sean Barrett 906 | Permission is hereby granted, free of charge, to any person obtaining a copy of 907 | this software and associated documentation files (the "Software"), to deal in 908 | the Software without restriction, including without limitation the rights to 909 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 910 | of the Software, and to permit persons to whom the Software is furnished to do 911 | so, subject to the following conditions: 912 | The above copyright notice and this permission notice shall be included in all 913 | copies or substantial portions of the Software. 914 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 915 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 916 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 917 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 918 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 919 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 920 | SOFTWARE. 921 | ------------------------------------------------------------------------------ 922 | ALTERNATIVE B - Public Domain (www.unlicense.org) 923 | This is free and unencumbered software released into the public domain. 924 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 925 | software, either in source code form or as a compiled binary, for any purpose, 926 | commercial or non-commercial, and by any means. 927 | In jurisdictions that recognize copyright laws, the author or authors of this 928 | software dedicate any and all copyright interest in the software to the public 929 | domain. We make this dedication for the benefit of the public at large and to 930 | the detriment of our heirs and successors. We intend this dedication to be an 931 | overt act of relinquishment in perpetuity of all present and future rights to 932 | this software under copyright law. 933 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 934 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 935 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 936 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 937 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 938 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 939 | ------------------------------------------------------------------------------ 940 | */ 941 | --------------------------------------------------------------------------------