Full Code of tsoding/jim for AI

master 2890b452593c cached
18 files
126.8 KB
35.8k tokens
179 symbols
1 requests
Download .txt
Repository: tsoding/jim
Branch: master
Commit: 2890b452593c
Files: 18
Total size: 126.8 KB

Directory structure:
gitextract_k3i8e3c7/

├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── examples/
│   ├── .gitignore
│   ├── 01_from_readme.c
│   ├── 02_binary_tree.c
│   ├── 03_parsing_database.c
│   ├── Makefile
│   ├── database.json
│   └── fruits.h
├── jim1.h
├── jim2.h
├── jimp.h
├── test_jim.c
├── test_jim_expected.h
└── thirdparty/
    └── nob.h

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on: [push, pull_request]

jobs:
  build-linux-gcc:
    runs-on: ubuntu-18.04
    steps:
      - uses: actions/checkout@v1
      - name: build all and examples
        run: |
          make -k
          ./test_jim
        env:
          CC: gcc
          CXX: g++
  build-linux-clang:
    runs-on: ubuntu-18.04
    steps:
      - uses: actions/checkout@v1
      - name: build all and examples
        run: |
          make -k
          ./test_jim
        env:
          CC: clang
          CXX: clang++
  build-macos:
    runs-on: macOS-latest
    steps:
      - uses: actions/checkout@v1
      - name: build all and examples
        run: |
          make -k
          ./test_jim
        env:
          CC: clang
          CXX: clang++
# TODO: there is not build for Windows


================================================
FILE: .gitignore
================================================
example
test_jim
jimp


================================================
FILE: LICENSE
================================================
Copyright 2021 Alexey Kutepov <reximkut@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

================================================
FILE: Makefile
================================================
CFLAGS=-Wall -Wextra -Wswitch-enum -ggdb -I./thirdparty/

.PHONY: all
all: examples test_jim

test_jim: test_jim.c jim.h
	$(CC) $(CFLAGS) -o test_jim test_jim.c 

.PHONY: examples
examples: 
	$(MAKE) -C examples/


================================================
FILE: README.md
================================================
# JIM

Immediate Mode JSON Serialization Library in C. Similar to [imgui](https://github.com/ocornut/imgui) but for generating JSON.

## Example

[`example.c`](./example.c):

```c
#include <stdio.h>

#define JIM_IMPLEMENTATION
#include "./jim.h"

int main()
{
    Jim jim = {.pp = 4};

    jim_object_begin(&jim);
        jim_member_key(&jim, "null");
        jim_null(&jim);

        jim_member_key(&jim, "bool");
        jim_array_begin(&jim);
            jim_bool(&jim, 0);
            jim_bool(&jim, 1);
        jim_array_end(&jim);

        jim_member_key(&jim, "integers");
        jim_array_begin(&jim);
            for (int i = -3; i <= 3; ++i) {
                jim_integer(&jim, i);
            }
        jim_array_end(&jim);

        jim_member_key(&jim, "floats");
        jim_array_begin(&jim);
            jim_float(&jim, 0.0, 4);
            jim_float(&jim, -0.0, 4);
            jim_float(&jim, 3.1415, 4);
            jim_float(&jim, 2.71828, 5);
            jim_float(&jim, 1.6180, 4);
            jim_float(&jim, 0.0 / 0.0, 4);
            jim_float(&jim, 1.0 / 0.0, 4);
            jim_float(&jim, -1.0 / 0.0, 4);
        jim_array_end(&jim);

        jim_member_key(&jim, "string");
        jim_array_begin(&jim);
            jim_string(&jim, "Hello\tWorld\n");
            jim_string_sized(&jim, "\0\0\0\0", 4);
        jim_array_end(&jim);

        jim_member_key(&jim, "nested_object");
        jim_object_begin(&jim);
            jim_member_key(&jim, "foo");
            jim_integer(&jim, 69);
            jim_member_key(&jim, "bar");
            jim_integer(&jim, 420);
            jim_member_key(&jim, "baz");
            jim_integer(&jim, 1337);
        jim_object_end(&jim);

        jim_member_key(&jim, "empty_array"),
        jim_array_begin(&jim);
        jim_array_end(&jim);

        jim_member_key(&jim, "empty_object"),
        jim_object_begin(&jim);
        jim_object_end(&jim);
    jim_object_end(&jim);

    fwrite(jim.sink, jim.sink_count, 1, stdout);

    return 0;
}
```

### Output

```console
$ cc -o example example.c
$ ./example
{
    "null": null,
    "bool": [
        false,
        true
    ],
    "integers": [
        -3,
        -2,
        -1,
        0,
        1,
        2,
        3
    ],
    "floats": [
        0.0,
        0.0,
        3.1415,
        2.71828,
        1.6180,
        null,
        null,
        null
    ],
    "string": [
        "Hello\tWorld\n",
        "\u0000\u0000\u0000\u0000"
    ],
    "nested_object": {
        "foo": 69,
        "bar": 420,
        "baz": 1337
    },
    "empty_array": [],
    "empty_object": {}
}
```

## Testing

```console
$ make
$ ./test_jim
```

The expected outputs of the test cases are stored in [./test_jim_expected.h](./test_jim_expected.h). To regenerate it just run:

```console
$ ./test_jim record
```

## Notes

1. Does not depends on libc. Could be theoretically used in embedded, but I know nothing about embedded, so maybe not.
2. `jim_float()` is quite likely very stupid and imprecise

<!-- TODO: document jimp.h here -->


================================================
FILE: examples/.gitignore
================================================
01_from_readme
02_binary_tree
03_parsing_database

================================================
FILE: examples/01_from_readme.c
================================================
#include <stdio.h>

#define JIM_IMPLEMENTATION
#include "../jim.h"

int main()
{
    Jim jim = {.pp = 4};

    jim_object_begin(&jim);
        jim_member_key(&jim, "null");
        jim_null(&jim);

        jim_member_key(&jim, "bool");
        jim_array_begin(&jim);
            jim_bool(&jim, 0);
            jim_bool(&jim, 1);
        jim_array_end(&jim);

        jim_member_key(&jim, "integers");
        jim_array_begin(&jim);
            for (int i = -3; i <= 3; ++i) {
                jim_integer(&jim, i);
            }
        jim_array_end(&jim);

        jim_member_key(&jim, "floats");
        jim_array_begin(&jim);
            jim_float(&jim, 0.0, 4);
            jim_float(&jim, -0.0, 4);
            jim_float(&jim, 3.1415, 4);
            jim_float(&jim, 2.71828, 5);
            jim_float(&jim, 1.6180, 4);
            jim_float(&jim, 0.0 / 0.0, 4);
            jim_float(&jim, 1.0 / 0.0, 4);
            jim_float(&jim, -1.0 / 0.0, 4);
        jim_array_end(&jim);

        jim_member_key(&jim, "string");
        jim_array_begin(&jim);
            jim_string(&jim, "Hello\tWorld\n");
            jim_string_sized(&jim, "\0\0\0\0", 4);
        jim_array_end(&jim);

        jim_member_key(&jim, "nested_object");
        jim_object_begin(&jim);
            jim_member_key(&jim, "foo");
            jim_integer(&jim, 69);
            jim_member_key(&jim, "bar");
            jim_integer(&jim, 420);
            jim_member_key(&jim, "baz");
            jim_integer(&jim, 1337);
        jim_object_end(&jim);

        jim_member_key(&jim, "empty_array"),
        jim_array_begin(&jim);
        jim_array_end(&jim);

        jim_member_key(&jim, "empty_object"),
        jim_object_begin(&jim);
        jim_object_end(&jim);
    jim_object_end(&jim);

    fwrite(jim.sink, jim.sink_count, 1, stdout);

    return 0;
}


================================================
FILE: examples/02_binary_tree.c
================================================
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define JIM_IMPLEMENTATION
#include "../jim.h"

#include "fruits.h"

typedef struct Node Node;

struct Node {
    const char *value;
    Node *left;
    Node *right;
};

Node *generate_tree_of_fruits(size_t level_cur, size_t level_max)
{
    if (level_cur < level_max) {
        // Let It Leak! Let It Leak!
        // Let It Leak! Oh, Let It Leak!
        // Memory costs nothing!
        // Let It Leak!
        Node *node = malloc(sizeof(*node));
        node->value = fruits[rand() % fruits_count];
        node->left = generate_tree_of_fruits(level_cur + 1, level_max);
        node->right = generate_tree_of_fruits(level_cur + 1, level_max);
        return node;
    } else {
        return NULL;
    }
}

void render_node_as_json(Jim *jim, Node *node)
{
    if (node == NULL) {
        jim_null(jim);
    } else {
        jim_object_begin(jim);
        jim_member_key(jim, "value");
        jim_string(jim, node->value);

        jim_member_key(jim, "left");
        render_node_as_json(jim, node->left);

        jim_member_key(jim, "right");
        render_node_as_json(jim, node->right);
        jim_object_end(jim);
    }
}

int main()
{
    srand(time(0));
    Jim jim = {0};
    render_node_as_json(&jim, generate_tree_of_fruits(0, 4));
    fwrite(jim.sink, jim.sink_count, 1, stdout);
    return 0;
}


================================================
FILE: examples/03_parsing_database.c
================================================
#include <stdio.h>
#include <stdbool.h>
#define NOB_IMPLEMENTATION
#define NOB_STRIP_PREFIX
#include "../thirdparty/nob.h"
#define JIMP_IMPLEMENTATION
#include "../jimp.h"

typedef struct {
    const char *name;
    double age;
    const char *location;
    double body_count;
} Person;

typedef struct {
    Person *items;
    size_t count;
    size_t capacity;
} People;

bool parse_person(Jimp *jimp, Person *p)
{
    if (!jimp_object_begin(jimp)) return false;
    while (jimp_object_member(jimp)) {
        if (strcmp(jimp->string, "name") == 0) {
            if (!jimp_string(jimp)) return false;
            p->name = strdup(jimp->string);
        } else if (strcmp(jimp->string, "age") == 0) {
            if (!jimp_number(jimp)) return false;
            p->age = jimp->number;
        } else if (strcmp(jimp->string, "location") == 0) {
            if (!jimp_string(jimp)) return false;
            p->location = strdup(jimp->string);
        } else if (strcmp(jimp->string, "body_count") == 0) {
            if (!jimp_number(jimp)) return false;
            p->body_count = jimp->number;
        } else {
            jimp_unknown_member(jimp);
            return false;
        }
    }
    return jimp_object_end(jimp);
}

bool parse_people(Jimp *jimp, People *ps)
{
    if (!jimp_array_begin(jimp)) return false;
    while (jimp_array_item(jimp)) {
        Person p = {0};
        if (!parse_person(jimp, &p)) return false;
        da_append(ps, p);
    }
    if (!jimp_array_end(jimp)) return false;

    return true;
}

void print_person(const Person *p)
{
    printf("name       = %s\n",  p->name);
    printf("age        = %lf\n", p->age);
    printf("location   = %s\n",  p->location);
    printf("body_count = %lf\n", p->body_count);
}

typedef struct {
    long *items;
    size_t count;
    size_t capacity;
} Numbers;

int main(void)
{
    const char *file_path = "database.json";

    String_Builder sb = {0};
    if (!read_entire_file(file_path, &sb)) return 1;

    Jimp jimp = {0};
    jimp_begin(&jimp, file_path, sb.items, sb.count);

    People ps = {0};
    Numbers xs = {0};
    if (!jimp_object_begin(&jimp)) return 1;
    while (jimp_object_member(&jimp)) {
        if (strcmp(jimp.string, "profile") == 0) {
            if (!parse_people(&jimp, &ps)) return 1;
        } else if (strcmp(jimp.string, "number") == 0) {
            if (!jimp_array_begin(&jimp)) return 1;
            while (jimp_array_item(&jimp)) {
                if (!jimp_number(&jimp)) return 1;
                da_append(&xs, jimp.number);
            }
            if (!jimp_array_end(&jimp)) return 1;
        } else {
            jimp_unknown_member(&jimp);
            return 1;
        }
    }
    if (!jimp_object_end(&jimp)) return 1;

    da_foreach(Person, p, &ps) {
        print_person(p);
        printf("\n");
    }
    printf("------------------------------\n");
    da_foreach(long, x, &xs) {
        printf("%ld ", *x);
    }
    printf("\n");

    return 0;
}


================================================
FILE: examples/Makefile
================================================
CFLAGS=-Wall -Wextra -Wswitch-enum -ggdb

.PHONY: all
all: 01_from_readme 02_binary_tree 03_parsing_database

01_from_readme: 01_from_readme.c ../jim.h
	$(CC) $(CFLAGS) -o 01_from_readme 01_from_readme.c

02_binary_tree: 02_binary_tree.c fruits.h ../jim.h
	$(CC) $(CFLAGS) -o 02_binary_tree 02_binary_tree.c

03_parsing_database: 03_parsing_database.c ../jimp.h ../thirdparty/nob.h
	$(CC) $(CFLAGS) -o 03_parsing_database 03_parsing_database.c


================================================
FILE: examples/database.json
================================================
{
    "number": [69, 420, 1337, 80085],
    "profile": [
        {
            "location": "Wonderland",
            "location": "Wonderland",
            "body_count": 150,
            "name": "Alice Smith",
            "age": 34
        },
        {
            "name": "Bob Johnson",
            "age": 45,
            "location": "Atlantis",
            "body_count": 300
        },
        {
            "name": "Charlie Brown",
            "age": 28,
            "location": "Chocolate Factory",
            "body_count": 200
        },
        {
            "name": "Diana Prince",
            "age": 32,
            "location": "Themyscira",
            "body_count": 250
        },
        {
            "name": "Ethan Hunt",
            "age": 40,
            "location": "Mission HQ",
            "body_count": 350
        },
        {
            "name": "Fiona Apple",
            "age": 37,
            "location": "Music City",
            "body_count": 180
        },
        {
            "name": "George Lucas",
            "age": 75,
            "location": "Galaxy Far Far Away",
            "body_count": 500
        },
        {
            "name": "Hannah Montana",
            "age": 25,
            "location": "Nashville",
            "body_count": 100
        },
        {
            "name": "Ian Malcolm",
            "age": 60,
            "location": "Jurassic Park",
            "body_count": 400
        },
        {
            "name": "Jessica Rabbit",
            "age": 30,
            "location": "Toontown",
            "body_count": 220
        }
    ]
}


================================================
FILE: examples/fruits.h
================================================
#ifndef FRUITS_H_
#define FRUITS_H_

// What? This is just a list of fruits. What did you expect?

const char *fruits[] = {
    "Apple",
    "Apricot",
    "Avocado",
    "Banana",
    "Bilberry",
    "Blackberry",
    "Blackcurrant",
    "Blueberry",
    "Boysenberry",
    "Currant",
    "Cherry",
    "Cherimoya",
    "Chico fruit",
    "Cloudberry",
    "Coconut",
    "Cranberry",
    "Cucumber",
    "Custard apple",
    "Damson",
    "Date",
    "Dragonfruit",
    "Durian",
    "Elderberry",
    "Feijoa",
    "Fig",
    "Goji berry",
    "Gooseberry",
    "Grape",
    "Raisin",
    "Grapefruit",
    "Guava",
    "Honeyberry",
    "Huckleberry",
    "Jabuticaba",
    "Jackfruit",
    "Jambul",
    "Jujube",
    "Juniper berry",
    "Kiwano",
    "Kiwifruit",
    "Kumquat",
    "Lemon",
    "Lime",
    "Loquat",
    "Longan",
    "Lychee",
    "Mango",
    "Mangosteen",
    "Marionberry",
    "Melon",
    "Cantaloupe",
    "Honeydew",
    "Watermelon",
    "Miracle fruit",
    "Mulberry",
    "Nectarine",
    "Nance",
    "Olive",
    "Orange",
    "Blood orange",
    "Clementine",
    "Mandarine",
    "Tangerine",
    "Papaya",
    "Passionfruit",
    "Peach",
    "Pear",
    "Persimmon",
    "Physalis",
    "Plantain",
    "Plum",
    "Prune",
    "Pineapple",
    "Plumcot",
    "Pomegranate",
    "Pomelo",
    "Purple mangosteen",
    "Quince",
    "Raspberry",
    "Salmonberry",
    "Rambutan",
    "Redcurrant",
    "Salal berry",
    "Salak",
    "Satsuma",
    "Soursop",
    "Star fruit",
    "Solanum quitoense",
    "Strawberry",
    "Tamarillo",
    "Tamarind",
    "Ugli fruit",
    "Yuzu"
};
const size_t fruits_count = sizeof(fruits) / sizeof(fruits[0]);

// Source: https://gist.github.com/lasagnaphil/7667eaeddb6ed0c565f0cb653d756942

#endif // FRUITS_H_


================================================
FILE: jim1.h
================================================
// Jim 1.0
//
// This is a legacy version of jim which uses
// - Fixed array for scopes,
// - Jim_Sink/Jim_Write interface to abstract away the IO,
// - Jim_Error mechanism for indicating IO errors and/or invalid usage of the API.
//
// 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.
#ifndef JIM_H_
#define JIM_H_

#ifndef JIM_SCOPES_CAPACITY
#define JIM_SCOPES_CAPACITY 128
#endif // JIM_SCOPES_CAPACITY

#include <stddef.h>

typedef void* Jim_Sink;
typedef size_t (*Jim_Write)(const void *ptr, size_t size, size_t nmemb, Jim_Sink sink);

typedef enum {
    JIM_OK = 0,
    JIM_WRITE_ERROR,
    JIM_SCOPES_OVERFLOW,
    JIM_SCOPES_UNDERFLOW,
    JIM_OUT_OF_SCOPE_KEY,
    JIM_DOUBLE_KEY
} Jim_Error;

const char *jim_error_string(Jim_Error error);

typedef enum {
    JIM_ARRAY_SCOPE,
    JIM_OBJECT_SCOPE,
} Jim_Scope_Kind;

typedef struct {
    Jim_Scope_Kind kind;
    int tail;
    int key;
} Jim_Scope;

typedef struct {
    Jim_Sink sink;
    Jim_Write write;
    Jim_Error error;
    Jim_Scope scopes[JIM_SCOPES_CAPACITY];
    size_t scopes_size;
} Jim;

void jim_null(Jim *jim);
void jim_bool(Jim *jim, int boolean);
void jim_integer(Jim *jim, long long int x);
void jim_float(Jim *jim, double x, int precision);
void jim_string(Jim *jim, const char *str);
void jim_string_sized(Jim *jim, const char *str, size_t size);

void jim_element_begin(Jim *jim);
void jim_element_end(Jim *jim);

void jim_array_begin(Jim *jim);
void jim_array_end(Jim *jim);

void jim_object_begin(Jim *jim);
void jim_member_key(Jim *jim, const char *str);
void jim_member_key_sized(Jim *jim, const char *str, size_t size);
void jim_object_end(Jim *jim);

#endif // JIM_H_

#ifdef JIM_IMPLEMENTATION

static size_t jim_strlen(const char *s)
{
    size_t count = 0;
    while (*(s + count)) {
        count += 1;
    }
    return count;
}

static void jim_scope_push(Jim *jim, Jim_Scope_Kind kind)
{
    if (jim->error == JIM_OK) {
        if (jim->scopes_size < JIM_SCOPES_CAPACITY) {
            jim->scopes[jim->scopes_size].kind = kind;
            jim->scopes[jim->scopes_size].tail = 0;
            jim->scopes[jim->scopes_size].key = 0;
            jim->scopes_size += 1;
        } else {
            jim->error = JIM_SCOPES_OVERFLOW;
        }
    }
}

static void jim_scope_pop(Jim *jim)
{
    if (jim->error == JIM_OK) {
        if (jim->scopes_size > 0) {
            jim->scopes_size--;
        } else {
            jim->error = JIM_SCOPES_UNDERFLOW;
        }
    }
}

static Jim_Scope *jim_current_scope(Jim *jim)
{
    if (jim->error == JIM_OK) {
        if (jim->scopes_size > 0) {
            return &jim->scopes[jim->scopes_size - 1];
        }
    }

    return NULL;
}

static void jim_write(Jim *jim, const char *buffer, size_t size)
{
    if (jim->error == JIM_OK) {
        if (jim->write(buffer, 1, size, jim->sink) < size) {
            jim->error = 1;
        }
    }
}

static void jim_write_cstr(Jim *jim, const char *cstr)
{
    if (jim->error == JIM_OK) {
        jim_write(jim, cstr, jim_strlen(cstr));
    }
}

static int jim_get_utf8_char_len(unsigned char ch)
{
    if ((ch & 0x80) == 0) return 1;
    switch (ch & 0xf0) {
    case 0xf0:
        return 4;
    case 0xe0:
        return 3;
    default:
        return 2;
    }
}

void jim_element_begin(Jim *jim)
{
    if (jim->error == JIM_OK) {
        Jim_Scope *scope = jim_current_scope(jim);
        if (scope && scope->tail && !scope->key) {
            jim_write_cstr(jim, ",");
        }
    }
}

void jim_element_end(Jim *jim)
{
    if (jim->error == JIM_OK) {
        Jim_Scope *scope = jim_current_scope(jim);
        if (scope) {
            scope->tail = 1;
            scope->key = 0;
        }
    }
}

const char *jim_error_string(Jim_Error error)
{
    // TODO(#1): error strings are not particularly useful
    switch (error) {
    case JIM_OK:
        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.";
    case JIM_WRITE_ERROR:
        return "Write error";
    case JIM_SCOPES_OVERFLOW:
        return "Stack of Scopes Overflow";
    case JIM_SCOPES_UNDERFLOW:
        return "Stack of Scopes Underflow";
    case JIM_OUT_OF_SCOPE_KEY:
        return "Out of Scope key";
    case JIM_DOUBLE_KEY:
        return "Tried to set the member key twice";
    default:
        return NULL;
    }
}

void jim_null(Jim *jim)
{
    if (jim->error == JIM_OK) {
        jim_element_begin(jim);
        jim_write_cstr(jim, "null");
        jim_element_end(jim);
    }
}

void jim_bool(Jim *jim, int boolean)
{
    if (jim->error == JIM_OK) {
        jim_element_begin(jim);
        if (boolean) {
            jim_write_cstr(jim, "true");
        } else {
            jim_write_cstr(jim, "false");
        }
        jim_element_end(jim);
    }
}

static void jim_integer_no_element(Jim *jim, long long int x)
{
    if (jim->error == JIM_OK) {
        if (x < 0) {
            jim_write_cstr(jim, "-");
            x = -x;
        }

        if (x == 0) {
            jim_write_cstr(jim, "0");
        } else {
            char buffer[64];
            size_t count = 0;

            while (x > 0) {
                buffer[count++] = (x % 10) + '0';
                x /= 10;
            }

            for (size_t i = 0; i < count / 2; ++i) {
                char t = buffer[i];
                buffer[i] = buffer[count - i - 1];
                buffer[count - i - 1] = t;
            }

            jim_write(jim, buffer, count);
        }

    }
}

void jim_integer(Jim *jim, long long int x)
{
    if (jim->error == JIM_OK) {
        jim_element_begin(jim);
        jim_integer_no_element(jim, x);
        jim_element_end(jim);
    }
}

static int is_nan_or_inf(double x)
{
    unsigned long long int mask = (1ULL << 11ULL) - 1ULL;
    return (((*(unsigned long long int*) &x) >> 52ULL) & mask) == mask;
}

void jim_float(Jim *jim, double x, int precision)
{
    if (jim->error == JIM_OK) {
        if (is_nan_or_inf(x)) {
            jim_null(jim);
        } else {
            jim_element_begin(jim);

            jim_integer_no_element(jim, (long long int) x);
            x -= (double) (long long int) x;
            while (precision-- > 0) {
                x *= 10.0;
            }
            jim_write_cstr(jim, ".");

            long long int y = (long long int) x;
            if (y < 0) {
                y = -y;
            }
            jim_integer_no_element(jim, y);

            jim_element_end(jim);
        }
    }
}

static void jim_string_sized_no_element(Jim *jim, const char *str, size_t size)
{
    if (jim->error == JIM_OK) {
        const char *hex_digits = "0123456789abcdef";
        const char *specials = "btnvfr";
        const char *p = str;
        size_t len = size;

        jim_write_cstr(jim, "\"");
        size_t cl;
        for (size_t i = 0; i < len; i++) {
            unsigned char ch = ((unsigned char *) p)[i];
            if (ch == '"' || ch == '\\') {
                jim_write(jim, "\\", 1);
                jim_write(jim, p + i, 1);
            } else if (ch >= '\b' && ch <= '\r') {
                jim_write(jim, "\\", 1);
                jim_write(jim, &specials[ch - '\b'], 1);
            } else if (0x20 <= ch && ch <= 0x7F) { // is printable
                jim_write(jim, p + i, 1);
            } else if ((cl = jim_get_utf8_char_len(ch)) == 1) {
                jim_write(jim, "\\u00", 4);
                jim_write(jim, &hex_digits[(ch >> 4) % 0xf], 1);
                jim_write(jim, &hex_digits[ch % 0xf], 1);
            } else {
                jim_write(jim, p + i, cl);
                i += cl - 1;
            }
        }

        jim_write_cstr(jim, "\"");
    }
}

void jim_string_sized(Jim *jim, const char *str, size_t size)
{
    if (jim->error == JIM_OK) {
        jim_element_begin(jim);
        jim_string_sized_no_element(jim, str, size);
        jim_element_end(jim);
    }
}

void jim_string(Jim *jim, const char *str)
{
    if (jim->error == JIM_OK) {
        jim_string_sized(jim, str, jim_strlen(str));
    }
}

void jim_array_begin(Jim *jim)
{
    if (jim->error == JIM_OK) {
        jim_element_begin(jim);
        jim_write_cstr(jim, "[");
        jim_scope_push(jim, JIM_ARRAY_SCOPE);
    }
}


void jim_array_end(Jim *jim)
{
    if (jim->error == JIM_OK) {
        jim_write_cstr(jim, "]");
        jim_scope_pop(jim);
        jim_element_end(jim);
    }
}

void jim_object_begin(Jim *jim)
{
    if (jim->error == JIM_OK) {
        jim_element_begin(jim);
        jim_write_cstr(jim, "{");
        jim_scope_push(jim, JIM_OBJECT_SCOPE);
    }
}

void jim_member_key(Jim *jim, const char *str)
{
    if (jim->error == JIM_OK) {
        jim_member_key_sized(jim, str, jim_strlen(str));
    }
}

void jim_member_key_sized(Jim *jim, const char *str, size_t size)
{
    if (jim->error == JIM_OK) {
        jim_element_begin(jim);
        Jim_Scope *scope = jim_current_scope(jim);
        if (scope && scope->kind == JIM_OBJECT_SCOPE) {
            if (!scope->key) {
                jim_string_sized_no_element(jim, str, size);
                jim_write_cstr(jim, ":");
                scope->key = 1;
            } else {
                jim->error = JIM_DOUBLE_KEY;
            }
        } else {
            jim->error = JIM_OUT_OF_SCOPE_KEY;
        }
    }
}

void jim_object_end(Jim *jim)
{
    if (jim->error == JIM_OK) {
        jim_write_cstr(jim, "}");
        jim_scope_pop(jim);
        jim_element_end(jim);
    }
}

#endif // JIM_IMPLEMENTATION


================================================
FILE: jim2.h
================================================
// Jim 2.0
//
// Current version of Jim. Main differences from Jim 1.0 are
// - Using Dynamic Arrays for scopes allowing them to be arbitrarily nested,
// - Collecting the output into a sink which is a String Builder now, delegating all the IO hustle to the user of the library,
// - 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.

#ifndef JIM_H_
#define JIM_H_

#ifndef JIM_SCOPES_CAPACITY
#define JIM_SCOPES_CAPACITY 128
#endif // JIM_SCOPES_CAPACITY

#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

typedef enum {
    JIM_ARRAY_SCOPE,
    JIM_OBJECT_SCOPE,
} Jim_Scope_Kind;

typedef struct {
    Jim_Scope_Kind kind;
    int tail;                   // Not the first element in an array or an object
    int key;                    // An object key was just placed
} Jim_Scope;

typedef struct {
    char *sink;
    size_t sink_count;
    size_t sink_capacity;
    Jim_Scope *scopes;
    size_t scopes_count;
    size_t scopes_capacity;
    size_t pp;
} Jim;

void jim_begin(Jim *jim);
void jim_null(Jim *jim);
void jim_bool(Jim *jim, int boolean);
void jim_integer(Jim *jim, long long int x);
// 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
void jim_float(Jim *jim, double x, int precision);
void jim_string(Jim *jim, const char *str);
void jim_string_sized(Jim *jim, const char *str, size_t size);

void jim_element_begin(Jim *jim);
void jim_element_end(Jim *jim);

void jim_array_begin(Jim *jim);
void jim_array_end(Jim *jim);

void jim_object_begin(Jim *jim);
void jim_member_key(Jim *jim, const char *str);
void jim_member_key_sized(Jim *jim, const char *str, size_t size);
void jim_object_end(Jim *jim);

#endif // JIM_H_

#ifdef JIM_IMPLEMENTATION

static void jim_scope_push(Jim *jim, Jim_Scope_Kind kind)
{
    if (jim->scopes_count >= jim->scopes_capacity) {
        if (jim->scopes_capacity == 0) jim->scopes_capacity = JIM_SCOPES_CAPACITY;
        else jim->scopes_capacity *= 2;
        jim->scopes = realloc(jim->scopes, sizeof(*jim->scopes)*jim->scopes_capacity);
        assert(jim->scopes);
    }
    jim->scopes[jim->scopes_count].kind = kind;
    jim->scopes[jim->scopes_count].tail = 0;
    jim->scopes[jim->scopes_count].key = 0;
    jim->scopes_count += 1;
}

static void jim_scope_pop(Jim *jim)
{
    assert(jim->scopes_count > 0);
    jim->scopes_count--;
}

static Jim_Scope *jim_current_scope(Jim *jim)
{
    if (jim->scopes_count > 0) {
        return &jim->scopes[jim->scopes_count - 1];
    }
    return NULL;
}

static void jim_write(Jim *jim, const char *buffer, size_t size)
{
    while (jim->sink_count + size >= jim->sink_capacity) {
        // TODO: rename JIM_SCOPES_CAPACITY to something else since it's used by both sink and scopes
        if (jim->sink_capacity == 0) jim->sink_capacity = JIM_SCOPES_CAPACITY;
        else jim->sink_capacity *= 2;
        jim->sink = realloc(jim->sink, sizeof(*jim->sink)*jim->sink_capacity);
    }
    memcpy(jim->sink + jim->sink_count, buffer, size);
    jim->sink_count += size;
}

static void jim_write_cstr(Jim *jim, const char *cstr)
{
    jim_write(jim, cstr, strlen(cstr));
}

static int jim_get_utf8_char_len(unsigned char ch)
{
    if ((ch & 0x80) == 0) return 1;
    switch (ch & 0xf0) {
    case 0xf0:
        return 4;
    case 0xe0:
        return 3;
    default:
        return 2;
    }
}

void jim_begin(Jim *jim)
{
    jim->sink_count = 0;
    jim->scopes_count = 0;
}

void jim_element_begin(Jim *jim)
{
    Jim_Scope *scope = jim_current_scope(jim);
    if (scope) {
        if (scope->tail && !scope->key) {
            jim_write_cstr(jim, ",");
        }
        if (jim->pp) {
            if (scope->key) {
                jim_write_cstr(jim, " ");
            } else {
                jim_write_cstr(jim, "\n");
                for (size_t i = 0; i < jim->scopes_count*jim->pp; ++i) {
                    jim_write_cstr(jim, " ");
                }
            }
        }
    }
}

void jim_element_end(Jim *jim)
{
    Jim_Scope *scope = jim_current_scope(jim);
    if (scope) {
        scope->tail = 1;
        scope->key = 0;
    }
}

void jim_null(Jim *jim)
{
    jim_element_begin(jim);
    jim_write_cstr(jim, "null");
    jim_element_end(jim);
}

void jim_bool(Jim *jim, int boolean)
{
    jim_element_begin(jim);
    if (boolean) {
        jim_write_cstr(jim, "true");
    } else {
        jim_write_cstr(jim, "false");
    }
    jim_element_end(jim);
}

static void jim_integer_no_element(Jim *jim, long long int x)
{
    if (x < 0) {
        jim_write_cstr(jim, "-");
        x = -x;
    }

    if (x == 0) {
        jim_write_cstr(jim, "0");
    } else {
        char buffer[64];
        size_t count = 0;

        while (x > 0) {
            buffer[count++] = (x % 10) + '0';
            x /= 10;
        }

        for (size_t i = 0; i < count / 2; ++i) {
            char t = buffer[i];
            buffer[i] = buffer[count - i - 1];
            buffer[count - i - 1] = t;
        }

        jim_write(jim, buffer, count);
    }
}

void jim_integer(Jim *jim, long long int x)
{
    jim_element_begin(jim);
    jim_integer_no_element(jim, x);
    jim_element_end(jim);
}

static int is_nan_or_inf(double x)
{
    unsigned long long int mask = (1ULL << 11ULL) - 1ULL;
    return (((*(unsigned long long int*) &x) >> 52ULL) & mask) == mask;
}

void jim_float(Jim *jim, double x, int precision)
{
    if (is_nan_or_inf(x)) {
        jim_null(jim);
    } else {
        jim_element_begin(jim);

        jim_integer_no_element(jim, (long long int) x);
        x -= (double) (long long int) x;
        while (precision-- > 0) {
            x *= 10.0;
        }
        jim_write_cstr(jim, ".");

        long long int y = (long long int) x;
        if (y < 0) {
            y = -y;
        }
        jim_integer_no_element(jim, y);

        jim_element_end(jim);
    }
}

static void jim_string_sized_no_element(Jim *jim, const char *str, size_t size)
{
    const char *hex_digits = "0123456789abcdef";
    const char *specials = "btnvfr";
    const char *p = str;
    size_t len = size;

    jim_write_cstr(jim, "\"");
    size_t cl;
    for (size_t i = 0; i < len; i++) {
        unsigned char ch = ((unsigned char *) p)[i];
        if (ch == '"' || ch == '\\') {
            jim_write(jim, "\\", 1);
            jim_write(jim, p + i, 1);
        } else if (ch >= '\b' && ch <= '\r') {
            jim_write(jim, "\\", 1);
            jim_write(jim, &specials[ch - '\b'], 1);
        } else if (0x20 <= ch && ch <= 0x7F) { // is printable
        jim_write(jim, p + i, 1);
    } else if ((cl = jim_get_utf8_char_len(ch)) == 1) {
        jim_write(jim, "\\u00", 4);
        jim_write(jim, &hex_digits[(ch >> 4) % 0xf], 1);
        jim_write(jim, &hex_digits[ch % 0xf], 1);
    } else {
        jim_write(jim, p + i, cl);
        i += cl - 1;
    }
}

jim_write_cstr(jim, "\"");
}

void jim_string_sized(Jim *jim, const char *str, size_t size)
{
    jim_element_begin(jim);
    jim_string_sized_no_element(jim, str, size);
    jim_element_end(jim);
}

void jim_string(Jim *jim, const char *str)
{
    jim_string_sized(jim, str, strlen(str));
}

void jim_array_begin(Jim *jim)
{
    jim_element_begin(jim);
    jim_write_cstr(jim, "[");
    jim_scope_push(jim, JIM_ARRAY_SCOPE);
}


void jim_array_end(Jim *jim)
{
    Jim_Scope *scope = jim_current_scope(jim);
    if (jim->pp && scope && scope->tail) {
        jim_write_cstr(jim, "\n");
        for (size_t i = 0; i < (jim->scopes_count - 1)*jim->pp; ++i) {
            jim_write_cstr(jim, " ");
        }
    }
    jim_write_cstr(jim, "]");
    jim_scope_pop(jim);
    jim_element_end(jim);
}

void jim_object_begin(Jim *jim)
{
    jim_element_begin(jim);
    jim_write_cstr(jim, "{");
    jim_scope_push(jim, JIM_OBJECT_SCOPE);
}

void jim_member_key(Jim *jim, const char *str)
{
    jim_member_key_sized(jim, str, strlen(str));
}

void jim_member_key_sized(Jim *jim, const char *str, size_t size)
{
    jim_element_begin(jim);
    Jim_Scope *scope = jim_current_scope(jim);
    assert(scope);
    assert(scope->kind == JIM_OBJECT_SCOPE);
    assert(!scope->key);
    jim_string_sized_no_element(jim, str, size);
    jim_write_cstr(jim, ":");
    scope->key = 1;
}

void jim_object_end(Jim *jim)
{
    Jim_Scope *scope = jim_current_scope(jim);
    if (jim->pp && scope && scope->tail) {
        jim_write_cstr(jim, "\n");
        for (size_t i = 0; i < (jim->scopes_count - 1)*jim->pp; ++i) {
            jim_write_cstr(jim, " ");
        }
    }
    jim_write_cstr(jim, "}");
    jim_scope_pop(jim);
    jim_element_end(jim);
}

#endif // JIM_IMPLEMENTATION


================================================
FILE: jimp.h
================================================
// Prototype of an Immediate Deserialization idea. Expect this API to change a lot.
#ifndef JIMP_H_
#define JIMP_H_

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>

// TODO: move all diagnostics reporting outside of the library
//   So the user has more options on how to report things

typedef enum {
    JIMP_INVALID,
    JIMP_EOF,

    // Puncts
    JIMP_OCURLY,
    JIMP_CCURLY,
    JIMP_OBRACKET,
    JIMP_CBRACKET,
    JIMP_COMMA,
    JIMP_COLON,

    // Symbols
    JIMP_TRUE,
    JIMP_FALSE,
    JIMP_NULL,

    // Values
    JIMP_STRING,
    JIMP_NUMBER,
} Jimp_Token;

typedef struct {
    const char *file_path;
    const char *start;
    const char *end;
    const char *point;

    Jimp_Token token;
    const char *token_start;    // TODO: `token_start` is primarily used for diagnostics location. Rename it accordingly.

    char *string;
    size_t string_count;
    size_t string_capacity;
    double number;
    bool boolean;
} Jimp;

// TODO: how do null-s fit into this entire system?

void jimp_begin(Jimp *jimp, const char *file_path, const char *input, size_t input_size);

/// If succeeds puts the freshly parsed boolean into jimp->boolean.
/// Any consequent calls to the jimp_* functions may invalidate jimp->boolean.
bool jimp_bool(Jimp *jimp);

/// If succeeds puts the freshly parsed number into jimp->number.
/// Any consequent calls to the jimp_* functions may invalidate jimp->number.
bool jimp_number(Jimp *jimp);

/// If succeeds puts the freshly parsed string into jimp->string as a NULL-terminated string.
/// Any consequent calls to the jimp_* functions may invalidate jimp->string.
/// strdup it if you don't wanna lose it (memory management is on you at that point).
bool jimp_string(Jimp *jimp);

/// Parses the beginning of the object `{`
bool jimp_object_begin(Jimp *jimp);

/// If succeeds puts the key of the member into jimp->string as a NULL-terminated string.
/// Any consequent calls to the jimp_* functions may invalidate jimp->string.
/// strdup it if you don't wanna lose it (memory management is on you at that point).
bool jimp_object_member(Jimp *jimp);

/// Parses the end of the object `}`
bool jimp_object_end(Jimp *jimp);

/// Reports jimp->string as an unknown member. jimp->string is expected to be populated by
/// jimp_object_member.
void jimp_unknown_member(Jimp *jimp);

/// Parses the beginning of the array `[`
bool jimp_array_begin(Jimp *jimp);

/// Checks whether there is any more items in the array.
bool jimp_array_item(Jimp *jimp);

/// Parses the end of the array `]`
bool jimp_array_end(Jimp *jimp);

/// Prints diagnostic at the current position of the parser.
void jimp_diagf(Jimp *jimp, const char *fmt, ...);

bool jimp_is_null_ahead(Jimp *jimp);
bool jimp_is_bool_ahead(Jimp *jimp);
bool jimp_is_number_ahead(Jimp *jimp);
bool jimp_is_string_ahead(Jimp *jimp);
bool jimp_is_array_ahead(Jimp *jimp);
bool jimp_is_object_ahead(Jimp *jimp);

#endif // JIMP_H_

#ifdef JIMP_IMPLEMENTATION

static bool jimp__expect_token(Jimp *jimp, Jimp_Token token);
static bool jimp__get_and_expect_token(Jimp *jimp, Jimp_Token token);
static const char *jimp__token_kind(Jimp_Token token);
static bool jimp__get_token(Jimp *jimp);
static void jimp__skip_whitespaces(Jimp *jimp);
static void jimp__append_to_string(Jimp *jimp, char x);

static void jimp__append_to_string(Jimp *jimp, char x)
{
    if (jimp->string_count >= jimp->string_capacity) {
        if (jimp->string_capacity == 0) jimp->string_capacity = 1024;
        else jimp->string_capacity *= 2;
        jimp->string = realloc(jimp->string, jimp->string_capacity);
    }
    jimp->string[jimp->string_count++] = x;
}

static void jimp__skip_whitespaces(Jimp *jimp)
{
    while (jimp->point < jimp->end && isspace(*jimp->point)) {
        jimp->point += 1;
    }
}

static Jimp_Token jimp__puncts[256] = {
    ['{'] = JIMP_OCURLY,
    ['}'] = JIMP_CCURLY,
    ['['] = JIMP_OBRACKET,
    [']'] = JIMP_CBRACKET,
    [','] = JIMP_COMMA,
    [':'] = JIMP_COLON,
};

static struct {
    Jimp_Token token;
    const char *symbol;
} jimp__symbols[] = {
    { .token = JIMP_TRUE,  .symbol = "true"  },
    { .token = JIMP_FALSE, .symbol = "false" },
    { .token = JIMP_NULL,  .symbol = "null"  },
};
#define jimp__symbols_count (sizeof(jimp__symbols)/sizeof(jimp__symbols[0]))

static bool jimp__get_token(Jimp *jimp)
{
    jimp__skip_whitespaces(jimp);

    jimp->token_start = jimp->point;

    if (jimp->point >= jimp->end) {
        jimp->token = JIMP_EOF;
        return false;
    }

    jimp->token = jimp__puncts[(unsigned char)*jimp->point];
    if (jimp->token) {
        jimp->point += 1;
        return true;
    }

    for (size_t i = 0; i < jimp__symbols_count; ++i) {
        const char *symbol = jimp__symbols[i].symbol;
        if (*symbol == *jimp->point) {
            while (*symbol && jimp->point < jimp->end && *symbol++ == *jimp->point++) {}
            if (*symbol) {
                jimp->token = JIMP_INVALID;
                jimp_diagf(jimp, "ERROR: invalid symbol\n");
                return false;
            } else {
                jimp->token = jimp__symbols[i].token;
                return true;
            }
        }
    }

    char *endptr = NULL;
    jimp->number = strtod(jimp->point, &endptr); // TODO: This implies that jimp->end is a valid address and *jimp->end == 0
    if (jimp->point != endptr) {
        jimp->point = endptr;
        jimp->token = JIMP_NUMBER;
        return true;
    }

    if (*jimp->point == '"') {
        jimp->point++;
        jimp->string_count = 0;
        while (jimp->point < jimp->end) {
            // TODO: support all the JSON escape sequences defined in the spec
            // Yes, including those dumb suroggate pairs. Spec is spec.
            switch (*jimp->point) {
            case '\\': {
                jimp->point++;
                if (jimp->point >= jimp->end) {
                    jimp->token_start = jimp->point;
                    jimp_diagf(jimp, "ERROR: unfinished escape sequence\n");
                    return false;
                }
                switch (*jimp->point) {
                case 'r':
                    jimp->point++;
                    jimp__append_to_string(jimp, '\r');
                    break;
                case 'n':
                    jimp->point++;
                    jimp__append_to_string(jimp, '\n');
                    break;
                case 't':
                    jimp->point++;
                    jimp__append_to_string(jimp, '\t');
                    break;
                case '\\':
                    jimp->point++;
                    jimp__append_to_string(jimp, '\\');
                    break;
                case '"':
                    jimp->point++;
                    jimp__append_to_string(jimp, '"');
                    break;
                default:
                    jimp->token_start = jimp->point;
                    jimp_diagf(jimp, "ERROR: invalid escape sequence\n");
                    return false;
                }
                break;
            }
            case '"': {
                jimp->point++;
                jimp__append_to_string(jimp, '\0');
                jimp->token = JIMP_STRING;
                return true;
            }
            default: {
                char x = *jimp->point++;
                jimp__append_to_string(jimp, x);
            }
            }
        }
        jimp->token = JIMP_INVALID;
        jimp_diagf(jimp, "ERROR: unfinished string\n");
        return false;
    }

    jimp->token = JIMP_INVALID;
    jimp_diagf(jimp, "ERROR: invalid token\n");
    return false;
}

void jimp_begin(Jimp *jimp, const char *file_path, const char *input, size_t input_size)
{
    jimp->file_path = file_path;
    jimp->start     = input;
    jimp->end       = input + input_size;
    jimp->point     = input;
}

void jimp_diagf(Jimp *jimp, const char *fmt, ...)
{
    long line_number = 0;
    const char *line_start = jimp->start;
    const char *point = jimp->start;
    while (point < jimp->token_start) {
        char x = *point++;
        if (x == '\n') {
            line_start = point;
            line_number += 1;
        }
    }

    fprintf(stderr, "%s:%ld:%ld: ", jimp->file_path, line_number + 1, point - line_start + 1);
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
}

static const char *jimp__token_kind(Jimp_Token token)
{
   switch (token) {
   case JIMP_EOF:      return "end of input";
   case JIMP_INVALID:  return "invalid";
   case JIMP_OCURLY:   return "{";
   case JIMP_CCURLY:   return "}";
   case JIMP_OBRACKET: return "[";
   case JIMP_CBRACKET: return "]";
   case JIMP_COMMA:    return ",";
   case JIMP_COLON:    return ":";
   case JIMP_TRUE:     return "true";
   case JIMP_FALSE:    return "false";
   case JIMP_NULL:     return "null";
   case JIMP_STRING:   return "string";
   case JIMP_NUMBER:   return "number";
   }
   assert(0 && "unreachable");
   return NULL;
}

bool jimp_array_begin(Jimp *jimp)
{
    return jimp__get_and_expect_token(jimp, JIMP_OBRACKET);
}

bool jimp_array_end(Jimp *jimp)
{
    return jimp__get_and_expect_token(jimp, JIMP_CBRACKET);
}

bool jimp_array_item(Jimp *jimp)
{
    const char *point = jimp->point;
    if (!jimp__get_token(jimp)) return false;
    if (jimp->token == JIMP_COMMA) return true;
    if (jimp->token == JIMP_CBRACKET) {
        jimp->point = point;
        return false;
    }
    jimp->point = point;
    return true;
}

void jimp_unknown_member(Jimp *jimp)
{
    jimp_diagf(jimp, "ERROR: unexpected object member `%s`\n", jimp->string);
}

bool jimp_object_begin(Jimp *jimp)
{
    return jimp__get_and_expect_token(jimp, JIMP_OCURLY);
}

bool jimp_object_member(Jimp *jimp)
{
    const char *point = jimp->point;
    if (!jimp__get_token(jimp)) return false;
    if (jimp->token == JIMP_COMMA) {
        if (!jimp__get_and_expect_token(jimp, JIMP_STRING)) return false;
        if (!jimp__get_and_expect_token(jimp, JIMP_COLON)) return false;
        return true;
    }
    if (jimp->token == JIMP_CCURLY) {
        jimp->point = point;
        return false;
    }
    if (!jimp__expect_token(jimp, JIMP_STRING)) return false;
    if (!jimp__get_and_expect_token(jimp, JIMP_COLON)) return false;
    return true;
}

bool jimp_object_end(Jimp *jimp)
{
    return jimp__get_and_expect_token(jimp, JIMP_CCURLY);
}

bool jimp_string(Jimp *jimp)
{
    return jimp__get_and_expect_token(jimp, JIMP_STRING);
}

bool jimp_bool(Jimp *jimp)
{
    jimp__get_token(jimp);
    if (jimp->token == JIMP_TRUE) {
        jimp->boolean = true;
    } else if (jimp->token == JIMP_FALSE) {
        jimp->boolean = false;
    } else {
        jimp_diagf(jimp, "ERROR: expected boolean, but got `%s`\n", jimp__token_kind(jimp->token));
        return false;
    }
    return true;
}

bool jimp_number(Jimp *jimp)
{
    return jimp__get_and_expect_token(jimp, JIMP_NUMBER);
}

bool jimp_is_null_ahead(Jimp *jimp)
{
    const char *point = jimp->point;
    if (!jimp__get_token(jimp)) return false;
    jimp->point = point;
    return jimp->token == JIMP_NULL;
}

bool jimp_is_bool_ahead(Jimp *jimp)
{
    const char *point = jimp->point;
    if (!jimp__get_token(jimp)) return false;
    jimp->point = point;
    return jimp->token == JIMP_TRUE || jimp->token == JIMP_FALSE;
}

bool jimp_is_number_ahead(Jimp *jimp)
{
    const char *point = jimp->point;
    if (!jimp__get_token(jimp)) return false;
    jimp->point = point;
    return jimp->token == JIMP_NUMBER;
}

bool jimp_is_string_ahead(Jimp *jimp)
{
    const char *point = jimp->point;
    if (!jimp__get_token(jimp)) return false;
    jimp->point = point;
    return jimp->token == JIMP_STRING;
}

bool jimp_is_array_ahead(Jimp *jimp)
{
    const char *point = jimp->point;
    if (!jimp__get_token(jimp)) return false;
    jimp->point = point;
    return jimp->token == JIMP_OBRACKET;
}

bool jimp_is_object_ahead(Jimp *jimp)
{
    const char *point = jimp->point;
    if (!jimp__get_token(jimp)) return false;
    jimp->point = point;
    return jimp->token == JIMP_OCURLY;
}

static bool jimp__get_and_expect_token(Jimp *jimp, Jimp_Token token)
{
    if (!jimp__get_token(jimp)) return false;
    return jimp__expect_token(jimp, token);
}

static bool jimp__expect_token(Jimp *jimp, Jimp_Token token)
{
    if (jimp->token != token) {
        jimp_diagf(jimp, "ERROR: expected %s, but got %s\n", jimp__token_kind(token), jimp__token_kind(jimp->token));
        return false;
    }
    return true;
}

#endif // JIMP_IMPLEMENTATION


================================================
FILE: test_jim.c
================================================
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define JIM_IMPLEMENTATION
#include "./jim.h"
#define NOB_IMPLEMENTATION
#define NOB_STRIP_PREFIX
#include "./nob.h"

#include "./test_jim_expected.h"

void null_case(Jim *jim)
{
    jim_begin(jim);
    jim_array_begin(jim);
    jim_null(jim);
    jim_array_end(jim);
}

void bool_case(Jim *jim)
{
    jim_begin(jim);
    jim_array_begin(jim);
    jim_bool(jim, 0);
    jim_bool(jim, 1);
    jim_array_end(jim);
}

void integer_case(Jim *jim)
{
    jim_begin(jim);
    jim_array_begin(jim);
    for (int i = -10; i <= 10; ++i) {
        jim_integer(jim, i);
    }
    jim_array_end(jim);
}

void float_case(Jim *jim)
{
    jim_begin(jim);
    jim_array_begin(jim);
    jim_float(jim, 0.0, 4);
    jim_float(jim, -0.0, 4);
    jim_float(jim, 3.1415, 4);
    jim_float(jim, 2.71828, 5);
    jim_float(jim, 1.6180, 1);
    jim_float(jim, 0.0 / 0.0, 4);
    jim_float(jim, 1.0 / 0.0, 4);
    jim_float(jim, -1.0 / 0.0, 4);
    jim_array_end(jim);
}

void string_case(Jim *jim)
{
    jim_begin(jim);
    jim_array_begin(jim);
    jim_string(jim, "hello");
    jim_string(jim, "world");
    jim_string(jim, "\n\b\t");
    jim_string_sized(jim, "\0\0\0\0", 4);
    jim_array_end(jim);
}

void array_case(Jim *jim)
{
    jim_begin(jim);
    jim_array_begin(jim);

    for (int n = 1; n <= 5; ++n) {
        for (int i = 0; i < n; ++i) jim_array_begin(jim);
        for (int i = 0; i < n; ++i) jim_array_end(jim);
    }

    jim_array_end(jim);
}

void object_case_rec(Jim *jim, int level, int *counter)
{
    if (level < 3) {
        jim_object_begin(jim);
        jim_member_key(jim, "l");
        object_case_rec(jim, level + 1, counter);
        jim_member_key(jim, "r");
        object_case_rec(jim, level + 1, counter);
        jim_object_end(jim);
    } else {
        jim_integer(jim, (*counter)++);
    }
}

void object_case(Jim *jim)
{
    jim_begin(jim);
    int counter = 0;
    object_case_rec(jim, 0, &counter);
}

void pp_empty_array_case(Jim *jim)
{
    jim_begin(jim);
    jim->pp = 4;
    jim_array_begin(jim);
    jim_array_end(jim);
}

void pp_singleton_array_case(Jim *jim)
{
    jim_begin(jim);
    jim->pp = 4;
    jim_array_begin(jim);
    jim_integer(jim, 69);
    jim_array_end(jim);
}

void pp_array_case(Jim *jim)
{
    jim_begin(jim);
    jim->pp = 4;
    jim_array_begin(jim);
    jim_integer(jim, 69);
    jim_integer(jim, 420);
    jim_integer(jim, 1337);
    jim_integer(jim, 80085);
    jim_array_end(jim);
}

void pp_empty_object_case(Jim *jim)
{
    jim_begin(jim);
    jim->pp = 4;
    jim_object_begin(jim);
    jim_object_end(jim);
}

void pp_singleton_object_case(Jim *jim)
{
    jim_begin(jim);
    jim->pp = 4;
    jim_object_begin(jim);
    jim_member_key(jim, "foo");
    jim_integer(jim, 69);
    jim_object_end(jim);
}

void pp_object_case(Jim *jim)
{
    jim_begin(jim);
    jim->pp = 4;
    jim_object_begin(jim);
    jim_member_key(jim, "foo");
    jim_integer(jim, 69);
    jim_member_key(jim, "bar");
    jim_integer(jim, 420);
    jim_member_key(jim, "baz");
    jim_integer(jim, 1337);
    jim_object_end(jim);
}

void pp_nested_case(Jim *jim)
{
    jim_begin(jim);
    jim->pp = 4;
    jim_object_begin(jim);
        jim_member_key(jim, "integer");
        jim_integer(jim, 69);
        jim_member_key(jim, "empty_array");
        jim_array_begin(jim);
        jim_array_end(jim);
        jim_member_key(jim, "empty_object");
        jim_object_begin(jim);
        jim_object_end(jim);
        jim_member_key(jim, "array_of_integers");
        jim_array_begin(jim);
            jim_integer(jim, 69);
            jim_integer(jim, 420);
            jim_integer(jim, 1337);
            jim_integer(jim, 80085);
        jim_array_end(jim);
        jim_member_key(jim, "object_of_integers");
        jim_object_begin(jim);
            jim_member_key(jim, "foo");
            jim_integer(jim, 69);
            jim_member_key(jim, "bar");
            jim_integer(jim, 420);
            jim_member_key(jim, "baz");
            jim_integer(jim, 1337);
            jim_member_key(jim, "karabaz");
            jim_integer(jim, 80085);
        jim_object_end(jim);
    jim_object_end(jim);
}

typedef struct {
    const char *name;
    void (*run)(Jim *jim);
} Test_Case;

#define TEST_CASE(case_name) \
    { \
        .name = #case_name, \
        .run = case_name \
    }

const Test_Case test_cases[] = {
    TEST_CASE(null_case),
    TEST_CASE(bool_case),
    TEST_CASE(integer_case),
    TEST_CASE(float_case),
    TEST_CASE(string_case),
    TEST_CASE(array_case),
    TEST_CASE(object_case),
    TEST_CASE(pp_empty_array_case),
    TEST_CASE(pp_singleton_array_case),
    TEST_CASE(pp_array_case),
    TEST_CASE(pp_empty_object_case),
    TEST_CASE(pp_singleton_object_case),
    TEST_CASE(pp_object_case),
    TEST_CASE(pp_nested_case),
};

bool record(const char *header_path)
{
    Jim jim_stream = {0};
    Jim jim_buffer = {0};

    jim_write_cstr(&jim_stream, "const char *test_cases_expected[] = {\n");
    for (size_t i = 0; i < ARRAY_LEN(test_cases); ++i) {
        jim_buffer.sink_count = 0;
        test_cases[i].run(&jim_buffer);
        jim_write_cstr(&jim_stream, "    ");
        jim_string_sized(&jim_stream, jim_buffer.sink, jim_buffer.sink_count);
        jim_write_cstr(&jim_stream, ",\n");
    }
    jim_write_cstr(&jim_stream, "};\n");

    if (!write_entire_file(header_path, jim_stream.sink, jim_stream.sink_count)) return false;
    printf("Updated %s\n", header_path);
    return true;
}

void test(void)
{
    Jim jim_buffer = {0};


    assert(ARRAY_LEN(test_cases) == ARRAY_LEN(test_cases_expected) && "Run `record` command to update expected test cases");
    for (size_t i = 0; i < ARRAY_LEN(test_cases); ++i) {
        printf("%s ... ", test_cases[i].name);

        jim_buffer.sink_count = 0;
        test_cases[i].run(&jim_buffer);

        if (jim_buffer.sink_count != strlen(test_cases_expected[i])
                || memcmp(jim_buffer.sink, test_cases_expected[i], jim_buffer.sink_count) != 0) {
            printf("FAILED!\n");
            printf("Expected: %s\n", test_cases_expected[i]);
            printf("Actual:   ");
            fwrite(jim_buffer.sink, jim_buffer.sink_count, 1, stdout);
            printf("\n");
            exit(1);
        }

        printf("OK\n");
    }
}

int main(int argc, char **argv)
{
    if (argc >= 2) {
        if (strcmp(argv[1], "record") == 0) {
            if (!record("test_jim_expected.h")) return 1;
        } else {
            fprintf(stderr, "Usage: ./test [record]\n");
            fprintf(stderr, "ERROR: unknown subcommand %s.\n", argv[1]);
        }
    } else  {
        test();
    }
}


================================================
FILE: test_jim_expected.h
================================================
const char *test_cases_expected[] = {
    "[null]",
    "[false,true]",
    "[-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,10]",
    "[0.0,0.0,3.1415,2.71828,1.6,null,null,null]",
    "[\"hello\",\"world\",\"\\n\\b\\t\",\"\\u0000\\u0000\\u0000\\u0000\"]",
    "[[],[[]],[[[]]],[[[[]]]],[[[[[]]]]]]",
    "{\"l\":{\"l\":{\"l\":0,\"r\":1},\"r\":{\"l\":2,\"r\":3}},\"r\":{\"l\":{\"l\":4,\"r\":5},\"r\":{\"l\":6,\"r\":7}}}",
    "[]",
    "[\n    69\n]",
    "[\n    69,\n    420,\n    1337,\n    80085\n]",
    "{}",
    "{\n    \"foo\": 69\n}",
    "{\n    \"foo\": 69,\n    \"bar\": 420,\n    \"baz\": 1337\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}",
};


================================================
FILE: thirdparty/nob.h
================================================
/* nob - v1.20.6 - Public Domain - https://github.com/tsoding/nob.h

   This library is the next generation of the [NoBuild](https://github.com/tsoding/nobuild) idea.

   # Quick Example

      ```c
      // nob.c
      #define NOB_IMPLEMENTATION
      #include "nob.h"

      int main(int argc, char **argv)
      {
          NOB_GO_REBUILD_URSELF(argc, argv);
          Nob_Cmd cmd = {0};
          nob_cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c");
          if (!nob_cmd_run_sync(cmd)) return 1;
          return 0;
      }
      ```

      ```console
      $ cc -o nob nob.c
      $ ./nob
      ```

      The `nob` automatically rebuilds itself if `nob.c` is modified thanks to
      the `NOB_GO_REBUILD_URSELF` macro (don't forget to check out how it works below)

   # The Zoo of `nob_cmd_run_*` Functions

      `Nob_Cmd` is just a dynamic array of strings which represents a command and its arguments.
      First you append the arguments

      ```c
      Nob_Cmd cmd = {0};
      nob_cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c");
      ```

      Then you run it

      ```c
      if (!nob_cmd_run_sync(cmd)) return 1;
      ```

      `*_sync` at the end indicates that the function blocks until the command finishes executing
      and returns `true` on success and `false` on failure. You can run the command asynchronously
      but you have to explitictly wait for it afterwards:

      ```c
      Nob_Proc p = nob_cmd_run_async(cmd);
      if (p == NOB_INVALID_PROC) return 1;
      if (!nob_proc_wait(p)) return 1;
      ```

      One of the problems with running commands like that is that `Nob_Cmd` still contains the arguments
      from the previously run command. If you want to reuse the same `Nob_Cmd` you have to not forget to reset
      it

      ```c
      Nob_Cmd cmd = {0};

      nob_cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c");
      if (!nob_cmd_run_sync(cmd)) return 1;
      cmd.count = 0;

      nob_cmd_append(&cmd, "./main", "foo", "bar", "baz");
      if (!nob_cmd_run_sync(cmd)) return 1;
      cmd.count = 0;
      ```

      Which is a bit error prone. To make it a bit easier we have `nob_cmd_run_sync_and_reset()` which
      accepts `Nob_Cmd` by reference and resets it for you:

      ```c
      Nob_Cmd cmd = {0};

      nob_cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c");
      if (!nob_cmd_run_sync_and_reset(&cmd)) return 1;

      nob_cmd_append(&cmd, "./main", "foo", "bar", "baz");
      if (!nob_cmd_run_sync_and_reset(&cmd)) return 1;
      ```

      There is of course also `nob_cmd_run_async_and_reset()` to maintain the pattern.

      The stdin, stdout and stderr of any command can be redirected by using `Nob_Cmd_Redirect` structure
      along with `nob_cmd_run_sync_redirect()` or `nob_cmd_run_async_redirect()`

      ```c
      // Opening all the necessary files
      Nob_Fd fdin = nob_fd_open_for_read("input.txt");
      if (fdin == NOB_INVALID_FD) return 1;
      Nob_Fd fdout = nob_fd_open_for_write("output.txt");
      if (fdout == NOB_INVALID_FD) return 1;
      Nob_Fd fderr = nob_fd_open_for_write("error.txt");
      if (fderr == NOB_INVALID_FD) return 1;

      // Preparing the command
      Nob_Cmd cmd = {0};
      nob_cmd_append(&cmd, "./main", "foo", "bar", "baz");

      // Running the command synchronously redirecting the standard streams
      bool ok = nob_cmd_run_sync_redirect(cmd, (Nob_Cmd_Redirect) {
          .fdin = fdin,
          .fdout = fdout,
          .fderr = fderr,
      });
      if (!ok) return 1;

      // Closing all the files
      nob_fd_close(fdin);
      nob_fd_close(fdout);
      nob_fd_close(fderr);

      // Reseting the command
      cmd.count = 0;
      ```

      And of course if you find closing the files and reseting the command annoying we have
      `nob_cmd_run_sync_redirect_and_reset()` and `nob_cmd_run_async_redirect_and_reset()`
      which do all of that for you automatically.

      All the Zoo of `nob_cmd_run_*` functions follows the same pattern: sync/async,
      redirect/no redirect, and_reset/no and_reset. They always come in that order.

   # Stripping off `nob_` Prefixes

      Since Pure C does not have any namespaces we prefix each name of the API with the `nob_` to avoid any
      potential conflicts with any other names in your code. But sometimes it is very annoying and makes
      the code noisy. If you know that none of the names from nob.h conflict with anything in your code
      you can enable NOB_STRIP_PREFIX macro and just drop all the prefixes:

      ```c
      // nob.c
      #define NOB_IMPLEMENTATION
      #define NOB_STRIP_PREFIX
      #include "nob.h"

      int main(int argc, char **argv)
      {
          NOB_GO_REBUILD_URSELF(argc, argv);
          Cmd cmd = {0};
          cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c");
          if (!cmd_run_sync(cmd)) return 1;
          return 0;
      }
      ```

      Not all the names have strippable prefixes. All the redefinable names like `NOB_GO_REBUILD_URSELF`
      for instance will retain their prefix even if NOB_STRIP_PREFIX is enabled. Notable exception is the
      nob_log() function. Stripping away the prefix results in log() which was historically always referring
      to the natural logarithmic function that is already defined in math.h. So there is no reason to strip
      off the prefix for nob_log(). Another exception is nob_rename() which collides with the widely known
      POSIX function rename(2) if you strip the prefix off.

      The prefixes are stripped off only on the level of preprocessor. The names of the functions in the
      compiled object file will still retain the `nob_` prefix. Keep that in mind when you FFI with nob.h
      from other languages (for whatever reason).

      If only few specific names create conflicts for you, you can just #undef those names after the
      `#include <nob.h>` since they are macros anyway.
*/

#ifndef NOB_H_
#define NOB_H_

#ifndef NOB_ASSERT
#include <assert.h>
#define NOB_ASSERT assert
#endif /* NOB_ASSERT */

#ifndef NOB_REALLOC
#include <stdlib.h>
#define NOB_REALLOC realloc
#endif /* NOB_REALLOC */

#ifndef NOB_FREE
#include <stdlib.h>
#define NOB_FREE free
#endif /* NOB_FREE */

#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <limits.h>

#ifdef _WIN32
#    define WIN32_LEAN_AND_MEAN
#    define _WINUSER_
#    define _WINGDI_
#    define _IMM_
#    define _WINCON_
#    include <windows.h>
#    include <direct.h>
#    include <shellapi.h>
#else
#    include <sys/types.h>
#    include <sys/wait.h>
#    include <sys/stat.h>
#    include <unistd.h>
#    include <fcntl.h>
#endif

#ifdef _WIN32
#    define NOB_LINE_END "\r\n"
#else
#    define NOB_LINE_END "\n"
#endif

#if defined(__GNUC__) || defined(__clang__)
//   https://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Function-Attributes.html
#    ifdef __MINGW_PRINTF_FORMAT
#        define NOB_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) __attribute__ ((format (__MINGW_PRINTF_FORMAT, STRING_INDEX, FIRST_TO_CHECK)))
#    else
#        define NOB_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) __attribute__ ((format (printf, STRING_INDEX, FIRST_TO_CHECK)))
#    endif // __MINGW_PRINTF_FORMAT
#else
//   TODO: implement NOB_PRINTF_FORMAT for MSVC
#    define NOB_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK)
#endif

#define NOB_UNUSED(value) (void)(value)
#define NOB_TODO(message) do { fprintf(stderr, "%s:%d: TODO: %s\n", __FILE__, __LINE__, message); abort(); } while(0)
#define NOB_UNREACHABLE(message) do { fprintf(stderr, "%s:%d: UNREACHABLE: %s\n", __FILE__, __LINE__, message); abort(); } while(0)

#define NOB_ARRAY_LEN(array) (sizeof(array)/sizeof(array[0]))
#define NOB_ARRAY_GET(array, index) \
    (NOB_ASSERT((size_t)index < NOB_ARRAY_LEN(array)), array[(size_t)index])

typedef enum {
    NOB_INFO,
    NOB_WARNING,
    NOB_ERROR,
    NOB_NO_LOGS,
} Nob_Log_Level;

// Any messages with the level below nob_minimal_log_level are going to be suppressed.
extern Nob_Log_Level nob_minimal_log_level;

void nob_log(Nob_Log_Level level, const char *fmt, ...) NOB_PRINTF_FORMAT(2, 3);

// It is an equivalent of shift command from bash. It basically pops an element from
// the beginning of a sized array.
#define nob_shift(xs, xs_sz) (NOB_ASSERT((xs_sz) > 0), (xs_sz)--, *(xs)++)
// NOTE: nob_shift_args() is an alias for an old variant of nob_shift that only worked with
// the command line arguments passed to the main() function. nob_shift() is more generic.
// So nob_shift_args() is semi-deprecated, but I don't see much reason to urgently
// remove it. This alias does not hurt anybody.
#define nob_shift_args(argc, argv) nob_shift(*argv, *argc)

typedef struct {
    const char **items;
    size_t count;
    size_t capacity;
} Nob_File_Paths;

typedef enum {
    NOB_FILE_REGULAR = 0,
    NOB_FILE_DIRECTORY,
    NOB_FILE_SYMLINK,
    NOB_FILE_OTHER,
} Nob_File_Type;

bool nob_mkdir_if_not_exists(const char *path);
bool nob_copy_file(const char *src_path, const char *dst_path);
bool nob_copy_directory_recursively(const char *src_path, const char *dst_path);
bool nob_read_entire_dir(const char *parent, Nob_File_Paths *children);
bool nob_write_entire_file(const char *path, const void *data, size_t size);
Nob_File_Type nob_get_file_type(const char *path);
bool nob_delete_file(const char *path);

#define nob_return_defer(value) do { result = (value); goto defer; } while(0)

// Initial capacity of a dynamic array
#ifndef NOB_DA_INIT_CAP
#define NOB_DA_INIT_CAP 256
#endif

#define nob_da_reserve(da, expected_capacity)                                              \
    do {                                                                                   \
        if ((expected_capacity) > (da)->capacity) {                                        \
            if ((da)->capacity == 0) {                                                     \
                (da)->capacity = NOB_DA_INIT_CAP;                                          \
            }                                                                              \
            while ((expected_capacity) > (da)->capacity) {                                 \
                (da)->capacity *= 2;                                                       \
            }                                                                              \
            (da)->items = NOB_REALLOC((da)->items, (da)->capacity * sizeof(*(da)->items)); \
            NOB_ASSERT((da)->items != NULL && "Buy more RAM lol");                         \
        }                                                                                  \
    } while (0)

// Append an item to a dynamic array
#define nob_da_append(da, item)                \
    do {                                       \
        nob_da_reserve((da), (da)->count + 1); \
        (da)->items[(da)->count++] = (item);   \
    } while (0)

#define nob_da_free(da) NOB_FREE((da).items)

// Append several items to a dynamic array
#define nob_da_append_many(da, new_items, new_items_count)                                      \
    do {                                                                                        \
        nob_da_reserve((da), (da)->count + (new_items_count));                                  \
        memcpy((da)->items + (da)->count, (new_items), (new_items_count)*sizeof(*(da)->items)); \
        (da)->count += (new_items_count);                                                       \
    } while (0)

#define nob_da_resize(da, new_size)     \
    do {                                \
        nob_da_reserve((da), new_size); \
        (da)->count = (new_size);       \
    } while (0)

#define nob_da_last(da) (da)->items[(NOB_ASSERT((da)->count > 0), (da)->count-1)]
#define nob_da_remove_unordered(da, i)               \
    do {                                             \
        size_t j = (i);                              \
        NOB_ASSERT(j < (da)->count);                 \
        (da)->items[j] = (da)->items[--(da)->count]; \
    } while(0)

// Foreach over Dynamic Arrays. Example:
// ```c
// typedef struct {
//     int *items;
//     size_t count;
//     size_t capacity;
// } Numbers;
//
// Numbers xs = {0};
//
// nob_da_append(&xs, 69);
// nob_da_append(&xs, 420);
// nob_da_append(&xs, 1337);
//
// nob_da_foreach(int, x, &xs) {
//     // `x` here is a pointer to the current element. You can get its index by taking a difference
//     // between `x` and the start of the array which is `x.items`.
//     size_t index = x - xs.items;
//     nob_log(INFO, "%zu: %d", index, *x);
// }
// ```
#define nob_da_foreach(Type, it, da) for (Type *it = (da)->items; it < (da)->items + (da)->count; ++it)

typedef struct {
    char *items;
    size_t count;
    size_t capacity;
} Nob_String_Builder;

bool nob_read_entire_file(const char *path, Nob_String_Builder *sb);
int nob_sb_appendf(Nob_String_Builder *sb, const char *fmt, ...) NOB_PRINTF_FORMAT(2, 3);

// Append a sized buffer to a string builder
#define nob_sb_append_buf(sb, buf, size) nob_da_append_many(sb, buf, size)

// Append a NULL-terminated string to a string builder
#define nob_sb_append_cstr(sb, cstr)  \
    do {                              \
        const char *s = (cstr);       \
        size_t n = strlen(s);         \
        nob_da_append_many(sb, s, n); \
    } while (0)

// Append a single NULL character at the end of a string builder. So then you can
// use it a NULL-terminated C string
#define nob_sb_append_null(sb) nob_da_append_many(sb, "", 1)

// Free the memory allocated by a string builder
#define nob_sb_free(sb) NOB_FREE((sb).items)

// Process handle
#ifdef _WIN32
typedef HANDLE Nob_Proc;
#define NOB_INVALID_PROC INVALID_HANDLE_VALUE
typedef HANDLE Nob_Fd;
#define NOB_INVALID_FD INVALID_HANDLE_VALUE
#else
typedef int Nob_Proc;
#define NOB_INVALID_PROC (-1)
typedef int Nob_Fd;
#define NOB_INVALID_FD (-1)
#endif // _WIN32

Nob_Fd nob_fd_open_for_read(const char *path);
Nob_Fd nob_fd_open_for_write(const char *path);
void nob_fd_close(Nob_Fd fd);

typedef struct {
    Nob_Proc *items;
    size_t count;
    size_t capacity;
} Nob_Procs;

// Wait until the process has finished
bool nob_proc_wait(Nob_Proc proc);
// Wait until all the processes have finished
bool nob_procs_wait(Nob_Procs procs);
// Wait until all the processes have finished and empty the procs array
bool nob_procs_wait_and_reset(Nob_Procs *procs);
// Append a new process to procs array and if procs.count reaches max_procs_count call nob_procs_wait_and_reset() on it
bool nob_procs_append_with_flush(Nob_Procs *procs, Nob_Proc proc, size_t max_procs_count);

// A command - the main workhorse of Nob. Nob is all about building commands and running them
typedef struct {
    const char **items;
    size_t count;
    size_t capacity;
} Nob_Cmd;

// Example:
// ```c
// Nob_Fd fdin = nob_fd_open_for_read("input.txt");
// if (fdin == NOB_INVALID_FD) fail();
// Nob_Fd fdout = nob_fd_open_for_write("output.txt");
// if (fdout == NOB_INVALID_FD) fail();
// Nob_Cmd cmd = {0};
// nob_cmd_append(&cmd, "cat");
// if (!nob_cmd_run_sync_redirect_and_reset(&cmd, (Nob_Cmd_Redirect) {
//     .fdin = &fdin,
//     .fdout = &fdout
// })) fail();
// ```
typedef struct {
    Nob_Fd *fdin;
    Nob_Fd *fdout;
    Nob_Fd *fderr;
} Nob_Cmd_Redirect;

// Render a string representation of a command into a string builder. Keep in mind the the
// string builder is not NULL-terminated by default. Use nob_sb_append_null if you plan to
// use it as a C string.
void nob_cmd_render(Nob_Cmd cmd, Nob_String_Builder *render);

// TODO: implement C++ support for nob.h
#define nob_cmd_append(cmd, ...) \
    nob_da_append_many(cmd, \
                       ((const char*[]){__VA_ARGS__}), \
                       (sizeof((const char*[]){__VA_ARGS__})/sizeof(const char*)))

#define nob_cmd_extend(cmd, other_cmd) \
    nob_da_append_many(cmd, (other_cmd)->items, (other_cmd)->count)

// Free all the memory allocated by command arguments
#define nob_cmd_free(cmd) NOB_FREE(cmd.items)

// Run command asynchronously
#define nob_cmd_run_async(cmd) nob_cmd_run_async_redirect(cmd, (Nob_Cmd_Redirect) {0})
// NOTE: nob_cmd_run_async_and_reset() is just like nob_cmd_run_async() except it also resets cmd.count to 0
// so the Nob_Cmd instance can be seamlessly used several times in a row
Nob_Proc nob_cmd_run_async_and_reset(Nob_Cmd *cmd);
// Run redirected command asynchronously
Nob_Proc nob_cmd_run_async_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect);
// Run redirected command asynchronously and set cmd.count to 0 and close all the opened files
Nob_Proc nob_cmd_run_async_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect);

// Run command synchronously
bool nob_cmd_run_sync(Nob_Cmd cmd);
// NOTE: nob_cmd_run_sync_and_reset() is just like nob_cmd_run_sync() except it also resets cmd.count to 0
// so the Nob_Cmd instance can be seamlessly used several times in a row
bool nob_cmd_run_sync_and_reset(Nob_Cmd *cmd);
// Run redirected command synchronously
bool nob_cmd_run_sync_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect);
// Run redirected command synchronously and set cmd.count to 0 and close all the opened files
bool nob_cmd_run_sync_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect);

#ifndef NOB_TEMP_CAPACITY
#define NOB_TEMP_CAPACITY (8*1024*1024)
#endif // NOB_TEMP_CAPACITY
char *nob_temp_strdup(const char *cstr);
void *nob_temp_alloc(size_t size);
char *nob_temp_sprintf(const char *format, ...) NOB_PRINTF_FORMAT(1, 2);
void nob_temp_reset(void);
size_t nob_temp_save(void);
void nob_temp_rewind(size_t checkpoint);

// Given any path returns the last part of that path.
// "/path/to/a/file.c" -> "file.c"; "/path/to/a/directory" -> "directory"
const char *nob_path_name(const char *path);
bool nob_rename(const char *old_path, const char *new_path);
int nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count);
int nob_needs_rebuild1(const char *output_path, const char *input_path);
int nob_file_exists(const char *file_path);
const char *nob_get_current_dir_temp(void);
bool nob_set_current_dir(const char *path);

// TODO: we should probably document somewhere all the compiler we support

// The nob_cc_* macros try to abstract away the specific compiler.
// They are verify basic and not particularly flexible, but you can redefine them if you need to
// or not use them at all and create your own abstraction on top of Nob_Cmd.

#ifndef nob_cc
#  if _WIN32
#    if defined(__GNUC__)
#       define nob_cc(cmd) nob_cmd_append(cmd, "cc")
#    elif defined(__clang__)
#       define nob_cc(cmd) nob_cmd_append(cmd, "clang")
#    elif defined(_MSC_VER)
#       define nob_cc(cmd) nob_cmd_append(cmd, "cl.exe")
#    endif
#  else
#    define nob_cc(cmd) nob_cmd_append(cmd, "cc")
#  endif
#endif // nob_cc

#ifndef nob_cc_flags
#  if defined(_MSC_VER) && !defined(__clang__)
#    define nob_cc_flags(...)  // TODO: Add some cool recommended flags for MSVC (I don't really know any)
#  else
#    define nob_cc_flags(cmd) nob_cmd_append(cmd, "-Wall", "-Wextra")
#  endif
#endif // nob_cc_output

#ifndef nob_cc_output
#  if defined(_MSC_VER) && !defined(__clang__)
#    define nob_cc_output(cmd, output_path) nob_cmd_append(cmd, nob_temp_sprintf("/Fe:%s", (output_path)))
#  else
#    define nob_cc_output(cmd, output_path) nob_cmd_append(cmd, "-o", (output_path))
#  endif
#endif // nob_cc_output

#ifndef nob_cc_inputs
#  define nob_cc_inputs(cmd, ...) nob_cmd_append(cmd, __VA_ARGS__)
#endif // nob_cc_inputs

// TODO: add MinGW support for Go Rebuild Urself™ Technology and all the nob_cc_* macros above
//   Musializer contributors came up with a pretty interesting idea of an optional prefix macro which could be useful for
//   MinGW support:
//   https://github.com/tsoding/musializer/blob/b7578cc76b9ecb573d239acc9ccf5a04d3aba2c9/src_build/nob_win64_mingw.c#L3-L9
// TODO: Maybe instead NOB_REBUILD_URSELF macro, the Go Rebuild Urself™ Technology should use the
//   user defined nob_cc_* macros instead?
#ifndef NOB_REBUILD_URSELF
#  if defined(_WIN32)
#    if defined(__GNUC__)
#       define NOB_REBUILD_URSELF(binary_path, source_path) "gcc", "-o", binary_path, source_path
#    elif defined(__clang__)
#       define NOB_REBUILD_URSELF(binary_path, source_path) "clang", "-o", binary_path, source_path
#    elif defined(_MSC_VER)
#       define NOB_REBUILD_URSELF(binary_path, source_path) "cl.exe", nob_temp_sprintf("/Fe:%s", (binary_path)), source_path
#    endif
#  else
#    define NOB_REBUILD_URSELF(binary_path, source_path) "cc", "-o", binary_path, source_path
#  endif
#endif

// Go Rebuild Urself™ Technology
//
//   How to use it:
//     int main(int argc, char** argv) {
//         NOB_GO_REBUILD_URSELF(argc, argv);
//         // actual work
//         return 0;
//     }
//
//   After your added this macro every time you run ./nob it will detect
//   that you modified its original source code and will try to rebuild itself
//   before doing any actual work. So you only need to bootstrap your build system
//   once.
//
//   The modification is detected by comparing the last modified times of the executable
//   and its source code. The same way the make utility usually does it.
//
//   The rebuilding is done by using the NOB_REBUILD_URSELF macro which you can redefine
//   if you need a special way of bootstraping your build system. (which I personally
//   do not recommend since the whole idea of NoBuild is to keep the process of bootstrapping
//   as simple as possible and doing all of the actual work inside of ./nob)
//
void nob__go_rebuild_urself(int argc, char **argv, const char *source_path, ...);
#define NOB_GO_REBUILD_URSELF(argc, argv) nob__go_rebuild_urself(argc, argv, __FILE__, NULL)
// Sometimes your nob.c includes additional files, so you want the Go Rebuild Urself™ Technology to check
// if they also were modified and rebuild nob.c accordingly. For that we have NOB_GO_REBUILD_URSELF_PLUS():
// ```c
// #define NOB_IMPLEMENTATION
// #include "nob.h"
//
// #include "foo.c"
// #include "bar.c"
//
// int main(int argc, char **argv)
// {
//     NOB_GO_REBUILD_URSELF_PLUS(argc, argv, "foo.c", "bar.c");
//     // ...
//     return 0;
// }
#define NOB_GO_REBUILD_URSELF_PLUS(argc, argv, ...) nob__go_rebuild_urself(argc, argv, __FILE__, __VA_ARGS__, NULL);

typedef struct {
    size_t count;
    const char *data;
} Nob_String_View;

const char *nob_temp_sv_to_cstr(Nob_String_View sv);

Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim);
Nob_String_View nob_sv_chop_left(Nob_String_View *sv, size_t n);
Nob_String_View nob_sv_trim(Nob_String_View sv);
Nob_String_View nob_sv_trim_left(Nob_String_View sv);
Nob_String_View nob_sv_trim_right(Nob_String_View sv);
bool nob_sv_eq(Nob_String_View a, Nob_String_View b);
bool nob_sv_end_with(Nob_String_View sv, const char *cstr);
bool nob_sv_starts_with(Nob_String_View sv, Nob_String_View expected_prefix);
Nob_String_View nob_sv_from_cstr(const char *cstr);
Nob_String_View nob_sv_from_parts(const char *data, size_t count);
// nob_sb_to_sv() enables you to just view Nob_String_Builder as Nob_String_View
#define nob_sb_to_sv(sb) nob_sv_from_parts((sb).items, (sb).count)

// printf macros for String_View
#ifndef SV_Fmt
#define SV_Fmt "%.*s"
#endif // SV_Fmt
#ifndef SV_Arg
#define SV_Arg(sv) (int) (sv).count, (sv).data
#endif // SV_Arg
// USAGE:
//   String_View name = ...;
//   printf("Name: "SV_Fmt"\n", SV_Arg(name));


// minirent.h HEADER BEGIN ////////////////////////////////////////
// Copyright 2021 Alexey Kutepov <reximkut@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ============================================================
//
// minirent — 0.0.1 — A subset of dirent interface for Windows.
//
// https://github.com/tsoding/minirent
//
// ============================================================
//
// ChangeLog (https://semver.org/ is implied)
//
//    0.0.2 Automatically include dirent.h on non-Windows
//          platforms
//    0.0.1 First Official Release

#ifndef _WIN32
#include <dirent.h>
#else // _WIN32

#define WIN32_LEAN_AND_MEAN
#include "windows.h"

struct dirent
{
    char d_name[MAX_PATH+1];
};

typedef struct DIR DIR;

static DIR *opendir(const char *dirpath);
static struct dirent *readdir(DIR *dirp);
static int closedir(DIR *dirp);

#endif // _WIN32
// minirent.h HEADER END ////////////////////////////////////////

#ifdef _WIN32

char *nob_win32_error_message(DWORD err);

#endif // _WIN32

#endif // NOB_H_

#ifdef NOB_IMPLEMENTATION

// Any messages with the level below nob_minimal_log_level are going to be suppressed.
Nob_Log_Level nob_minimal_log_level = NOB_INFO;

#ifdef _WIN32

// Base on https://stackoverflow.com/a/75644008
// > .NET Core uses 4096 * sizeof(WCHAR) buffer on stack for FormatMessageW call. And...thats it.
// >
// > https://github.com/dotnet/runtime/blob/3b63eb1346f1ddbc921374a5108d025662fb5ffd/src/coreclr/utilcode/posterror.cpp#L264-L265
#ifndef NOB_WIN32_ERR_MSG_SIZE
#define NOB_WIN32_ERR_MSG_SIZE (4 * 1024)
#endif // NOB_WIN32_ERR_MSG_SIZE

char *nob_win32_error_message(DWORD err) {
    static char win32ErrMsg[NOB_WIN32_ERR_MSG_SIZE] = {0};
    DWORD errMsgSize = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, LANG_USER_DEFAULT, win32ErrMsg,
                                      NOB_WIN32_ERR_MSG_SIZE, NULL);

    if (errMsgSize == 0) {
        if (GetLastError() != ERROR_MR_MID_NOT_FOUND) {
            if (sprintf(win32ErrMsg, "Could not get error message for 0x%lX", err) > 0) {
                return (char *)&win32ErrMsg;
            } else {
                return NULL;
            }
        } else {
            if (sprintf(win32ErrMsg, "Invalid Windows Error code (0x%lX)", err) > 0) {
                return (char *)&win32ErrMsg;
            } else {
                return NULL;
            }
        }
    }

    while (errMsgSize > 1 && isspace(win32ErrMsg[errMsgSize - 1])) {
        win32ErrMsg[--errMsgSize] = '\0';
    }

    return win32ErrMsg;
}

#endif // _WIN32

// The implementation idea is stolen from https://github.com/zhiayang/nabs
void nob__go_rebuild_urself(int argc, char **argv, const char *source_path, ...)
{
    const char *binary_path = nob_shift(argv, argc);
#ifdef _WIN32
    // On Windows executables almost always invoked without extension, so
    // it's ./nob, not ./nob.exe. For renaming the extension is a must.
    if (!nob_sv_end_with(nob_sv_from_cstr(binary_path), ".exe")) {
        binary_path = nob_temp_sprintf("%s.exe", binary_path);
    }
#endif

    Nob_File_Paths source_paths = {0};
    nob_da_append(&source_paths, source_path);
    va_list args;
    va_start(args, source_path);
    for (;;) {
        const char *path = va_arg(args, const char*);
        if (path == NULL) break;
        nob_da_append(&source_paths, path);
    }
    va_end(args);

    int rebuild_is_needed = nob_needs_rebuild(binary_path, source_paths.items, source_paths.count);
    if (rebuild_is_needed < 0) exit(1); // error
    if (!rebuild_is_needed) {           // no rebuild is needed
        NOB_FREE(source_paths.items);
        return;
    }

    Nob_Cmd cmd = {0};

    const char *old_binary_path = nob_temp_sprintf("%s.old", binary_path);

    if (!nob_rename(binary_path, old_binary_path)) exit(1);
    nob_cmd_append(&cmd, NOB_REBUILD_URSELF(binary_path, source_path));
    if (!nob_cmd_run_sync_and_reset(&cmd)) {
        nob_rename(old_binary_path, binary_path);
        exit(1);
    }
#ifdef NOB_EXPERIMENTAL_DELETE_OLD
    // TODO: this is an experimental behavior behind a compilation flag.
    // Once it is confirmed that it does not cause much problems on both POSIX and Windows
    // we may turn it on by default.
    nob_delete_file(old_binary_path);
#endif // NOB_EXPERIMENTAL_DELETE_OLD

    nob_cmd_append(&cmd, binary_path);
    nob_da_append_many(&cmd, argv, argc);
    if (!nob_cmd_run_sync_and_reset(&cmd)) exit(1);
    exit(0);
}

static size_t nob_temp_size = 0;
static char nob_temp[NOB_TEMP_CAPACITY] = {0};

bool nob_mkdir_if_not_exists(const char *path)
{
#ifdef _WIN32
    int result = mkdir(path);
#else
    int result = mkdir(path, 0755);
#endif
    if (result < 0) {
        if (errno == EEXIST) {
            nob_log(NOB_INFO, "directory `%s` already exists", path);
            return true;
        }
        nob_log(NOB_ERROR, "could not create directory `%s`: %s", path, strerror(errno));
        return false;
    }

    nob_log(NOB_INFO, "created directory `%s`", path);
    return true;
}

bool nob_copy_file(const char *src_path, const char *dst_path)
{
    nob_log(NOB_INFO, "copying %s -> %s", src_path, dst_path);
#ifdef _WIN32
    if (!CopyFile(src_path, dst_path, FALSE)) {
        nob_log(NOB_ERROR, "Could not copy file: %s", nob_win32_error_message(GetLastError()));
        return false;
    }
    return true;
#else
    int src_fd = -1;
    int dst_fd = -1;
    size_t buf_size = 32*1024;
    char *buf = NOB_REALLOC(NULL, buf_size);
    NOB_ASSERT(buf != NULL && "Buy more RAM lol!!");
    bool result = true;

    src_fd = open(src_path, O_RDONLY);
    if (src_fd < 0) {
        nob_log(NOB_ERROR, "Could not open file %s: %s", src_path, strerror(errno));
        nob_return_defer(false);
    }

    struct stat src_stat;
    if (fstat(src_fd, &src_stat) < 0) {
        nob_log(NOB_ERROR, "Could not get mode of file %s: %s", src_path, strerror(errno));
        nob_return_defer(false);
    }

    dst_fd = open(dst_path, O_CREAT | O_TRUNC | O_WRONLY, src_stat.st_mode);
    if (dst_fd < 0) {
        nob_log(NOB_ERROR, "Could not create file %s: %s", dst_path, strerror(errno));
        nob_return_defer(false);
    }

    for (;;) {
        ssize_t n = read(src_fd, buf, buf_size);
        if (n == 0) break;
        if (n < 0) {
            nob_log(NOB_ERROR, "Could not read from file %s: %s", src_path, strerror(errno));
            nob_return_defer(false);
        }
        char *buf2 = buf;
        while (n > 0) {
            ssize_t m = write(dst_fd, buf2, n);
            if (m < 0) {
                nob_log(NOB_ERROR, "Could not write to file %s: %s", dst_path, strerror(errno));
                nob_return_defer(false);
            }
            n    -= m;
            buf2 += m;
        }
    }

defer:
    NOB_FREE(buf);
    close(src_fd);
    close(dst_fd);
    return result;
#endif
}

void nob_cmd_render(Nob_Cmd cmd, Nob_String_Builder *render)
{
    for (size_t i = 0; i < cmd.count; ++i) {
        const char *arg = cmd.items[i];
        if (arg == NULL) break;
        if (i > 0) nob_sb_append_cstr(render, " ");
        if (!strchr(arg, ' ')) {
            nob_sb_append_cstr(render, arg);
        } else {
            nob_da_append(render, '\'');
            nob_sb_append_cstr(render, arg);
            nob_da_append(render, '\'');
        }
    }
}

#ifdef _WIN32
// https://learn.microsoft.com/en-gb/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way
static void nob__win32_cmd_quote(Nob_Cmd cmd, Nob_String_Builder *quoted)
{
    for (size_t i = 0; i < cmd.count; ++i) {
        const char *arg = cmd.items[i];
        if (arg == NULL) break;
        size_t len = strlen(arg);
        if (i > 0) nob_da_append(quoted, ' ');
        if (len != 0 && NULL == strpbrk(arg, " \t\n\v\"")) {
            // no need to quote
            nob_da_append_many(quoted, arg, len);
        } else {
            // we need to escape:
            // 1. double quotes in the original arg
            // 2. consequent backslashes before a double quote
            size_t backslashes = 0;
            nob_da_append(quoted, '\"');
            for (size_t j = 0; j < len; ++j) {
                char x = arg[j];
                if (x == '\\') {
                    backslashes += 1;
                } else {
                    if (x == '\"') {
                        // escape backslashes (if any) and the double quote
                        for (size_t k = 0; k < 1+backslashes; ++k) {
                            nob_da_append(quoted, '\\');
                        }
                    }
                    backslashes = 0;
                }
                nob_da_append(quoted, x);
            }
            // escape backslashes (if any)
            for (size_t k = 0; k < backslashes; ++k) {
                nob_da_append(quoted, '\\');
            }
            nob_da_append(quoted, '\"');
        }
    }
}
#endif

Nob_Proc nob_cmd_run_async_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect)
{
    if (cmd.count < 1) {
        nob_log(NOB_ERROR, "Could not run empty command");
        return NOB_INVALID_PROC;
    }

    Nob_String_Builder sb = {0};
    nob_cmd_render(cmd, &sb);
    nob_sb_append_null(&sb);
    nob_log(NOB_INFO, "CMD: %s", sb.items);
    nob_sb_free(sb);
    memset(&sb, 0, sizeof(sb));

#ifdef _WIN32
    // https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output

    STARTUPINFO siStartInfo;
    ZeroMemory(&siStartInfo, sizeof(siStartInfo));
    siStartInfo.cb = sizeof(STARTUPINFO);
    // NOTE: theoretically setting NULL to std handles should not be a problem
    // https://docs.microsoft.com/en-us/windows/console/getstdhandle?redirectedfrom=MSDN#attachdetach-behavior
    // TODO: check for errors in GetStdHandle
    siStartInfo.hStdError = redirect.fderr ? *redirect.fderr : GetStdHandle(STD_ERROR_HANDLE);
    siStartInfo.hStdOutput = redirect.fdout ? *redirect.fdout : GetStdHandle(STD_OUTPUT_HANDLE);
    siStartInfo.hStdInput = redirect.fdin ? *redirect.fdin : GetStdHandle(STD_INPUT_HANDLE);
    siStartInfo.dwFlags |= STARTF_USESTDHANDLES;

    PROCESS_INFORMATION piProcInfo;
    ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));

    nob__win32_cmd_quote(cmd, &sb);
    nob_sb_append_null(&sb);
    BOOL bSuccess = CreateProcessA(NULL, sb.items, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo);
    nob_sb_free(sb);

    if (!bSuccess) {
        nob_log(NOB_ERROR, "Could not create child process for %s: %s", cmd.items[0], nob_win32_error_message(GetLastError()));
        return NOB_INVALID_PROC;
    }

    CloseHandle(piProcInfo.hThread);

    return piProcInfo.hProcess;
#else
    pid_t cpid = fork();
    if (cpid < 0) {
        nob_log(NOB_ERROR, "Could not fork child process: %s", strerror(errno));
        return NOB_INVALID_PROC;
    }

    if (cpid == 0) {
        if (redirect.fdin) {
            if (dup2(*redirect.fdin, STDIN_FILENO) < 0) {
                nob_log(NOB_ERROR, "Could not setup stdin for child process: %s", strerror(errno));
                exit(1);
            }
        }

        if (redirect.fdout) {
            if (dup2(*redirect.fdout, STDOUT_FILENO) < 0) {
                nob_log(NOB_ERROR, "Could not setup stdout for child process: %s", strerror(errno));
                exit(1);
            }
        }

        if (redirect.fderr) {
            if (dup2(*redirect.fderr, STDERR_FILENO) < 0) {
                nob_log(NOB_ERROR, "Could not setup stderr for child process: %s", strerror(errno));
                exit(1);
            }
        }

        // NOTE: This leaks a bit of memory in the child process.
        // But do we actually care? It's a one off leak anyway...
        Nob_Cmd cmd_null = {0};
        nob_da_append_many(&cmd_null, cmd.items, cmd.count);
        nob_cmd_append(&cmd_null, NULL);

        if (execvp(cmd.items[0], (char * const*) cmd_null.items) < 0) {
            nob_log(NOB_ERROR, "Could not exec child process for %s: %s", cmd.items[0], strerror(errno));
            exit(1);
        }
        NOB_UNREACHABLE("nob_cmd_run_async_redirect");
    }

    return cpid;
#endif
}

Nob_Proc nob_cmd_run_async_and_reset(Nob_Cmd *cmd)
{
    Nob_Proc proc = nob_cmd_run_async(*cmd);
    cmd->count = 0;
    return proc;
}

Nob_Proc nob_cmd_run_async_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect)
{
    Nob_Proc proc = nob_cmd_run_async_redirect(*cmd, redirect);
    cmd->count = 0;
    if (redirect.fdin) {
        nob_fd_close(*redirect.fdin);
        *redirect.fdin = NOB_INVALID_FD;
    }
    if (redirect.fdout) {
        nob_fd_close(*redirect.fdout);
        *redirect.fdout = NOB_INVALID_FD;
    }
    if (redirect.fderr) {
        nob_fd_close(*redirect.fderr);
        *redirect.fderr = NOB_INVALID_FD;
    }
    return proc;
}

Nob_Fd nob_fd_open_for_read(const char *path)
{
#ifndef _WIN32
    Nob_Fd result = open(path, O_RDONLY);
    if (result < 0) {
        nob_log(NOB_ERROR, "Could not open file %s: %s", path, strerror(errno));
        return NOB_INVALID_FD;
    }
    return result;
#else
    // https://docs.microsoft.com/en-us/windows/win32/fileio/opening-a-file-for-reading-or-writing
    SECURITY_ATTRIBUTES saAttr = {0};
    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    saAttr.bInheritHandle = TRUE;

    Nob_Fd result = CreateFile(
                    path,
                    GENERIC_READ,
                    0,
                    &saAttr,
                    OPEN_EXISTING,
                    FILE_ATTRIBUTE_READONLY,
                    NULL);

    if (result == INVALID_HANDLE_VALUE) {
        nob_log(NOB_ERROR, "Could not open file %s: %s", path, nob_win32_error_message(GetLastError()));
        return NOB_INVALID_FD;
    }

    return result;
#endif // _WIN32
}

Nob_Fd nob_fd_open_for_write(const char *path)
{
#ifndef _WIN32
    Nob_Fd result = open(path,
                     O_WRONLY | O_CREAT | O_TRUNC,
                     S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    if (result < 0) {
        nob_log(NOB_ERROR, "could not open file %s: %s", path, strerror(errno));
        return NOB_INVALID_FD;
    }
    return result;
#else
    SECURITY_ATTRIBUTES saAttr = {0};
    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    saAttr.bInheritHandle = TRUE;

    Nob_Fd result = CreateFile(
                    path,                            // name of the write
                    GENERIC_WRITE,                   // open for writing
                    0,                               // do not share
                    &saAttr,                         // default security
                    CREATE_ALWAYS,                   // create always
                    FILE_ATTRIBUTE_NORMAL,           // normal file
                    NULL                             // no attr. template
                );

    if (result == INVALID_HANDLE_VALUE) {
        nob_log(NOB_ERROR, "Could not open file %s: %s", path, nob_win32_error_message(GetLastError()));
        return NOB_INVALID_FD;
    }

    return result;
#endif // _WIN32
}

void nob_fd_close(Nob_Fd fd)
{
#ifdef _WIN32
    CloseHandle(fd);
#else
    close(fd);
#endif // _WIN32
}

bool nob_procs_wait(Nob_Procs procs)
{
    bool success = true;
    for (size_t i = 0; i < procs.count; ++i) {
        success = nob_proc_wait(procs.items[i]) && success;
    }
    return success;
}

bool nob_procs_wait_and_reset(Nob_Procs *procs)
{
    bool success = nob_procs_wait(*procs);
    procs->count = 0;
    return success;
}

bool nob_proc_wait(Nob_Proc proc)
{
    if (proc == NOB_INVALID_PROC) return false;

#ifdef _WIN32
    DWORD result = WaitForSingleObject(
                       proc,    // HANDLE hHandle,
                       INFINITE // DWORD  dwMilliseconds
                   );

    if (result == WAIT_FAILED) {
        nob_log(NOB_ERROR, "could not wait on child process: %s", nob_win32_error_message(GetLastError()));
        return false;
    }

    DWORD exit_status;
    if (!GetExitCodeProcess(proc, &exit_status)) {
        nob_log(NOB_ERROR, "could not get process exit code: %s", nob_win32_error_message(GetLastError()));
        return false;
    }

    if (exit_status != 0) {
        nob_log(NOB_ERROR, "command exited with exit code %lu", exit_status);
        return false;
    }

    CloseHandle(proc);

    return true;
#else
    for (;;) {
        int wstatus = 0;
        if (waitpid(proc, &wstatus, 0) < 0) {
            nob_log(NOB_ERROR, "could not wait on command (pid %d): %s", proc, strerror(errno));
            return false;
        }

        if (WIFEXITED(wstatus)) {
            int exit_status = WEXITSTATUS(wstatus);
            if (exit_status != 0) {
                nob_log(NOB_ERROR, "command exited with exit code %d", exit_status);
                return false;
            }

            break;
        }

        if (WIFSIGNALED(wstatus)) {
            nob_log(NOB_ERROR, "command process was terminated by signal %d", WTERMSIG(wstatus));
            return false;
        }
    }

    return true;
#endif
}

bool nob_procs_append_with_flush(Nob_Procs *procs, Nob_Proc proc, size_t max_procs_count)
{
    nob_da_append(procs, proc);

    if (procs->count >= max_procs_count) {
        if (!nob_procs_wait_and_reset(procs)) return false;
    }

    return true;
}

bool nob_cmd_run_sync_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect)
{
    Nob_Proc p = nob_cmd_run_async_redirect(cmd, redirect);
    if (p == NOB_INVALID_PROC) return false;
    return nob_proc_wait(p);
}

bool nob_cmd_run_sync(Nob_Cmd cmd)
{
    Nob_Proc p = nob_cmd_run_async(cmd);
    if (p == NOB_INVALID_PROC) return false;
    return nob_proc_wait(p);
}

bool nob_cmd_run_sync_and_reset(Nob_Cmd *cmd)
{
    bool p = nob_cmd_run_sync(*cmd);
    cmd->count = 0;
    return p;
}

bool nob_cmd_run_sync_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect)
{
    bool p = nob_cmd_run_sync_redirect(*cmd, redirect);
    cmd->count = 0;
    if (redirect.fdin) {
        nob_fd_close(*redirect.fdin);
        *redirect.fdin = NOB_INVALID_FD;
    }
    if (redirect.fdout) {
        nob_fd_close(*redirect.fdout);
        *redirect.fdout = NOB_INVALID_FD;
    }
    if (redirect.fderr) {
        nob_fd_close(*redirect.fderr);
        *redirect.fderr = NOB_INVALID_FD;
    }
    return p;
}

void nob_log(Nob_Log_Level level, const char *fmt, ...)
{
    if (level < nob_minimal_log_level) return;

    switch (level) {
    case NOB_INFO:
        fprintf(stderr, "[INFO] ");
        break;
    case NOB_WARNING:
        fprintf(stderr, "[WARNING] ");
        break;
    case NOB_ERROR:
        fprintf(stderr, "[ERROR] ");
        break;
    case NOB_NO_LOGS: return;
    default:
        NOB_UNREACHABLE("nob_log");
    }

    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    fprintf(stderr, "\n");
}

bool nob_read_entire_dir(const char *parent, Nob_File_Paths *children)
{
    bool result = true;
    DIR *dir = NULL;

    dir = opendir(parent);
    if (dir == NULL) {
        #ifdef _WIN32
        nob_log(NOB_ERROR, "Could not open directory %s: %s", parent, nob_win32_error_message(GetLastError()));
        #else
        nob_log(NOB_ERROR, "Could not open directory %s: %s", parent, strerror(errno));
        #endif // _WIN32
        nob_return_defer(false);
    }

    errno = 0;
    struct dirent *ent = readdir(dir);
    while (ent != NULL) {
        nob_da_append(children, nob_temp_strdup(ent->d_name));
        ent = readdir(dir);
    }

    if (errno != 0) {
        #ifdef _WIN32
        nob_log(NOB_ERROR, "Could not read directory %s: %s", parent, nob_win32_error_message(GetLastError()));
        #else
        nob_log(NOB_ERROR, "Could not read directory %s: %s", parent, strerror(errno));
        #endif // _WIN32
        nob_return_defer(false);
    }

defer:
    if (dir) closedir(dir);
    return result;
}

bool nob_write_entire_file(const char *path, const void *data, size_t size)
{
    bool result = true;

    FILE *f = fopen(path, "wb");
    if (f == NULL) {
        nob_log(NOB_ERROR, "Could not open file %s for writing: %s\n", path, strerror(errno));
        nob_return_defer(false);
    }

    //           len
    //           v
    // aaaaaaaaaa
    //     ^
    //     data

    const char *buf = data;
    while (size > 0) {
        size_t n = fwrite(buf, 1, size, f);
        if (ferror(f)) {
            nob_log(NOB_ERROR, "Could not write into file %s: %s\n", path, strerror(errno));
            nob_return_defer(false);
        }
        size -= n;
        buf  += n;
    }

defer:
    if (f) fclose(f);
    return result;
}

Nob_File_Type nob_get_file_type(const char *path)
{
#ifdef _WIN32
    DWORD attr = GetFileAttributesA(path);
    if (attr == INVALID_FILE_ATTRIBUTES) {
        nob_log(NOB_ERROR, "Could not get file attributes of %s: %s", path, nob_win32_error_message(GetLastError()));
        return -1;
    }

    if (attr & FILE_ATTRIBUTE_DIRECTORY) return NOB_FILE_DIRECTORY;
    // TODO: detect symlinks on Windows (whatever that means on Windows anyway)
    return NOB_FILE_REGULAR;
#else // _WIN32
    struct stat statbuf;
    if (stat(path, &statbuf) < 0) {
        nob_log(NOB_ERROR, "Could not get stat of %s: %s", path, strerror(errno));
        return -1;
    }

    if (S_ISREG(statbuf.st_mode)) return NOB_FILE_REGULAR;
    if (S_ISDIR(statbuf.st_mode)) return NOB_FILE_DIRECTORY;
    if (S_ISLNK(statbuf.st_mode)) return NOB_FILE_SYMLINK;
    return NOB_FILE_OTHER;
#endif // _WIN32
}

bool nob_delete_file(const char *path)
{
    nob_log(NOB_INFO, "deleting %s", path);
#ifdef _WIN32
    if (!DeleteFileA(path)) {
        nob_log(NOB_ERROR, "Could not delete file %s: %s", path, nob_win32_error_message(GetLastError()));
        return false;
    }
    return true;
#else
    if (remove(path) < 0) {
        nob_log(NOB_ERROR, "Could not delete file %s: %s", path, strerror(errno));
        return false;
    }
    return true;
#endif // _WIN32
}

bool nob_copy_directory_recursively(const char *src_path, const char *dst_path)
{
    bool result = true;
    Nob_File_Paths children = {0};
    Nob_String_Builder src_sb = {0};
    Nob_String_Builder dst_sb = {0};
    size_t temp_checkpoint = nob_temp_save();

    Nob_File_Type type = nob_get_file_type(src_path);
    if (type < 0) return false;

    switch (type) {
        case NOB_FILE_DIRECTORY: {
            if (!nob_mkdir_if_not_exists(dst_path)) nob_return_defer(false);
            if (!nob_read_entire_dir(src_path, &children)) nob_return_defer(false);

            for (size_t i = 0; i < children.count; ++i) {
                if (strcmp(children.items[i], ".") == 0) continue;
                if (strcmp(children.items[i], "..") == 0) continue;

                src_sb.count = 0;
                nob_sb_append_cstr(&src_sb, src_path);
                nob_sb_append_cstr(&src_sb, "/");
                nob_sb_append_cstr(&src_sb, children.items[i]);
                nob_sb_append_null(&src_sb);

                dst_sb.count = 0;
                nob_sb_append_cstr(&dst_sb, dst_path);
                nob_sb_append_cstr(&dst_sb, "/");
                nob_sb_append_cstr(&dst_sb, children.items[i]);
                nob_sb_append_null(&dst_sb);

                if (!nob_copy_directory_recursively(src_sb.items, dst_sb.items)) {
                    nob_return_defer(false);
                }
            }
        } break;

        case NOB_FILE_REGULAR: {
            if (!nob_copy_file(src_path, dst_path)) {
                nob_return_defer(false);
            }
        } break;

        case NOB_FILE_SYMLINK: {
            nob_log(NOB_WARNING, "TODO: Copying symlinks is not supported yet");
        } break;

        case NOB_FILE_OTHER: {
            nob_log(NOB_ERROR, "Unsupported type of file %s", src_path);
            nob_return_defer(false);
        } break;

        default: NOB_UNREACHABLE("nob_copy_directory_recursively");
    }

defer:
    nob_temp_rewind(temp_checkpoint);
    nob_da_free(src_sb);
    nob_da_free(dst_sb);
    nob_da_free(children);
    return result;
}

char *nob_temp_strdup(const char *cstr)
{
    size_t n = strlen(cstr);
    char *result = nob_temp_alloc(n + 1);
    NOB_ASSERT(result != NULL && "Increase NOB_TEMP_CAPACITY");
    memcpy(result, cstr, n);
    result[n] = '\0';
    return result;
}

void *nob_temp_alloc(size_t size)
{
    if (nob_temp_size + size > NOB_TEMP_CAPACITY) return NULL;
    void *result = &nob_temp[nob_temp_size];
    nob_temp_size += size;
    return result;
}

char *nob_temp_sprintf(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    int n = vsnprintf(NULL, 0, format, args);
    va_end(args);

    NOB_ASSERT(n >= 0);
    char *result = nob_temp_alloc(n + 1);
    NOB_ASSERT(result != NULL && "Extend the size of the temporary allocator");
    // TODO: use proper arenas for the temporary allocator;
    va_start(args, format);
    vsnprintf(result, n + 1, format, args);
    va_end(args);

    return result;
}

void nob_temp_reset(void)
{
    nob_temp_size = 0;
}

size_t nob_temp_save(void)
{
    return nob_temp_size;
}

void nob_temp_rewind(size_t checkpoint)
{
    nob_temp_size = checkpoint;
}

const char *nob_temp_sv_to_cstr(Nob_String_View sv)
{
    char *result = nob_temp_alloc(sv.count + 1);
    NOB_ASSERT(result != NULL && "Extend the size of the temporary allocator");
    memcpy(result, sv.data, sv.count);
    result[sv.count] = '\0';
    return result;
}

int nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count)
{
#ifdef _WIN32
    BOOL bSuccess;

    HANDLE output_path_fd = CreateFile(output_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
    if (output_path_fd == INVALID_HANDLE_VALUE) {
        // NOTE: if output does not exist it 100% must be rebuilt
        if (GetLastError() == ERROR_FILE_NOT_FOUND) return 1;
        nob_log(NOB_ERROR, "Could not open file %s: %s", output_path, nob_win32_error_message(GetLastError()));
        return -1;
    }
    FILETIME output_path_time;
    bSuccess = GetFileTime(output_path_fd, NULL, NULL, &output_path_time);
    CloseHandle(output_path_fd);
    if (!bSuccess) {
        nob_log(NOB_ERROR, "Could not get time of %s: %s", output_path, nob_win32_error_message(GetLastError()));
        return -1;
    }

    for (size_t i = 0; i < input_paths_count; ++i) {
        const char *input_path = input_paths[i];
        HANDLE input_path_fd = CreateFile(input_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
        if (input_path_fd == INVALID_HANDLE_VALUE) {
            // NOTE: non-existing input is an error cause it is needed for building in the first place
            nob_log(NOB_ERROR, "Could not open file %s: %s", input_path, nob_win32_error_message(GetLastError()));
            return -1;
        }
        FILETIME input_path_time;
        bSuccess = GetFileTime(input_path_fd, NULL, NULL, &input_path_time);
        CloseHandle(input_path_fd);
        if (!bSuccess) {
            nob_log(NOB_ERROR, "Could not get time of %s: %s", input_path, nob_win32_error_message(GetLastError()));
            return -1;
        }

        // NOTE: if even a single input_path is fresher than output_path that's 100% rebuild
        if (CompareFileTime(&input_path_time, &output_path_time) == 1) return 1;
    }

    return 0;
#else
    struct stat statbuf = {0};

    if (stat(output_path, &statbuf) < 0) {
        // NOTE: if output does not exist it 100% must be rebuilt
        if (errno == ENOENT) return 1;
        nob_log(NOB_ERROR, "could not stat %s: %s", output_path, strerror(errno));
        return -1;
    }
    int output_path_time = statbuf.st_mtime;

    for (size_t i = 0; i < input_paths_count; ++i) {
        const char *input_path = input_paths[i];
        if (stat(input_path, &statbuf) < 0) {
            // NOTE: non-existing input is an error cause it is needed for building in the first place
            nob_log(NOB_ERROR, "could not stat %s: %s", input_path, strerror(errno));
            return -1;
        }
        int input_path_time = statbuf.st_mtime;
        // NOTE: if even a single input_path is fresher than output_path that's 100% rebuild
        if (input_path_time > output_path_time) return 1;
    }

    return 0;
#endif
}

int nob_needs_rebuild1(const char *output_path, const char *input_path)
{
    return nob_needs_rebuild(output_path, &input_path, 1);
}

const char *nob_path_name(const char *path)
{
#ifdef _WIN32
    const char *p1 = strrchr(path, '/');
    const char *p2 = strrchr(path, '\\');
    const char *p = (p1 > p2)? p1 : p2;  // NULL is ignored if the other search is successful
    return p ? p + 1 : path;
#else
    const char *p = strrchr(path, '/');
    return p ? p + 1 : path;
#endif // _WIN32
}

bool nob_rename(const char *old_path, const char *new_path)
{
    nob_log(NOB_INFO, "renaming %s -> %s", old_path, new_path);
#ifdef _WIN32
    if (!MoveFileEx(old_path, new_path, MOVEFILE_REPLACE_EXISTING)) {
        nob_log(NOB_ERROR, "could not rename %s to %s: %s", old_path, new_path, nob_win32_error_message(GetLastError()));
        return false;
    }
#else
    if (rename(old_path, new_path) < 0) {
        nob_log(NOB_ERROR, "could not rename %s to %s: %s", old_path, new_path, strerror(errno));
        return false;
    }
#endif // _WIN32
    return true;
}

bool nob_read_entire_file(const char *path, Nob_String_Builder *sb)
{
    bool result = true;

    FILE *f = fopen(path, "rb");
    if (f == NULL)                 nob_return_defer(false);
    if (fseek(f, 0, SEEK_END) < 0) nob_return_defer(false);
#ifndef _WIN32
    long m = ftell(f);
#else
    long long m = _ftelli64(f);
#endif
    if (m < 0)                     nob_return_defer(false);
    if (fseek(f, 0, SEEK_SET) < 0) nob_return_defer(false);

    size_t new_count = sb->count + m;
    if (new_count > sb->capacity) {
        sb->items = NOB_REALLOC(sb->items, new_count);
        NOB_ASSERT(sb->items != NULL && "Buy more RAM lool!!");
        sb->capacity = new_count;
    }

    fread(sb->items + sb->count, m, 1, f);
    if (ferror(f)) {
        // TODO: Afaik, ferror does not set errno. So the error reporting in defer is not correct in this case.
        nob_return_defer(false);
    }
    sb->count = new_count;

defer:
    if (!result) nob_log(NOB_ERROR, "Could not read file %s: %s", path, strerror(errno));
    if (f) fclose(f);
    return result;
}

int nob_sb_appendf(Nob_String_Builder *sb, const char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    int n = vsnprintf(NULL, 0, fmt, args);
    va_end(args);

    // NOTE: the new_capacity needs to be +1 because of the null terminator.
    // However, further below we increase sb->count by n, not n + 1.
    // 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
    nob_da_reserve(sb, sb->count + n + 1);
    char *dest = sb->items + sb->count;
    va_start(args, fmt);
    vsnprintf(dest, n+1, fmt, args);
    va_end(args);

    sb->count += n;

    return n;
}

Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim)
{
    size_t i = 0;
    while (i < sv->count && sv->data[i] != delim) {
        i += 1;
    }

    Nob_String_View result = nob_sv_from_parts(sv->data, i);

    if (i < sv->count) {
        sv->count -= i + 1;
        sv->data  += i + 1;
    } else {
        sv->count -= i;
        sv->data  += i;
    }

    return result;
}

Nob_String_View nob_sv_chop_left(Nob_String_View *sv, size_t n)
{
    if (n > sv->count) {
        n = sv->count;
    }

    Nob_String_View result = nob_sv_from_parts(sv->data, n);

    sv->data  += n;
    sv->count -= n;

    return result;
}

Nob_String_View nob_sv_from_parts(const char *data, size_t count)
{
    Nob_String_View sv;
    sv.count = count;
    sv.data = data;
    return sv;
}

Nob_String_View nob_sv_trim_left(Nob_String_View sv)
{
    size_t i = 0;
    while (i < sv.count && isspace(sv.data[i])) {
        i += 1;
    }

    return nob_sv_from_parts(sv.data + i, sv.count - i);
}

Nob_String_View nob_sv_trim_right(Nob_String_View sv)
{
    size_t i = 0;
    while (i < sv.count && isspace(sv.data[sv.count - 1 - i])) {
        i += 1;
    }

    return nob_sv_from_parts(sv.data, sv.count - i);
}

Nob_String_View nob_sv_trim(Nob_String_View sv)
{
    return nob_sv_trim_right(nob_sv_trim_left(sv));
}

Nob_String_View nob_sv_from_cstr(const char *cstr)
{
    return nob_sv_from_parts(cstr, strlen(cstr));
}

bool nob_sv_eq(Nob_String_View a, Nob_String_View b)
{
    if (a.count != b.count) {
        return false;
    } else {
        return memcmp(a.data, b.data, a.count) == 0;
    }
}

bool nob_sv_end_with(Nob_String_View sv, const char *cstr)
{
    size_t cstr_count = strlen(cstr);
    if (sv.count >= cstr_count) {
        size_t ending_start = sv.count - cstr_count;
        Nob_String_View sv_ending = nob_sv_from_parts(sv.data + ending_start, cstr_count);
        return nob_sv_eq(sv_ending, nob_sv_from_cstr(cstr));
    }
    return false;
}


bool nob_sv_starts_with(Nob_String_View sv, Nob_String_View expected_prefix)
{
    if (expected_prefix.count <= sv.count) {
        Nob_String_View actual_prefix = nob_sv_from_parts(sv.data, expected_prefix.count);
        return nob_sv_eq(expected_prefix, actual_prefix);
    }

    return false;
}

// RETURNS:
//  0 - file does not exists
//  1 - file exists
// -1 - error while checking if file exists. The error is logged
int nob_file_exists(const char *file_path)
{
#if _WIN32
    // TODO: distinguish between "does not exists" and other errors
    DWORD dwAttrib = GetFileAttributesA(file_path);
    return dwAttrib != INVALID_FILE_ATTRIBUTES;
#else
    struct stat statbuf;
    if (stat(file_path, &statbuf) < 0) {
        if (errno == ENOENT) return 0;
        nob_log(NOB_ERROR, "Could not check if file %s exists: %s", file_path, strerror(errno));
        return -1;
    }
    return 1;
#endif
}

const char *nob_get_current_dir_temp(void)
{
#ifdef _WIN32
    DWORD nBufferLength = GetCurrentDirectory(0, NULL);
    if (nBufferLength == 0) {
        nob_log(NOB_ERROR, "could not get current directory: %s", nob_win32_error_message(GetLastError()));
        return NULL;
    }

    char *buffer = (char*) nob_temp_alloc(nBufferLength);
    if (GetCurrentDirectory(nBufferLength, buffer) == 0) {
        nob_log(NOB_ERROR, "could not get current directory: %s", nob_win32_error_message(GetLastError()));
        return NULL;
    }

    return buffer;
#else
    char *buffer = (char*) nob_temp_alloc(PATH_MAX);
    if (getcwd(buffer, PATH_MAX) == NULL) {
        nob_log(NOB_ERROR, "could not get current directory: %s", strerror(errno));
        return NULL;
    }

    return buffer;
#endif // _WIN32
}

bool nob_set_current_dir(const char *path)
{
#ifdef _WIN32
    if (!SetCurrentDirectory(path)) {
        nob_log(NOB_ERROR, "could not set current directory to %s: %s", path, nob_win32_error_message(GetLastError()));
        return false;
    }
    return true;
#else
    if (chdir(path) < 0) {
        nob_log(NOB_ERROR, "could not set current directory to %s: %s", path, strerror(errno));
        return false;
    }
    return true;
#endif // _WIN32
}

// minirent.h SOURCE BEGIN ////////////////////////////////////////
#ifdef _WIN32
struct DIR
{
    HANDLE hFind;
    WIN32_FIND_DATA data;
    struct dirent *dirent;
};

DIR *opendir(const char *dirpath)
{
    NOB_ASSERT(dirpath);

    char buffer[MAX_PATH];
    snprintf(buffer, MAX_PATH, "%s\\*", dirpath);

    DIR *dir = (DIR*)NOB_REALLOC(NULL, sizeof(DIR));
    memset(dir, 0, sizeof(DIR));

    dir->hFind = FindFirstFile(buffer, &dir->data);
    if (dir->hFind == INVALID_HANDLE_VALUE) {
        // TODO: opendir should set errno accordingly on FindFirstFile fail
        // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror
        errno = ENOSYS;
        goto fail;
    }

    return dir;

fail:
    if (dir) {
        NOB_FREE(dir);
    }

    return NULL;
}

struct dirent *readdir(DIR *dirp)
{
    NOB_ASSERT(dirp);

    if (dirp->dirent == NULL) {
        dirp->dirent = (struct dirent*)NOB_REALLOC(NULL, sizeof(struct dirent));
        memset(dirp->dirent, 0, sizeof(struct dirent));
    } else {
        if(!FindNextFile(dirp->hFind, &dirp->data)) {
            if (GetLastError() != ERROR_NO_MORE_FILES) {
                // TODO: readdir should set errno accordingly on FindNextFile fail
                // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror
                errno = ENOSYS;
            }

            return NULL;
        }
    }

    memset(dirp->dirent->d_name, 0, sizeof(dirp->dirent->d_name));

    strncpy(
        dirp->dirent->d_name,
        dirp->data.cFileName,
        sizeof(dirp->dirent->d_name) - 1);

    return dirp->dirent;
}

int closedir(DIR *dirp)
{
    NOB_ASSERT(dirp);

    if(!FindClose(dirp->hFind)) {
        // TODO: closedir should set errno accordingly on FindClose fail
        // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror
        errno = ENOSYS;
        return -1;
    }

    if (dirp->dirent) {
        NOB_FREE(dirp->dirent);
    }
    NOB_FREE(dirp);

    return 0;
}
#endif // _WIN32
// minirent.h SOURCE END ////////////////////////////////////////

#endif // NOB_IMPLEMENTATION

#ifndef NOB_STRIP_PREFIX_GUARD_
#define NOB_STRIP_PREFIX_GUARD_
    // NOTE: The name stripping should be part of the header so it's not accidentally included
    // several times. At the same time, it should be at the end of the file so to not create any
    // potential conflicts in the NOB_IMPLEMENTATION. The header obviously cannot be at the end
    // of the file because NOB_IMPLEMENTATION needs the forward declarations from there. So the
    // solution is to split the header into two parts where the name stripping part is at the
    // end of the file after the NOB_IMPLEMENTATION.
    #ifdef NOB_STRIP_PREFIX
        #define TODO NOB_TODO
        #define UNREACHABLE NOB_UNREACHABLE
        #define UNUSED NOB_UNUSED
        #define ARRAY_LEN NOB_ARRAY_LEN
        #define ARRAY_GET NOB_ARRAY_GET
        #define INFO NOB_INFO
        #define WARNING NOB_WARNING
        #define ERROR NOB_ERROR
        #define NO_LOGS NOB_NO_LOGS
        #define Log_Level Nob_Log_Level
        #define minimal_log_level nob_minimal_log_level
        // NOTE: Name log is already defined in math.h and historically always was the natural logarithmic function.
        // So there should be no reason to strip the `nob_` prefix in this specific case.
        // #define log nob_log
        #define shift nob_shift
        #define shift_args nob_shift_args
        #define File_Paths Nob_File_Paths
        #define FILE_REGULAR NOB_FILE_REGULAR
        #define FILE_DIRECTORY NOB_FILE_DIRECTORY
        #define FILE_SYMLINK NOB_FILE_SYMLINK
        #define FILE_OTHER NOB_FILE_OTHER
        #define File_Type Nob_File_Type
        #define mkdir_if_not_exists nob_mkdir_if_not_exists
        #define copy_file nob_copy_file
        #define copy_directory_recursively nob_copy_directory_recursively
        #define read_entire_dir nob_read_entire_dir
        #define write_entire_file nob_write_entire_file
        #define get_file_type nob_get_file_type
        #define delete_file nob_delete_file
        #define return_defer nob_return_defer
        #define da_append nob_da_append
        #define da_free nob_da_free
        #define da_append_many nob_da_append_many
        #define da_resize nob_da_resize
        #define da_reserve nob_da_reserve
        #define da_last nob_da_last
        #define da_remove_unordered nob_da_remove_unordered
        #define da_foreach nob_da_foreach
        #define String_Builder Nob_String_Builder
        #define read_entire_file nob_read_entire_file
        #define sb_appendf nob_sb_appendf
        #define sb_append_buf nob_sb_append_buf
        #define sb_append_cstr nob_sb_append_cstr
        #define sb_append_null nob_sb_append_null
        #define sb_free nob_sb_free
        #define Proc Nob_Proc
        #define INVALID_PROC NOB_INVALID_PROC
        #define Fd Nob_Fd
        #define INVALID_FD NOB_INVALID_FD
        #define fd_open_for_read nob_fd_open_for_read
        #define fd_open_for_write nob_fd_open_for_write
        #define fd_close nob_fd_close
        #define Procs Nob_Procs
        #define proc_wait nob_proc_wait
        #define procs_wait nob_procs_wait
        #define procs_wait_and_reset nob_procs_wait_and_reset
        #define procs_append_with_flush nob_procs_append_with_flush
        #define Cmd Nob_Cmd
        #define Cmd_Redirect Nob_Cmd_Redirect
        #define cmd_render nob_cmd_render
        #define cmd_append nob_cmd_append
        #define cmd_extend nob_cmd_extend
        #define cmd_free nob_cmd_free
        #define cmd_run_async nob_cmd_run_async
        #define cmd_run_async_and_reset nob_cmd_run_async_and_reset
        #define cmd_run_async_redirect nob_cmd_run_async_redirect
        #define cmd_run_async_redirect_and_reset nob_cmd_run_async_redirect_and_reset
        #define cmd_run_sync nob_cmd_run_sync
        #define cmd_run_sync_and_reset nob_cmd_run_sync_and_reset
        #define cmd_run_sync_redirect nob_cmd_run_sync_redirect
        #define cmd_run_sync_redirect_and_reset nob_cmd_run_sync_redirect_and_reset
        #define temp_strdup nob_temp_strdup
        #define temp_alloc nob_temp_alloc
        #define temp_sprintf nob_temp_sprintf
        #define temp_reset nob_temp_reset
        #define temp_save nob_temp_save
        #define temp_rewind nob_temp_rewind
        #define path_name nob_path_name
        // NOTE: rename(2) is widely known POSIX function. We never wanna collide with it.
        // #define rename nob_rename
        #define needs_rebuild nob_needs_rebuild
        #define needs_rebuild1 nob_needs_rebuild1
        #define file_exists nob_file_exists
        #define get_current_dir_temp nob_get_current_dir_temp
        #define set_current_dir nob_set_current_dir
        #define String_View Nob_String_View
        #define temp_sv_to_cstr nob_temp_sv_to_cstr
        #define sv_chop_by_delim nob_sv_chop_by_delim
        #define sv_chop_left nob_sv_chop_left
        #define sv_trim nob_sv_trim
        #define sv_trim_left nob_sv_trim_left
        #define sv_trim_right nob_sv_trim_right
        #define sv_eq nob_sv_eq
        #define sv_starts_with nob_sv_starts_with
        #define sv_end_with nob_sv_end_with
        #define sv_from_cstr nob_sv_from_cstr
        #define sv_from_parts nob_sv_from_parts
        #define sb_to_sv nob_sb_to_sv
        #define win32_error_message nob_win32_error_message
    #endif // NOB_STRIP_PREFIX
#endif // NOB_STRIP_PREFIX_GUARD_

/*
   Revision history:

     1.20.6 (2025-05-16) Never strip nob_* suffix from nob_rename (By @rexim)
     1.20.5 (2025-05-16) NOB_PRINTF_FORMAT() support for MinGW (By @KillerxDBr)
     1.20.4 (2025-05-16) More reliable rendering of the Windows command (By @vylsaz)
     1.20.3 (2025-05-16) Add check for __clang__ along with _MSC_VER checks (By @nashiora)
     1.20.2 (2025-04-24) Report the program name that failed to start up in nob_cmd_run_async_redirect() (By @rexim)
     1.20.1 (2025-04-16) Use vsnprintf() in nob_sb_appendf() instead of vsprintf() (By @LainLayer)
     1.20.0 (2025-04-16) Introduce nob_cc(), nob_cc_flags(), nob_cc_inputs(), nob_cc_output() macros (By @rexim)
     1.19.0 (2025-03-25) Add nob_procs_append_with_flush() (By @rexim and @anion155)
     1.18.0 (2025-03-24) Add nob_da_foreach() (By @rexim)
                         Allow file sizes greater than 2GB to be read on windows (By @satchelfrost and @KillerxDBr)
                         Fix nob_fd_open_for_write behaviour on windows so it truncates the opened files (By @twixuss)
     1.17.0 (2025-03-16) Factor out nob_da_reserve() (By @rexim)
                         Add nob_sb_appendf() (By @angelcaru)
     1.16.1 (2025-03-16) Make nob_da_resize() exponentially grow capacity similar to no_da_append_many()
     1.16.0 (2025-03-16) Introduce NOB_PRINTF_FORMAT
     1.15.1 (2025-03-16) Make nob.h compilable in gcc/clang with -std=c99 on POSIX. This includes:
                           not using strsignal()
                           using S_IS* stat macros instead of S_IF* flags
     1.15.0 (2025-03-03) Add nob_sv_chop_left()
     1.14.1 (2025-03-02) Add NOB_EXPERIMENTAL_DELETE_OLD flag that enables deletion of nob.old in Go Rebuild Urself™ Technology
     1.14.0 (2025-02-17) Add nob_da_last()
                         Add nob_da_remove_unordered()
     1.13.1 (2025-02-17) Fix segfault in nob_delete_file() (By @SileNce5k)
     1.13.0 (2025-02-11) Add nob_da_resize() (By @satchelfrost)
     1.12.0 (2025-02-04) Add nob_delete_file()
                         Add nob_sv_start_with()
     1.11.0 (2025-02-04) Add NOB_GO_REBUILD_URSELF_PLUS() (By @rexim)
     1.10.0 (2025-02-04) Make NOB_ASSERT, NOB_REALLOC, and NOB_FREE redefinable (By @OleksiiBulba)
      1.9.1 (2025-02-04) Fix signature of nob_get_current_dir_temp() (By @julianstoerig)
      1.9.0 (2024-11-06) Add Nob_Cmd_Redirect mechanism (By @rexim)
                         Add nob_path_name() (By @0dminnimda)
      1.8.0 (2024-11-03) Add nob_cmd_extend() (By @0dminnimda)
      1.7.0 (2024-11-03) Add nob_win32_error_message and NOB_WIN32_ERR_MSG_SIZE (By @KillerxDBr)
      1.6.0 (2024-10-27) Add nob_cmd_run_sync_and_reset()
                         Add nob_sb_to_sv()
                         Add nob_procs_wait_and_reset()
      1.5.1 (2024-10-25) Include limits.h for Linux musl libc (by @pgalkin)
      1.5.0 (2024-10-23) Add nob_get_current_dir_temp()
                         Add nob_set_current_dir()
      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)
                         Add nob_sv_end_with (By @pgalkin)
      1.3.2 (2024-10-21) Fix unreachable error in nob_log on passing NOB_NO_LOGS
      1.3.1 (2024-10-21) Fix redeclaration error for minimal_log_level (By @KillerxDBr)
      1.3.0 (2024-10-17) Add NOB_UNREACHABLE
      1.2.2 (2024-10-16) Fix compilation of nob_cmd_run_sync_and_reset on Windows (By @KillerxDBr)
      1.2.1 (2024-10-16) Add a separate include guard for NOB_STRIP_PREFIX.
      1.2.0 (2024-10-15) Make NOB_DA_INIT_CAP redefinable
                         Add NOB_STRIP_PREFIX which strips off nob_* prefix from all the user facing names
                         Add NOB_UNUSED macro
                         Add NOB_TODO macro
                         Add nob_sv_trim_left and nob_sv_trim_right declarations to the header part
      1.1.1 (2024-10-15) Remove forward declaration for is_path1_modified_after_path2
      1.1.0 (2024-10-15) nob_minimal_log_level
                         nob_cmd_run_sync_and_reset
      1.0.0 (2024-10-15) first release based on https://github.com/tsoding/musializer/blob/4ac7cce9874bc19e02d8c160c8c6229de8919401/nob.h
*/

/*
   Version Conventions:

      We are following https://semver.org/ so the version has a format MAJOR.MINOR.PATCH:
      - Modifying comments does not update the version.
      - PATCH is incremented in case of a bug fix or refactoring without touching the API.
      - MINOR is incremented when new functions and/or types are added in a way that does
        not break any existing user code. We want to do this in the majority of the situation.
        If we want to delete a certain function or type in favor of another one we should
        just add the new function/type and deprecate the old one in a backward compatible way
        and let them co-exist for a while.
      - MAJOR update should be just a periodic cleanup of the deprecated functions and types
        without really modifying any existing functionality.

   Naming Conventions:

      - All the user facing names should be prefixed with `nob_` or `NOB_` depending on the case.
      - The prefixes of non-redefinable names should be strippable with NOB_STRIP_PREFIX (unless
        explicitly stated otherwise like in case of nob_log).
      - Internal functions should be prefixed with `nob__` (double underscore).
*/

/*
   ------------------------------------------------------------------------------
   This software is available under 2 licenses -- choose whichever you prefer.
   ------------------------------------------------------------------------------
   ALTERNATIVE A - MIT License
   Copyright (c) 2024 Alexey Kutepov
   Permission is hereby granted, free of charge, to any person obtaining a copy of
   this software and associated documentation files (the "Software"), to deal in
   the Software without restriction, including without limitation the rights to
   use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
   of the Software, and to permit persons to whom the Software is furnished to do
   so, subject to the following conditions:
   The above copyright notice and this permission notice shall be included in all
   copies or substantial portions of the Software.
   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
   SOFTWARE.
   ------------------------------------------------------------------------------
   ALTERNATIVE B - Public Domain (www.unlicense.org)
   This is free and unencumbered software released into the public domain.
   Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
   software, either in source code form or as a compiled binary, for any purpose,
   commercial or non-commercial, and by any means.
   In jurisdictions that recognize copyright laws, the author or authors of this
   software dedicate any and all copyright interest in the software to the public
   domain. We make this dedication for the benefit of the public at large and to
   the detriment of our heirs and successors. We intend this dedication to be an
   overt act of relinquishment in perpetuity of all present and future rights to
   this software under copyright law.
   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
   WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   ------------------------------------------------------------------------------
*/
Download .txt
gitextract_k3i8e3c7/

├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── examples/
│   ├── .gitignore
│   ├── 01_from_readme.c
│   ├── 02_binary_tree.c
│   ├── 03_parsing_database.c
│   ├── Makefile
│   ├── database.json
│   └── fruits.h
├── jim1.h
├── jim2.h
├── jimp.h
├── test_jim.c
├── test_jim_expected.h
└── thirdparty/
    └── nob.h
Download .txt
SYMBOL INDEX (179 symbols across 8 files)

FILE: examples/01_from_readme.c
  function main (line 6) | int main()

FILE: examples/02_binary_tree.c
  type Node (line 10) | typedef struct Node Node;
  type Node (line 12) | struct Node {
  function Node (line 18) | Node *generate_tree_of_fruits(size_t level_cur, size_t level_max)
  function render_node_as_json (line 35) | void render_node_as_json(Jim *jim, Node *node)
  function main (line 53) | int main()

FILE: examples/03_parsing_database.c
  type Person (line 9) | typedef struct {
  type People (line 16) | typedef struct {
  function parse_person (line 22) | bool parse_person(Jimp *jimp, Person *p)
  function parse_people (line 46) | bool parse_people(Jimp *jimp, People *ps)
  function print_person (line 59) | void print_person(const Person *p)
  type Numbers (line 67) | typedef struct {
  function main (line 73) | int main(void)

FILE: jim1.h
  type Jim_Error (line 21) | typedef enum {
  type Jim_Scope_Kind (line 32) | typedef enum {
  type Jim_Scope (line 37) | typedef struct {
  type Jim (line 43) | typedef struct {
  function jim_strlen (line 73) | static size_t jim_strlen(const char *s)
  function jim_scope_push (line 82) | static void jim_scope_push(Jim *jim, Jim_Scope_Kind kind)
  function jim_scope_pop (line 96) | static void jim_scope_pop(Jim *jim)
  function Jim_Scope (line 107) | static Jim_Scope *jim_current_scope(Jim *jim)
  function jim_write (line 118) | static void jim_write(Jim *jim, const char *buffer, size_t size)
  function jim_write_cstr (line 127) | static void jim_write_cstr(Jim *jim, const char *cstr)
  function jim_get_utf8_char_len (line 134) | static int jim_get_utf8_char_len(unsigned char ch)
  function jim_element_begin (line 147) | void jim_element_begin(Jim *jim)
  function jim_element_end (line 157) | void jim_element_end(Jim *jim)
  function jim_null (line 189) | void jim_null(Jim *jim)
  function jim_bool (line 198) | void jim_bool(Jim *jim, int boolean)
  function jim_integer_no_element (line 211) | static void jim_integer_no_element(Jim *jim, long long int x)
  function jim_integer (line 242) | void jim_integer(Jim *jim, long long int x)
  function is_nan_or_inf (line 251) | static int is_nan_or_inf(double x)
  function jim_float (line 257) | void jim_float(Jim *jim, double x, int precision)
  function jim_string_sized_no_element (line 283) | static void jim_string_sized_no_element(Jim *jim, const char *str, size_...
  function jim_string_sized (line 317) | void jim_string_sized(Jim *jim, const char *str, size_t size)
  function jim_string (line 326) | void jim_string(Jim *jim, const char *str)
  function jim_array_begin (line 333) | void jim_array_begin(Jim *jim)
  function jim_array_end (line 343) | void jim_array_end(Jim *jim)
  function jim_object_begin (line 352) | void jim_object_begin(Jim *jim)
  function jim_member_key (line 361) | void jim_member_key(Jim *jim, const char *str)
  function jim_member_key_sized (line 368) | void jim_member_key_sized(Jim *jim, const char *str, size_t size)
  function jim_object_end (line 387) | void jim_object_end(Jim *jim)

FILE: jim2.h
  type Jim_Scope_Kind (line 20) | typedef enum {
  type Jim_Scope (line 25) | typedef struct {
  type Jim (line 31) | typedef struct {
  function jim_scope_push (line 65) | static void jim_scope_push(Jim *jim, Jim_Scope_Kind kind)
  function jim_scope_pop (line 79) | static void jim_scope_pop(Jim *jim)
  function Jim_Scope (line 85) | static Jim_Scope *jim_current_scope(Jim *jim)
  function jim_write (line 93) | static void jim_write(Jim *jim, const char *buffer, size_t size)
  function jim_write_cstr (line 105) | static void jim_write_cstr(Jim *jim, const char *cstr)
  function jim_get_utf8_char_len (line 110) | static int jim_get_utf8_char_len(unsigned char ch)
  function jim_begin (line 123) | void jim_begin(Jim *jim)
  function jim_element_begin (line 129) | void jim_element_begin(Jim *jim)
  function jim_element_end (line 149) | void jim_element_end(Jim *jim)
  function jim_null (line 158) | void jim_null(Jim *jim)
  function jim_bool (line 165) | void jim_bool(Jim *jim, int boolean)
  function jim_integer_no_element (line 176) | static void jim_integer_no_element(Jim *jim, long long int x)
  function jim_integer (line 204) | void jim_integer(Jim *jim, long long int x)
  function is_nan_or_inf (line 211) | static int is_nan_or_inf(double x)
  function jim_float (line 217) | void jim_float(Jim *jim, double x, int precision)
  function jim_string_sized_no_element (line 241) | static void jim_string_sized_no_element(Jim *jim, const char *str, size_...
  function jim_string_sized (line 273) | void jim_string_sized(Jim *jim, const char *str, size_t size)
  function jim_string (line 280) | void jim_string(Jim *jim, const char *str)
  function jim_array_begin (line 285) | void jim_array_begin(Jim *jim)
  function jim_array_end (line 293) | void jim_array_end(Jim *jim)
  function jim_object_begin (line 307) | void jim_object_begin(Jim *jim)
  function jim_member_key (line 314) | void jim_member_key(Jim *jim, const char *str)
  function jim_member_key_sized (line 319) | void jim_member_key_sized(Jim *jim, const char *str, size_t size)
  function jim_object_end (line 331) | void jim_object_end(Jim *jim)

FILE: jimp.h
  type Jimp_Token (line 16) | typedef enum {
  type Jimp (line 38) | typedef struct {
  function jimp__append_to_string (line 116) | static void jimp__append_to_string(Jimp *jimp, char x)
  function jimp__skip_whitespaces (line 126) | static void jimp__skip_whitespaces(Jimp *jimp)
  function jimp__get_token (line 152) | static bool jimp__get_token(Jimp *jimp)
  function jimp_begin (line 256) | void jimp_begin(Jimp *jimp, const char *file_path, const char *input, si...
  function jimp_diagf (line 264) | void jimp_diagf(Jimp *jimp, const char *fmt, ...)
  function jimp_array_begin (line 305) | bool jimp_array_begin(Jimp *jimp)
  function jimp_array_end (line 310) | bool jimp_array_end(Jimp *jimp)
  function jimp_array_item (line 315) | bool jimp_array_item(Jimp *jimp)
  function jimp_unknown_member (line 328) | void jimp_unknown_member(Jimp *jimp)
  function jimp_object_begin (line 333) | bool jimp_object_begin(Jimp *jimp)
  function jimp_object_member (line 338) | bool jimp_object_member(Jimp *jimp)
  function jimp_object_end (line 356) | bool jimp_object_end(Jimp *jimp)
  function jimp_string (line 361) | bool jimp_string(Jimp *jimp)
  function jimp_bool (line 366) | bool jimp_bool(Jimp *jimp)
  function jimp_number (line 380) | bool jimp_number(Jimp *jimp)
  function jimp_is_null_ahead (line 385) | bool jimp_is_null_ahead(Jimp *jimp)
  function jimp_is_bool_ahead (line 393) | bool jimp_is_bool_ahead(Jimp *jimp)
  function jimp_is_number_ahead (line 401) | bool jimp_is_number_ahead(Jimp *jimp)
  function jimp_is_string_ahead (line 409) | bool jimp_is_string_ahead(Jimp *jimp)
  function jimp_is_array_ahead (line 417) | bool jimp_is_array_ahead(Jimp *jimp)
  function jimp_is_object_ahead (line 425) | bool jimp_is_object_ahead(Jimp *jimp)
  function jimp__get_and_expect_token (line 433) | static bool jimp__get_and_expect_token(Jimp *jimp, Jimp_Token token)
  function jimp__expect_token (line 439) | static bool jimp__expect_token(Jimp *jimp, Jimp_Token token)

FILE: test_jim.c
  function null_case (line 13) | void null_case(Jim *jim)
  function bool_case (line 21) | void bool_case(Jim *jim)
  function integer_case (line 30) | void integer_case(Jim *jim)
  function float_case (line 40) | void float_case(Jim *jim)
  function string_case (line 55) | void string_case(Jim *jim)
  function array_case (line 66) | void array_case(Jim *jim)
  function object_case_rec (line 79) | void object_case_rec(Jim *jim, int level, int *counter)
  function object_case (line 93) | void object_case(Jim *jim)
  function pp_empty_array_case (line 100) | void pp_empty_array_case(Jim *jim)
  function pp_singleton_array_case (line 108) | void pp_singleton_array_case(Jim *jim)
  function pp_array_case (line 117) | void pp_array_case(Jim *jim)
  function pp_empty_object_case (line 129) | void pp_empty_object_case(Jim *jim)
  function pp_singleton_object_case (line 137) | void pp_singleton_object_case(Jim *jim)
  function pp_object_case (line 147) | void pp_object_case(Jim *jim)
  function pp_nested_case (line 161) | void pp_nested_case(Jim *jim)
  type Test_Case (line 195) | typedef struct {
  function record (line 223) | bool record(const char *header_path)
  function test (line 243) | void test(void)
  function main (line 269) | int main(int argc, char **argv)

FILE: thirdparty/nob.h
  type Nob_Log_Level (line 235) | typedef enum {
  type Nob_File_Paths (line 256) | typedef struct {
  type Nob_File_Type (line 262) | typedef enum {
  type Nob_String_Builder (line 352) | typedef struct {
  type HANDLE (line 381) | typedef HANDLE Nob_Proc;
  type HANDLE (line 383) | typedef HANDLE Nob_Fd;
  type Nob_Proc (line 386) | typedef int Nob_Proc;
  type Nob_Fd (line 388) | typedef int Nob_Fd;
  type Nob_Procs (line 396) | typedef struct {
  type Nob_Cmd (line 412) | typedef struct {
  type Nob_Cmd_Redirect (line 431) | typedef struct {
  type Nob_String_View (line 595) | typedef struct {
  type dirent (line 670) | struct dirent
  type DIR (line 675) | typedef struct DIR DIR;
  type dirent (line 678) | struct dirent
  function nob__go_rebuild_urself (line 738) | void nob__go_rebuild_urself(int argc, char **argv, const char *source_pa...
  function nob_mkdir_if_not_exists (line 793) | bool nob_mkdir_if_not_exists(const char *path)
  function nob_copy_file (line 813) | bool nob_copy_file(const char *src_path, const char *dst_path)
  function nob_cmd_render (line 875) | void nob_cmd_render(Nob_Cmd cmd, Nob_String_Builder *render)
  function nob__win32_cmd_quote (line 893) | static void nob__win32_cmd_quote(Nob_Cmd cmd, Nob_String_Builder *quoted)
  function Nob_Proc (line 934) | Nob_Proc nob_cmd_run_async_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect)
  function Nob_Proc (line 1024) | Nob_Proc nob_cmd_run_async_and_reset(Nob_Cmd *cmd)
  function Nob_Proc (line 1031) | Nob_Proc nob_cmd_run_async_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redi...
  function Nob_Fd (line 1050) | Nob_Fd nob_fd_open_for_read(const char *path)
  function Nob_Fd (line 1083) | Nob_Fd nob_fd_open_for_write(const char *path)
  function nob_fd_close (line 1118) | void nob_fd_close(Nob_Fd fd)
  function nob_procs_wait (line 1127) | bool nob_procs_wait(Nob_Procs procs)
  function nob_procs_wait_and_reset (line 1136) | bool nob_procs_wait_and_reset(Nob_Procs *procs)
  function nob_proc_wait (line 1143) | bool nob_proc_wait(Nob_Proc proc)
  function nob_procs_append_with_flush (line 1200) | bool nob_procs_append_with_flush(Nob_Procs *procs, Nob_Proc proc, size_t...
  function nob_cmd_run_sync_redirect (line 1211) | bool nob_cmd_run_sync_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect)
  function nob_cmd_run_sync (line 1218) | bool nob_cmd_run_sync(Nob_Cmd cmd)
  function nob_cmd_run_sync_and_reset (line 1225) | bool nob_cmd_run_sync_and_reset(Nob_Cmd *cmd)
  function nob_cmd_run_sync_redirect_and_reset (line 1232) | bool nob_cmd_run_sync_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect ...
  function nob_log (line 1251) | void nob_log(Nob_Log_Level level, const char *fmt, ...)
  function nob_read_entire_dir (line 1277) | bool nob_read_entire_dir(const char *parent, Nob_File_Paths *children)
  function nob_write_entire_file (line 1313) | bool nob_write_entire_file(const char *path, const void *data, size_t size)
  function Nob_File_Type (line 1345) | Nob_File_Type nob_get_file_type(const char *path)
  function nob_delete_file (line 1371) | bool nob_delete_file(const char *path)
  function nob_copy_directory_recursively (line 1389) | bool nob_copy_directory_recursively(const char *src_path, const char *ds...
  function nob_temp_reset (line 1489) | void nob_temp_reset(void)
  function nob_temp_save (line 1494) | size_t nob_temp_save(void)
  function nob_temp_rewind (line 1499) | void nob_temp_rewind(size_t checkpoint)
  function nob_needs_rebuild (line 1513) | int nob_needs_rebuild(const char *output_path, const char **input_paths,...
  function nob_needs_rebuild1 (line 1581) | int nob_needs_rebuild1(const char *output_path, const char *input_path)
  function nob_rename (line 1599) | bool nob_rename(const char *old_path, const char *new_path)
  function nob_read_entire_file (line 1616) | bool nob_read_entire_file(const char *path, Nob_String_Builder *sb)
  function nob_sb_appendf (line 1651) | int nob_sb_appendf(Nob_String_Builder *sb, const char *fmt, ...)
  function Nob_String_View (line 1673) | Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim)
  function Nob_String_View (line 1693) | Nob_String_View nob_sv_chop_left(Nob_String_View *sv, size_t n)
  function Nob_String_View (line 1707) | Nob_String_View nob_sv_from_parts(const char *data, size_t count)
  function Nob_String_View (line 1715) | Nob_String_View nob_sv_trim_left(Nob_String_View sv)
  function Nob_String_View (line 1725) | Nob_String_View nob_sv_trim_right(Nob_String_View sv)
  function Nob_String_View (line 1735) | Nob_String_View nob_sv_trim(Nob_String_View sv)
  function Nob_String_View (line 1740) | Nob_String_View nob_sv_from_cstr(const char *cstr)
  function nob_sv_eq (line 1745) | bool nob_sv_eq(Nob_String_View a, Nob_String_View b)
  function nob_sv_end_with (line 1754) | bool nob_sv_end_with(Nob_String_View sv, const char *cstr)
  function nob_sv_starts_with (line 1766) | bool nob_sv_starts_with(Nob_String_View sv, Nob_String_View expected_pre...
  function nob_file_exists (line 1780) | int nob_file_exists(const char *file_path)
  function nob_set_current_dir (line 1824) | bool nob_set_current_dir(const char *path)
  type DIR (line 1843) | struct DIR
  function DIR (line 1850) | DIR *opendir(const char *dirpath)
  type dirent (line 1878) | struct dirent
  type dirent (line 1883) | struct dirent
  type dirent (line 1883) | struct dirent
  type dirent (line 1884) | struct dirent
  function closedir (line 1907) | int closedir(DIR *dirp)
Condensed preview — 18 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (136K chars).
[
  {
    "path": ".github/workflows/ci.yml",
    "chars": 783,
    "preview": "name: CI\non: [push, pull_request]\n\njobs:\n  build-linux-gcc:\n    runs-on: ubuntu-18.04\n    steps:\n      - uses: actions/c"
  },
  {
    "path": ".gitignore",
    "chars": 22,
    "preview": "example\ntest_jim\njimp\n"
  },
  {
    "path": "LICENSE",
    "chars": 1074,
    "preview": "Copyright 2021 Alexey Kutepov <reximkut@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtainin"
  },
  {
    "path": "Makefile",
    "chars": 213,
    "preview": "CFLAGS=-Wall -Wextra -Wswitch-enum -ggdb -I./thirdparty/\n\n.PHONY: all\nall: examples test_jim\n\ntest_jim: test_jim.c jim.h"
  },
  {
    "path": "README.md",
    "chars": 3054,
    "preview": "# JIM\n\nImmediate Mode JSON Serialization Library in C. Similar to [imgui](https://github.com/ocornut/imgui) but for gene"
  },
  {
    "path": "examples/.gitignore",
    "chars": 49,
    "preview": "01_from_readme\n02_binary_tree\n03_parsing_database"
  },
  {
    "path": "examples/01_from_readme.c",
    "chars": 1833,
    "preview": "#include <stdio.h>\n\n#define JIM_IMPLEMENTATION\n#include \"../jim.h\"\n\nint main()\n{\n    Jim jim = {.pp = 4};\n\n    jim_objec"
  },
  {
    "path": "examples/02_binary_tree.c",
    "chars": 1373,
    "preview": "#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n\n#define JIM_IMPLEMENTATION\n#include \"../jim.h\"\n\n#include \"frui"
  },
  {
    "path": "examples/03_parsing_database.c",
    "chars": 2981,
    "preview": "#include <stdio.h>\n#include <stdbool.h>\n#define NOB_IMPLEMENTATION\n#define NOB_STRIP_PREFIX\n#include \"../thirdparty/nob."
  },
  {
    "path": "examples/Makefile",
    "chars": 444,
    "preview": "CFLAGS=-Wall -Wextra -Wswitch-enum -ggdb\n\n.PHONY: all\nall: 01_from_readme 02_binary_tree 03_parsing_database\n\n01_from_re"
  },
  {
    "path": "examples/database.json",
    "chars": 1595,
    "preview": "{\n    \"number\": [69, 420, 1337, 80085],\n    \"profile\": [\n        {\n            \"location\": \"Wonderland\",\n            \"lo"
  },
  {
    "path": "examples/fruits.h",
    "chars": 1795,
    "preview": "#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[] "
  },
  {
    "path": "jim1.h",
    "chars": 9692,
    "preview": "// Jim 1.0\n//\n// This is a legacy version of jim which uses\n// - Fixed array for scopes,\n// - Jim_Sink/Jim_Write interfa"
  },
  {
    "path": "jim2.h",
    "chars": 8812,
    "preview": "// Jim 2.0\n//\n// Current version of Jim. Main differences from Jim 1.0 are\n// - Using Dynamic Arrays for scopes allowing"
  },
  {
    "path": "jimp.h",
    "chars": 12640,
    "preview": "// Prototype of an Immediate Deserialization idea. Expect this API to change a lot.\n#ifndef JIMP_H_\n#define JIMP_H_\n\n#in"
  },
  {
    "path": "test_jim.c",
    "chars": 6678,
    "preview": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#define JIM_IMPLEMENTATION\n#include \"./jim.h\"\n#define NOB_IM"
  },
  {
    "path": "test_jim_expected.h",
    "chars": 937,
    "preview": "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"
  },
  {
    "path": "thirdparty/nob.h",
    "chars": 75871,
    "preview": "/* nob - v1.20.6 - Public Domain - https://github.com/tsoding/nob.h\n\n   This library is the next generation of the [NoBu"
  }
]

About this extraction

This page contains the full source code of the tsoding/jim GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 18 files (126.8 KB), approximately 35.8k tokens, and a symbol index with 179 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!