[
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\non: [push, pull_request]\n\njobs:\n  build-linux-gcc:\n    runs-on: ubuntu-18.04\n    steps:\n      - uses: actions/checkout@v1\n      - name: build all and examples\n        run: |\n          make -k\n          ./test_jim\n        env:\n          CC: gcc\n          CXX: g++\n  build-linux-clang:\n    runs-on: ubuntu-18.04\n    steps:\n      - uses: actions/checkout@v1\n      - name: build all and examples\n        run: |\n          make -k\n          ./test_jim\n        env:\n          CC: clang\n          CXX: clang++\n  build-macos:\n    runs-on: macOS-latest\n    steps:\n      - uses: actions/checkout@v1\n      - name: build all and examples\n        run: |\n          make -k\n          ./test_jim\n        env:\n          CC: clang\n          CXX: clang++\n# TODO: there is not build for Windows\n"
  },
  {
    "path": ".gitignore",
    "content": "example\ntest_jim\njimp\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2021 Alexey Kutepov <reximkut@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
  },
  {
    "path": "Makefile",
    "content": "CFLAGS=-Wall -Wextra -Wswitch-enum -ggdb -I./thirdparty/\n\n.PHONY: all\nall: examples test_jim\n\ntest_jim: test_jim.c jim.h\n\t$(CC) $(CFLAGS) -o test_jim test_jim.c \n\n.PHONY: examples\nexamples: \n\t$(MAKE) -C examples/\n"
  },
  {
    "path": "README.md",
    "content": "# JIM\n\nImmediate Mode JSON Serialization Library in C. Similar to [imgui](https://github.com/ocornut/imgui) but for generating JSON.\n\n## Example\n\n[`example.c`](./example.c):\n\n```c\n#include <stdio.h>\n\n#define JIM_IMPLEMENTATION\n#include \"./jim.h\"\n\nint main()\n{\n    Jim jim = {.pp = 4};\n\n    jim_object_begin(&jim);\n        jim_member_key(&jim, \"null\");\n        jim_null(&jim);\n\n        jim_member_key(&jim, \"bool\");\n        jim_array_begin(&jim);\n            jim_bool(&jim, 0);\n            jim_bool(&jim, 1);\n        jim_array_end(&jim);\n\n        jim_member_key(&jim, \"integers\");\n        jim_array_begin(&jim);\n            for (int i = -3; i <= 3; ++i) {\n                jim_integer(&jim, i);\n            }\n        jim_array_end(&jim);\n\n        jim_member_key(&jim, \"floats\");\n        jim_array_begin(&jim);\n            jim_float(&jim, 0.0, 4);\n            jim_float(&jim, -0.0, 4);\n            jim_float(&jim, 3.1415, 4);\n            jim_float(&jim, 2.71828, 5);\n            jim_float(&jim, 1.6180, 4);\n            jim_float(&jim, 0.0 / 0.0, 4);\n            jim_float(&jim, 1.0 / 0.0, 4);\n            jim_float(&jim, -1.0 / 0.0, 4);\n        jim_array_end(&jim);\n\n        jim_member_key(&jim, \"string\");\n        jim_array_begin(&jim);\n            jim_string(&jim, \"Hello\\tWorld\\n\");\n            jim_string_sized(&jim, \"\\0\\0\\0\\0\", 4);\n        jim_array_end(&jim);\n\n        jim_member_key(&jim, \"nested_object\");\n        jim_object_begin(&jim);\n            jim_member_key(&jim, \"foo\");\n            jim_integer(&jim, 69);\n            jim_member_key(&jim, \"bar\");\n            jim_integer(&jim, 420);\n            jim_member_key(&jim, \"baz\");\n            jim_integer(&jim, 1337);\n        jim_object_end(&jim);\n\n        jim_member_key(&jim, \"empty_array\"),\n        jim_array_begin(&jim);\n        jim_array_end(&jim);\n\n        jim_member_key(&jim, \"empty_object\"),\n        jim_object_begin(&jim);\n        jim_object_end(&jim);\n    jim_object_end(&jim);\n\n    fwrite(jim.sink, jim.sink_count, 1, stdout);\n\n    return 0;\n}\n```\n\n### Output\n\n```console\n$ cc -o example example.c\n$ ./example\n{\n    \"null\": null,\n    \"bool\": [\n        false,\n        true\n    ],\n    \"integers\": [\n        -3,\n        -2,\n        -1,\n        0,\n        1,\n        2,\n        3\n    ],\n    \"floats\": [\n        0.0,\n        0.0,\n        3.1415,\n        2.71828,\n        1.6180,\n        null,\n        null,\n        null\n    ],\n    \"string\": [\n        \"Hello\\tWorld\\n\",\n        \"\\u0000\\u0000\\u0000\\u0000\"\n    ],\n    \"nested_object\": {\n        \"foo\": 69,\n        \"bar\": 420,\n        \"baz\": 1337\n    },\n    \"empty_array\": [],\n    \"empty_object\": {}\n}\n```\n\n## Testing\n\n```console\n$ make\n$ ./test_jim\n```\n\nThe expected outputs of the test cases are stored in [./test_jim_expected.h](./test_jim_expected.h). To regenerate it just run:\n\n```console\n$ ./test_jim record\n```\n\n## Notes\n\n1. Does not depends on libc. Could be theoretically used in embedded, but I know nothing about embedded, so maybe not.\n2. `jim_float()` is quite likely very stupid and imprecise\n\n<!-- TODO: document jimp.h here -->\n"
  },
  {
    "path": "examples/.gitignore",
    "content": "01_from_readme\n02_binary_tree\n03_parsing_database"
  },
  {
    "path": "examples/01_from_readme.c",
    "content": "#include <stdio.h>\n\n#define JIM_IMPLEMENTATION\n#include \"../jim.h\"\n\nint main()\n{\n    Jim jim = {.pp = 4};\n\n    jim_object_begin(&jim);\n        jim_member_key(&jim, \"null\");\n        jim_null(&jim);\n\n        jim_member_key(&jim, \"bool\");\n        jim_array_begin(&jim);\n            jim_bool(&jim, 0);\n            jim_bool(&jim, 1);\n        jim_array_end(&jim);\n\n        jim_member_key(&jim, \"integers\");\n        jim_array_begin(&jim);\n            for (int i = -3; i <= 3; ++i) {\n                jim_integer(&jim, i);\n            }\n        jim_array_end(&jim);\n\n        jim_member_key(&jim, \"floats\");\n        jim_array_begin(&jim);\n            jim_float(&jim, 0.0, 4);\n            jim_float(&jim, -0.0, 4);\n            jim_float(&jim, 3.1415, 4);\n            jim_float(&jim, 2.71828, 5);\n            jim_float(&jim, 1.6180, 4);\n            jim_float(&jim, 0.0 / 0.0, 4);\n            jim_float(&jim, 1.0 / 0.0, 4);\n            jim_float(&jim, -1.0 / 0.0, 4);\n        jim_array_end(&jim);\n\n        jim_member_key(&jim, \"string\");\n        jim_array_begin(&jim);\n            jim_string(&jim, \"Hello\\tWorld\\n\");\n            jim_string_sized(&jim, \"\\0\\0\\0\\0\", 4);\n        jim_array_end(&jim);\n\n        jim_member_key(&jim, \"nested_object\");\n        jim_object_begin(&jim);\n            jim_member_key(&jim, \"foo\");\n            jim_integer(&jim, 69);\n            jim_member_key(&jim, \"bar\");\n            jim_integer(&jim, 420);\n            jim_member_key(&jim, \"baz\");\n            jim_integer(&jim, 1337);\n        jim_object_end(&jim);\n\n        jim_member_key(&jim, \"empty_array\"),\n        jim_array_begin(&jim);\n        jim_array_end(&jim);\n\n        jim_member_key(&jim, \"empty_object\"),\n        jim_object_begin(&jim);\n        jim_object_end(&jim);\n    jim_object_end(&jim);\n\n    fwrite(jim.sink, jim.sink_count, 1, stdout);\n\n    return 0;\n}\n"
  },
  {
    "path": "examples/02_binary_tree.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n\n#define JIM_IMPLEMENTATION\n#include \"../jim.h\"\n\n#include \"fruits.h\"\n\ntypedef struct Node Node;\n\nstruct Node {\n    const char *value;\n    Node *left;\n    Node *right;\n};\n\nNode *generate_tree_of_fruits(size_t level_cur, size_t level_max)\n{\n    if (level_cur < level_max) {\n        // Let It Leak! Let It Leak!\n        // Let It Leak! Oh, Let It Leak!\n        // Memory costs nothing!\n        // Let It Leak!\n        Node *node = malloc(sizeof(*node));\n        node->value = fruits[rand() % fruits_count];\n        node->left = generate_tree_of_fruits(level_cur + 1, level_max);\n        node->right = generate_tree_of_fruits(level_cur + 1, level_max);\n        return node;\n    } else {\n        return NULL;\n    }\n}\n\nvoid render_node_as_json(Jim *jim, Node *node)\n{\n    if (node == NULL) {\n        jim_null(jim);\n    } else {\n        jim_object_begin(jim);\n        jim_member_key(jim, \"value\");\n        jim_string(jim, node->value);\n\n        jim_member_key(jim, \"left\");\n        render_node_as_json(jim, node->left);\n\n        jim_member_key(jim, \"right\");\n        render_node_as_json(jim, node->right);\n        jim_object_end(jim);\n    }\n}\n\nint main()\n{\n    srand(time(0));\n    Jim jim = {0};\n    render_node_as_json(&jim, generate_tree_of_fruits(0, 4));\n    fwrite(jim.sink, jim.sink_count, 1, stdout);\n    return 0;\n}\n"
  },
  {
    "path": "examples/03_parsing_database.c",
    "content": "#include <stdio.h>\n#include <stdbool.h>\n#define NOB_IMPLEMENTATION\n#define NOB_STRIP_PREFIX\n#include \"../thirdparty/nob.h\"\n#define JIMP_IMPLEMENTATION\n#include \"../jimp.h\"\n\ntypedef struct {\n    const char *name;\n    double age;\n    const char *location;\n    double body_count;\n} Person;\n\ntypedef struct {\n    Person *items;\n    size_t count;\n    size_t capacity;\n} People;\n\nbool parse_person(Jimp *jimp, Person *p)\n{\n    if (!jimp_object_begin(jimp)) return false;\n    while (jimp_object_member(jimp)) {\n        if (strcmp(jimp->string, \"name\") == 0) {\n            if (!jimp_string(jimp)) return false;\n            p->name = strdup(jimp->string);\n        } else if (strcmp(jimp->string, \"age\") == 0) {\n            if (!jimp_number(jimp)) return false;\n            p->age = jimp->number;\n        } else if (strcmp(jimp->string, \"location\") == 0) {\n            if (!jimp_string(jimp)) return false;\n            p->location = strdup(jimp->string);\n        } else if (strcmp(jimp->string, \"body_count\") == 0) {\n            if (!jimp_number(jimp)) return false;\n            p->body_count = jimp->number;\n        } else {\n            jimp_unknown_member(jimp);\n            return false;\n        }\n    }\n    return jimp_object_end(jimp);\n}\n\nbool parse_people(Jimp *jimp, People *ps)\n{\n    if (!jimp_array_begin(jimp)) return false;\n    while (jimp_array_item(jimp)) {\n        Person p = {0};\n        if (!parse_person(jimp, &p)) return false;\n        da_append(ps, p);\n    }\n    if (!jimp_array_end(jimp)) return false;\n\n    return true;\n}\n\nvoid print_person(const Person *p)\n{\n    printf(\"name       = %s\\n\",  p->name);\n    printf(\"age        = %lf\\n\", p->age);\n    printf(\"location   = %s\\n\",  p->location);\n    printf(\"body_count = %lf\\n\", p->body_count);\n}\n\ntypedef struct {\n    long *items;\n    size_t count;\n    size_t capacity;\n} Numbers;\n\nint main(void)\n{\n    const char *file_path = \"database.json\";\n\n    String_Builder sb = {0};\n    if (!read_entire_file(file_path, &sb)) return 1;\n\n    Jimp jimp = {0};\n    jimp_begin(&jimp, file_path, sb.items, sb.count);\n\n    People ps = {0};\n    Numbers xs = {0};\n    if (!jimp_object_begin(&jimp)) return 1;\n    while (jimp_object_member(&jimp)) {\n        if (strcmp(jimp.string, \"profile\") == 0) {\n            if (!parse_people(&jimp, &ps)) return 1;\n        } else if (strcmp(jimp.string, \"number\") == 0) {\n            if (!jimp_array_begin(&jimp)) return 1;\n            while (jimp_array_item(&jimp)) {\n                if (!jimp_number(&jimp)) return 1;\n                da_append(&xs, jimp.number);\n            }\n            if (!jimp_array_end(&jimp)) return 1;\n        } else {\n            jimp_unknown_member(&jimp);\n            return 1;\n        }\n    }\n    if (!jimp_object_end(&jimp)) return 1;\n\n    da_foreach(Person, p, &ps) {\n        print_person(p);\n        printf(\"\\n\");\n    }\n    printf(\"------------------------------\\n\");\n    da_foreach(long, x, &xs) {\n        printf(\"%ld \", *x);\n    }\n    printf(\"\\n\");\n\n    return 0;\n}\n"
  },
  {
    "path": "examples/Makefile",
    "content": "CFLAGS=-Wall -Wextra -Wswitch-enum -ggdb\n\n.PHONY: all\nall: 01_from_readme 02_binary_tree 03_parsing_database\n\n01_from_readme: 01_from_readme.c ../jim.h\n\t$(CC) $(CFLAGS) -o 01_from_readme 01_from_readme.c\n\n02_binary_tree: 02_binary_tree.c fruits.h ../jim.h\n\t$(CC) $(CFLAGS) -o 02_binary_tree 02_binary_tree.c\n\n03_parsing_database: 03_parsing_database.c ../jimp.h ../thirdparty/nob.h\n\t$(CC) $(CFLAGS) -o 03_parsing_database 03_parsing_database.c\n"
  },
  {
    "path": "examples/database.json",
    "content": "{\n    \"number\": [69, 420, 1337, 80085],\n    \"profile\": [\n        {\n            \"location\": \"Wonderland\",\n            \"location\": \"Wonderland\",\n            \"body_count\": 150,\n            \"name\": \"Alice Smith\",\n            \"age\": 34\n        },\n        {\n            \"name\": \"Bob Johnson\",\n            \"age\": 45,\n            \"location\": \"Atlantis\",\n            \"body_count\": 300\n        },\n        {\n            \"name\": \"Charlie Brown\",\n            \"age\": 28,\n            \"location\": \"Chocolate Factory\",\n            \"body_count\": 200\n        },\n        {\n            \"name\": \"Diana Prince\",\n            \"age\": 32,\n            \"location\": \"Themyscira\",\n            \"body_count\": 250\n        },\n        {\n            \"name\": \"Ethan Hunt\",\n            \"age\": 40,\n            \"location\": \"Mission HQ\",\n            \"body_count\": 350\n        },\n        {\n            \"name\": \"Fiona Apple\",\n            \"age\": 37,\n            \"location\": \"Music City\",\n            \"body_count\": 180\n        },\n        {\n            \"name\": \"George Lucas\",\n            \"age\": 75,\n            \"location\": \"Galaxy Far Far Away\",\n            \"body_count\": 500\n        },\n        {\n            \"name\": \"Hannah Montana\",\n            \"age\": 25,\n            \"location\": \"Nashville\",\n            \"body_count\": 100\n        },\n        {\n            \"name\": \"Ian Malcolm\",\n            \"age\": 60,\n            \"location\": \"Jurassic Park\",\n            \"body_count\": 400\n        },\n        {\n            \"name\": \"Jessica Rabbit\",\n            \"age\": 30,\n            \"location\": \"Toontown\",\n            \"body_count\": 220\n        }\n    ]\n}\n"
  },
  {
    "path": "examples/fruits.h",
    "content": "#ifndef FRUITS_H_\n#define FRUITS_H_\n\n// What? This is just a list of fruits. What did you expect?\n\nconst char *fruits[] = {\n    \"Apple\",\n    \"Apricot\",\n    \"Avocado\",\n    \"Banana\",\n    \"Bilberry\",\n    \"Blackberry\",\n    \"Blackcurrant\",\n    \"Blueberry\",\n    \"Boysenberry\",\n    \"Currant\",\n    \"Cherry\",\n    \"Cherimoya\",\n    \"Chico fruit\",\n    \"Cloudberry\",\n    \"Coconut\",\n    \"Cranberry\",\n    \"Cucumber\",\n    \"Custard apple\",\n    \"Damson\",\n    \"Date\",\n    \"Dragonfruit\",\n    \"Durian\",\n    \"Elderberry\",\n    \"Feijoa\",\n    \"Fig\",\n    \"Goji berry\",\n    \"Gooseberry\",\n    \"Grape\",\n    \"Raisin\",\n    \"Grapefruit\",\n    \"Guava\",\n    \"Honeyberry\",\n    \"Huckleberry\",\n    \"Jabuticaba\",\n    \"Jackfruit\",\n    \"Jambul\",\n    \"Jujube\",\n    \"Juniper berry\",\n    \"Kiwano\",\n    \"Kiwifruit\",\n    \"Kumquat\",\n    \"Lemon\",\n    \"Lime\",\n    \"Loquat\",\n    \"Longan\",\n    \"Lychee\",\n    \"Mango\",\n    \"Mangosteen\",\n    \"Marionberry\",\n    \"Melon\",\n    \"Cantaloupe\",\n    \"Honeydew\",\n    \"Watermelon\",\n    \"Miracle fruit\",\n    \"Mulberry\",\n    \"Nectarine\",\n    \"Nance\",\n    \"Olive\",\n    \"Orange\",\n    \"Blood orange\",\n    \"Clementine\",\n    \"Mandarine\",\n    \"Tangerine\",\n    \"Papaya\",\n    \"Passionfruit\",\n    \"Peach\",\n    \"Pear\",\n    \"Persimmon\",\n    \"Physalis\",\n    \"Plantain\",\n    \"Plum\",\n    \"Prune\",\n    \"Pineapple\",\n    \"Plumcot\",\n    \"Pomegranate\",\n    \"Pomelo\",\n    \"Purple mangosteen\",\n    \"Quince\",\n    \"Raspberry\",\n    \"Salmonberry\",\n    \"Rambutan\",\n    \"Redcurrant\",\n    \"Salal berry\",\n    \"Salak\",\n    \"Satsuma\",\n    \"Soursop\",\n    \"Star fruit\",\n    \"Solanum quitoense\",\n    \"Strawberry\",\n    \"Tamarillo\",\n    \"Tamarind\",\n    \"Ugli fruit\",\n    \"Yuzu\"\n};\nconst size_t fruits_count = sizeof(fruits) / sizeof(fruits[0]);\n\n// Source: https://gist.github.com/lasagnaphil/7667eaeddb6ed0c565f0cb653d756942\n\n#endif // FRUITS_H_\n"
  },
  {
    "path": "jim1.h",
    "content": "// Jim 1.0\n//\n// This is a legacy version of jim which uses\n// - Fixed array for scopes,\n// - Jim_Sink/Jim_Write interface to abstract away the IO,\n// - Jim_Error mechanism for indicating IO errors and/or invalid usage of the API.\n//\n// Not actively supported. Please use Jim 2.0 for new projects. This code is preserved because 2.0 is not backward compatible with 1.0.\n#ifndef JIM_H_\n#define JIM_H_\n\n#ifndef JIM_SCOPES_CAPACITY\n#define JIM_SCOPES_CAPACITY 128\n#endif // JIM_SCOPES_CAPACITY\n\n#include <stddef.h>\n\ntypedef void* Jim_Sink;\ntypedef size_t (*Jim_Write)(const void *ptr, size_t size, size_t nmemb, Jim_Sink sink);\n\ntypedef enum {\n    JIM_OK = 0,\n    JIM_WRITE_ERROR,\n    JIM_SCOPES_OVERFLOW,\n    JIM_SCOPES_UNDERFLOW,\n    JIM_OUT_OF_SCOPE_KEY,\n    JIM_DOUBLE_KEY\n} Jim_Error;\n\nconst char *jim_error_string(Jim_Error error);\n\ntypedef enum {\n    JIM_ARRAY_SCOPE,\n    JIM_OBJECT_SCOPE,\n} Jim_Scope_Kind;\n\ntypedef struct {\n    Jim_Scope_Kind kind;\n    int tail;\n    int key;\n} Jim_Scope;\n\ntypedef struct {\n    Jim_Sink sink;\n    Jim_Write write;\n    Jim_Error error;\n    Jim_Scope scopes[JIM_SCOPES_CAPACITY];\n    size_t scopes_size;\n} Jim;\n\nvoid jim_null(Jim *jim);\nvoid jim_bool(Jim *jim, int boolean);\nvoid jim_integer(Jim *jim, long long int x);\nvoid jim_float(Jim *jim, double x, int precision);\nvoid jim_string(Jim *jim, const char *str);\nvoid jim_string_sized(Jim *jim, const char *str, size_t size);\n\nvoid jim_element_begin(Jim *jim);\nvoid jim_element_end(Jim *jim);\n\nvoid jim_array_begin(Jim *jim);\nvoid jim_array_end(Jim *jim);\n\nvoid jim_object_begin(Jim *jim);\nvoid jim_member_key(Jim *jim, const char *str);\nvoid jim_member_key_sized(Jim *jim, const char *str, size_t size);\nvoid jim_object_end(Jim *jim);\n\n#endif // JIM_H_\n\n#ifdef JIM_IMPLEMENTATION\n\nstatic size_t jim_strlen(const char *s)\n{\n    size_t count = 0;\n    while (*(s + count)) {\n        count += 1;\n    }\n    return count;\n}\n\nstatic void jim_scope_push(Jim *jim, Jim_Scope_Kind kind)\n{\n    if (jim->error == JIM_OK) {\n        if (jim->scopes_size < JIM_SCOPES_CAPACITY) {\n            jim->scopes[jim->scopes_size].kind = kind;\n            jim->scopes[jim->scopes_size].tail = 0;\n            jim->scopes[jim->scopes_size].key = 0;\n            jim->scopes_size += 1;\n        } else {\n            jim->error = JIM_SCOPES_OVERFLOW;\n        }\n    }\n}\n\nstatic void jim_scope_pop(Jim *jim)\n{\n    if (jim->error == JIM_OK) {\n        if (jim->scopes_size > 0) {\n            jim->scopes_size--;\n        } else {\n            jim->error = JIM_SCOPES_UNDERFLOW;\n        }\n    }\n}\n\nstatic Jim_Scope *jim_current_scope(Jim *jim)\n{\n    if (jim->error == JIM_OK) {\n        if (jim->scopes_size > 0) {\n            return &jim->scopes[jim->scopes_size - 1];\n        }\n    }\n\n    return NULL;\n}\n\nstatic void jim_write(Jim *jim, const char *buffer, size_t size)\n{\n    if (jim->error == JIM_OK) {\n        if (jim->write(buffer, 1, size, jim->sink) < size) {\n            jim->error = 1;\n        }\n    }\n}\n\nstatic void jim_write_cstr(Jim *jim, const char *cstr)\n{\n    if (jim->error == JIM_OK) {\n        jim_write(jim, cstr, jim_strlen(cstr));\n    }\n}\n\nstatic int jim_get_utf8_char_len(unsigned char ch)\n{\n    if ((ch & 0x80) == 0) return 1;\n    switch (ch & 0xf0) {\n    case 0xf0:\n        return 4;\n    case 0xe0:\n        return 3;\n    default:\n        return 2;\n    }\n}\n\nvoid jim_element_begin(Jim *jim)\n{\n    if (jim->error == JIM_OK) {\n        Jim_Scope *scope = jim_current_scope(jim);\n        if (scope && scope->tail && !scope->key) {\n            jim_write_cstr(jim, \",\");\n        }\n    }\n}\n\nvoid jim_element_end(Jim *jim)\n{\n    if (jim->error == JIM_OK) {\n        Jim_Scope *scope = jim_current_scope(jim);\n        if (scope) {\n            scope->tail = 1;\n            scope->key = 0;\n        }\n    }\n}\n\nconst char *jim_error_string(Jim_Error error)\n{\n    // TODO(#1): error strings are not particularly useful\n    switch (error) {\n    case JIM_OK:\n        return \"There is no error. The developer of this software just had a case of \\\"Task failed successfully\\\" https://i.imgur.com/Bdb3rkq.jpg - Please contact the developer and tell them that they are very lazy for not checking errors properly.\";\n    case JIM_WRITE_ERROR:\n        return \"Write error\";\n    case JIM_SCOPES_OVERFLOW:\n        return \"Stack of Scopes Overflow\";\n    case JIM_SCOPES_UNDERFLOW:\n        return \"Stack of Scopes Underflow\";\n    case JIM_OUT_OF_SCOPE_KEY:\n        return \"Out of Scope key\";\n    case JIM_DOUBLE_KEY:\n        return \"Tried to set the member key twice\";\n    default:\n        return NULL;\n    }\n}\n\nvoid jim_null(Jim *jim)\n{\n    if (jim->error == JIM_OK) {\n        jim_element_begin(jim);\n        jim_write_cstr(jim, \"null\");\n        jim_element_end(jim);\n    }\n}\n\nvoid jim_bool(Jim *jim, int boolean)\n{\n    if (jim->error == JIM_OK) {\n        jim_element_begin(jim);\n        if (boolean) {\n            jim_write_cstr(jim, \"true\");\n        } else {\n            jim_write_cstr(jim, \"false\");\n        }\n        jim_element_end(jim);\n    }\n}\n\nstatic void jim_integer_no_element(Jim *jim, long long int x)\n{\n    if (jim->error == JIM_OK) {\n        if (x < 0) {\n            jim_write_cstr(jim, \"-\");\n            x = -x;\n        }\n\n        if (x == 0) {\n            jim_write_cstr(jim, \"0\");\n        } else {\n            char buffer[64];\n            size_t count = 0;\n\n            while (x > 0) {\n                buffer[count++] = (x % 10) + '0';\n                x /= 10;\n            }\n\n            for (size_t i = 0; i < count / 2; ++i) {\n                char t = buffer[i];\n                buffer[i] = buffer[count - i - 1];\n                buffer[count - i - 1] = t;\n            }\n\n            jim_write(jim, buffer, count);\n        }\n\n    }\n}\n\nvoid jim_integer(Jim *jim, long long int x)\n{\n    if (jim->error == JIM_OK) {\n        jim_element_begin(jim);\n        jim_integer_no_element(jim, x);\n        jim_element_end(jim);\n    }\n}\n\nstatic int is_nan_or_inf(double x)\n{\n    unsigned long long int mask = (1ULL << 11ULL) - 1ULL;\n    return (((*(unsigned long long int*) &x) >> 52ULL) & mask) == mask;\n}\n\nvoid jim_float(Jim *jim, double x, int precision)\n{\n    if (jim->error == JIM_OK) {\n        if (is_nan_or_inf(x)) {\n            jim_null(jim);\n        } else {\n            jim_element_begin(jim);\n\n            jim_integer_no_element(jim, (long long int) x);\n            x -= (double) (long long int) x;\n            while (precision-- > 0) {\n                x *= 10.0;\n            }\n            jim_write_cstr(jim, \".\");\n\n            long long int y = (long long int) x;\n            if (y < 0) {\n                y = -y;\n            }\n            jim_integer_no_element(jim, y);\n\n            jim_element_end(jim);\n        }\n    }\n}\n\nstatic void jim_string_sized_no_element(Jim *jim, const char *str, size_t size)\n{\n    if (jim->error == JIM_OK) {\n        const char *hex_digits = \"0123456789abcdef\";\n        const char *specials = \"btnvfr\";\n        const char *p = str;\n        size_t len = size;\n\n        jim_write_cstr(jim, \"\\\"\");\n        size_t cl;\n        for (size_t i = 0; i < len; i++) {\n            unsigned char ch = ((unsigned char *) p)[i];\n            if (ch == '\"' || ch == '\\\\') {\n                jim_write(jim, \"\\\\\", 1);\n                jim_write(jim, p + i, 1);\n            } else if (ch >= '\\b' && ch <= '\\r') {\n                jim_write(jim, \"\\\\\", 1);\n                jim_write(jim, &specials[ch - '\\b'], 1);\n            } else if (0x20 <= ch && ch <= 0x7F) { // is printable\n                jim_write(jim, p + i, 1);\n            } else if ((cl = jim_get_utf8_char_len(ch)) == 1) {\n                jim_write(jim, \"\\\\u00\", 4);\n                jim_write(jim, &hex_digits[(ch >> 4) % 0xf], 1);\n                jim_write(jim, &hex_digits[ch % 0xf], 1);\n            } else {\n                jim_write(jim, p + i, cl);\n                i += cl - 1;\n            }\n        }\n\n        jim_write_cstr(jim, \"\\\"\");\n    }\n}\n\nvoid jim_string_sized(Jim *jim, const char *str, size_t size)\n{\n    if (jim->error == JIM_OK) {\n        jim_element_begin(jim);\n        jim_string_sized_no_element(jim, str, size);\n        jim_element_end(jim);\n    }\n}\n\nvoid jim_string(Jim *jim, const char *str)\n{\n    if (jim->error == JIM_OK) {\n        jim_string_sized(jim, str, jim_strlen(str));\n    }\n}\n\nvoid jim_array_begin(Jim *jim)\n{\n    if (jim->error == JIM_OK) {\n        jim_element_begin(jim);\n        jim_write_cstr(jim, \"[\");\n        jim_scope_push(jim, JIM_ARRAY_SCOPE);\n    }\n}\n\n\nvoid jim_array_end(Jim *jim)\n{\n    if (jim->error == JIM_OK) {\n        jim_write_cstr(jim, \"]\");\n        jim_scope_pop(jim);\n        jim_element_end(jim);\n    }\n}\n\nvoid jim_object_begin(Jim *jim)\n{\n    if (jim->error == JIM_OK) {\n        jim_element_begin(jim);\n        jim_write_cstr(jim, \"{\");\n        jim_scope_push(jim, JIM_OBJECT_SCOPE);\n    }\n}\n\nvoid jim_member_key(Jim *jim, const char *str)\n{\n    if (jim->error == JIM_OK) {\n        jim_member_key_sized(jim, str, jim_strlen(str));\n    }\n}\n\nvoid jim_member_key_sized(Jim *jim, const char *str, size_t size)\n{\n    if (jim->error == JIM_OK) {\n        jim_element_begin(jim);\n        Jim_Scope *scope = jim_current_scope(jim);\n        if (scope && scope->kind == JIM_OBJECT_SCOPE) {\n            if (!scope->key) {\n                jim_string_sized_no_element(jim, str, size);\n                jim_write_cstr(jim, \":\");\n                scope->key = 1;\n            } else {\n                jim->error = JIM_DOUBLE_KEY;\n            }\n        } else {\n            jim->error = JIM_OUT_OF_SCOPE_KEY;\n        }\n    }\n}\n\nvoid jim_object_end(Jim *jim)\n{\n    if (jim->error == JIM_OK) {\n        jim_write_cstr(jim, \"}\");\n        jim_scope_pop(jim);\n        jim_element_end(jim);\n    }\n}\n\n#endif // JIM_IMPLEMENTATION\n"
  },
  {
    "path": "jim2.h",
    "content": "// Jim 2.0\n//\n// Current version of Jim. Main differences from Jim 1.0 are\n// - Using Dynamic Arrays for scopes allowing them to be arbitrarily nested,\n// - Collecting the output into a sink which is a String Builder now, delegating all the IO hustle to the user of the library,\n// - Lack of Jim_Error mechanism, which dealt with IO errors and invalid usage of the API. Since we don't deal with IO anymore we have no IO errors. And invalid usage of the API is simply assert()-ed.\n\n#ifndef JIM_H_\n#define JIM_H_\n\n#ifndef JIM_SCOPES_CAPACITY\n#define JIM_SCOPES_CAPACITY 128\n#endif // JIM_SCOPES_CAPACITY\n\n#include <assert.h>\n#include <stdlib.h>\n#include <stdbool.h>\n#include <string.h>\n\ntypedef enum {\n    JIM_ARRAY_SCOPE,\n    JIM_OBJECT_SCOPE,\n} Jim_Scope_Kind;\n\ntypedef struct {\n    Jim_Scope_Kind kind;\n    int tail;                   // Not the first element in an array or an object\n    int key;                    // An object key was just placed\n} Jim_Scope;\n\ntypedef struct {\n    char *sink;\n    size_t sink_count;\n    size_t sink_capacity;\n    Jim_Scope *scopes;\n    size_t scopes_count;\n    size_t scopes_capacity;\n    size_t pp;\n} Jim;\n\nvoid jim_begin(Jim *jim);\nvoid jim_null(Jim *jim);\nvoid jim_bool(Jim *jim, int boolean);\nvoid jim_integer(Jim *jim, long long int x);\n// TODO: deprecate this version of jim_float introduce the one that does not require precision and uses something like sprintf from libc to render the floats\nvoid jim_float(Jim *jim, double x, int precision);\nvoid jim_string(Jim *jim, const char *str);\nvoid jim_string_sized(Jim *jim, const char *str, size_t size);\n\nvoid jim_element_begin(Jim *jim);\nvoid jim_element_end(Jim *jim);\n\nvoid jim_array_begin(Jim *jim);\nvoid jim_array_end(Jim *jim);\n\nvoid jim_object_begin(Jim *jim);\nvoid jim_member_key(Jim *jim, const char *str);\nvoid jim_member_key_sized(Jim *jim, const char *str, size_t size);\nvoid jim_object_end(Jim *jim);\n\n#endif // JIM_H_\n\n#ifdef JIM_IMPLEMENTATION\n\nstatic void jim_scope_push(Jim *jim, Jim_Scope_Kind kind)\n{\n    if (jim->scopes_count >= jim->scopes_capacity) {\n        if (jim->scopes_capacity == 0) jim->scopes_capacity = JIM_SCOPES_CAPACITY;\n        else jim->scopes_capacity *= 2;\n        jim->scopes = realloc(jim->scopes, sizeof(*jim->scopes)*jim->scopes_capacity);\n        assert(jim->scopes);\n    }\n    jim->scopes[jim->scopes_count].kind = kind;\n    jim->scopes[jim->scopes_count].tail = 0;\n    jim->scopes[jim->scopes_count].key = 0;\n    jim->scopes_count += 1;\n}\n\nstatic void jim_scope_pop(Jim *jim)\n{\n    assert(jim->scopes_count > 0);\n    jim->scopes_count--;\n}\n\nstatic Jim_Scope *jim_current_scope(Jim *jim)\n{\n    if (jim->scopes_count > 0) {\n        return &jim->scopes[jim->scopes_count - 1];\n    }\n    return NULL;\n}\n\nstatic void jim_write(Jim *jim, const char *buffer, size_t size)\n{\n    while (jim->sink_count + size >= jim->sink_capacity) {\n        // TODO: rename JIM_SCOPES_CAPACITY to something else since it's used by both sink and scopes\n        if (jim->sink_capacity == 0) jim->sink_capacity = JIM_SCOPES_CAPACITY;\n        else jim->sink_capacity *= 2;\n        jim->sink = realloc(jim->sink, sizeof(*jim->sink)*jim->sink_capacity);\n    }\n    memcpy(jim->sink + jim->sink_count, buffer, size);\n    jim->sink_count += size;\n}\n\nstatic void jim_write_cstr(Jim *jim, const char *cstr)\n{\n    jim_write(jim, cstr, strlen(cstr));\n}\n\nstatic int jim_get_utf8_char_len(unsigned char ch)\n{\n    if ((ch & 0x80) == 0) return 1;\n    switch (ch & 0xf0) {\n    case 0xf0:\n        return 4;\n    case 0xe0:\n        return 3;\n    default:\n        return 2;\n    }\n}\n\nvoid jim_begin(Jim *jim)\n{\n    jim->sink_count = 0;\n    jim->scopes_count = 0;\n}\n\nvoid jim_element_begin(Jim *jim)\n{\n    Jim_Scope *scope = jim_current_scope(jim);\n    if (scope) {\n        if (scope->tail && !scope->key) {\n            jim_write_cstr(jim, \",\");\n        }\n        if (jim->pp) {\n            if (scope->key) {\n                jim_write_cstr(jim, \" \");\n            } else {\n                jim_write_cstr(jim, \"\\n\");\n                for (size_t i = 0; i < jim->scopes_count*jim->pp; ++i) {\n                    jim_write_cstr(jim, \" \");\n                }\n            }\n        }\n    }\n}\n\nvoid jim_element_end(Jim *jim)\n{\n    Jim_Scope *scope = jim_current_scope(jim);\n    if (scope) {\n        scope->tail = 1;\n        scope->key = 0;\n    }\n}\n\nvoid jim_null(Jim *jim)\n{\n    jim_element_begin(jim);\n    jim_write_cstr(jim, \"null\");\n    jim_element_end(jim);\n}\n\nvoid jim_bool(Jim *jim, int boolean)\n{\n    jim_element_begin(jim);\n    if (boolean) {\n        jim_write_cstr(jim, \"true\");\n    } else {\n        jim_write_cstr(jim, \"false\");\n    }\n    jim_element_end(jim);\n}\n\nstatic void jim_integer_no_element(Jim *jim, long long int x)\n{\n    if (x < 0) {\n        jim_write_cstr(jim, \"-\");\n        x = -x;\n    }\n\n    if (x == 0) {\n        jim_write_cstr(jim, \"0\");\n    } else {\n        char buffer[64];\n        size_t count = 0;\n\n        while (x > 0) {\n            buffer[count++] = (x % 10) + '0';\n            x /= 10;\n        }\n\n        for (size_t i = 0; i < count / 2; ++i) {\n            char t = buffer[i];\n            buffer[i] = buffer[count - i - 1];\n            buffer[count - i - 1] = t;\n        }\n\n        jim_write(jim, buffer, count);\n    }\n}\n\nvoid jim_integer(Jim *jim, long long int x)\n{\n    jim_element_begin(jim);\n    jim_integer_no_element(jim, x);\n    jim_element_end(jim);\n}\n\nstatic int is_nan_or_inf(double x)\n{\n    unsigned long long int mask = (1ULL << 11ULL) - 1ULL;\n    return (((*(unsigned long long int*) &x) >> 52ULL) & mask) == mask;\n}\n\nvoid jim_float(Jim *jim, double x, int precision)\n{\n    if (is_nan_or_inf(x)) {\n        jim_null(jim);\n    } else {\n        jim_element_begin(jim);\n\n        jim_integer_no_element(jim, (long long int) x);\n        x -= (double) (long long int) x;\n        while (precision-- > 0) {\n            x *= 10.0;\n        }\n        jim_write_cstr(jim, \".\");\n\n        long long int y = (long long int) x;\n        if (y < 0) {\n            y = -y;\n        }\n        jim_integer_no_element(jim, y);\n\n        jim_element_end(jim);\n    }\n}\n\nstatic void jim_string_sized_no_element(Jim *jim, const char *str, size_t size)\n{\n    const char *hex_digits = \"0123456789abcdef\";\n    const char *specials = \"btnvfr\";\n    const char *p = str;\n    size_t len = size;\n\n    jim_write_cstr(jim, \"\\\"\");\n    size_t cl;\n    for (size_t i = 0; i < len; i++) {\n        unsigned char ch = ((unsigned char *) p)[i];\n        if (ch == '\"' || ch == '\\\\') {\n            jim_write(jim, \"\\\\\", 1);\n            jim_write(jim, p + i, 1);\n        } else if (ch >= '\\b' && ch <= '\\r') {\n            jim_write(jim, \"\\\\\", 1);\n            jim_write(jim, &specials[ch - '\\b'], 1);\n        } else if (0x20 <= ch && ch <= 0x7F) { // is printable\n        jim_write(jim, p + i, 1);\n    } else if ((cl = jim_get_utf8_char_len(ch)) == 1) {\n        jim_write(jim, \"\\\\u00\", 4);\n        jim_write(jim, &hex_digits[(ch >> 4) % 0xf], 1);\n        jim_write(jim, &hex_digits[ch % 0xf], 1);\n    } else {\n        jim_write(jim, p + i, cl);\n        i += cl - 1;\n    }\n}\n\njim_write_cstr(jim, \"\\\"\");\n}\n\nvoid jim_string_sized(Jim *jim, const char *str, size_t size)\n{\n    jim_element_begin(jim);\n    jim_string_sized_no_element(jim, str, size);\n    jim_element_end(jim);\n}\n\nvoid jim_string(Jim *jim, const char *str)\n{\n    jim_string_sized(jim, str, strlen(str));\n}\n\nvoid jim_array_begin(Jim *jim)\n{\n    jim_element_begin(jim);\n    jim_write_cstr(jim, \"[\");\n    jim_scope_push(jim, JIM_ARRAY_SCOPE);\n}\n\n\nvoid jim_array_end(Jim *jim)\n{\n    Jim_Scope *scope = jim_current_scope(jim);\n    if (jim->pp && scope && scope->tail) {\n        jim_write_cstr(jim, \"\\n\");\n        for (size_t i = 0; i < (jim->scopes_count - 1)*jim->pp; ++i) {\n            jim_write_cstr(jim, \" \");\n        }\n    }\n    jim_write_cstr(jim, \"]\");\n    jim_scope_pop(jim);\n    jim_element_end(jim);\n}\n\nvoid jim_object_begin(Jim *jim)\n{\n    jim_element_begin(jim);\n    jim_write_cstr(jim, \"{\");\n    jim_scope_push(jim, JIM_OBJECT_SCOPE);\n}\n\nvoid jim_member_key(Jim *jim, const char *str)\n{\n    jim_member_key_sized(jim, str, strlen(str));\n}\n\nvoid jim_member_key_sized(Jim *jim, const char *str, size_t size)\n{\n    jim_element_begin(jim);\n    Jim_Scope *scope = jim_current_scope(jim);\n    assert(scope);\n    assert(scope->kind == JIM_OBJECT_SCOPE);\n    assert(!scope->key);\n    jim_string_sized_no_element(jim, str, size);\n    jim_write_cstr(jim, \":\");\n    scope->key = 1;\n}\n\nvoid jim_object_end(Jim *jim)\n{\n    Jim_Scope *scope = jim_current_scope(jim);\n    if (jim->pp && scope && scope->tail) {\n        jim_write_cstr(jim, \"\\n\");\n        for (size_t i = 0; i < (jim->scopes_count - 1)*jim->pp; ++i) {\n            jim_write_cstr(jim, \" \");\n        }\n    }\n    jim_write_cstr(jim, \"}\");\n    jim_scope_pop(jim);\n    jim_element_end(jim);\n}\n\n#endif // JIM_IMPLEMENTATION\n"
  },
  {
    "path": "jimp.h",
    "content": "// Prototype of an Immediate Deserialization idea. Expect this API to change a lot.\n#ifndef JIMP_H_\n#define JIMP_H_\n\n#include <assert.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdbool.h>\n#include <stdarg.h>\n#include <string.h>\n#include <ctype.h>\n\n// TODO: move all diagnostics reporting outside of the library\n//   So the user has more options on how to report things\n\ntypedef enum {\n    JIMP_INVALID,\n    JIMP_EOF,\n\n    // Puncts\n    JIMP_OCURLY,\n    JIMP_CCURLY,\n    JIMP_OBRACKET,\n    JIMP_CBRACKET,\n    JIMP_COMMA,\n    JIMP_COLON,\n\n    // Symbols\n    JIMP_TRUE,\n    JIMP_FALSE,\n    JIMP_NULL,\n\n    // Values\n    JIMP_STRING,\n    JIMP_NUMBER,\n} Jimp_Token;\n\ntypedef struct {\n    const char *file_path;\n    const char *start;\n    const char *end;\n    const char *point;\n\n    Jimp_Token token;\n    const char *token_start;    // TODO: `token_start` is primarily used for diagnostics location. Rename it accordingly.\n\n    char *string;\n    size_t string_count;\n    size_t string_capacity;\n    double number;\n    bool boolean;\n} Jimp;\n\n// TODO: how do null-s fit into this entire system?\n\nvoid jimp_begin(Jimp *jimp, const char *file_path, const char *input, size_t input_size);\n\n/// If succeeds puts the freshly parsed boolean into jimp->boolean.\n/// Any consequent calls to the jimp_* functions may invalidate jimp->boolean.\nbool jimp_bool(Jimp *jimp);\n\n/// If succeeds puts the freshly parsed number into jimp->number.\n/// Any consequent calls to the jimp_* functions may invalidate jimp->number.\nbool jimp_number(Jimp *jimp);\n\n/// If succeeds puts the freshly parsed string into jimp->string as a NULL-terminated string.\n/// Any consequent calls to the jimp_* functions may invalidate jimp->string.\n/// strdup it if you don't wanna lose it (memory management is on you at that point).\nbool jimp_string(Jimp *jimp);\n\n/// Parses the beginning of the object `{`\nbool jimp_object_begin(Jimp *jimp);\n\n/// If succeeds puts the key of the member into jimp->string as a NULL-terminated string.\n/// Any consequent calls to the jimp_* functions may invalidate jimp->string.\n/// strdup it if you don't wanna lose it (memory management is on you at that point).\nbool jimp_object_member(Jimp *jimp);\n\n/// Parses the end of the object `}`\nbool jimp_object_end(Jimp *jimp);\n\n/// Reports jimp->string as an unknown member. jimp->string is expected to be populated by\n/// jimp_object_member.\nvoid jimp_unknown_member(Jimp *jimp);\n\n/// Parses the beginning of the array `[`\nbool jimp_array_begin(Jimp *jimp);\n\n/// Checks whether there is any more items in the array.\nbool jimp_array_item(Jimp *jimp);\n\n/// Parses the end of the array `]`\nbool jimp_array_end(Jimp *jimp);\n\n/// Prints diagnostic at the current position of the parser.\nvoid jimp_diagf(Jimp *jimp, const char *fmt, ...);\n\nbool jimp_is_null_ahead(Jimp *jimp);\nbool jimp_is_bool_ahead(Jimp *jimp);\nbool jimp_is_number_ahead(Jimp *jimp);\nbool jimp_is_string_ahead(Jimp *jimp);\nbool jimp_is_array_ahead(Jimp *jimp);\nbool jimp_is_object_ahead(Jimp *jimp);\n\n#endif // JIMP_H_\n\n#ifdef JIMP_IMPLEMENTATION\n\nstatic bool jimp__expect_token(Jimp *jimp, Jimp_Token token);\nstatic bool jimp__get_and_expect_token(Jimp *jimp, Jimp_Token token);\nstatic const char *jimp__token_kind(Jimp_Token token);\nstatic bool jimp__get_token(Jimp *jimp);\nstatic void jimp__skip_whitespaces(Jimp *jimp);\nstatic void jimp__append_to_string(Jimp *jimp, char x);\n\nstatic void jimp__append_to_string(Jimp *jimp, char x)\n{\n    if (jimp->string_count >= jimp->string_capacity) {\n        if (jimp->string_capacity == 0) jimp->string_capacity = 1024;\n        else jimp->string_capacity *= 2;\n        jimp->string = realloc(jimp->string, jimp->string_capacity);\n    }\n    jimp->string[jimp->string_count++] = x;\n}\n\nstatic void jimp__skip_whitespaces(Jimp *jimp)\n{\n    while (jimp->point < jimp->end && isspace(*jimp->point)) {\n        jimp->point += 1;\n    }\n}\n\nstatic Jimp_Token jimp__puncts[256] = {\n    ['{'] = JIMP_OCURLY,\n    ['}'] = JIMP_CCURLY,\n    ['['] = JIMP_OBRACKET,\n    [']'] = JIMP_CBRACKET,\n    [','] = JIMP_COMMA,\n    [':'] = JIMP_COLON,\n};\n\nstatic struct {\n    Jimp_Token token;\n    const char *symbol;\n} jimp__symbols[] = {\n    { .token = JIMP_TRUE,  .symbol = \"true\"  },\n    { .token = JIMP_FALSE, .symbol = \"false\" },\n    { .token = JIMP_NULL,  .symbol = \"null\"  },\n};\n#define jimp__symbols_count (sizeof(jimp__symbols)/sizeof(jimp__symbols[0]))\n\nstatic bool jimp__get_token(Jimp *jimp)\n{\n    jimp__skip_whitespaces(jimp);\n\n    jimp->token_start = jimp->point;\n\n    if (jimp->point >= jimp->end) {\n        jimp->token = JIMP_EOF;\n        return false;\n    }\n\n    jimp->token = jimp__puncts[(unsigned char)*jimp->point];\n    if (jimp->token) {\n        jimp->point += 1;\n        return true;\n    }\n\n    for (size_t i = 0; i < jimp__symbols_count; ++i) {\n        const char *symbol = jimp__symbols[i].symbol;\n        if (*symbol == *jimp->point) {\n            while (*symbol && jimp->point < jimp->end && *symbol++ == *jimp->point++) {}\n            if (*symbol) {\n                jimp->token = JIMP_INVALID;\n                jimp_diagf(jimp, \"ERROR: invalid symbol\\n\");\n                return false;\n            } else {\n                jimp->token = jimp__symbols[i].token;\n                return true;\n            }\n        }\n    }\n\n    char *endptr = NULL;\n    jimp->number = strtod(jimp->point, &endptr); // TODO: This implies that jimp->end is a valid address and *jimp->end == 0\n    if (jimp->point != endptr) {\n        jimp->point = endptr;\n        jimp->token = JIMP_NUMBER;\n        return true;\n    }\n\n    if (*jimp->point == '\"') {\n        jimp->point++;\n        jimp->string_count = 0;\n        while (jimp->point < jimp->end) {\n            // TODO: support all the JSON escape sequences defined in the spec\n            // Yes, including those dumb suroggate pairs. Spec is spec.\n            switch (*jimp->point) {\n            case '\\\\': {\n                jimp->point++;\n                if (jimp->point >= jimp->end) {\n                    jimp->token_start = jimp->point;\n                    jimp_diagf(jimp, \"ERROR: unfinished escape sequence\\n\");\n                    return false;\n                }\n                switch (*jimp->point) {\n                case 'r':\n                    jimp->point++;\n                    jimp__append_to_string(jimp, '\\r');\n                    break;\n                case 'n':\n                    jimp->point++;\n                    jimp__append_to_string(jimp, '\\n');\n                    break;\n                case 't':\n                    jimp->point++;\n                    jimp__append_to_string(jimp, '\\t');\n                    break;\n                case '\\\\':\n                    jimp->point++;\n                    jimp__append_to_string(jimp, '\\\\');\n                    break;\n                case '\"':\n                    jimp->point++;\n                    jimp__append_to_string(jimp, '\"');\n                    break;\n                default:\n                    jimp->token_start = jimp->point;\n                    jimp_diagf(jimp, \"ERROR: invalid escape sequence\\n\");\n                    return false;\n                }\n                break;\n            }\n            case '\"': {\n                jimp->point++;\n                jimp__append_to_string(jimp, '\\0');\n                jimp->token = JIMP_STRING;\n                return true;\n            }\n            default: {\n                char x = *jimp->point++;\n                jimp__append_to_string(jimp, x);\n            }\n            }\n        }\n        jimp->token = JIMP_INVALID;\n        jimp_diagf(jimp, \"ERROR: unfinished string\\n\");\n        return false;\n    }\n\n    jimp->token = JIMP_INVALID;\n    jimp_diagf(jimp, \"ERROR: invalid token\\n\");\n    return false;\n}\n\nvoid jimp_begin(Jimp *jimp, const char *file_path, const char *input, size_t input_size)\n{\n    jimp->file_path = file_path;\n    jimp->start     = input;\n    jimp->end       = input + input_size;\n    jimp->point     = input;\n}\n\nvoid jimp_diagf(Jimp *jimp, const char *fmt, ...)\n{\n    long line_number = 0;\n    const char *line_start = jimp->start;\n    const char *point = jimp->start;\n    while (point < jimp->token_start) {\n        char x = *point++;\n        if (x == '\\n') {\n            line_start = point;\n            line_number += 1;\n        }\n    }\n\n    fprintf(stderr, \"%s:%ld:%ld: \", jimp->file_path, line_number + 1, point - line_start + 1);\n    va_list args;\n    va_start(args, fmt);\n    vfprintf(stderr, fmt, args);\n    va_end(args);\n}\n\nstatic const char *jimp__token_kind(Jimp_Token token)\n{\n   switch (token) {\n   case JIMP_EOF:      return \"end of input\";\n   case JIMP_INVALID:  return \"invalid\";\n   case JIMP_OCURLY:   return \"{\";\n   case JIMP_CCURLY:   return \"}\";\n   case JIMP_OBRACKET: return \"[\";\n   case JIMP_CBRACKET: return \"]\";\n   case JIMP_COMMA:    return \",\";\n   case JIMP_COLON:    return \":\";\n   case JIMP_TRUE:     return \"true\";\n   case JIMP_FALSE:    return \"false\";\n   case JIMP_NULL:     return \"null\";\n   case JIMP_STRING:   return \"string\";\n   case JIMP_NUMBER:   return \"number\";\n   }\n   assert(0 && \"unreachable\");\n   return NULL;\n}\n\nbool jimp_array_begin(Jimp *jimp)\n{\n    return jimp__get_and_expect_token(jimp, JIMP_OBRACKET);\n}\n\nbool jimp_array_end(Jimp *jimp)\n{\n    return jimp__get_and_expect_token(jimp, JIMP_CBRACKET);\n}\n\nbool jimp_array_item(Jimp *jimp)\n{\n    const char *point = jimp->point;\n    if (!jimp__get_token(jimp)) return false;\n    if (jimp->token == JIMP_COMMA) return true;\n    if (jimp->token == JIMP_CBRACKET) {\n        jimp->point = point;\n        return false;\n    }\n    jimp->point = point;\n    return true;\n}\n\nvoid jimp_unknown_member(Jimp *jimp)\n{\n    jimp_diagf(jimp, \"ERROR: unexpected object member `%s`\\n\", jimp->string);\n}\n\nbool jimp_object_begin(Jimp *jimp)\n{\n    return jimp__get_and_expect_token(jimp, JIMP_OCURLY);\n}\n\nbool jimp_object_member(Jimp *jimp)\n{\n    const char *point = jimp->point;\n    if (!jimp__get_token(jimp)) return false;\n    if (jimp->token == JIMP_COMMA) {\n        if (!jimp__get_and_expect_token(jimp, JIMP_STRING)) return false;\n        if (!jimp__get_and_expect_token(jimp, JIMP_COLON)) return false;\n        return true;\n    }\n    if (jimp->token == JIMP_CCURLY) {\n        jimp->point = point;\n        return false;\n    }\n    if (!jimp__expect_token(jimp, JIMP_STRING)) return false;\n    if (!jimp__get_and_expect_token(jimp, JIMP_COLON)) return false;\n    return true;\n}\n\nbool jimp_object_end(Jimp *jimp)\n{\n    return jimp__get_and_expect_token(jimp, JIMP_CCURLY);\n}\n\nbool jimp_string(Jimp *jimp)\n{\n    return jimp__get_and_expect_token(jimp, JIMP_STRING);\n}\n\nbool jimp_bool(Jimp *jimp)\n{\n    jimp__get_token(jimp);\n    if (jimp->token == JIMP_TRUE) {\n        jimp->boolean = true;\n    } else if (jimp->token == JIMP_FALSE) {\n        jimp->boolean = false;\n    } else {\n        jimp_diagf(jimp, \"ERROR: expected boolean, but got `%s`\\n\", jimp__token_kind(jimp->token));\n        return false;\n    }\n    return true;\n}\n\nbool jimp_number(Jimp *jimp)\n{\n    return jimp__get_and_expect_token(jimp, JIMP_NUMBER);\n}\n\nbool jimp_is_null_ahead(Jimp *jimp)\n{\n    const char *point = jimp->point;\n    if (!jimp__get_token(jimp)) return false;\n    jimp->point = point;\n    return jimp->token == JIMP_NULL;\n}\n\nbool jimp_is_bool_ahead(Jimp *jimp)\n{\n    const char *point = jimp->point;\n    if (!jimp__get_token(jimp)) return false;\n    jimp->point = point;\n    return jimp->token == JIMP_TRUE || jimp->token == JIMP_FALSE;\n}\n\nbool jimp_is_number_ahead(Jimp *jimp)\n{\n    const char *point = jimp->point;\n    if (!jimp__get_token(jimp)) return false;\n    jimp->point = point;\n    return jimp->token == JIMP_NUMBER;\n}\n\nbool jimp_is_string_ahead(Jimp *jimp)\n{\n    const char *point = jimp->point;\n    if (!jimp__get_token(jimp)) return false;\n    jimp->point = point;\n    return jimp->token == JIMP_STRING;\n}\n\nbool jimp_is_array_ahead(Jimp *jimp)\n{\n    const char *point = jimp->point;\n    if (!jimp__get_token(jimp)) return false;\n    jimp->point = point;\n    return jimp->token == JIMP_OBRACKET;\n}\n\nbool jimp_is_object_ahead(Jimp *jimp)\n{\n    const char *point = jimp->point;\n    if (!jimp__get_token(jimp)) return false;\n    jimp->point = point;\n    return jimp->token == JIMP_OCURLY;\n}\n\nstatic bool jimp__get_and_expect_token(Jimp *jimp, Jimp_Token token)\n{\n    if (!jimp__get_token(jimp)) return false;\n    return jimp__expect_token(jimp, token);\n}\n\nstatic bool jimp__expect_token(Jimp *jimp, Jimp_Token token)\n{\n    if (jimp->token != token) {\n        jimp_diagf(jimp, \"ERROR: expected %s, but got %s\\n\", jimp__token_kind(token), jimp__token_kind(jimp->token));\n        return false;\n    }\n    return true;\n}\n\n#endif // JIMP_IMPLEMENTATION\n"
  },
  {
    "path": "test_jim.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#define JIM_IMPLEMENTATION\n#include \"./jim.h\"\n#define NOB_IMPLEMENTATION\n#define NOB_STRIP_PREFIX\n#include \"./nob.h\"\n\n#include \"./test_jim_expected.h\"\n\nvoid null_case(Jim *jim)\n{\n    jim_begin(jim);\n    jim_array_begin(jim);\n    jim_null(jim);\n    jim_array_end(jim);\n}\n\nvoid bool_case(Jim *jim)\n{\n    jim_begin(jim);\n    jim_array_begin(jim);\n    jim_bool(jim, 0);\n    jim_bool(jim, 1);\n    jim_array_end(jim);\n}\n\nvoid integer_case(Jim *jim)\n{\n    jim_begin(jim);\n    jim_array_begin(jim);\n    for (int i = -10; i <= 10; ++i) {\n        jim_integer(jim, i);\n    }\n    jim_array_end(jim);\n}\n\nvoid float_case(Jim *jim)\n{\n    jim_begin(jim);\n    jim_array_begin(jim);\n    jim_float(jim, 0.0, 4);\n    jim_float(jim, -0.0, 4);\n    jim_float(jim, 3.1415, 4);\n    jim_float(jim, 2.71828, 5);\n    jim_float(jim, 1.6180, 1);\n    jim_float(jim, 0.0 / 0.0, 4);\n    jim_float(jim, 1.0 / 0.0, 4);\n    jim_float(jim, -1.0 / 0.0, 4);\n    jim_array_end(jim);\n}\n\nvoid string_case(Jim *jim)\n{\n    jim_begin(jim);\n    jim_array_begin(jim);\n    jim_string(jim, \"hello\");\n    jim_string(jim, \"world\");\n    jim_string(jim, \"\\n\\b\\t\");\n    jim_string_sized(jim, \"\\0\\0\\0\\0\", 4);\n    jim_array_end(jim);\n}\n\nvoid array_case(Jim *jim)\n{\n    jim_begin(jim);\n    jim_array_begin(jim);\n\n    for (int n = 1; n <= 5; ++n) {\n        for (int i = 0; i < n; ++i) jim_array_begin(jim);\n        for (int i = 0; i < n; ++i) jim_array_end(jim);\n    }\n\n    jim_array_end(jim);\n}\n\nvoid object_case_rec(Jim *jim, int level, int *counter)\n{\n    if (level < 3) {\n        jim_object_begin(jim);\n        jim_member_key(jim, \"l\");\n        object_case_rec(jim, level + 1, counter);\n        jim_member_key(jim, \"r\");\n        object_case_rec(jim, level + 1, counter);\n        jim_object_end(jim);\n    } else {\n        jim_integer(jim, (*counter)++);\n    }\n}\n\nvoid object_case(Jim *jim)\n{\n    jim_begin(jim);\n    int counter = 0;\n    object_case_rec(jim, 0, &counter);\n}\n\nvoid pp_empty_array_case(Jim *jim)\n{\n    jim_begin(jim);\n    jim->pp = 4;\n    jim_array_begin(jim);\n    jim_array_end(jim);\n}\n\nvoid pp_singleton_array_case(Jim *jim)\n{\n    jim_begin(jim);\n    jim->pp = 4;\n    jim_array_begin(jim);\n    jim_integer(jim, 69);\n    jim_array_end(jim);\n}\n\nvoid pp_array_case(Jim *jim)\n{\n    jim_begin(jim);\n    jim->pp = 4;\n    jim_array_begin(jim);\n    jim_integer(jim, 69);\n    jim_integer(jim, 420);\n    jim_integer(jim, 1337);\n    jim_integer(jim, 80085);\n    jim_array_end(jim);\n}\n\nvoid pp_empty_object_case(Jim *jim)\n{\n    jim_begin(jim);\n    jim->pp = 4;\n    jim_object_begin(jim);\n    jim_object_end(jim);\n}\n\nvoid pp_singleton_object_case(Jim *jim)\n{\n    jim_begin(jim);\n    jim->pp = 4;\n    jim_object_begin(jim);\n    jim_member_key(jim, \"foo\");\n    jim_integer(jim, 69);\n    jim_object_end(jim);\n}\n\nvoid pp_object_case(Jim *jim)\n{\n    jim_begin(jim);\n    jim->pp = 4;\n    jim_object_begin(jim);\n    jim_member_key(jim, \"foo\");\n    jim_integer(jim, 69);\n    jim_member_key(jim, \"bar\");\n    jim_integer(jim, 420);\n    jim_member_key(jim, \"baz\");\n    jim_integer(jim, 1337);\n    jim_object_end(jim);\n}\n\nvoid pp_nested_case(Jim *jim)\n{\n    jim_begin(jim);\n    jim->pp = 4;\n    jim_object_begin(jim);\n        jim_member_key(jim, \"integer\");\n        jim_integer(jim, 69);\n        jim_member_key(jim, \"empty_array\");\n        jim_array_begin(jim);\n        jim_array_end(jim);\n        jim_member_key(jim, \"empty_object\");\n        jim_object_begin(jim);\n        jim_object_end(jim);\n        jim_member_key(jim, \"array_of_integers\");\n        jim_array_begin(jim);\n            jim_integer(jim, 69);\n            jim_integer(jim, 420);\n            jim_integer(jim, 1337);\n            jim_integer(jim, 80085);\n        jim_array_end(jim);\n        jim_member_key(jim, \"object_of_integers\");\n        jim_object_begin(jim);\n            jim_member_key(jim, \"foo\");\n            jim_integer(jim, 69);\n            jim_member_key(jim, \"bar\");\n            jim_integer(jim, 420);\n            jim_member_key(jim, \"baz\");\n            jim_integer(jim, 1337);\n            jim_member_key(jim, \"karabaz\");\n            jim_integer(jim, 80085);\n        jim_object_end(jim);\n    jim_object_end(jim);\n}\n\ntypedef struct {\n    const char *name;\n    void (*run)(Jim *jim);\n} Test_Case;\n\n#define TEST_CASE(case_name) \\\n    { \\\n        .name = #case_name, \\\n        .run = case_name \\\n    }\n\nconst Test_Case test_cases[] = {\n    TEST_CASE(null_case),\n    TEST_CASE(bool_case),\n    TEST_CASE(integer_case),\n    TEST_CASE(float_case),\n    TEST_CASE(string_case),\n    TEST_CASE(array_case),\n    TEST_CASE(object_case),\n    TEST_CASE(pp_empty_array_case),\n    TEST_CASE(pp_singleton_array_case),\n    TEST_CASE(pp_array_case),\n    TEST_CASE(pp_empty_object_case),\n    TEST_CASE(pp_singleton_object_case),\n    TEST_CASE(pp_object_case),\n    TEST_CASE(pp_nested_case),\n};\n\nbool record(const char *header_path)\n{\n    Jim jim_stream = {0};\n    Jim jim_buffer = {0};\n\n    jim_write_cstr(&jim_stream, \"const char *test_cases_expected[] = {\\n\");\n    for (size_t i = 0; i < ARRAY_LEN(test_cases); ++i) {\n        jim_buffer.sink_count = 0;\n        test_cases[i].run(&jim_buffer);\n        jim_write_cstr(&jim_stream, \"    \");\n        jim_string_sized(&jim_stream, jim_buffer.sink, jim_buffer.sink_count);\n        jim_write_cstr(&jim_stream, \",\\n\");\n    }\n    jim_write_cstr(&jim_stream, \"};\\n\");\n\n    if (!write_entire_file(header_path, jim_stream.sink, jim_stream.sink_count)) return false;\n    printf(\"Updated %s\\n\", header_path);\n    return true;\n}\n\nvoid test(void)\n{\n    Jim jim_buffer = {0};\n\n\n    assert(ARRAY_LEN(test_cases) == ARRAY_LEN(test_cases_expected) && \"Run `record` command to update expected test cases\");\n    for (size_t i = 0; i < ARRAY_LEN(test_cases); ++i) {\n        printf(\"%s ... \", test_cases[i].name);\n\n        jim_buffer.sink_count = 0;\n        test_cases[i].run(&jim_buffer);\n\n        if (jim_buffer.sink_count != strlen(test_cases_expected[i])\n                || memcmp(jim_buffer.sink, test_cases_expected[i], jim_buffer.sink_count) != 0) {\n            printf(\"FAILED!\\n\");\n            printf(\"Expected: %s\\n\", test_cases_expected[i]);\n            printf(\"Actual:   \");\n            fwrite(jim_buffer.sink, jim_buffer.sink_count, 1, stdout);\n            printf(\"\\n\");\n            exit(1);\n        }\n\n        printf(\"OK\\n\");\n    }\n}\n\nint main(int argc, char **argv)\n{\n    if (argc >= 2) {\n        if (strcmp(argv[1], \"record\") == 0) {\n            if (!record(\"test_jim_expected.h\")) return 1;\n        } else {\n            fprintf(stderr, \"Usage: ./test [record]\\n\");\n            fprintf(stderr, \"ERROR: unknown subcommand %s.\\n\", argv[1]);\n        }\n    } else  {\n        test();\n    }\n}\n"
  },
  {
    "path": "test_jim_expected.h",
    "content": "const char *test_cases_expected[] = {\n    \"[null]\",\n    \"[false,true]\",\n    \"[-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,10]\",\n    \"[0.0,0.0,3.1415,2.71828,1.6,null,null,null]\",\n    \"[\\\"hello\\\",\\\"world\\\",\\\"\\\\n\\\\b\\\\t\\\",\\\"\\\\u0000\\\\u0000\\\\u0000\\\\u0000\\\"]\",\n    \"[[],[[]],[[[]]],[[[[]]]],[[[[[]]]]]]\",\n    \"{\\\"l\\\":{\\\"l\\\":{\\\"l\\\":0,\\\"r\\\":1},\\\"r\\\":{\\\"l\\\":2,\\\"r\\\":3}},\\\"r\\\":{\\\"l\\\":{\\\"l\\\":4,\\\"r\\\":5},\\\"r\\\":{\\\"l\\\":6,\\\"r\\\":7}}}\",\n    \"[]\",\n    \"[\\n    69\\n]\",\n    \"[\\n    69,\\n    420,\\n    1337,\\n    80085\\n]\",\n    \"{}\",\n    \"{\\n    \\\"foo\\\": 69\\n}\",\n    \"{\\n    \\\"foo\\\": 69,\\n    \\\"bar\\\": 420,\\n    \\\"baz\\\": 1337\\n}\",\n    \"{\\n    \\\"integer\\\": 69,\\n    \\\"empty_array\\\": [],\\n    \\\"empty_object\\\": {},\\n    \\\"array_of_integers\\\": [\\n        69,\\n        420,\\n        1337,\\n        80085\\n    ],\\n    \\\"object_of_integers\\\": {\\n        \\\"foo\\\": 69,\\n        \\\"bar\\\": 420,\\n        \\\"baz\\\": 1337,\\n        \\\"karabaz\\\": 80085\\n    }\\n}\",\n};\n"
  },
  {
    "path": "thirdparty/nob.h",
    "content": "/* nob - v1.20.6 - Public Domain - https://github.com/tsoding/nob.h\n\n   This library is the next generation of the [NoBuild](https://github.com/tsoding/nobuild) idea.\n\n   # Quick Example\n\n      ```c\n      // nob.c\n      #define NOB_IMPLEMENTATION\n      #include \"nob.h\"\n\n      int main(int argc, char **argv)\n      {\n          NOB_GO_REBUILD_URSELF(argc, argv);\n          Nob_Cmd cmd = {0};\n          nob_cmd_append(&cmd, \"cc\", \"-Wall\", \"-Wextra\", \"-o\", \"main\", \"main.c\");\n          if (!nob_cmd_run_sync(cmd)) return 1;\n          return 0;\n      }\n      ```\n\n      ```console\n      $ cc -o nob nob.c\n      $ ./nob\n      ```\n\n      The `nob` automatically rebuilds itself if `nob.c` is modified thanks to\n      the `NOB_GO_REBUILD_URSELF` macro (don't forget to check out how it works below)\n\n   # The Zoo of `nob_cmd_run_*` Functions\n\n      `Nob_Cmd` is just a dynamic array of strings which represents a command and its arguments.\n      First you append the arguments\n\n      ```c\n      Nob_Cmd cmd = {0};\n      nob_cmd_append(&cmd, \"cc\", \"-Wall\", \"-Wextra\", \"-o\", \"main\", \"main.c\");\n      ```\n\n      Then you run it\n\n      ```c\n      if (!nob_cmd_run_sync(cmd)) return 1;\n      ```\n\n      `*_sync` at the end indicates that the function blocks until the command finishes executing\n      and returns `true` on success and `false` on failure. You can run the command asynchronously\n      but you have to explitictly wait for it afterwards:\n\n      ```c\n      Nob_Proc p = nob_cmd_run_async(cmd);\n      if (p == NOB_INVALID_PROC) return 1;\n      if (!nob_proc_wait(p)) return 1;\n      ```\n\n      One of the problems with running commands like that is that `Nob_Cmd` still contains the arguments\n      from the previously run command. If you want to reuse the same `Nob_Cmd` you have to not forget to reset\n      it\n\n      ```c\n      Nob_Cmd cmd = {0};\n\n      nob_cmd_append(&cmd, \"cc\", \"-Wall\", \"-Wextra\", \"-o\", \"main\", \"main.c\");\n      if (!nob_cmd_run_sync(cmd)) return 1;\n      cmd.count = 0;\n\n      nob_cmd_append(&cmd, \"./main\", \"foo\", \"bar\", \"baz\");\n      if (!nob_cmd_run_sync(cmd)) return 1;\n      cmd.count = 0;\n      ```\n\n      Which is a bit error prone. To make it a bit easier we have `nob_cmd_run_sync_and_reset()` which\n      accepts `Nob_Cmd` by reference and resets it for you:\n\n      ```c\n      Nob_Cmd cmd = {0};\n\n      nob_cmd_append(&cmd, \"cc\", \"-Wall\", \"-Wextra\", \"-o\", \"main\", \"main.c\");\n      if (!nob_cmd_run_sync_and_reset(&cmd)) return 1;\n\n      nob_cmd_append(&cmd, \"./main\", \"foo\", \"bar\", \"baz\");\n      if (!nob_cmd_run_sync_and_reset(&cmd)) return 1;\n      ```\n\n      There is of course also `nob_cmd_run_async_and_reset()` to maintain the pattern.\n\n      The stdin, stdout and stderr of any command can be redirected by using `Nob_Cmd_Redirect` structure\n      along with `nob_cmd_run_sync_redirect()` or `nob_cmd_run_async_redirect()`\n\n      ```c\n      // Opening all the necessary files\n      Nob_Fd fdin = nob_fd_open_for_read(\"input.txt\");\n      if (fdin == NOB_INVALID_FD) return 1;\n      Nob_Fd fdout = nob_fd_open_for_write(\"output.txt\");\n      if (fdout == NOB_INVALID_FD) return 1;\n      Nob_Fd fderr = nob_fd_open_for_write(\"error.txt\");\n      if (fderr == NOB_INVALID_FD) return 1;\n\n      // Preparing the command\n      Nob_Cmd cmd = {0};\n      nob_cmd_append(&cmd, \"./main\", \"foo\", \"bar\", \"baz\");\n\n      // Running the command synchronously redirecting the standard streams\n      bool ok = nob_cmd_run_sync_redirect(cmd, (Nob_Cmd_Redirect) {\n          .fdin = fdin,\n          .fdout = fdout,\n          .fderr = fderr,\n      });\n      if (!ok) return 1;\n\n      // Closing all the files\n      nob_fd_close(fdin);\n      nob_fd_close(fdout);\n      nob_fd_close(fderr);\n\n      // Reseting the command\n      cmd.count = 0;\n      ```\n\n      And of course if you find closing the files and reseting the command annoying we have\n      `nob_cmd_run_sync_redirect_and_reset()` and `nob_cmd_run_async_redirect_and_reset()`\n      which do all of that for you automatically.\n\n      All the Zoo of `nob_cmd_run_*` functions follows the same pattern: sync/async,\n      redirect/no redirect, and_reset/no and_reset. They always come in that order.\n\n   # Stripping off `nob_` Prefixes\n\n      Since Pure C does not have any namespaces we prefix each name of the API with the `nob_` to avoid any\n      potential conflicts with any other names in your code. But sometimes it is very annoying and makes\n      the code noisy. If you know that none of the names from nob.h conflict with anything in your code\n      you can enable NOB_STRIP_PREFIX macro and just drop all the prefixes:\n\n      ```c\n      // nob.c\n      #define NOB_IMPLEMENTATION\n      #define NOB_STRIP_PREFIX\n      #include \"nob.h\"\n\n      int main(int argc, char **argv)\n      {\n          NOB_GO_REBUILD_URSELF(argc, argv);\n          Cmd cmd = {0};\n          cmd_append(&cmd, \"cc\", \"-Wall\", \"-Wextra\", \"-o\", \"main\", \"main.c\");\n          if (!cmd_run_sync(cmd)) return 1;\n          return 0;\n      }\n      ```\n\n      Not all the names have strippable prefixes. All the redefinable names like `NOB_GO_REBUILD_URSELF`\n      for instance will retain their prefix even if NOB_STRIP_PREFIX is enabled. Notable exception is the\n      nob_log() function. Stripping away the prefix results in log() which was historically always referring\n      to the natural logarithmic function that is already defined in math.h. So there is no reason to strip\n      off the prefix for nob_log(). Another exception is nob_rename() which collides with the widely known\n      POSIX function rename(2) if you strip the prefix off.\n\n      The prefixes are stripped off only on the level of preprocessor. The names of the functions in the\n      compiled object file will still retain the `nob_` prefix. Keep that in mind when you FFI with nob.h\n      from other languages (for whatever reason).\n\n      If only few specific names create conflicts for you, you can just #undef those names after the\n      `#include <nob.h>` since they are macros anyway.\n*/\n\n#ifndef NOB_H_\n#define NOB_H_\n\n#ifndef NOB_ASSERT\n#include <assert.h>\n#define NOB_ASSERT assert\n#endif /* NOB_ASSERT */\n\n#ifndef NOB_REALLOC\n#include <stdlib.h>\n#define NOB_REALLOC realloc\n#endif /* NOB_REALLOC */\n\n#ifndef NOB_FREE\n#include <stdlib.h>\n#define NOB_FREE free\n#endif /* NOB_FREE */\n\n#include <stdbool.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <stdarg.h>\n#include <string.h>\n#include <errno.h>\n#include <ctype.h>\n#include <limits.h>\n\n#ifdef _WIN32\n#    define WIN32_LEAN_AND_MEAN\n#    define _WINUSER_\n#    define _WINGDI_\n#    define _IMM_\n#    define _WINCON_\n#    include <windows.h>\n#    include <direct.h>\n#    include <shellapi.h>\n#else\n#    include <sys/types.h>\n#    include <sys/wait.h>\n#    include <sys/stat.h>\n#    include <unistd.h>\n#    include <fcntl.h>\n#endif\n\n#ifdef _WIN32\n#    define NOB_LINE_END \"\\r\\n\"\n#else\n#    define NOB_LINE_END \"\\n\"\n#endif\n\n#if defined(__GNUC__) || defined(__clang__)\n//   https://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Function-Attributes.html\n#    ifdef __MINGW_PRINTF_FORMAT\n#        define NOB_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) __attribute__ ((format (__MINGW_PRINTF_FORMAT, STRING_INDEX, FIRST_TO_CHECK)))\n#    else\n#        define NOB_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) __attribute__ ((format (printf, STRING_INDEX, FIRST_TO_CHECK)))\n#    endif // __MINGW_PRINTF_FORMAT\n#else\n//   TODO: implement NOB_PRINTF_FORMAT for MSVC\n#    define NOB_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK)\n#endif\n\n#define NOB_UNUSED(value) (void)(value)\n#define NOB_TODO(message) do { fprintf(stderr, \"%s:%d: TODO: %s\\n\", __FILE__, __LINE__, message); abort(); } while(0)\n#define NOB_UNREACHABLE(message) do { fprintf(stderr, \"%s:%d: UNREACHABLE: %s\\n\", __FILE__, __LINE__, message); abort(); } while(0)\n\n#define NOB_ARRAY_LEN(array) (sizeof(array)/sizeof(array[0]))\n#define NOB_ARRAY_GET(array, index) \\\n    (NOB_ASSERT((size_t)index < NOB_ARRAY_LEN(array)), array[(size_t)index])\n\ntypedef enum {\n    NOB_INFO,\n    NOB_WARNING,\n    NOB_ERROR,\n    NOB_NO_LOGS,\n} Nob_Log_Level;\n\n// Any messages with the level below nob_minimal_log_level are going to be suppressed.\nextern Nob_Log_Level nob_minimal_log_level;\n\nvoid nob_log(Nob_Log_Level level, const char *fmt, ...) NOB_PRINTF_FORMAT(2, 3);\n\n// It is an equivalent of shift command from bash. It basically pops an element from\n// the beginning of a sized array.\n#define nob_shift(xs, xs_sz) (NOB_ASSERT((xs_sz) > 0), (xs_sz)--, *(xs)++)\n// NOTE: nob_shift_args() is an alias for an old variant of nob_shift that only worked with\n// the command line arguments passed to the main() function. nob_shift() is more generic.\n// So nob_shift_args() is semi-deprecated, but I don't see much reason to urgently\n// remove it. This alias does not hurt anybody.\n#define nob_shift_args(argc, argv) nob_shift(*argv, *argc)\n\ntypedef struct {\n    const char **items;\n    size_t count;\n    size_t capacity;\n} Nob_File_Paths;\n\ntypedef enum {\n    NOB_FILE_REGULAR = 0,\n    NOB_FILE_DIRECTORY,\n    NOB_FILE_SYMLINK,\n    NOB_FILE_OTHER,\n} Nob_File_Type;\n\nbool nob_mkdir_if_not_exists(const char *path);\nbool nob_copy_file(const char *src_path, const char *dst_path);\nbool nob_copy_directory_recursively(const char *src_path, const char *dst_path);\nbool nob_read_entire_dir(const char *parent, Nob_File_Paths *children);\nbool nob_write_entire_file(const char *path, const void *data, size_t size);\nNob_File_Type nob_get_file_type(const char *path);\nbool nob_delete_file(const char *path);\n\n#define nob_return_defer(value) do { result = (value); goto defer; } while(0)\n\n// Initial capacity of a dynamic array\n#ifndef NOB_DA_INIT_CAP\n#define NOB_DA_INIT_CAP 256\n#endif\n\n#define nob_da_reserve(da, expected_capacity)                                              \\\n    do {                                                                                   \\\n        if ((expected_capacity) > (da)->capacity) {                                        \\\n            if ((da)->capacity == 0) {                                                     \\\n                (da)->capacity = NOB_DA_INIT_CAP;                                          \\\n            }                                                                              \\\n            while ((expected_capacity) > (da)->capacity) {                                 \\\n                (da)->capacity *= 2;                                                       \\\n            }                                                                              \\\n            (da)->items = NOB_REALLOC((da)->items, (da)->capacity * sizeof(*(da)->items)); \\\n            NOB_ASSERT((da)->items != NULL && \"Buy more RAM lol\");                         \\\n        }                                                                                  \\\n    } while (0)\n\n// Append an item to a dynamic array\n#define nob_da_append(da, item)                \\\n    do {                                       \\\n        nob_da_reserve((da), (da)->count + 1); \\\n        (da)->items[(da)->count++] = (item);   \\\n    } while (0)\n\n#define nob_da_free(da) NOB_FREE((da).items)\n\n// Append several items to a dynamic array\n#define nob_da_append_many(da, new_items, new_items_count)                                      \\\n    do {                                                                                        \\\n        nob_da_reserve((da), (da)->count + (new_items_count));                                  \\\n        memcpy((da)->items + (da)->count, (new_items), (new_items_count)*sizeof(*(da)->items)); \\\n        (da)->count += (new_items_count);                                                       \\\n    } while (0)\n\n#define nob_da_resize(da, new_size)     \\\n    do {                                \\\n        nob_da_reserve((da), new_size); \\\n        (da)->count = (new_size);       \\\n    } while (0)\n\n#define nob_da_last(da) (da)->items[(NOB_ASSERT((da)->count > 0), (da)->count-1)]\n#define nob_da_remove_unordered(da, i)               \\\n    do {                                             \\\n        size_t j = (i);                              \\\n        NOB_ASSERT(j < (da)->count);                 \\\n        (da)->items[j] = (da)->items[--(da)->count]; \\\n    } while(0)\n\n// Foreach over Dynamic Arrays. Example:\n// ```c\n// typedef struct {\n//     int *items;\n//     size_t count;\n//     size_t capacity;\n// } Numbers;\n//\n// Numbers xs = {0};\n//\n// nob_da_append(&xs, 69);\n// nob_da_append(&xs, 420);\n// nob_da_append(&xs, 1337);\n//\n// nob_da_foreach(int, x, &xs) {\n//     // `x` here is a pointer to the current element. You can get its index by taking a difference\n//     // between `x` and the start of the array which is `x.items`.\n//     size_t index = x - xs.items;\n//     nob_log(INFO, \"%zu: %d\", index, *x);\n// }\n// ```\n#define nob_da_foreach(Type, it, da) for (Type *it = (da)->items; it < (da)->items + (da)->count; ++it)\n\ntypedef struct {\n    char *items;\n    size_t count;\n    size_t capacity;\n} Nob_String_Builder;\n\nbool nob_read_entire_file(const char *path, Nob_String_Builder *sb);\nint nob_sb_appendf(Nob_String_Builder *sb, const char *fmt, ...) NOB_PRINTF_FORMAT(2, 3);\n\n// Append a sized buffer to a string builder\n#define nob_sb_append_buf(sb, buf, size) nob_da_append_many(sb, buf, size)\n\n// Append a NULL-terminated string to a string builder\n#define nob_sb_append_cstr(sb, cstr)  \\\n    do {                              \\\n        const char *s = (cstr);       \\\n        size_t n = strlen(s);         \\\n        nob_da_append_many(sb, s, n); \\\n    } while (0)\n\n// Append a single NULL character at the end of a string builder. So then you can\n// use it a NULL-terminated C string\n#define nob_sb_append_null(sb) nob_da_append_many(sb, \"\", 1)\n\n// Free the memory allocated by a string builder\n#define nob_sb_free(sb) NOB_FREE((sb).items)\n\n// Process handle\n#ifdef _WIN32\ntypedef HANDLE Nob_Proc;\n#define NOB_INVALID_PROC INVALID_HANDLE_VALUE\ntypedef HANDLE Nob_Fd;\n#define NOB_INVALID_FD INVALID_HANDLE_VALUE\n#else\ntypedef int Nob_Proc;\n#define NOB_INVALID_PROC (-1)\ntypedef int Nob_Fd;\n#define NOB_INVALID_FD (-1)\n#endif // _WIN32\n\nNob_Fd nob_fd_open_for_read(const char *path);\nNob_Fd nob_fd_open_for_write(const char *path);\nvoid nob_fd_close(Nob_Fd fd);\n\ntypedef struct {\n    Nob_Proc *items;\n    size_t count;\n    size_t capacity;\n} Nob_Procs;\n\n// Wait until the process has finished\nbool nob_proc_wait(Nob_Proc proc);\n// Wait until all the processes have finished\nbool nob_procs_wait(Nob_Procs procs);\n// Wait until all the processes have finished and empty the procs array\nbool nob_procs_wait_and_reset(Nob_Procs *procs);\n// Append a new process to procs array and if procs.count reaches max_procs_count call nob_procs_wait_and_reset() on it\nbool nob_procs_append_with_flush(Nob_Procs *procs, Nob_Proc proc, size_t max_procs_count);\n\n// A command - the main workhorse of Nob. Nob is all about building commands and running them\ntypedef struct {\n    const char **items;\n    size_t count;\n    size_t capacity;\n} Nob_Cmd;\n\n// Example:\n// ```c\n// Nob_Fd fdin = nob_fd_open_for_read(\"input.txt\");\n// if (fdin == NOB_INVALID_FD) fail();\n// Nob_Fd fdout = nob_fd_open_for_write(\"output.txt\");\n// if (fdout == NOB_INVALID_FD) fail();\n// Nob_Cmd cmd = {0};\n// nob_cmd_append(&cmd, \"cat\");\n// if (!nob_cmd_run_sync_redirect_and_reset(&cmd, (Nob_Cmd_Redirect) {\n//     .fdin = &fdin,\n//     .fdout = &fdout\n// })) fail();\n// ```\ntypedef struct {\n    Nob_Fd *fdin;\n    Nob_Fd *fdout;\n    Nob_Fd *fderr;\n} Nob_Cmd_Redirect;\n\n// Render a string representation of a command into a string builder. Keep in mind the the\n// string builder is not NULL-terminated by default. Use nob_sb_append_null if you plan to\n// use it as a C string.\nvoid nob_cmd_render(Nob_Cmd cmd, Nob_String_Builder *render);\n\n// TODO: implement C++ support for nob.h\n#define nob_cmd_append(cmd, ...) \\\n    nob_da_append_many(cmd, \\\n                       ((const char*[]){__VA_ARGS__}), \\\n                       (sizeof((const char*[]){__VA_ARGS__})/sizeof(const char*)))\n\n#define nob_cmd_extend(cmd, other_cmd) \\\n    nob_da_append_many(cmd, (other_cmd)->items, (other_cmd)->count)\n\n// Free all the memory allocated by command arguments\n#define nob_cmd_free(cmd) NOB_FREE(cmd.items)\n\n// Run command asynchronously\n#define nob_cmd_run_async(cmd) nob_cmd_run_async_redirect(cmd, (Nob_Cmd_Redirect) {0})\n// NOTE: nob_cmd_run_async_and_reset() is just like nob_cmd_run_async() except it also resets cmd.count to 0\n// so the Nob_Cmd instance can be seamlessly used several times in a row\nNob_Proc nob_cmd_run_async_and_reset(Nob_Cmd *cmd);\n// Run redirected command asynchronously\nNob_Proc nob_cmd_run_async_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect);\n// Run redirected command asynchronously and set cmd.count to 0 and close all the opened files\nNob_Proc nob_cmd_run_async_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect);\n\n// Run command synchronously\nbool nob_cmd_run_sync(Nob_Cmd cmd);\n// NOTE: nob_cmd_run_sync_and_reset() is just like nob_cmd_run_sync() except it also resets cmd.count to 0\n// so the Nob_Cmd instance can be seamlessly used several times in a row\nbool nob_cmd_run_sync_and_reset(Nob_Cmd *cmd);\n// Run redirected command synchronously\nbool nob_cmd_run_sync_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect);\n// Run redirected command synchronously and set cmd.count to 0 and close all the opened files\nbool nob_cmd_run_sync_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect);\n\n#ifndef NOB_TEMP_CAPACITY\n#define NOB_TEMP_CAPACITY (8*1024*1024)\n#endif // NOB_TEMP_CAPACITY\nchar *nob_temp_strdup(const char *cstr);\nvoid *nob_temp_alloc(size_t size);\nchar *nob_temp_sprintf(const char *format, ...) NOB_PRINTF_FORMAT(1, 2);\nvoid nob_temp_reset(void);\nsize_t nob_temp_save(void);\nvoid nob_temp_rewind(size_t checkpoint);\n\n// Given any path returns the last part of that path.\n// \"/path/to/a/file.c\" -> \"file.c\"; \"/path/to/a/directory\" -> \"directory\"\nconst char *nob_path_name(const char *path);\nbool nob_rename(const char *old_path, const char *new_path);\nint nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count);\nint nob_needs_rebuild1(const char *output_path, const char *input_path);\nint nob_file_exists(const char *file_path);\nconst char *nob_get_current_dir_temp(void);\nbool nob_set_current_dir(const char *path);\n\n// TODO: we should probably document somewhere all the compiler we support\n\n// The nob_cc_* macros try to abstract away the specific compiler.\n// They are verify basic and not particularly flexible, but you can redefine them if you need to\n// or not use them at all and create your own abstraction on top of Nob_Cmd.\n\n#ifndef nob_cc\n#  if _WIN32\n#    if defined(__GNUC__)\n#       define nob_cc(cmd) nob_cmd_append(cmd, \"cc\")\n#    elif defined(__clang__)\n#       define nob_cc(cmd) nob_cmd_append(cmd, \"clang\")\n#    elif defined(_MSC_VER)\n#       define nob_cc(cmd) nob_cmd_append(cmd, \"cl.exe\")\n#    endif\n#  else\n#    define nob_cc(cmd) nob_cmd_append(cmd, \"cc\")\n#  endif\n#endif // nob_cc\n\n#ifndef nob_cc_flags\n#  if defined(_MSC_VER) && !defined(__clang__)\n#    define nob_cc_flags(...)  // TODO: Add some cool recommended flags for MSVC (I don't really know any)\n#  else\n#    define nob_cc_flags(cmd) nob_cmd_append(cmd, \"-Wall\", \"-Wextra\")\n#  endif\n#endif // nob_cc_output\n\n#ifndef nob_cc_output\n#  if defined(_MSC_VER) && !defined(__clang__)\n#    define nob_cc_output(cmd, output_path) nob_cmd_append(cmd, nob_temp_sprintf(\"/Fe:%s\", (output_path)))\n#  else\n#    define nob_cc_output(cmd, output_path) nob_cmd_append(cmd, \"-o\", (output_path))\n#  endif\n#endif // nob_cc_output\n\n#ifndef nob_cc_inputs\n#  define nob_cc_inputs(cmd, ...) nob_cmd_append(cmd, __VA_ARGS__)\n#endif // nob_cc_inputs\n\n// TODO: add MinGW support for Go Rebuild Urself™ Technology and all the nob_cc_* macros above\n//   Musializer contributors came up with a pretty interesting idea of an optional prefix macro which could be useful for\n//   MinGW support:\n//   https://github.com/tsoding/musializer/blob/b7578cc76b9ecb573d239acc9ccf5a04d3aba2c9/src_build/nob_win64_mingw.c#L3-L9\n// TODO: Maybe instead NOB_REBUILD_URSELF macro, the Go Rebuild Urself™ Technology should use the\n//   user defined nob_cc_* macros instead?\n#ifndef NOB_REBUILD_URSELF\n#  if defined(_WIN32)\n#    if defined(__GNUC__)\n#       define NOB_REBUILD_URSELF(binary_path, source_path) \"gcc\", \"-o\", binary_path, source_path\n#    elif defined(__clang__)\n#       define NOB_REBUILD_URSELF(binary_path, source_path) \"clang\", \"-o\", binary_path, source_path\n#    elif defined(_MSC_VER)\n#       define NOB_REBUILD_URSELF(binary_path, source_path) \"cl.exe\", nob_temp_sprintf(\"/Fe:%s\", (binary_path)), source_path\n#    endif\n#  else\n#    define NOB_REBUILD_URSELF(binary_path, source_path) \"cc\", \"-o\", binary_path, source_path\n#  endif\n#endif\n\n// Go Rebuild Urself™ Technology\n//\n//   How to use it:\n//     int main(int argc, char** argv) {\n//         NOB_GO_REBUILD_URSELF(argc, argv);\n//         // actual work\n//         return 0;\n//     }\n//\n//   After your added this macro every time you run ./nob it will detect\n//   that you modified its original source code and will try to rebuild itself\n//   before doing any actual work. So you only need to bootstrap your build system\n//   once.\n//\n//   The modification is detected by comparing the last modified times of the executable\n//   and its source code. The same way the make utility usually does it.\n//\n//   The rebuilding is done by using the NOB_REBUILD_URSELF macro which you can redefine\n//   if you need a special way of bootstraping your build system. (which I personally\n//   do not recommend since the whole idea of NoBuild is to keep the process of bootstrapping\n//   as simple as possible and doing all of the actual work inside of ./nob)\n//\nvoid nob__go_rebuild_urself(int argc, char **argv, const char *source_path, ...);\n#define NOB_GO_REBUILD_URSELF(argc, argv) nob__go_rebuild_urself(argc, argv, __FILE__, NULL)\n// Sometimes your nob.c includes additional files, so you want the Go Rebuild Urself™ Technology to check\n// if they also were modified and rebuild nob.c accordingly. For that we have NOB_GO_REBUILD_URSELF_PLUS():\n// ```c\n// #define NOB_IMPLEMENTATION\n// #include \"nob.h\"\n//\n// #include \"foo.c\"\n// #include \"bar.c\"\n//\n// int main(int argc, char **argv)\n// {\n//     NOB_GO_REBUILD_URSELF_PLUS(argc, argv, \"foo.c\", \"bar.c\");\n//     // ...\n//     return 0;\n// }\n#define NOB_GO_REBUILD_URSELF_PLUS(argc, argv, ...) nob__go_rebuild_urself(argc, argv, __FILE__, __VA_ARGS__, NULL);\n\ntypedef struct {\n    size_t count;\n    const char *data;\n} Nob_String_View;\n\nconst char *nob_temp_sv_to_cstr(Nob_String_View sv);\n\nNob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim);\nNob_String_View nob_sv_chop_left(Nob_String_View *sv, size_t n);\nNob_String_View nob_sv_trim(Nob_String_View sv);\nNob_String_View nob_sv_trim_left(Nob_String_View sv);\nNob_String_View nob_sv_trim_right(Nob_String_View sv);\nbool nob_sv_eq(Nob_String_View a, Nob_String_View b);\nbool nob_sv_end_with(Nob_String_View sv, const char *cstr);\nbool nob_sv_starts_with(Nob_String_View sv, Nob_String_View expected_prefix);\nNob_String_View nob_sv_from_cstr(const char *cstr);\nNob_String_View nob_sv_from_parts(const char *data, size_t count);\n// nob_sb_to_sv() enables you to just view Nob_String_Builder as Nob_String_View\n#define nob_sb_to_sv(sb) nob_sv_from_parts((sb).items, (sb).count)\n\n// printf macros for String_View\n#ifndef SV_Fmt\n#define SV_Fmt \"%.*s\"\n#endif // SV_Fmt\n#ifndef SV_Arg\n#define SV_Arg(sv) (int) (sv).count, (sv).data\n#endif // SV_Arg\n// USAGE:\n//   String_View name = ...;\n//   printf(\"Name: \"SV_Fmt\"\\n\", SV_Arg(name));\n\n\n// minirent.h HEADER BEGIN ////////////////////////////////////////\n// Copyright 2021 Alexey Kutepov <reximkut@gmail.com>\n//\n// Permission is hereby granted, free of charge, to any person obtaining\n// a copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to\n// the following conditions:\n//\n// The above copyright notice and this permission notice shall be\n// included in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n//\n// ============================================================\n//\n// minirent — 0.0.1 — A subset of dirent interface for Windows.\n//\n// https://github.com/tsoding/minirent\n//\n// ============================================================\n//\n// ChangeLog (https://semver.org/ is implied)\n//\n//    0.0.2 Automatically include dirent.h on non-Windows\n//          platforms\n//    0.0.1 First Official Release\n\n#ifndef _WIN32\n#include <dirent.h>\n#else // _WIN32\n\n#define WIN32_LEAN_AND_MEAN\n#include \"windows.h\"\n\nstruct dirent\n{\n    char d_name[MAX_PATH+1];\n};\n\ntypedef struct DIR DIR;\n\nstatic DIR *opendir(const char *dirpath);\nstatic struct dirent *readdir(DIR *dirp);\nstatic int closedir(DIR *dirp);\n\n#endif // _WIN32\n// minirent.h HEADER END ////////////////////////////////////////\n\n#ifdef _WIN32\n\nchar *nob_win32_error_message(DWORD err);\n\n#endif // _WIN32\n\n#endif // NOB_H_\n\n#ifdef NOB_IMPLEMENTATION\n\n// Any messages with the level below nob_minimal_log_level are going to be suppressed.\nNob_Log_Level nob_minimal_log_level = NOB_INFO;\n\n#ifdef _WIN32\n\n// Base on https://stackoverflow.com/a/75644008\n// > .NET Core uses 4096 * sizeof(WCHAR) buffer on stack for FormatMessageW call. And...thats it.\n// >\n// > https://github.com/dotnet/runtime/blob/3b63eb1346f1ddbc921374a5108d025662fb5ffd/src/coreclr/utilcode/posterror.cpp#L264-L265\n#ifndef NOB_WIN32_ERR_MSG_SIZE\n#define NOB_WIN32_ERR_MSG_SIZE (4 * 1024)\n#endif // NOB_WIN32_ERR_MSG_SIZE\n\nchar *nob_win32_error_message(DWORD err) {\n    static char win32ErrMsg[NOB_WIN32_ERR_MSG_SIZE] = {0};\n    DWORD errMsgSize = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, LANG_USER_DEFAULT, win32ErrMsg,\n                                      NOB_WIN32_ERR_MSG_SIZE, NULL);\n\n    if (errMsgSize == 0) {\n        if (GetLastError() != ERROR_MR_MID_NOT_FOUND) {\n            if (sprintf(win32ErrMsg, \"Could not get error message for 0x%lX\", err) > 0) {\n                return (char *)&win32ErrMsg;\n            } else {\n                return NULL;\n            }\n        } else {\n            if (sprintf(win32ErrMsg, \"Invalid Windows Error code (0x%lX)\", err) > 0) {\n                return (char *)&win32ErrMsg;\n            } else {\n                return NULL;\n            }\n        }\n    }\n\n    while (errMsgSize > 1 && isspace(win32ErrMsg[errMsgSize - 1])) {\n        win32ErrMsg[--errMsgSize] = '\\0';\n    }\n\n    return win32ErrMsg;\n}\n\n#endif // _WIN32\n\n// The implementation idea is stolen from https://github.com/zhiayang/nabs\nvoid nob__go_rebuild_urself(int argc, char **argv, const char *source_path, ...)\n{\n    const char *binary_path = nob_shift(argv, argc);\n#ifdef _WIN32\n    // On Windows executables almost always invoked without extension, so\n    // it's ./nob, not ./nob.exe. For renaming the extension is a must.\n    if (!nob_sv_end_with(nob_sv_from_cstr(binary_path), \".exe\")) {\n        binary_path = nob_temp_sprintf(\"%s.exe\", binary_path);\n    }\n#endif\n\n    Nob_File_Paths source_paths = {0};\n    nob_da_append(&source_paths, source_path);\n    va_list args;\n    va_start(args, source_path);\n    for (;;) {\n        const char *path = va_arg(args, const char*);\n        if (path == NULL) break;\n        nob_da_append(&source_paths, path);\n    }\n    va_end(args);\n\n    int rebuild_is_needed = nob_needs_rebuild(binary_path, source_paths.items, source_paths.count);\n    if (rebuild_is_needed < 0) exit(1); // error\n    if (!rebuild_is_needed) {           // no rebuild is needed\n        NOB_FREE(source_paths.items);\n        return;\n    }\n\n    Nob_Cmd cmd = {0};\n\n    const char *old_binary_path = nob_temp_sprintf(\"%s.old\", binary_path);\n\n    if (!nob_rename(binary_path, old_binary_path)) exit(1);\n    nob_cmd_append(&cmd, NOB_REBUILD_URSELF(binary_path, source_path));\n    if (!nob_cmd_run_sync_and_reset(&cmd)) {\n        nob_rename(old_binary_path, binary_path);\n        exit(1);\n    }\n#ifdef NOB_EXPERIMENTAL_DELETE_OLD\n    // TODO: this is an experimental behavior behind a compilation flag.\n    // Once it is confirmed that it does not cause much problems on both POSIX and Windows\n    // we may turn it on by default.\n    nob_delete_file(old_binary_path);\n#endif // NOB_EXPERIMENTAL_DELETE_OLD\n\n    nob_cmd_append(&cmd, binary_path);\n    nob_da_append_many(&cmd, argv, argc);\n    if (!nob_cmd_run_sync_and_reset(&cmd)) exit(1);\n    exit(0);\n}\n\nstatic size_t nob_temp_size = 0;\nstatic char nob_temp[NOB_TEMP_CAPACITY] = {0};\n\nbool nob_mkdir_if_not_exists(const char *path)\n{\n#ifdef _WIN32\n    int result = mkdir(path);\n#else\n    int result = mkdir(path, 0755);\n#endif\n    if (result < 0) {\n        if (errno == EEXIST) {\n            nob_log(NOB_INFO, \"directory `%s` already exists\", path);\n            return true;\n        }\n        nob_log(NOB_ERROR, \"could not create directory `%s`: %s\", path, strerror(errno));\n        return false;\n    }\n\n    nob_log(NOB_INFO, \"created directory `%s`\", path);\n    return true;\n}\n\nbool nob_copy_file(const char *src_path, const char *dst_path)\n{\n    nob_log(NOB_INFO, \"copying %s -> %s\", src_path, dst_path);\n#ifdef _WIN32\n    if (!CopyFile(src_path, dst_path, FALSE)) {\n        nob_log(NOB_ERROR, \"Could not copy file: %s\", nob_win32_error_message(GetLastError()));\n        return false;\n    }\n    return true;\n#else\n    int src_fd = -1;\n    int dst_fd = -1;\n    size_t buf_size = 32*1024;\n    char *buf = NOB_REALLOC(NULL, buf_size);\n    NOB_ASSERT(buf != NULL && \"Buy more RAM lol!!\");\n    bool result = true;\n\n    src_fd = open(src_path, O_RDONLY);\n    if (src_fd < 0) {\n        nob_log(NOB_ERROR, \"Could not open file %s: %s\", src_path, strerror(errno));\n        nob_return_defer(false);\n    }\n\n    struct stat src_stat;\n    if (fstat(src_fd, &src_stat) < 0) {\n        nob_log(NOB_ERROR, \"Could not get mode of file %s: %s\", src_path, strerror(errno));\n        nob_return_defer(false);\n    }\n\n    dst_fd = open(dst_path, O_CREAT | O_TRUNC | O_WRONLY, src_stat.st_mode);\n    if (dst_fd < 0) {\n        nob_log(NOB_ERROR, \"Could not create file %s: %s\", dst_path, strerror(errno));\n        nob_return_defer(false);\n    }\n\n    for (;;) {\n        ssize_t n = read(src_fd, buf, buf_size);\n        if (n == 0) break;\n        if (n < 0) {\n            nob_log(NOB_ERROR, \"Could not read from file %s: %s\", src_path, strerror(errno));\n            nob_return_defer(false);\n        }\n        char *buf2 = buf;\n        while (n > 0) {\n            ssize_t m = write(dst_fd, buf2, n);\n            if (m < 0) {\n                nob_log(NOB_ERROR, \"Could not write to file %s: %s\", dst_path, strerror(errno));\n                nob_return_defer(false);\n            }\n            n    -= m;\n            buf2 += m;\n        }\n    }\n\ndefer:\n    NOB_FREE(buf);\n    close(src_fd);\n    close(dst_fd);\n    return result;\n#endif\n}\n\nvoid nob_cmd_render(Nob_Cmd cmd, Nob_String_Builder *render)\n{\n    for (size_t i = 0; i < cmd.count; ++i) {\n        const char *arg = cmd.items[i];\n        if (arg == NULL) break;\n        if (i > 0) nob_sb_append_cstr(render, \" \");\n        if (!strchr(arg, ' ')) {\n            nob_sb_append_cstr(render, arg);\n        } else {\n            nob_da_append(render, '\\'');\n            nob_sb_append_cstr(render, arg);\n            nob_da_append(render, '\\'');\n        }\n    }\n}\n\n#ifdef _WIN32\n// https://learn.microsoft.com/en-gb/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way\nstatic void nob__win32_cmd_quote(Nob_Cmd cmd, Nob_String_Builder *quoted)\n{\n    for (size_t i = 0; i < cmd.count; ++i) {\n        const char *arg = cmd.items[i];\n        if (arg == NULL) break;\n        size_t len = strlen(arg);\n        if (i > 0) nob_da_append(quoted, ' ');\n        if (len != 0 && NULL == strpbrk(arg, \" \\t\\n\\v\\\"\")) {\n            // no need to quote\n            nob_da_append_many(quoted, arg, len);\n        } else {\n            // we need to escape:\n            // 1. double quotes in the original arg\n            // 2. consequent backslashes before a double quote\n            size_t backslashes = 0;\n            nob_da_append(quoted, '\\\"');\n            for (size_t j = 0; j < len; ++j) {\n                char x = arg[j];\n                if (x == '\\\\') {\n                    backslashes += 1;\n                } else {\n                    if (x == '\\\"') {\n                        // escape backslashes (if any) and the double quote\n                        for (size_t k = 0; k < 1+backslashes; ++k) {\n                            nob_da_append(quoted, '\\\\');\n                        }\n                    }\n                    backslashes = 0;\n                }\n                nob_da_append(quoted, x);\n            }\n            // escape backslashes (if any)\n            for (size_t k = 0; k < backslashes; ++k) {\n                nob_da_append(quoted, '\\\\');\n            }\n            nob_da_append(quoted, '\\\"');\n        }\n    }\n}\n#endif\n\nNob_Proc nob_cmd_run_async_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect)\n{\n    if (cmd.count < 1) {\n        nob_log(NOB_ERROR, \"Could not run empty command\");\n        return NOB_INVALID_PROC;\n    }\n\n    Nob_String_Builder sb = {0};\n    nob_cmd_render(cmd, &sb);\n    nob_sb_append_null(&sb);\n    nob_log(NOB_INFO, \"CMD: %s\", sb.items);\n    nob_sb_free(sb);\n    memset(&sb, 0, sizeof(sb));\n\n#ifdef _WIN32\n    // https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output\n\n    STARTUPINFO siStartInfo;\n    ZeroMemory(&siStartInfo, sizeof(siStartInfo));\n    siStartInfo.cb = sizeof(STARTUPINFO);\n    // NOTE: theoretically setting NULL to std handles should not be a problem\n    // https://docs.microsoft.com/en-us/windows/console/getstdhandle?redirectedfrom=MSDN#attachdetach-behavior\n    // TODO: check for errors in GetStdHandle\n    siStartInfo.hStdError = redirect.fderr ? *redirect.fderr : GetStdHandle(STD_ERROR_HANDLE);\n    siStartInfo.hStdOutput = redirect.fdout ? *redirect.fdout : GetStdHandle(STD_OUTPUT_HANDLE);\n    siStartInfo.hStdInput = redirect.fdin ? *redirect.fdin : GetStdHandle(STD_INPUT_HANDLE);\n    siStartInfo.dwFlags |= STARTF_USESTDHANDLES;\n\n    PROCESS_INFORMATION piProcInfo;\n    ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));\n\n    nob__win32_cmd_quote(cmd, &sb);\n    nob_sb_append_null(&sb);\n    BOOL bSuccess = CreateProcessA(NULL, sb.items, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo);\n    nob_sb_free(sb);\n\n    if (!bSuccess) {\n        nob_log(NOB_ERROR, \"Could not create child process for %s: %s\", cmd.items[0], nob_win32_error_message(GetLastError()));\n        return NOB_INVALID_PROC;\n    }\n\n    CloseHandle(piProcInfo.hThread);\n\n    return piProcInfo.hProcess;\n#else\n    pid_t cpid = fork();\n    if (cpid < 0) {\n        nob_log(NOB_ERROR, \"Could not fork child process: %s\", strerror(errno));\n        return NOB_INVALID_PROC;\n    }\n\n    if (cpid == 0) {\n        if (redirect.fdin) {\n            if (dup2(*redirect.fdin, STDIN_FILENO) < 0) {\n                nob_log(NOB_ERROR, \"Could not setup stdin for child process: %s\", strerror(errno));\n                exit(1);\n            }\n        }\n\n        if (redirect.fdout) {\n            if (dup2(*redirect.fdout, STDOUT_FILENO) < 0) {\n                nob_log(NOB_ERROR, \"Could not setup stdout for child process: %s\", strerror(errno));\n                exit(1);\n            }\n        }\n\n        if (redirect.fderr) {\n            if (dup2(*redirect.fderr, STDERR_FILENO) < 0) {\n                nob_log(NOB_ERROR, \"Could not setup stderr for child process: %s\", strerror(errno));\n                exit(1);\n            }\n        }\n\n        // NOTE: This leaks a bit of memory in the child process.\n        // But do we actually care? It's a one off leak anyway...\n        Nob_Cmd cmd_null = {0};\n        nob_da_append_many(&cmd_null, cmd.items, cmd.count);\n        nob_cmd_append(&cmd_null, NULL);\n\n        if (execvp(cmd.items[0], (char * const*) cmd_null.items) < 0) {\n            nob_log(NOB_ERROR, \"Could not exec child process for %s: %s\", cmd.items[0], strerror(errno));\n            exit(1);\n        }\n        NOB_UNREACHABLE(\"nob_cmd_run_async_redirect\");\n    }\n\n    return cpid;\n#endif\n}\n\nNob_Proc nob_cmd_run_async_and_reset(Nob_Cmd *cmd)\n{\n    Nob_Proc proc = nob_cmd_run_async(*cmd);\n    cmd->count = 0;\n    return proc;\n}\n\nNob_Proc nob_cmd_run_async_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect)\n{\n    Nob_Proc proc = nob_cmd_run_async_redirect(*cmd, redirect);\n    cmd->count = 0;\n    if (redirect.fdin) {\n        nob_fd_close(*redirect.fdin);\n        *redirect.fdin = NOB_INVALID_FD;\n    }\n    if (redirect.fdout) {\n        nob_fd_close(*redirect.fdout);\n        *redirect.fdout = NOB_INVALID_FD;\n    }\n    if (redirect.fderr) {\n        nob_fd_close(*redirect.fderr);\n        *redirect.fderr = NOB_INVALID_FD;\n    }\n    return proc;\n}\n\nNob_Fd nob_fd_open_for_read(const char *path)\n{\n#ifndef _WIN32\n    Nob_Fd result = open(path, O_RDONLY);\n    if (result < 0) {\n        nob_log(NOB_ERROR, \"Could not open file %s: %s\", path, strerror(errno));\n        return NOB_INVALID_FD;\n    }\n    return result;\n#else\n    // https://docs.microsoft.com/en-us/windows/win32/fileio/opening-a-file-for-reading-or-writing\n    SECURITY_ATTRIBUTES saAttr = {0};\n    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);\n    saAttr.bInheritHandle = TRUE;\n\n    Nob_Fd result = CreateFile(\n                    path,\n                    GENERIC_READ,\n                    0,\n                    &saAttr,\n                    OPEN_EXISTING,\n                    FILE_ATTRIBUTE_READONLY,\n                    NULL);\n\n    if (result == INVALID_HANDLE_VALUE) {\n        nob_log(NOB_ERROR, \"Could not open file %s: %s\", path, nob_win32_error_message(GetLastError()));\n        return NOB_INVALID_FD;\n    }\n\n    return result;\n#endif // _WIN32\n}\n\nNob_Fd nob_fd_open_for_write(const char *path)\n{\n#ifndef _WIN32\n    Nob_Fd result = open(path,\n                     O_WRONLY | O_CREAT | O_TRUNC,\n                     S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);\n    if (result < 0) {\n        nob_log(NOB_ERROR, \"could not open file %s: %s\", path, strerror(errno));\n        return NOB_INVALID_FD;\n    }\n    return result;\n#else\n    SECURITY_ATTRIBUTES saAttr = {0};\n    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);\n    saAttr.bInheritHandle = TRUE;\n\n    Nob_Fd result = CreateFile(\n                    path,                            // name of the write\n                    GENERIC_WRITE,                   // open for writing\n                    0,                               // do not share\n                    &saAttr,                         // default security\n                    CREATE_ALWAYS,                   // create always\n                    FILE_ATTRIBUTE_NORMAL,           // normal file\n                    NULL                             // no attr. template\n                );\n\n    if (result == INVALID_HANDLE_VALUE) {\n        nob_log(NOB_ERROR, \"Could not open file %s: %s\", path, nob_win32_error_message(GetLastError()));\n        return NOB_INVALID_FD;\n    }\n\n    return result;\n#endif // _WIN32\n}\n\nvoid nob_fd_close(Nob_Fd fd)\n{\n#ifdef _WIN32\n    CloseHandle(fd);\n#else\n    close(fd);\n#endif // _WIN32\n}\n\nbool nob_procs_wait(Nob_Procs procs)\n{\n    bool success = true;\n    for (size_t i = 0; i < procs.count; ++i) {\n        success = nob_proc_wait(procs.items[i]) && success;\n    }\n    return success;\n}\n\nbool nob_procs_wait_and_reset(Nob_Procs *procs)\n{\n    bool success = nob_procs_wait(*procs);\n    procs->count = 0;\n    return success;\n}\n\nbool nob_proc_wait(Nob_Proc proc)\n{\n    if (proc == NOB_INVALID_PROC) return false;\n\n#ifdef _WIN32\n    DWORD result = WaitForSingleObject(\n                       proc,    // HANDLE hHandle,\n                       INFINITE // DWORD  dwMilliseconds\n                   );\n\n    if (result == WAIT_FAILED) {\n        nob_log(NOB_ERROR, \"could not wait on child process: %s\", nob_win32_error_message(GetLastError()));\n        return false;\n    }\n\n    DWORD exit_status;\n    if (!GetExitCodeProcess(proc, &exit_status)) {\n        nob_log(NOB_ERROR, \"could not get process exit code: %s\", nob_win32_error_message(GetLastError()));\n        return false;\n    }\n\n    if (exit_status != 0) {\n        nob_log(NOB_ERROR, \"command exited with exit code %lu\", exit_status);\n        return false;\n    }\n\n    CloseHandle(proc);\n\n    return true;\n#else\n    for (;;) {\n        int wstatus = 0;\n        if (waitpid(proc, &wstatus, 0) < 0) {\n            nob_log(NOB_ERROR, \"could not wait on command (pid %d): %s\", proc, strerror(errno));\n            return false;\n        }\n\n        if (WIFEXITED(wstatus)) {\n            int exit_status = WEXITSTATUS(wstatus);\n            if (exit_status != 0) {\n                nob_log(NOB_ERROR, \"command exited with exit code %d\", exit_status);\n                return false;\n            }\n\n            break;\n        }\n\n        if (WIFSIGNALED(wstatus)) {\n            nob_log(NOB_ERROR, \"command process was terminated by signal %d\", WTERMSIG(wstatus));\n            return false;\n        }\n    }\n\n    return true;\n#endif\n}\n\nbool nob_procs_append_with_flush(Nob_Procs *procs, Nob_Proc proc, size_t max_procs_count)\n{\n    nob_da_append(procs, proc);\n\n    if (procs->count >= max_procs_count) {\n        if (!nob_procs_wait_and_reset(procs)) return false;\n    }\n\n    return true;\n}\n\nbool nob_cmd_run_sync_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect)\n{\n    Nob_Proc p = nob_cmd_run_async_redirect(cmd, redirect);\n    if (p == NOB_INVALID_PROC) return false;\n    return nob_proc_wait(p);\n}\n\nbool nob_cmd_run_sync(Nob_Cmd cmd)\n{\n    Nob_Proc p = nob_cmd_run_async(cmd);\n    if (p == NOB_INVALID_PROC) return false;\n    return nob_proc_wait(p);\n}\n\nbool nob_cmd_run_sync_and_reset(Nob_Cmd *cmd)\n{\n    bool p = nob_cmd_run_sync(*cmd);\n    cmd->count = 0;\n    return p;\n}\n\nbool nob_cmd_run_sync_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect)\n{\n    bool p = nob_cmd_run_sync_redirect(*cmd, redirect);\n    cmd->count = 0;\n    if (redirect.fdin) {\n        nob_fd_close(*redirect.fdin);\n        *redirect.fdin = NOB_INVALID_FD;\n    }\n    if (redirect.fdout) {\n        nob_fd_close(*redirect.fdout);\n        *redirect.fdout = NOB_INVALID_FD;\n    }\n    if (redirect.fderr) {\n        nob_fd_close(*redirect.fderr);\n        *redirect.fderr = NOB_INVALID_FD;\n    }\n    return p;\n}\n\nvoid nob_log(Nob_Log_Level level, const char *fmt, ...)\n{\n    if (level < nob_minimal_log_level) return;\n\n    switch (level) {\n    case NOB_INFO:\n        fprintf(stderr, \"[INFO] \");\n        break;\n    case NOB_WARNING:\n        fprintf(stderr, \"[WARNING] \");\n        break;\n    case NOB_ERROR:\n        fprintf(stderr, \"[ERROR] \");\n        break;\n    case NOB_NO_LOGS: return;\n    default:\n        NOB_UNREACHABLE(\"nob_log\");\n    }\n\n    va_list args;\n    va_start(args, fmt);\n    vfprintf(stderr, fmt, args);\n    va_end(args);\n    fprintf(stderr, \"\\n\");\n}\n\nbool nob_read_entire_dir(const char *parent, Nob_File_Paths *children)\n{\n    bool result = true;\n    DIR *dir = NULL;\n\n    dir = opendir(parent);\n    if (dir == NULL) {\n        #ifdef _WIN32\n        nob_log(NOB_ERROR, \"Could not open directory %s: %s\", parent, nob_win32_error_message(GetLastError()));\n        #else\n        nob_log(NOB_ERROR, \"Could not open directory %s: %s\", parent, strerror(errno));\n        #endif // _WIN32\n        nob_return_defer(false);\n    }\n\n    errno = 0;\n    struct dirent *ent = readdir(dir);\n    while (ent != NULL) {\n        nob_da_append(children, nob_temp_strdup(ent->d_name));\n        ent = readdir(dir);\n    }\n\n    if (errno != 0) {\n        #ifdef _WIN32\n        nob_log(NOB_ERROR, \"Could not read directory %s: %s\", parent, nob_win32_error_message(GetLastError()));\n        #else\n        nob_log(NOB_ERROR, \"Could not read directory %s: %s\", parent, strerror(errno));\n        #endif // _WIN32\n        nob_return_defer(false);\n    }\n\ndefer:\n    if (dir) closedir(dir);\n    return result;\n}\n\nbool nob_write_entire_file(const char *path, const void *data, size_t size)\n{\n    bool result = true;\n\n    FILE *f = fopen(path, \"wb\");\n    if (f == NULL) {\n        nob_log(NOB_ERROR, \"Could not open file %s for writing: %s\\n\", path, strerror(errno));\n        nob_return_defer(false);\n    }\n\n    //           len\n    //           v\n    // aaaaaaaaaa\n    //     ^\n    //     data\n\n    const char *buf = data;\n    while (size > 0) {\n        size_t n = fwrite(buf, 1, size, f);\n        if (ferror(f)) {\n            nob_log(NOB_ERROR, \"Could not write into file %s: %s\\n\", path, strerror(errno));\n            nob_return_defer(false);\n        }\n        size -= n;\n        buf  += n;\n    }\n\ndefer:\n    if (f) fclose(f);\n    return result;\n}\n\nNob_File_Type nob_get_file_type(const char *path)\n{\n#ifdef _WIN32\n    DWORD attr = GetFileAttributesA(path);\n    if (attr == INVALID_FILE_ATTRIBUTES) {\n        nob_log(NOB_ERROR, \"Could not get file attributes of %s: %s\", path, nob_win32_error_message(GetLastError()));\n        return -1;\n    }\n\n    if (attr & FILE_ATTRIBUTE_DIRECTORY) return NOB_FILE_DIRECTORY;\n    // TODO: detect symlinks on Windows (whatever that means on Windows anyway)\n    return NOB_FILE_REGULAR;\n#else // _WIN32\n    struct stat statbuf;\n    if (stat(path, &statbuf) < 0) {\n        nob_log(NOB_ERROR, \"Could not get stat of %s: %s\", path, strerror(errno));\n        return -1;\n    }\n\n    if (S_ISREG(statbuf.st_mode)) return NOB_FILE_REGULAR;\n    if (S_ISDIR(statbuf.st_mode)) return NOB_FILE_DIRECTORY;\n    if (S_ISLNK(statbuf.st_mode)) return NOB_FILE_SYMLINK;\n    return NOB_FILE_OTHER;\n#endif // _WIN32\n}\n\nbool nob_delete_file(const char *path)\n{\n    nob_log(NOB_INFO, \"deleting %s\", path);\n#ifdef _WIN32\n    if (!DeleteFileA(path)) {\n        nob_log(NOB_ERROR, \"Could not delete file %s: %s\", path, nob_win32_error_message(GetLastError()));\n        return false;\n    }\n    return true;\n#else\n    if (remove(path) < 0) {\n        nob_log(NOB_ERROR, \"Could not delete file %s: %s\", path, strerror(errno));\n        return false;\n    }\n    return true;\n#endif // _WIN32\n}\n\nbool nob_copy_directory_recursively(const char *src_path, const char *dst_path)\n{\n    bool result = true;\n    Nob_File_Paths children = {0};\n    Nob_String_Builder src_sb = {0};\n    Nob_String_Builder dst_sb = {0};\n    size_t temp_checkpoint = nob_temp_save();\n\n    Nob_File_Type type = nob_get_file_type(src_path);\n    if (type < 0) return false;\n\n    switch (type) {\n        case NOB_FILE_DIRECTORY: {\n            if (!nob_mkdir_if_not_exists(dst_path)) nob_return_defer(false);\n            if (!nob_read_entire_dir(src_path, &children)) nob_return_defer(false);\n\n            for (size_t i = 0; i < children.count; ++i) {\n                if (strcmp(children.items[i], \".\") == 0) continue;\n                if (strcmp(children.items[i], \"..\") == 0) continue;\n\n                src_sb.count = 0;\n                nob_sb_append_cstr(&src_sb, src_path);\n                nob_sb_append_cstr(&src_sb, \"/\");\n                nob_sb_append_cstr(&src_sb, children.items[i]);\n                nob_sb_append_null(&src_sb);\n\n                dst_sb.count = 0;\n                nob_sb_append_cstr(&dst_sb, dst_path);\n                nob_sb_append_cstr(&dst_sb, \"/\");\n                nob_sb_append_cstr(&dst_sb, children.items[i]);\n                nob_sb_append_null(&dst_sb);\n\n                if (!nob_copy_directory_recursively(src_sb.items, dst_sb.items)) {\n                    nob_return_defer(false);\n                }\n            }\n        } break;\n\n        case NOB_FILE_REGULAR: {\n            if (!nob_copy_file(src_path, dst_path)) {\n                nob_return_defer(false);\n            }\n        } break;\n\n        case NOB_FILE_SYMLINK: {\n            nob_log(NOB_WARNING, \"TODO: Copying symlinks is not supported yet\");\n        } break;\n\n        case NOB_FILE_OTHER: {\n            nob_log(NOB_ERROR, \"Unsupported type of file %s\", src_path);\n            nob_return_defer(false);\n        } break;\n\n        default: NOB_UNREACHABLE(\"nob_copy_directory_recursively\");\n    }\n\ndefer:\n    nob_temp_rewind(temp_checkpoint);\n    nob_da_free(src_sb);\n    nob_da_free(dst_sb);\n    nob_da_free(children);\n    return result;\n}\n\nchar *nob_temp_strdup(const char *cstr)\n{\n    size_t n = strlen(cstr);\n    char *result = nob_temp_alloc(n + 1);\n    NOB_ASSERT(result != NULL && \"Increase NOB_TEMP_CAPACITY\");\n    memcpy(result, cstr, n);\n    result[n] = '\\0';\n    return result;\n}\n\nvoid *nob_temp_alloc(size_t size)\n{\n    if (nob_temp_size + size > NOB_TEMP_CAPACITY) return NULL;\n    void *result = &nob_temp[nob_temp_size];\n    nob_temp_size += size;\n    return result;\n}\n\nchar *nob_temp_sprintf(const char *format, ...)\n{\n    va_list args;\n    va_start(args, format);\n    int n = vsnprintf(NULL, 0, format, args);\n    va_end(args);\n\n    NOB_ASSERT(n >= 0);\n    char *result = nob_temp_alloc(n + 1);\n    NOB_ASSERT(result != NULL && \"Extend the size of the temporary allocator\");\n    // TODO: use proper arenas for the temporary allocator;\n    va_start(args, format);\n    vsnprintf(result, n + 1, format, args);\n    va_end(args);\n\n    return result;\n}\n\nvoid nob_temp_reset(void)\n{\n    nob_temp_size = 0;\n}\n\nsize_t nob_temp_save(void)\n{\n    return nob_temp_size;\n}\n\nvoid nob_temp_rewind(size_t checkpoint)\n{\n    nob_temp_size = checkpoint;\n}\n\nconst char *nob_temp_sv_to_cstr(Nob_String_View sv)\n{\n    char *result = nob_temp_alloc(sv.count + 1);\n    NOB_ASSERT(result != NULL && \"Extend the size of the temporary allocator\");\n    memcpy(result, sv.data, sv.count);\n    result[sv.count] = '\\0';\n    return result;\n}\n\nint nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count)\n{\n#ifdef _WIN32\n    BOOL bSuccess;\n\n    HANDLE output_path_fd = CreateFile(output_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);\n    if (output_path_fd == INVALID_HANDLE_VALUE) {\n        // NOTE: if output does not exist it 100% must be rebuilt\n        if (GetLastError() == ERROR_FILE_NOT_FOUND) return 1;\n        nob_log(NOB_ERROR, \"Could not open file %s: %s\", output_path, nob_win32_error_message(GetLastError()));\n        return -1;\n    }\n    FILETIME output_path_time;\n    bSuccess = GetFileTime(output_path_fd, NULL, NULL, &output_path_time);\n    CloseHandle(output_path_fd);\n    if (!bSuccess) {\n        nob_log(NOB_ERROR, \"Could not get time of %s: %s\", output_path, nob_win32_error_message(GetLastError()));\n        return -1;\n    }\n\n    for (size_t i = 0; i < input_paths_count; ++i) {\n        const char *input_path = input_paths[i];\n        HANDLE input_path_fd = CreateFile(input_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);\n        if (input_path_fd == INVALID_HANDLE_VALUE) {\n            // NOTE: non-existing input is an error cause it is needed for building in the first place\n            nob_log(NOB_ERROR, \"Could not open file %s: %s\", input_path, nob_win32_error_message(GetLastError()));\n            return -1;\n        }\n        FILETIME input_path_time;\n        bSuccess = GetFileTime(input_path_fd, NULL, NULL, &input_path_time);\n        CloseHandle(input_path_fd);\n        if (!bSuccess) {\n            nob_log(NOB_ERROR, \"Could not get time of %s: %s\", input_path, nob_win32_error_message(GetLastError()));\n            return -1;\n        }\n\n        // NOTE: if even a single input_path is fresher than output_path that's 100% rebuild\n        if (CompareFileTime(&input_path_time, &output_path_time) == 1) return 1;\n    }\n\n    return 0;\n#else\n    struct stat statbuf = {0};\n\n    if (stat(output_path, &statbuf) < 0) {\n        // NOTE: if output does not exist it 100% must be rebuilt\n        if (errno == ENOENT) return 1;\n        nob_log(NOB_ERROR, \"could not stat %s: %s\", output_path, strerror(errno));\n        return -1;\n    }\n    int output_path_time = statbuf.st_mtime;\n\n    for (size_t i = 0; i < input_paths_count; ++i) {\n        const char *input_path = input_paths[i];\n        if (stat(input_path, &statbuf) < 0) {\n            // NOTE: non-existing input is an error cause it is needed for building in the first place\n            nob_log(NOB_ERROR, \"could not stat %s: %s\", input_path, strerror(errno));\n            return -1;\n        }\n        int input_path_time = statbuf.st_mtime;\n        // NOTE: if even a single input_path is fresher than output_path that's 100% rebuild\n        if (input_path_time > output_path_time) return 1;\n    }\n\n    return 0;\n#endif\n}\n\nint nob_needs_rebuild1(const char *output_path, const char *input_path)\n{\n    return nob_needs_rebuild(output_path, &input_path, 1);\n}\n\nconst char *nob_path_name(const char *path)\n{\n#ifdef _WIN32\n    const char *p1 = strrchr(path, '/');\n    const char *p2 = strrchr(path, '\\\\');\n    const char *p = (p1 > p2)? p1 : p2;  // NULL is ignored if the other search is successful\n    return p ? p + 1 : path;\n#else\n    const char *p = strrchr(path, '/');\n    return p ? p + 1 : path;\n#endif // _WIN32\n}\n\nbool nob_rename(const char *old_path, const char *new_path)\n{\n    nob_log(NOB_INFO, \"renaming %s -> %s\", old_path, new_path);\n#ifdef _WIN32\n    if (!MoveFileEx(old_path, new_path, MOVEFILE_REPLACE_EXISTING)) {\n        nob_log(NOB_ERROR, \"could not rename %s to %s: %s\", old_path, new_path, nob_win32_error_message(GetLastError()));\n        return false;\n    }\n#else\n    if (rename(old_path, new_path) < 0) {\n        nob_log(NOB_ERROR, \"could not rename %s to %s: %s\", old_path, new_path, strerror(errno));\n        return false;\n    }\n#endif // _WIN32\n    return true;\n}\n\nbool nob_read_entire_file(const char *path, Nob_String_Builder *sb)\n{\n    bool result = true;\n\n    FILE *f = fopen(path, \"rb\");\n    if (f == NULL)                 nob_return_defer(false);\n    if (fseek(f, 0, SEEK_END) < 0) nob_return_defer(false);\n#ifndef _WIN32\n    long m = ftell(f);\n#else\n    long long m = _ftelli64(f);\n#endif\n    if (m < 0)                     nob_return_defer(false);\n    if (fseek(f, 0, SEEK_SET) < 0) nob_return_defer(false);\n\n    size_t new_count = sb->count + m;\n    if (new_count > sb->capacity) {\n        sb->items = NOB_REALLOC(sb->items, new_count);\n        NOB_ASSERT(sb->items != NULL && \"Buy more RAM lool!!\");\n        sb->capacity = new_count;\n    }\n\n    fread(sb->items + sb->count, m, 1, f);\n    if (ferror(f)) {\n        // TODO: Afaik, ferror does not set errno. So the error reporting in defer is not correct in this case.\n        nob_return_defer(false);\n    }\n    sb->count = new_count;\n\ndefer:\n    if (!result) nob_log(NOB_ERROR, \"Could not read file %s: %s\", path, strerror(errno));\n    if (f) fclose(f);\n    return result;\n}\n\nint nob_sb_appendf(Nob_String_Builder *sb, const char *fmt, ...)\n{\n    va_list args;\n\n    va_start(args, fmt);\n    int n = vsnprintf(NULL, 0, fmt, args);\n    va_end(args);\n\n    // NOTE: the new_capacity needs to be +1 because of the null terminator.\n    // However, further below we increase sb->count by n, not n + 1.\n    // This is because we don't want the sb to include the null terminator. The user can always sb_append_null() if they want it\n    nob_da_reserve(sb, sb->count + n + 1);\n    char *dest = sb->items + sb->count;\n    va_start(args, fmt);\n    vsnprintf(dest, n+1, fmt, args);\n    va_end(args);\n\n    sb->count += n;\n\n    return n;\n}\n\nNob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim)\n{\n    size_t i = 0;\n    while (i < sv->count && sv->data[i] != delim) {\n        i += 1;\n    }\n\n    Nob_String_View result = nob_sv_from_parts(sv->data, i);\n\n    if (i < sv->count) {\n        sv->count -= i + 1;\n        sv->data  += i + 1;\n    } else {\n        sv->count -= i;\n        sv->data  += i;\n    }\n\n    return result;\n}\n\nNob_String_View nob_sv_chop_left(Nob_String_View *sv, size_t n)\n{\n    if (n > sv->count) {\n        n = sv->count;\n    }\n\n    Nob_String_View result = nob_sv_from_parts(sv->data, n);\n\n    sv->data  += n;\n    sv->count -= n;\n\n    return result;\n}\n\nNob_String_View nob_sv_from_parts(const char *data, size_t count)\n{\n    Nob_String_View sv;\n    sv.count = count;\n    sv.data = data;\n    return sv;\n}\n\nNob_String_View nob_sv_trim_left(Nob_String_View sv)\n{\n    size_t i = 0;\n    while (i < sv.count && isspace(sv.data[i])) {\n        i += 1;\n    }\n\n    return nob_sv_from_parts(sv.data + i, sv.count - i);\n}\n\nNob_String_View nob_sv_trim_right(Nob_String_View sv)\n{\n    size_t i = 0;\n    while (i < sv.count && isspace(sv.data[sv.count - 1 - i])) {\n        i += 1;\n    }\n\n    return nob_sv_from_parts(sv.data, sv.count - i);\n}\n\nNob_String_View nob_sv_trim(Nob_String_View sv)\n{\n    return nob_sv_trim_right(nob_sv_trim_left(sv));\n}\n\nNob_String_View nob_sv_from_cstr(const char *cstr)\n{\n    return nob_sv_from_parts(cstr, strlen(cstr));\n}\n\nbool nob_sv_eq(Nob_String_View a, Nob_String_View b)\n{\n    if (a.count != b.count) {\n        return false;\n    } else {\n        return memcmp(a.data, b.data, a.count) == 0;\n    }\n}\n\nbool nob_sv_end_with(Nob_String_View sv, const char *cstr)\n{\n    size_t cstr_count = strlen(cstr);\n    if (sv.count >= cstr_count) {\n        size_t ending_start = sv.count - cstr_count;\n        Nob_String_View sv_ending = nob_sv_from_parts(sv.data + ending_start, cstr_count);\n        return nob_sv_eq(sv_ending, nob_sv_from_cstr(cstr));\n    }\n    return false;\n}\n\n\nbool nob_sv_starts_with(Nob_String_View sv, Nob_String_View expected_prefix)\n{\n    if (expected_prefix.count <= sv.count) {\n        Nob_String_View actual_prefix = nob_sv_from_parts(sv.data, expected_prefix.count);\n        return nob_sv_eq(expected_prefix, actual_prefix);\n    }\n\n    return false;\n}\n\n// RETURNS:\n//  0 - file does not exists\n//  1 - file exists\n// -1 - error while checking if file exists. The error is logged\nint nob_file_exists(const char *file_path)\n{\n#if _WIN32\n    // TODO: distinguish between \"does not exists\" and other errors\n    DWORD dwAttrib = GetFileAttributesA(file_path);\n    return dwAttrib != INVALID_FILE_ATTRIBUTES;\n#else\n    struct stat statbuf;\n    if (stat(file_path, &statbuf) < 0) {\n        if (errno == ENOENT) return 0;\n        nob_log(NOB_ERROR, \"Could not check if file %s exists: %s\", file_path, strerror(errno));\n        return -1;\n    }\n    return 1;\n#endif\n}\n\nconst char *nob_get_current_dir_temp(void)\n{\n#ifdef _WIN32\n    DWORD nBufferLength = GetCurrentDirectory(0, NULL);\n    if (nBufferLength == 0) {\n        nob_log(NOB_ERROR, \"could not get current directory: %s\", nob_win32_error_message(GetLastError()));\n        return NULL;\n    }\n\n    char *buffer = (char*) nob_temp_alloc(nBufferLength);\n    if (GetCurrentDirectory(nBufferLength, buffer) == 0) {\n        nob_log(NOB_ERROR, \"could not get current directory: %s\", nob_win32_error_message(GetLastError()));\n        return NULL;\n    }\n\n    return buffer;\n#else\n    char *buffer = (char*) nob_temp_alloc(PATH_MAX);\n    if (getcwd(buffer, PATH_MAX) == NULL) {\n        nob_log(NOB_ERROR, \"could not get current directory: %s\", strerror(errno));\n        return NULL;\n    }\n\n    return buffer;\n#endif // _WIN32\n}\n\nbool nob_set_current_dir(const char *path)\n{\n#ifdef _WIN32\n    if (!SetCurrentDirectory(path)) {\n        nob_log(NOB_ERROR, \"could not set current directory to %s: %s\", path, nob_win32_error_message(GetLastError()));\n        return false;\n    }\n    return true;\n#else\n    if (chdir(path) < 0) {\n        nob_log(NOB_ERROR, \"could not set current directory to %s: %s\", path, strerror(errno));\n        return false;\n    }\n    return true;\n#endif // _WIN32\n}\n\n// minirent.h SOURCE BEGIN ////////////////////////////////////////\n#ifdef _WIN32\nstruct DIR\n{\n    HANDLE hFind;\n    WIN32_FIND_DATA data;\n    struct dirent *dirent;\n};\n\nDIR *opendir(const char *dirpath)\n{\n    NOB_ASSERT(dirpath);\n\n    char buffer[MAX_PATH];\n    snprintf(buffer, MAX_PATH, \"%s\\\\*\", dirpath);\n\n    DIR *dir = (DIR*)NOB_REALLOC(NULL, sizeof(DIR));\n    memset(dir, 0, sizeof(DIR));\n\n    dir->hFind = FindFirstFile(buffer, &dir->data);\n    if (dir->hFind == INVALID_HANDLE_VALUE) {\n        // TODO: opendir should set errno accordingly on FindFirstFile fail\n        // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror\n        errno = ENOSYS;\n        goto fail;\n    }\n\n    return dir;\n\nfail:\n    if (dir) {\n        NOB_FREE(dir);\n    }\n\n    return NULL;\n}\n\nstruct dirent *readdir(DIR *dirp)\n{\n    NOB_ASSERT(dirp);\n\n    if (dirp->dirent == NULL) {\n        dirp->dirent = (struct dirent*)NOB_REALLOC(NULL, sizeof(struct dirent));\n        memset(dirp->dirent, 0, sizeof(struct dirent));\n    } else {\n        if(!FindNextFile(dirp->hFind, &dirp->data)) {\n            if (GetLastError() != ERROR_NO_MORE_FILES) {\n                // TODO: readdir should set errno accordingly on FindNextFile fail\n                // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror\n                errno = ENOSYS;\n            }\n\n            return NULL;\n        }\n    }\n\n    memset(dirp->dirent->d_name, 0, sizeof(dirp->dirent->d_name));\n\n    strncpy(\n        dirp->dirent->d_name,\n        dirp->data.cFileName,\n        sizeof(dirp->dirent->d_name) - 1);\n\n    return dirp->dirent;\n}\n\nint closedir(DIR *dirp)\n{\n    NOB_ASSERT(dirp);\n\n    if(!FindClose(dirp->hFind)) {\n        // TODO: closedir should set errno accordingly on FindClose fail\n        // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror\n        errno = ENOSYS;\n        return -1;\n    }\n\n    if (dirp->dirent) {\n        NOB_FREE(dirp->dirent);\n    }\n    NOB_FREE(dirp);\n\n    return 0;\n}\n#endif // _WIN32\n// minirent.h SOURCE END ////////////////////////////////////////\n\n#endif // NOB_IMPLEMENTATION\n\n#ifndef NOB_STRIP_PREFIX_GUARD_\n#define NOB_STRIP_PREFIX_GUARD_\n    // NOTE: The name stripping should be part of the header so it's not accidentally included\n    // several times. At the same time, it should be at the end of the file so to not create any\n    // potential conflicts in the NOB_IMPLEMENTATION. The header obviously cannot be at the end\n    // of the file because NOB_IMPLEMENTATION needs the forward declarations from there. So the\n    // solution is to split the header into two parts where the name stripping part is at the\n    // end of the file after the NOB_IMPLEMENTATION.\n    #ifdef NOB_STRIP_PREFIX\n        #define TODO NOB_TODO\n        #define UNREACHABLE NOB_UNREACHABLE\n        #define UNUSED NOB_UNUSED\n        #define ARRAY_LEN NOB_ARRAY_LEN\n        #define ARRAY_GET NOB_ARRAY_GET\n        #define INFO NOB_INFO\n        #define WARNING NOB_WARNING\n        #define ERROR NOB_ERROR\n        #define NO_LOGS NOB_NO_LOGS\n        #define Log_Level Nob_Log_Level\n        #define minimal_log_level nob_minimal_log_level\n        // NOTE: Name log is already defined in math.h and historically always was the natural logarithmic function.\n        // So there should be no reason to strip the `nob_` prefix in this specific case.\n        // #define log nob_log\n        #define shift nob_shift\n        #define shift_args nob_shift_args\n        #define File_Paths Nob_File_Paths\n        #define FILE_REGULAR NOB_FILE_REGULAR\n        #define FILE_DIRECTORY NOB_FILE_DIRECTORY\n        #define FILE_SYMLINK NOB_FILE_SYMLINK\n        #define FILE_OTHER NOB_FILE_OTHER\n        #define File_Type Nob_File_Type\n        #define mkdir_if_not_exists nob_mkdir_if_not_exists\n        #define copy_file nob_copy_file\n        #define copy_directory_recursively nob_copy_directory_recursively\n        #define read_entire_dir nob_read_entire_dir\n        #define write_entire_file nob_write_entire_file\n        #define get_file_type nob_get_file_type\n        #define delete_file nob_delete_file\n        #define return_defer nob_return_defer\n        #define da_append nob_da_append\n        #define da_free nob_da_free\n        #define da_append_many nob_da_append_many\n        #define da_resize nob_da_resize\n        #define da_reserve nob_da_reserve\n        #define da_last nob_da_last\n        #define da_remove_unordered nob_da_remove_unordered\n        #define da_foreach nob_da_foreach\n        #define String_Builder Nob_String_Builder\n        #define read_entire_file nob_read_entire_file\n        #define sb_appendf nob_sb_appendf\n        #define sb_append_buf nob_sb_append_buf\n        #define sb_append_cstr nob_sb_append_cstr\n        #define sb_append_null nob_sb_append_null\n        #define sb_free nob_sb_free\n        #define Proc Nob_Proc\n        #define INVALID_PROC NOB_INVALID_PROC\n        #define Fd Nob_Fd\n        #define INVALID_FD NOB_INVALID_FD\n        #define fd_open_for_read nob_fd_open_for_read\n        #define fd_open_for_write nob_fd_open_for_write\n        #define fd_close nob_fd_close\n        #define Procs Nob_Procs\n        #define proc_wait nob_proc_wait\n        #define procs_wait nob_procs_wait\n        #define procs_wait_and_reset nob_procs_wait_and_reset\n        #define procs_append_with_flush nob_procs_append_with_flush\n        #define Cmd Nob_Cmd\n        #define Cmd_Redirect Nob_Cmd_Redirect\n        #define cmd_render nob_cmd_render\n        #define cmd_append nob_cmd_append\n        #define cmd_extend nob_cmd_extend\n        #define cmd_free nob_cmd_free\n        #define cmd_run_async nob_cmd_run_async\n        #define cmd_run_async_and_reset nob_cmd_run_async_and_reset\n        #define cmd_run_async_redirect nob_cmd_run_async_redirect\n        #define cmd_run_async_redirect_and_reset nob_cmd_run_async_redirect_and_reset\n        #define cmd_run_sync nob_cmd_run_sync\n        #define cmd_run_sync_and_reset nob_cmd_run_sync_and_reset\n        #define cmd_run_sync_redirect nob_cmd_run_sync_redirect\n        #define cmd_run_sync_redirect_and_reset nob_cmd_run_sync_redirect_and_reset\n        #define temp_strdup nob_temp_strdup\n        #define temp_alloc nob_temp_alloc\n        #define temp_sprintf nob_temp_sprintf\n        #define temp_reset nob_temp_reset\n        #define temp_save nob_temp_save\n        #define temp_rewind nob_temp_rewind\n        #define path_name nob_path_name\n        // NOTE: rename(2) is widely known POSIX function. We never wanna collide with it.\n        // #define rename nob_rename\n        #define needs_rebuild nob_needs_rebuild\n        #define needs_rebuild1 nob_needs_rebuild1\n        #define file_exists nob_file_exists\n        #define get_current_dir_temp nob_get_current_dir_temp\n        #define set_current_dir nob_set_current_dir\n        #define String_View Nob_String_View\n        #define temp_sv_to_cstr nob_temp_sv_to_cstr\n        #define sv_chop_by_delim nob_sv_chop_by_delim\n        #define sv_chop_left nob_sv_chop_left\n        #define sv_trim nob_sv_trim\n        #define sv_trim_left nob_sv_trim_left\n        #define sv_trim_right nob_sv_trim_right\n        #define sv_eq nob_sv_eq\n        #define sv_starts_with nob_sv_starts_with\n        #define sv_end_with nob_sv_end_with\n        #define sv_from_cstr nob_sv_from_cstr\n        #define sv_from_parts nob_sv_from_parts\n        #define sb_to_sv nob_sb_to_sv\n        #define win32_error_message nob_win32_error_message\n    #endif // NOB_STRIP_PREFIX\n#endif // NOB_STRIP_PREFIX_GUARD_\n\n/*\n   Revision history:\n\n     1.20.6 (2025-05-16) Never strip nob_* suffix from nob_rename (By @rexim)\n     1.20.5 (2025-05-16) NOB_PRINTF_FORMAT() support for MinGW (By @KillerxDBr)\n     1.20.4 (2025-05-16) More reliable rendering of the Windows command (By @vylsaz)\n     1.20.3 (2025-05-16) Add check for __clang__ along with _MSC_VER checks (By @nashiora)\n     1.20.2 (2025-04-24) Report the program name that failed to start up in nob_cmd_run_async_redirect() (By @rexim)\n     1.20.1 (2025-04-16) Use vsnprintf() in nob_sb_appendf() instead of vsprintf() (By @LainLayer)\n     1.20.0 (2025-04-16) Introduce nob_cc(), nob_cc_flags(), nob_cc_inputs(), nob_cc_output() macros (By @rexim)\n     1.19.0 (2025-03-25) Add nob_procs_append_with_flush() (By @rexim and @anion155)\n     1.18.0 (2025-03-24) Add nob_da_foreach() (By @rexim)\n                         Allow file sizes greater than 2GB to be read on windows (By @satchelfrost and @KillerxDBr)\n                         Fix nob_fd_open_for_write behaviour on windows so it truncates the opened files (By @twixuss)\n     1.17.0 (2025-03-16) Factor out nob_da_reserve() (By @rexim)\n                         Add nob_sb_appendf() (By @angelcaru)\n     1.16.1 (2025-03-16) Make nob_da_resize() exponentially grow capacity similar to no_da_append_many()\n     1.16.0 (2025-03-16) Introduce NOB_PRINTF_FORMAT\n     1.15.1 (2025-03-16) Make nob.h compilable in gcc/clang with -std=c99 on POSIX. This includes:\n                           not using strsignal()\n                           using S_IS* stat macros instead of S_IF* flags\n     1.15.0 (2025-03-03) Add nob_sv_chop_left()\n     1.14.1 (2025-03-02) Add NOB_EXPERIMENTAL_DELETE_OLD flag that enables deletion of nob.old in Go Rebuild Urself™ Technology\n     1.14.0 (2025-02-17) Add nob_da_last()\n                         Add nob_da_remove_unordered()\n     1.13.1 (2025-02-17) Fix segfault in nob_delete_file() (By @SileNce5k)\n     1.13.0 (2025-02-11) Add nob_da_resize() (By @satchelfrost)\n     1.12.0 (2025-02-04) Add nob_delete_file()\n                         Add nob_sv_start_with()\n     1.11.0 (2025-02-04) Add NOB_GO_REBUILD_URSELF_PLUS() (By @rexim)\n     1.10.0 (2025-02-04) Make NOB_ASSERT, NOB_REALLOC, and NOB_FREE redefinable (By @OleksiiBulba)\n      1.9.1 (2025-02-04) Fix signature of nob_get_current_dir_temp() (By @julianstoerig)\n      1.9.0 (2024-11-06) Add Nob_Cmd_Redirect mechanism (By @rexim)\n                         Add nob_path_name() (By @0dminnimda)\n      1.8.0 (2024-11-03) Add nob_cmd_extend() (By @0dminnimda)\n      1.7.0 (2024-11-03) Add nob_win32_error_message and NOB_WIN32_ERR_MSG_SIZE (By @KillerxDBr)\n      1.6.0 (2024-10-27) Add nob_cmd_run_sync_and_reset()\n                         Add nob_sb_to_sv()\n                         Add nob_procs_wait_and_reset()\n      1.5.1 (2024-10-25) Include limits.h for Linux musl libc (by @pgalkin)\n      1.5.0 (2024-10-23) Add nob_get_current_dir_temp()\n                         Add nob_set_current_dir()\n      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)\n                         Add nob_sv_end_with (By @pgalkin)\n      1.3.2 (2024-10-21) Fix unreachable error in nob_log on passing NOB_NO_LOGS\n      1.3.1 (2024-10-21) Fix redeclaration error for minimal_log_level (By @KillerxDBr)\n      1.3.0 (2024-10-17) Add NOB_UNREACHABLE\n      1.2.2 (2024-10-16) Fix compilation of nob_cmd_run_sync_and_reset on Windows (By @KillerxDBr)\n      1.2.1 (2024-10-16) Add a separate include guard for NOB_STRIP_PREFIX.\n      1.2.0 (2024-10-15) Make NOB_DA_INIT_CAP redefinable\n                         Add NOB_STRIP_PREFIX which strips off nob_* prefix from all the user facing names\n                         Add NOB_UNUSED macro\n                         Add NOB_TODO macro\n                         Add nob_sv_trim_left and nob_sv_trim_right declarations to the header part\n      1.1.1 (2024-10-15) Remove forward declaration for is_path1_modified_after_path2\n      1.1.0 (2024-10-15) nob_minimal_log_level\n                         nob_cmd_run_sync_and_reset\n      1.0.0 (2024-10-15) first release based on https://github.com/tsoding/musializer/blob/4ac7cce9874bc19e02d8c160c8c6229de8919401/nob.h\n*/\n\n/*\n   Version Conventions:\n\n      We are following https://semver.org/ so the version has a format MAJOR.MINOR.PATCH:\n      - Modifying comments does not update the version.\n      - PATCH is incremented in case of a bug fix or refactoring without touching the API.\n      - MINOR is incremented when new functions and/or types are added in a way that does\n        not break any existing user code. We want to do this in the majority of the situation.\n        If we want to delete a certain function or type in favor of another one we should\n        just add the new function/type and deprecate the old one in a backward compatible way\n        and let them co-exist for a while.\n      - MAJOR update should be just a periodic cleanup of the deprecated functions and types\n        without really modifying any existing functionality.\n\n   Naming Conventions:\n\n      - All the user facing names should be prefixed with `nob_` or `NOB_` depending on the case.\n      - The prefixes of non-redefinable names should be strippable with NOB_STRIP_PREFIX (unless\n        explicitly stated otherwise like in case of nob_log).\n      - Internal functions should be prefixed with `nob__` (double underscore).\n*/\n\n/*\n   ------------------------------------------------------------------------------\n   This software is available under 2 licenses -- choose whichever you prefer.\n   ------------------------------------------------------------------------------\n   ALTERNATIVE A - MIT License\n   Copyright (c) 2024 Alexey Kutepov\n   Permission is hereby granted, free of charge, to any person obtaining a copy of\n   this software and associated documentation files (the \"Software\"), to deal in\n   the Software without restriction, including without limitation the rights to\n   use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\n   of the Software, and to permit persons to whom the Software is furnished to do\n   so, subject to the following conditions:\n   The above copyright notice and this permission notice shall be included in all\n   copies or substantial portions of the Software.\n   THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n   SOFTWARE.\n   ------------------------------------------------------------------------------\n   ALTERNATIVE B - Public Domain (www.unlicense.org)\n   This is free and unencumbered software released into the public domain.\n   Anyone is free to copy, modify, publish, use, compile, sell, or distribute this\n   software, either in source code form or as a compiled binary, for any purpose,\n   commercial or non-commercial, and by any means.\n   In jurisdictions that recognize copyright laws, the author or authors of this\n   software dedicate any and all copyright interest in the software to the public\n   domain. We make this dedication for the benefit of the public at large and to\n   the detriment of our heirs and successors. We intend this dedication to be an\n   overt act of relinquishment in perpetuity of all present and future rights to\n   this software under copyright law.\n   THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n   AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN\n   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n   WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n   ------------------------------------------------------------------------------\n*/\n"
  }
]