summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Moc <personal@cdatgoose.org>2026-03-09 17:40:18 +0100
committerDavid Moc <personal@cdatgoose.org>2026-03-09 17:40:18 +0100
commit1af8b8a568ba1782c7f54c575dd7cbe352e0d4a4 (patch)
tree098fa8507bf30537a1b4cfa0b2389f5f142b17cb
Pushing to repo.HEADmaster
-rw-r--r--Makefile14
-rw-r--r--README.md353
-rw-r--r--main.c13
-rwxr-xr-xsetup.sh6
-rw-r--r--src/lib/arglist.h51
-rw-r--r--src/lib/cb_py.h1404
-rw-r--r--src/lib/cbuild.h679
-rw-r--r--src/lib/stb_cbuild.h1368
-rw-r--r--src/main.c50
9 files changed, 3938 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..6386603
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,14 @@
+CC=cc
+FILES=src/main.c
+
+all: main
+
+main: $(FILES)
+ $(CC) $(FILES) -D_POSIX_C_SOURCE=200809L -D_GNU_SOURCE -std=c99 -lssl -lcrypto -o bin/main
+
+clean:
+ rm -f bin/main
+
+
+
+.PHONY: all clean
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6dee142
--- /dev/null
+++ b/README.md
@@ -0,0 +1,353 @@
+# C BUILD
+
+## Description
+
+A lightweight single-header build system library for managing **one or more C projects** — without Makefiles.
+
+It lets you:
+
+* Define build logic directly in C.
+* Build **sequentially** or **in parallel**.
+* Run executables right after building.
+* Automatically handle **self-rebuilding** build scripts.
+* Use **wildcards** for file lists.
+* Extend with your own macros.
+
+---
+
+## Installation
+
+```bash
+wget https://raw.githubusercontent.com/AnAnnoyinGoose/cbuild/refs/heads/main/src/lib/stb_cbuild.h -O cbuild.h
+sudo mkdir -p /usr/local/include/cbuild
+sudo cp cbuild.h /usr/local/include/cbuild
+```
+
+Now include it in your projects:
+
+```c
+#include <cbuild/cbuild.h>
+```
+
+---
+
+## Showcase
+
+This section shows **all functionality** of CBuild.
+
+---
+
+### 1. A simple project
+
+```c
+#include <cbuild/cbuild.h>
+
+static _CB_PROJECT *hello = {0};
+
+int main(void) {
+ _CB_CREATE_PROJECT(hello,
+ .name = "Hello",
+ .files = CB_STRLIST("hello.c"),
+ .output = "hello"
+ );
+
+ _CB_PROJECT_BUILD(.projects = CB_PROJECT_LIST(hello), .run = 1);
+}
+```
+
+Builds `hello.c`, produces `./hello`, and runs it.
+
+---
+
+### 2. Rebuild mode
+
+```c
+#include <cbuild/cbuild.h>
+#include <stdio.h>
+
+static _CB_PROJECT *self = {0};
+
+int main(void) {
+ _CB_CREATE_PROJECT(self,
+ .name = "Rebuild",
+ .files = CB_STRLIST("main.c"),
+ .output = "rebuild",
+ .is_rebuild = 1
+ );
+
+ _CB_PROJECT_BUILD(.projects = CB_PROJECT_LIST(self), .run = 1);
+
+ printf("You will never see this if rebuild triggers.\n");
+}
+```
+
+With `.is_rebuild = 1`:
+
+* Builds the project.
+* Runs it.
+* Exits the whole process immediately.
+
+---
+
+### 3. Multiple projects
+
+```c
+#include <cbuild/cbuild.h>
+
+static _CB_PROJECT *a = {0};
+static _CB_PROJECT *b = {0};
+static _CB_PROJECT *c = {0};
+
+int main(void) {
+ _CB_CREATE_PROJECT(a, .name = "A", .files = CB_STRLIST("a.c"), .output = "a");
+ _CB_CREATE_PROJECT(b, .name = "B", .files = CB_STRLIST("b.c"), .output = "b");
+ _CB_CREATE_PROJECT(c, .name = "C", .files = CB_STRLIST("c.c"), .output = "c");
+
+ _CB_PROJECT_BUILD(.projects = CB_PROJECT_LIST(a, b, c), .run = 1);
+}
+```
+
+Builds projects **sequentially** and runs them in order.
+
+---
+
+### 4. Parallel builds
+
+```c
+_CB_PROJECT_BUILD(
+ .projects = CB_PROJECT_LIST(a, b, c),
+ .run = 0,
+ .parallel = 3
+);
+```
+
+Builds 3 projects at the same time, **without running them**.
+
+---
+
+### 5. Wildcards
+
+```c
+_CB_CREATE_PROJECT(a,
+ .name = "Wildcard Example",
+ .files = CB_STRLIST("src/*.c"), // expands to all C files in src/
+ .output = "app"
+);
+```
+
+Automatically expands `*.c` into a list of files.
+
+---
+
+### 6. Build flags
+
+```c
+_CB_CREATE_PROJECT(crypto,
+ .name = "CryptoTool",
+ .files = CB_STRLIST("main.c"),
+ .output = "cryptotool",
+ .buildflags = CB_STRLIST("-lssl -lcrypto")
+);
+```
+
+Adds extra compiler/linker flags.
+*(Rebuild projects automatically add `-lssl -lcrypto`.)*
+
+---
+
+### 7. Dumping project info
+
+```c
+cb_dump_to_console(crypto);
+```
+
+Prints details of the project (name, files, output, flags).
+
+---
+
+### 8. Freeing resources
+
+```c
+cb_free_project(crypto);
+```
+
+Frees memory used by a project.
+
+---
+
+## API Reference
+
+### Macros
+
+```c
+#define CB_DEBUG // Enable debug logging
+#define _CB_LOG_TO_FILE // Write logs to cbuild.log
+
+MACRO CB_DEBUG_LOG(fmt, ...);
+MACRO _CB_PROJECT_BUILD(projects, run, parallel, run_if_skipped);
+MACRO _CB_CREATE_PROJECT(name, output, CB_STRLIST(files), CB_STRLIST(buildflags), CB_STRLIST(flags), is_rebuild);
+MACRO CB_PROJECT_LIST(...);
+MACRO CB_STRLIST(...);
+```
+
+### Types
+
+```c
+TYPE _CB_PROJECT // Represents a single project
+```
+
+### Functions
+
+```c
+static void cb_dump_to_console(const _CB_PROJECT*); // Print project info
+static void cb_free_project(const _CB_PROJECT*); // Free memory
+```
+
+---
+
+## Full Demo Program
+
+This example uses **all features at once**:
+
+```c
+#include <cbuild/cbuild.h>
+#include <stdio.h>
+
+static _CB_PROJECT *self = {0};
+static _CB_PROJECT *lib = {0};
+static _CB_PROJECT *tool = {0};
+static _CB_PROJECT *tests = {0};
+
+int main(void) {
+ // Self-rebuild project (bootstrap)
+ _CB_CREATE_PROJECT(self,
+ .name = "BuildScript",
+ .files = CB_STRLIST("main.c"),
+ .output = "rebuild",
+ .is_rebuild = 1
+ );
+
+ // Library project with wildcards
+ _CB_CREATE_PROJECT(lib,
+ .name = "MyLibrary",
+ .files = CB_STRLIST("src/*.c"), // collect all .c files
+ .output = "libmylib.a"
+ );
+
+ // Tool project with custom flags
+ _CB_CREATE_PROJECT(tool,
+ .name = "Tool",
+ .files = CB_STRLIST("tool.c"),
+ .output = "tool",
+ .buildflags = CB_STRLIST("-lssl -lcrypto")
+ );
+
+ // Test suite project
+ _CB_CREATE_PROJECT(tests,
+ .name = "Tests",
+ .files = CB_STRLIST("tests/*.c"),
+ .output = "tests"
+ );
+
+ // Print project info
+ cb_dump_to_console(lib);
+ cb_dump_to_console(tool);
+
+ // Build all projects, run only tests, build others in parallel
+
+ // Cleanup
+ cb_free_project(lib);
+ cb_free_project(tool);
+ cb_free_project(tests);
+
+ return 0;
+}
+```
+
+What happens here:
+
+1. **Self-rebuilds** the build script.
+2. Builds a **library** from all `src/*.c`.
+3. Builds a **tool** with extra flags.
+4. Builds and runs a **test suite**.
+5. Uses **parallelism** (`.parallel = 2`).
+6. Dumps project info for debugging.
+7. Frees resources before exiting.
+
+---
+
+
+## Py DSL
+
+This is a [Py DSL](https://en.wikipedia.org/wiki/Domain-specific_language) for CBuild.
+It allows the user to write Python in C projects.
+CBuild translates it to C and compiles it.
+
+### Example
+`main.c`
+```C
+#define _CB_IMPLEMENTATION
+#define CB_DEBUG
+#define _CB_PY // Enable Python support
+#include <cbuild/cbuild.h>
+
+static _CB_PROJECT *this = {0};
+static _CB_PROJECT *CB_PY = {0};
+
+int main(int argc, char *argv[]) {
+ _CB_CREATE_PROJECT(
+ this, .name = "cmpy-rebuild",
+ .files = CB_STRLIST("src/main.c"), .build_type = BUILD_EXEC, .is_rebuild = 1,
+ .output = "bin/cmpy-rebuild",
+ .buildflags =
+ CB_STRLIST("-std=c99 -D_POSIX_C_SOURCE=200809L -D_GNU_SOURCE"));
+ _CB_PROJECT_BUILD(.projects = CB_PROJECT_LIST(this));
+
+ _CB_CREATE_PROJECT(
+ CB_PY, .name = "cmpy",
+ .files = CB_STRLIST("src/cmpy.c"), .build_type = BUILD_EXEC, .CB_PYTHON = 1, // Sets the project to use the DSL
+ .output = "bin/cmpy",
+ .buildflags =
+ CB_STRLIST("-std=c99"));
+ _CB_PROJECT_BUILD(.projects = CB_PROJECT_LIST(CB_PY), .run = 1, .run_if_skipped = 1);
+
+ printf("rebuild complete\n");
+ return 0;
+}
+```
+
+`src/cmpy.c`
+```C
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+int main(void) {
+// __CB_PY_BEGIN
+// x = 1
+// x = 2
+//
+// name = "John"
+// surname = "Doe"
+//
+//
+// for i in range(10):
+// print(x)
+// x = x*2
+// print("Name: ", name, surname)
+// __CB_PY_END
+ return EXIT_SUCCESS;
+}
+```
+
+
+## Notes
+
+* Rebuild projects auto-add `-lssl -lcrypto`.
+* Supports **wildcards**, **parallel builds**, and **macro-based configuration**.
+* Debug logging can be redirected to console or file.
+
+---
+
+## License
+
+MIT License – free to use, modify, and distribute.
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..0b1ca8e
--- /dev/null
+++ b/main.c
@@ -0,0 +1,13 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+int main(int argc, char *argv[])
+{
+ sleep(2);
+ if (argc < 2) {
+ printf("Usage: %s <name>\n", argv[1]);
+ return EXIT_FAILURE;
+ }
+ printf("Hello World from %s\n", argv[1]);
+ return EXIT_SUCCESS;
+}
diff --git a/setup.sh b/setup.sh
new file mode 100755
index 0000000..08e1ffa
--- /dev/null
+++ b/setup.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+src=./src/lib/*
+dst=/usr/include/cbuild/
+
+sudo mkdir -p $dst
+sudo cp $src $dst
diff --git a/src/lib/arglist.h b/src/lib/arglist.h
new file mode 100644
index 0000000..ecd4455
--- /dev/null
+++ b/src/lib/arglist.h
@@ -0,0 +1,51 @@
+#ifndef _CB_ARGLIST_H
+#define _CB_ARGLIST_H
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct {
+ char **list;
+ int count;
+ int capacity;
+} CB_ARGLIST;
+
+static inline CB_ARGLIST *arglist_new() {
+ CB_ARGLIST *arglist = (CB_ARGLIST*) malloc(sizeof(CB_ARGLIST));
+ arglist->count = 0;
+ arglist->capacity = 8;
+ arglist->list = (char**) malloc(sizeof(char *) * arglist->capacity);
+ return arglist;
+}
+
+static inline void arglist_append(CB_ARGLIST *arglist, ...) {
+ va_list args;
+ va_start(args, arglist);
+ char *arg;
+ while ((arg = va_arg(args, char *)) != NULL) {
+ if (arglist->count >= arglist->capacity) {
+ arglist->capacity *= 2;
+ arglist->list = (char**) realloc(arglist->list, sizeof(char *) * arglist->capacity);
+ }
+ arglist->list[arglist->count++] = strdup(arg);
+ }
+ va_end(args);
+}
+
+static inline void arglist_append_array(CB_ARGLIST *arglist, const char **arr) {
+ for (int i = 0; arr[i] != NULL; i++) {
+ arglist_append(arglist, arr[i], NULL);
+ }
+}
+
+static inline void arglist_free(CB_ARGLIST *arglist) {
+ for (int i = 0; i < arglist->count; i++) {
+ free(arglist->list[i]);
+ }
+ free(arglist->list);
+ free(arglist);
+}
+
+#endif // _CB_ARGLIST_H
+
diff --git a/src/lib/cb_py.h b/src/lib/cb_py.h
new file mode 100644
index 0000000..e59ec45
--- /dev/null
+++ b/src/lib/cb_py.h
@@ -0,0 +1,1404 @@
+#ifndef CB_PY_H
+#define CB_PY_H
+
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static inline bool starts_with(const char *s, const char *prefix) {
+ size_t a = strlen(prefix);
+ return strncmp(s, prefix, a) == 0;
+}
+static inline bool ends_with(const char *s, const char *suffix) {
+ size_t ls = strlen(s), lt = strlen(suffix);
+ if (lt > ls)
+ return false;
+ return strcmp(s + (ls - lt), suffix) == 0;
+}
+static int cb_count_indent(const char *s) {
+ int n = 0;
+ for (; *s; s++) {
+ if (*s == ' ')
+ n++;
+ else if (*s == '\t')
+ n += 4;
+ else
+ break;
+ }
+ return n;
+}
+static char *cb_strdup_printf(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ va_list ap2;
+ va_copy(ap2, ap);
+ int n = vsnprintf(NULL, 0, fmt, ap2);
+ va_end(ap2);
+ char *buf = (char *)malloc((size_t)n + 1);
+ vsnprintf(buf, (size_t)n + 1, fmt, ap);
+ va_end(ap);
+ return buf;
+}
+static void cb_push_line(char ***out, int *count, const char *line) {
+ *out = (char **)realloc(*out, (size_t)(*count + 1) * sizeof(char *));
+ (*out)[(*count)++] = strdup(line);
+}
+static void cb_push_line_indent(char ***out, int *count, int depth,
+ const char *content) {
+ int pad = depth * 2;
+ size_t L = strlen(content);
+ char *buf = (char *)malloc((size_t)pad + L + 1);
+ memset(buf, ' ', (size_t)pad);
+ memcpy(buf + pad, content, L + 1);
+ cb_push_line(out, count, buf);
+ free(buf);
+}
+static inline char *str_dup_trim(const char *s, int len) {
+ while (len > 0 && isspace((unsigned char)s[0])) {
+ s++;
+ len--;
+ }
+ while (len > 0 && isspace((unsigned char)s[len - 1]))
+ len--;
+ char *out = (char *)malloc((size_t)len + 1);
+ memcpy(out, s, (size_t)len);
+ out[len] = '\0';
+ return out;
+}
+static inline char *cb_str_append(char *dst, const char *add) {
+ size_t a = dst ? strlen(dst) : 0;
+ size_t b = add ? strlen(add) : 0;
+ char *res = (char *)realloc(dst, a + b + 1);
+ memcpy(res + a, add, b);
+ res[a + b] = '\0';
+ return res;
+}
+static char *cb_str_appendf(char *dst, const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ va_list ap2;
+ va_copy(ap2, ap);
+ int need = vsnprintf(NULL, 0, fmt, ap2);
+ va_end(ap2);
+ char *buf = (char *)malloc((size_t)need + 1);
+ vsnprintf(buf, (size_t)need + 1, fmt, ap);
+ va_end(ap);
+ char *res = cb_str_append(dst, buf);
+ free(buf);
+ return res;
+}
+static inline bool is_ident_char(char c) {
+ return isalnum((unsigned char)c) || c == '_' || c == '$';
+}
+static inline bool is_string_literal(const char *s) {
+ if (!s || !*s)
+ return false;
+ char c = *s;
+ if (c != '"' && c != '\'')
+ return false;
+ size_t L = strlen(s);
+ if (L < 2)
+ return false;
+ return s[L - 1] == c;
+}
+static inline bool is_numeric_literal(const char *s) {
+ if (!s)
+ return false;
+ const char *p = s;
+ if (*p == '+' || *p == '-')
+ p++;
+ bool has_digit = false, dot = false;
+ while (*p) {
+ if (isdigit((unsigned char)*p))
+ has_digit = true;
+ else if (*p == '.' && !dot)
+ dot = true;
+ else
+ return false;
+ p++;
+ }
+ return has_digit;
+}
+
+static char *normalize_condition_expr(const char *expr) {
+ const char *p = expr;
+ char *out = strdup("");
+ while (*p) {
+ if ((p == expr || !is_ident_char(p[-1])) && starts_with(p, "and") &&
+ !is_ident_char(p[3])) {
+ out = cb_str_append(out, "&&");
+ p += 3;
+ } else if ((p == expr || !is_ident_char(p[-1])) && starts_with(p, "or") &&
+ !is_ident_char(p[2])) {
+ out = cb_str_append(out, "||");
+ p += 2;
+ } else if ((p == expr || !is_ident_char(p[-1])) && starts_with(p, "not") &&
+ !is_ident_char(p[3])) {
+ out = cb_str_append(out, "!");
+ p += 3;
+ } else if ((p == expr || !is_ident_char(p[-1])) && starts_with(p, "True") &&
+ !is_ident_char(p[4])) {
+ out = cb_str_append(out, "1");
+ p += 4;
+ } else if ((p == expr || !is_ident_char(p[-1])) &&
+ starts_with(p, "False") && !is_ident_char(p[5])) {
+ out = cb_str_append(out, "0");
+ p += 5;
+ } else {
+ char buf[2] = {*p, 0};
+ out = cb_str_append(out, buf);
+ p++;
+ }
+ }
+ return out;
+}
+
+typedef struct {
+ char *name;
+ const char *ctype; // "int", "double", "char *", "T[]"
+} Symbol;
+
+static int sym_find(Symbol *symbols, int n, const char *name) {
+ for (int i = 0; i < n; i++)
+ if (strcmp(symbols[i].name, name) == 0)
+ return i;
+ return -1;
+}
+
+static int expr_mentions_symbol_of_type(const char *expr, Symbol *symbols,
+ int n, const char *ctype) {
+ for (int i = 0; i < n; i++) {
+ if (!symbols[i].ctype || strcmp(symbols[i].ctype, ctype) != 0)
+ continue;
+ const char *name = symbols[i].name;
+ const char *p = expr;
+ size_t len = strlen(name);
+ while ((p = strstr(p, name)) != NULL) {
+ char b = (p == expr) ? ' ' : p[-1];
+ char a = p[len];
+ int left_ok = !(isalnum((unsigned char)b) || b == '_');
+ int right_ok = !(isalnum((unsigned char)a) || a == '_');
+ if (left_ok && right_ok)
+ return 1;
+ p += len;
+ }
+ }
+ return 0;
+}
+
+static const char *infer_c_type_from_expr(const char *value, Symbol *symbols,
+ int n) {
+ if (!value || !*value)
+ return "int";
+ if (value[0] == '"' || value[0] == '\'')
+ return "char *";
+ if (expr_mentions_symbol_of_type(value, symbols, n, "char *"))
+ return "char *";
+
+ // If using list[index] -> element type
+ for (int i = 0; i < n; i++) {
+ if (symbols[i].ctype && ends_with(symbols[i].ctype, "[]")) {
+ const char *name = symbols[i].name;
+ const char *p = strstr(value, name);
+ if (p) { // check for name[ ... ]
+ const char *after = p + strlen(name);
+ while (*after == ' ' || *after == '\t')
+ after++;
+ if (*after == '[') {
+ static char buf[64];
+ snprintf(buf, sizeof(buf), "%s", symbols[i].ctype);
+ size_t L = strlen(buf);
+ if (L >= 2)
+ buf[L - 2] = '\0';
+ return buf;
+ }
+ }
+ }
+ }
+
+ // If contains '.', or refers to a known double, call double
+ for (const char *p = value; *p; ++p)
+ if (*p == '.')
+ return "double";
+ if (expr_mentions_symbol_of_type(value, symbols, n, "double"))
+ return "double";
+ if (is_numeric_literal(value))
+ return (strchr(value, '.') ? "double" : "int");
+
+ // Unknown identifiers present? default to int (don't block on raw C
+ // variables).
+ return "int";
+}
+
+static char **split_args(const char *s, int *out_count) {
+ char **out = NULL;
+ int n = 0, cap = 0;
+ int dpar = 0, dbr = 0;
+ int in_s = 0, in_d = 0;
+ const char *start = s;
+ for (const char *p = s;; p++) {
+ char c = *p;
+ bool end = (c == '\0');
+ bool at_comma =
+ (!end && c == ',' && dpar == 0 && dbr == 0 && !in_s && !in_d);
+ if (at_comma || end) {
+ int len = (int)(p - start);
+ char *piece = str_dup_trim(start, len);
+ if (n == cap) {
+ cap = cap ? cap * 2 : 4;
+ out = (char **)realloc(out, (size_t)cap * sizeof(char *));
+ }
+ out[n++] = piece;
+ if (end)
+ break;
+ start = p + 1;
+ continue;
+ }
+ if (!in_s && !in_d) {
+ if (c == '(')
+ dpar++;
+ else if (c == ')') {
+ if (dpar > 0)
+ dpar--;
+ } else if (c == '[')
+ dbr++;
+ else if (c == ']') {
+ if (dbr > 0)
+ dbr--;
+ } else if (c == '\'')
+ in_s = 1;
+ else if (c == '"')
+ in_d = 1;
+ } else {
+ if (in_s && c == '\'')
+ in_s = 0;
+ if (in_d && c == '"')
+ in_d = 0;
+ }
+ }
+ *out_count = n;
+ return out;
+}
+static void free_split(char **arr, int n) {
+ for (int i = 0; i < n; i++)
+ free(arr[i]);
+ free(arr);
+}
+
+static const char *ctype_to_fmt(const char *ctype) {
+ if (!ctype)
+ return NULL;
+ if (strcmp(ctype, "int") == 0)
+ return "%d";
+ if (strcmp(ctype, "long") == 0 || strcmp(ctype, "long long") == 0)
+ return "%lld";
+ if (strcmp(ctype, "unsigned") == 0 || strcmp(ctype, "unsigned int") == 0)
+ return "%u";
+ if (strcmp(ctype, "float") == 0 || strcmp(ctype, "double") == 0)
+ return "%f";
+ if (strcmp(ctype, "char *") == 0)
+ return "%s";
+ if (strcmp(ctype, "bool") == 0)
+ return "%d";
+ if (ends_with(ctype, "[]"))
+ return NULL;
+ return NULL;
+}
+
+static char *extract_rhs_operand(const char *expr, const char **cursor_out) {
+ const char *p = *cursor_out;
+ p++;
+ while (*p == ' ' || *p == '\t')
+ p++;
+ const char *start = p;
+ int dpar = 0, dbr = 0;
+ int in_s = 0, in_d = 0;
+ if (*p == '+' || *p == '-')
+ p++;
+ while (*p) {
+ char c = *p;
+ if (!in_s && !in_d) {
+ if (c == '(') {
+ dpar++;
+ p++;
+ continue;
+ }
+ if (c == ')') {
+ if (dpar == 0 && dbr == 0)
+ break;
+ dpar--;
+ p++;
+ continue;
+ }
+ if (c == '[') {
+ dbr++;
+ p++;
+ continue;
+ }
+ if (c == ']') {
+ dbr--;
+ p++;
+ continue;
+ }
+ if (c == '\'') {
+ in_s = 1;
+ p++;
+ continue;
+ }
+ if (c == '"') {
+ in_d = 1;
+ p++;
+ continue;
+ }
+ if ((c == '+' || c == '-' || c == '*' || c == '/' || c == '%' ||
+ c == '&' || c == '|' || c == '^' || c == ',' || c == ';' ||
+ c == '?' || c == ':' || c == '>' || c == '<' || c == '=') &&
+ dpar == 0 && dbr == 0)
+ break;
+ p++;
+ } else {
+ if (in_s && c == '\'')
+ in_s = 0;
+ if (in_d && c == '"')
+ in_d = 0;
+ p++;
+ }
+ }
+ char *rhs = str_dup_trim(start, (int)(p - start));
+ *cursor_out = p;
+ return rhs;
+}
+static void emit_division_asserts(char ***out, int *out_size, int depth,
+ const char *expr) {
+ const char *p = expr;
+ int dpar = 0, dbr = 0;
+ int in_s = 0, in_d = 0;
+ while (*p) {
+ char c = *p;
+ if (!in_s && !in_d) {
+ if (c == '(') {
+ dpar++;
+ p++;
+ continue;
+ }
+ if (c == ')') {
+ if (dpar > 0)
+ dpar--;
+ p++;
+ continue;
+ }
+ if (c == '[') {
+ dbr++;
+ p++;
+ continue;
+ }
+ if (c == ']') {
+ if (dbr > 0)
+ dbr--;
+ p++;
+ continue;
+ }
+ if (c == '\'') {
+ in_s = 1;
+ p++;
+ continue;
+ }
+ if (c == '"') {
+ in_d = 1;
+ p++;
+ continue;
+ }
+ if ((c == '/' || c == '%') && dpar == 0 && dbr == 0) {
+ const char *cur = p;
+ char *den = extract_rhs_operand(expr, &cur);
+ if (den && den[0]) {
+ char *line = cb_strdup_printf("assert((%s) != 0);", den);
+ cb_push_line_indent(out, out_size, depth, line);
+ free(line);
+ }
+ free(den);
+ p = cur;
+ continue;
+ }
+ p++;
+ } else {
+ if (in_s && c == '\'')
+ in_s = 0;
+ if (in_d && c == '"')
+ in_d = 0;
+ p++;
+ }
+ }
+}
+static void emit_index_bounds_asserts(char ***out, int *out_size, int depth,
+ const char *expr, Symbol *symbols,
+ int sym_n) {
+ const char *p = expr;
+ int in_s = 0, in_d = 0, dpar = 0;
+ while (*p) {
+ char c = *p;
+ if (!in_s && !in_d) {
+ if (c == '(') {
+ dpar++;
+ p++;
+ continue;
+ }
+ if (c == ')') {
+ if (dpar > 0)
+ dpar--;
+ p++;
+ continue;
+ }
+ if (c == '\'') {
+ in_s = 1;
+ p++;
+ continue;
+ }
+ if (c == '"') {
+ in_d = 1;
+ p++;
+ continue;
+ }
+ if (c == '[') {
+ const char *q = p - 1;
+ while (q > expr && isspace((unsigned char)*q))
+ q--;
+ const char *end = q + 1;
+ while (q >= expr && is_ident_char(*q))
+ q--;
+ const char *begin = q + 1;
+ if (begin < end) {
+ char *name = str_dup_trim(begin, (int)(end - begin));
+ int si = sym_find(symbols, sym_n, name);
+ bool is_list = (si >= 0 && symbols[si].ctype &&
+ ends_with(symbols[si].ctype, "[]"));
+
+ int depth_br = 1;
+ const char *idx_start = p + 1;
+ const char *r = idx_start;
+ int in_s2 = 0, in_d2 = 0, dpar2 = 0;
+ while (*r) {
+ char ch = *r;
+ if (!in_s2 && !in_d2) {
+ if (ch == '(')
+ dpar2++;
+ else if (ch == ')') {
+ if (dpar2 > 0)
+ dpar2--;
+ } else if (ch == '[')
+ depth_br++;
+ else if (ch == ']') {
+ depth_br--;
+ if (depth_br == 0)
+ break;
+ } else if (ch == '\'')
+ in_s2 = 1;
+ else if (ch == '"')
+ in_d2 = 1;
+ } else {
+ if (in_s2 && ch == '\'')
+ in_s2 = 0;
+ if (in_d2 && ch == '"')
+ in_d2 = 0;
+ }
+ r++;
+ }
+ char *idx = str_dup_trim(idx_start, (int)(r - idx_start));
+ if (is_list) {
+ char *line = cb_strdup_printf("assert((%s) >= 0);", idx);
+ cb_push_line_indent(out, out_size, depth, line);
+ free(line);
+ line = cb_strdup_printf("assert((%s) < %s_len);", idx, name);
+ cb_push_line_indent(out, out_size, depth, line);
+ free(line);
+ }
+ free(idx);
+ free(name);
+ if (*r == ']') {
+ p = r + 1;
+ continue;
+ }
+ }
+ }
+ } else {
+ if (in_s && c == '\'')
+ in_s = 0;
+ if (in_d && c == '"')
+ in_d = 0;
+ }
+ p++;
+ }
+}
+
+static bool is_list_literal(const char *s) {
+ if (!s)
+ return false;
+ size_t L = strlen(s);
+ if (L < 2)
+ return false;
+ while (*s && isspace((unsigned char)*s))
+ s++;
+ if (*s != '[')
+ return false;
+ const char *e = s + strlen(s) - 1;
+ while (e > s && isspace((unsigned char)*e))
+ e--;
+ return *e == ']';
+}
+static char *strip_brackets(const char *s) {
+ const char *p = s;
+ while (*p && isspace((unsigned char)*p))
+ p++;
+ if (*p == '[')
+ p++;
+ const char *q = s + strlen(s) - 1;
+ while (q > p && isspace((unsigned char)*q))
+ q--;
+ if (*q == ']')
+ q--;
+ int len = (int)(q - p + 1);
+ return str_dup_trim(p, len);
+}
+static const char *deduce_list_base_ctype(char **elems, int n, Symbol *symbols,
+ int sym_n) {
+ if (n == 0)
+ return "int"; // default empty list base
+ const char *first = NULL;
+ for (int i = 0; i < n; i++) {
+ const char *e = elems[i];
+ const char *t = NULL;
+ if (is_string_literal(e))
+ t = "char *";
+ else if (is_numeric_literal(e))
+ t = (strchr(e, '.') ? "double" : "int");
+ else {
+ bool bare_ident = true;
+ for (const char *k = e; *k; k++) {
+ if (!is_ident_char(*k)) {
+ bare_ident = false;
+ break;
+ }
+ }
+ if (bare_ident) {
+ int si = sym_find(symbols, sym_n, e);
+ if (si >= 0)
+ t = symbols[si].ctype;
+ }
+ if (!t)
+ t = infer_c_type_from_expr(e, symbols, sym_n);
+ }
+ if (!first)
+ first = t;
+ else if (strcmp(first, t) != 0)
+ return NULL; // heterogeneous not supported
+ }
+ return first ? first : "int";
+}
+
+static void emit_list_set_from_literal(char ***out, int *out_size, int depth,
+ const char *lhs, char **elems, int n,
+ const char *base, bool existed_before) {
+ if (!existed_before) {
+ char *decl0 = cb_strdup_printf("%s *%s = NULL;", base, lhs);
+ cb_push_line_indent(out, out_size, depth, decl0);
+ free(decl0);
+ char *declL = cb_strdup_printf("int %s_len = 0;", lhs);
+ cb_push_line_indent(out, out_size, depth, declL);
+ free(declL);
+ char *declC = cb_strdup_printf("int %s_cap = 0;", lhs);
+ cb_push_line_indent(out, out_size, depth, declC);
+ free(declC);
+ } else {
+ // if reassigning, clear previous contents
+ char *clr = cb_strdup_printf(
+ "if (%s) { free(%s); %s = NULL; } %s_len = 0; %s_cap = 0;", lhs, lhs,
+ lhs, lhs, lhs);
+ cb_push_line_indent(out, out_size, depth, clr);
+ free(clr);
+ }
+
+ // Ensure capacity and copy elements
+ char *need = cb_strdup_printf("int __need_%s = %d;", lhs, n);
+ cb_push_line_indent(out, out_size, depth, need);
+ free(need);
+ char *grow1 =
+ cb_strdup_printf("int __cap_%s = %s_cap ? %s_cap : 4;", lhs, lhs, lhs);
+ cb_push_line_indent(out, out_size, depth, grow1);
+ free(grow1);
+ char *grow2 = cb_strdup_printf("while (__cap_%s < __need_%s) __cap_%s *= 2;",
+ lhs, lhs, lhs);
+ cb_push_line_indent(out, out_size, depth, grow2);
+ free(grow2);
+ char *alloc = cb_strdup_printf(
+ "%s = (%s*)realloc(%s, (size_t)__cap_%s * sizeof(*%s)); assert(%s);", lhs,
+ base, lhs, lhs, lhs, lhs);
+ cb_push_line_indent(out, out_size, depth, alloc);
+ free(alloc);
+ char *setcap = cb_strdup_printf("%s_cap = __cap_%s;", lhs, lhs);
+ cb_push_line_indent(out, out_size, depth, setcap);
+ free(setcap);
+
+ for (int i = 0; i < n; i++) {
+ char *seti = cb_strdup_printf("%s[%s_len++] = %s;", lhs, lhs, elems[i]);
+ cb_push_line_indent(out, out_size, depth, seti);
+ free(seti);
+ }
+}
+
+// Emit ensure-capacity for a list for additional "add" count
+static void emit_list_ensure_capacity(char ***out, int *out_size, int depth,
+ const char *name,
+ const char *extra_expr) {
+ char *need = cb_strdup_printf("int __need_%s = %s_len + (%s);", name, name,
+ extra_expr);
+ cb_push_line_indent(out, out_size, depth, need);
+ free(need);
+ char *grow1 =
+ cb_strdup_printf("int __cap_%s = %s_cap ? %s_cap : 4;", name, name, name);
+ cb_push_line_indent(out, out_size, depth, grow1);
+ free(grow1);
+ char *grow2 = cb_strdup_printf("while (__cap_%s < __need_%s) __cap_%s *= 2;",
+ name, name, name);
+ cb_push_line_indent(out, out_size, depth, grow2);
+ free(grow2);
+ char *alloc = cb_strdup_printf(
+ "%s = realloc(%s, (size_t)__cap_%s * sizeof(*%s)); assert(%s);", name,
+ name, name, name, name);
+ cb_push_line_indent(out, out_size, depth, alloc);
+ free(alloc);
+ char *setcap = cb_strdup_printf("%s_cap = __cap_%s;", name, name);
+ cb_push_line_indent(out, out_size, depth, setcap);
+ free(setcap);
+}
+
+static int __print_counter = 0;
+
+static void emit_print_arg_scalar(char ***out, int *out_size, int depth,
+ const char *a, const char *ctype,
+ const char *space_guard_name) {
+ const char *ph = ctype_to_fmt(ctype);
+ if (!ph)
+ ph = "%d"; // default, don't fail for unknowns
+ char *pre = cb_strdup_printf("if (%s) printf(\" \");", space_guard_name);
+ cb_push_line_indent(out, out_size, depth, pre);
+ free(pre);
+ if (is_string_literal(a)) {
+ char *ln = cb_strdup_printf("printf(\"%%s\", %s);", a);
+ cb_push_line_indent(out, out_size, depth, ln);
+ free(ln);
+ } else {
+ char *ln = cb_strdup_printf("printf(\"%s\", %s);", ph, a);
+ cb_push_line_indent(out, out_size, depth, ln);
+ free(ln);
+ }
+ char *setp = cb_strdup_printf("%s = 1;", space_guard_name);
+ cb_push_line_indent(out, out_size, depth, setp);
+ free(setp);
+}
+static void emit_print_arg_list(char ***out, int *out_size, int depth,
+ const char *name, const char *elem_ctype,
+ const char *space_guard_name) {
+ const char *ph = ctype_to_fmt(elem_ctype);
+ if (!ph)
+ ph = "%d";
+ char *pre = cb_strdup_printf("if (%s) printf(\" \");", space_guard_name);
+ cb_push_line_indent(out, out_size, depth, pre);
+ free(pre);
+ cb_push_line_indent(out, out_size, depth, "printf(\"[\");");
+ int kid = __print_counter++;
+ char idx[32];
+ snprintf(idx, sizeof(idx), "__pj%d", kid);
+ char *loop = cb_strdup_printf("for (int %s = 0; %s < %s_len; %s++) {", idx,
+ idx, name, idx);
+ cb_push_line_indent(out, out_size, depth, loop);
+ free(loop);
+ cb_push_line_indent(out, out_size, depth + 1,
+ cb_strdup_printf("if (%s) printf(\", \");", idx));
+ if (strcmp(elem_ctype, "char *") == 0) {
+ char *ln = cb_strdup_printf("printf(\"'%%s'\", %s[%s]);", name, idx);
+ cb_push_line_indent(out, out_size, depth + 1, ln);
+ free(ln);
+ } else {
+ char *ln = cb_strdup_printf("printf(\"%s\", %s[%s]);", ph, name, idx);
+ cb_push_line_indent(out, out_size, depth + 1, ln);
+ free(ln);
+ }
+ cb_push_line_indent(out, out_size, depth, "}");
+ cb_push_line_indent(out, out_size, depth, "printf(\"]\");");
+ char *setp = cb_strdup_printf("%s = 1;", space_guard_name);
+ cb_push_line_indent(out, out_size, depth, setp);
+ free(setp);
+}
+static void emit_printf_from_print(char ***out, int *out_size, int depth,
+ const char *arglist, Symbol *symbols,
+ int sym_n) {
+ int argc = 0;
+ char **args = split_args(arglist, &argc);
+ bool has_list = false;
+ int *is_list = (int *)calloc((size_t)argc, sizeof(int));
+ const char **elem_types = (const char **)calloc((size_t)argc, sizeof(char *));
+ const char **scalar_types =
+ (const char **)calloc((size_t)argc, sizeof(char *));
+ for (int i = 0; i < argc; i++) {
+ const char *a = args[i];
+ bool bare_ident = true;
+ for (const char *t = a; *t; t++)
+ if (!is_ident_char(*t)) {
+ bare_ident = false;
+ break;
+ }
+ if (bare_ident) {
+ int si = sym_find(symbols, sym_n, a);
+ if (si >= 0 && symbols[si].ctype && ends_with(symbols[si].ctype, "[]")) {
+ has_list = true;
+ is_list[i] = 1;
+ static char buf[64];
+ snprintf(buf, sizeof(buf), "%s", symbols[si].ctype);
+ size_t L = strlen(buf);
+ if (L >= 2)
+ buf[L - 2] = '\0';
+ elem_types[i] = strdup(buf);
+ continue;
+ }
+ }
+ const char *ctype = NULL;
+ if (is_string_literal(a))
+ ctype = "char *";
+ else {
+ if (bare_ident) {
+ int si = sym_find(symbols, sym_n, a);
+ if (si >= 0)
+ ctype = symbols[si].ctype;
+ }
+ if (!ctype)
+ ctype = infer_c_type_from_expr(a, symbols, sym_n);
+ }
+ scalar_types[i] = ctype;
+ }
+
+ if (!has_list) {
+ char *fmt = strdup("");
+ char *params = strdup("");
+ for (int i = 0; i < argc; i++) {
+ const char *a = args[i];
+ if (i > 0)
+ fmt = cb_str_append(fmt, " ");
+ if (is_string_literal(a)) {
+ fmt = cb_str_append(fmt, "%s");
+ params = cb_str_appendf(params, "%s%s", (params[0] ? ", " : ""), a);
+ } else {
+ const char *ph = ctype_to_fmt(scalar_types[i]);
+ if (!ph)
+ ph = "%d";
+ fmt = cb_str_append(fmt, ph);
+ params = cb_str_appendf(params, "%s%s", (params[0] ? ", " : ""), a);
+ }
+ }
+ fmt = cb_str_append(fmt, "\\n");
+ char *line = NULL;
+ if (params[0])
+ line = cb_strdup_printf("printf(\"%s\", %s);", fmt, params);
+ else
+ line = cb_strdup_printf("printf(\"%s\");", fmt);
+ cb_push_line_indent(out, out_size, depth, line);
+ free(line);
+ free(fmt);
+ free(params);
+ } else {
+ int kid = __print_counter++;
+ char guard[32];
+ snprintf(guard, sizeof(guard), "__p%d", kid);
+ char *decl = cb_strdup_printf("int %s = 0;", guard);
+ cb_push_line_indent(out, out_size, depth, decl);
+ free(decl);
+ for (int i = 0; i < argc; i++) {
+ if (is_list[i]) {
+ if (!args[i][0])
+ continue;
+ emit_print_arg_list(out, out_size, depth, args[i], elem_types[i],
+ guard);
+ } else {
+ const char *a = args[i];
+ const char *ctype = scalar_types[i];
+ if (is_string_literal(a))
+ ctype = "char *";
+ emit_print_arg_scalar(out, out_size, depth, a, ctype, guard);
+ }
+ }
+ cb_push_line_indent(out, out_size, depth, "printf(\"\\n\");");
+ }
+
+ for (int i = 0; i < argc; i++)
+ if (elem_types[i])
+ free((void *)elem_types[i]);
+ free(elem_types);
+ free(scalar_types);
+ free(is_list);
+ free_split(args, argc);
+}
+
+static int __loop_counter = 0;
+char *__pending_for_bind_line = NULL;
+
+static void handle_for_header(char ***out, int *out_size, int *depth,
+ int indent, const char *head, Symbol **symbols,
+ int *sym_n) {
+ const char *var = head + 4; // after "for "
+ const char *in = strstr(var, " in ");
+ if (!in) {
+ char *err =
+ cb_strdup_printf("assert(0 && \"Malformed for header: %s\");", head);
+ cb_push_line_indent(out, out_size, *depth, err);
+ free(err);
+ return;
+ }
+ char *lhs = str_dup_trim(var, (int)(in - var));
+ const char *iter = in + 4;
+
+ if (starts_with(iter, "range(")) {
+ const char *rp = strrchr(iter, ')');
+ if (!rp) {
+ char *err =
+ cb_strdup_printf("assert(0 && \"Malformed range() in: %s\");", head);
+ cb_push_line_indent(out, out_size, *depth, err);
+ free(err);
+ free(lhs);
+ return;
+ }
+ char *inside = str_dup_trim(iter + 6, (int)(rp - (iter + 6)));
+ int argc = 0;
+ char **argv = split_args(inside, &argc);
+
+ const char *c_start = "0", *c_stop = NULL, *c_step = "1";
+ if (argc == 1)
+ c_stop = argv[0];
+ else if (argc == 2) {
+ c_start = argv[0];
+ c_stop = argv[1];
+ } else if (argc >= 3) {
+ c_start = argv[0];
+ c_stop = argv[1];
+ c_step = argv[2];
+ }
+
+ char *a1 = cb_strdup_printf("assert(%s != 0);", c_step);
+ cb_push_line_indent(out, out_size, *depth, a1);
+ free(a1);
+
+ char *cond = cb_strdup_printf("(%s) > 0 ? (%s) < (%s) : (%s) > (%s)",
+ c_step, lhs, c_stop, lhs, c_stop);
+ char *line = cb_strdup_printf("for (int %s = (%s); %s; %s += (%s)) {", lhs,
+ c_start, cond, lhs, c_step);
+ cb_push_line_indent(out, out_size, *depth, line);
+ free(cond);
+ free(line);
+ free_split(argv, argc);
+ free(inside);
+ free(lhs);
+ return;
+ }
+
+ // for x in list_name:
+ int si = sym_find(*symbols, *sym_n, iter);
+ const char *arr_t = (si >= 0 ? (*symbols)[si].ctype : NULL);
+ if (!arr_t || !ends_with(arr_t, "[]")) {
+ char *err = cb_strdup_printf(
+ "assert(0 && \"for-in expects list variable: %s\");", head);
+ cb_push_line_indent(out, out_size, *depth, err);
+ free(err);
+ free(lhs);
+ return;
+ }
+
+ char *elem_t = str_dup_trim(arr_t, (int)strlen(arr_t) - 2);
+ int k = __loop_counter++;
+ char idx_name[32];
+ snprintf(idx_name, sizeof(idx_name), "__idx%d", k);
+
+ char *line = cb_strdup_printf("for (int %s = 0; %s < %s_len; %s++) {",
+ idx_name, idx_name, iter, idx_name);
+ cb_push_line_indent(out, out_size, *depth, line);
+ free(line);
+
+ char *bind = cb_strdup_printf("%s %s = %s[%s];", elem_t, lhs, iter, idx_name);
+ __pending_for_bind_line = bind;
+
+ free(elem_t);
+ free(lhs);
+}
+
+static bool parse_list_method_call(const char *stmt, char *list_out,
+ size_t list_sz, char *method_out,
+ size_t meth_sz, char **inside_out) {
+ const char *dot = strchr(stmt, '.');
+ if (!dot)
+ return false;
+ const char *lp = strchr(stmt, '(');
+ if (!lp)
+ return false;
+ const char *rp = strrchr(stmt, ')');
+ if (!rp || rp < lp)
+ return false;
+ // Extract list name
+ int L = (int)(dot - stmt);
+ if (L <= 0 || (size_t)L >= list_sz)
+ return false;
+ memcpy(list_out, stmt, (size_t)L);
+ list_out[L] = 0;
+ // Extract method
+ int M = (int)(lp - (dot + 1));
+ if (M <= 0 || (size_t)M >= meth_sz)
+ return false;
+ memcpy(method_out, dot + 1, (size_t)M);
+ method_out[M] = 0;
+ // Extract inside
+ *inside_out = str_dup_trim(lp + 1, (int)(rp - (lp + 1)));
+ return true;
+}
+
+static bool stmt_is_bare_list_call(const char *stmt) {
+ // Rough check: "<ident>.<method>(...)" and no trailing stuff
+ const char *rp = strrchr(stmt, ')');
+ if (!rp)
+ return false;
+ const char *after = rp + 1;
+ while (*after && isspace((unsigned char)*after))
+ after++;
+ return *after == '\0';
+}
+
+static void emit_list_method_stmt(char ***out, int *out_size, int depth,
+ const char *stmt, Symbol *symbols,
+ int sym_n) {
+ char list[128], method[64];
+ char *inside = NULL;
+ if (!parse_list_method_call(stmt, list, sizeof(list), method, sizeof(method),
+ &inside))
+ return;
+
+ if (strcmp(method, "append") == 0) {
+ emit_list_ensure_capacity(out, out_size, depth, list, "1");
+ char *ln = cb_strdup_printf("%s[%s_len++] = %s;", list, list,
+ inside[0] ? inside : "0");
+ cb_push_line_indent(out, out_size, depth, ln);
+ free(ln);
+ goto done;
+ }
+ // pop([i])
+ if (strcmp(method, "pop") == 0) {
+ if (inside[0] == '\0') {
+ cb_push_line_indent(out, out_size, depth,
+ cb_strdup_printf("assert(%s_len>0);", list));
+ char *ln = cb_strdup_printf("%s_len--;", list);
+ cb_push_line_indent(out, out_size, depth, ln);
+ free(ln);
+ } else {
+ cb_push_line_indent(out, out_size, depth,
+ cb_strdup_printf("assert(%s_len>0);", list));
+ char *ai = cb_strdup_printf(
+ "int __i_%s = (%s); assert(__i_%s>=0 && __i_%s<%s_len);", list,
+ inside, list, list, list);
+ cb_push_line_indent(out, out_size, depth, ai);
+ free(ai);
+ char *mv = cb_strdup_printf("memmove(%s+__i_%s, %s+__i_%s+1, "
+ "(size_t)(%s_len-__i_%s-1)*sizeof(*%s));",
+ list, list, list, list, list, list, list);
+ cb_push_line_indent(out, out_size, depth, mv);
+ free(mv);
+ char *dec = cb_strdup_printf("%s_len--;", list);
+ cb_push_line_indent(out, out_size, depth, dec);
+ free(dec);
+ }
+ goto done;
+ }
+ // insert(i, x)
+ if (strcmp(method, "insert") == 0) {
+ int ac = 0;
+ char **av = split_args(inside, &ac);
+ const char *idx = (ac >= 1 ? av[0] : "0");
+ const char *val = (ac >= 2 ? av[1] : "0");
+ emit_list_ensure_capacity(out, out_size, depth, list, "1");
+ char *chk = cb_strdup_printf(
+ "int __i_%s = (%s); assert(__i_%s>=0 && __i_%s<=%s_len);", list, idx,
+ list, list, list);
+ cb_push_line_indent(out, out_size, depth, chk);
+ free(chk);
+ char *mv = cb_strdup_printf(
+ "memmove(%s+__i_%s+1, %s+__i_%s, (size_t)(%s_len-__i_%s)*sizeof(*%s));",
+ list, list, list, list, list, list, list);
+ cb_push_line_indent(out, out_size, depth, mv);
+ free(mv);
+ char *seti = cb_strdup_printf("%s[__i_%s] = %s;", list, list, val);
+ cb_push_line_indent(out, out_size, depth, seti);
+ free(seti);
+ char *inc = cb_strdup_printf("%s_len++;", list);
+ cb_push_line_indent(out, out_size, depth, inc);
+ free(inc);
+ free_split(av, ac);
+ goto done;
+ }
+ // remove(x)
+ if (strcmp(method, "remove") == 0) {
+ const char *val = inside[0] ? inside : "0";
+ char *find = cb_strdup_printf(
+ "int __j_%s=-1; for (int __k_%s=0; __k_%s<%s_len; __k_%s++){ if "
+ "(%s[__k_%s]==(%s)) { __j_%s=__k_%s; break; } }",
+ list, list, list, list, list, list, list, val, list, list);
+ cb_push_line_indent(out, out_size, depth, find);
+ free(find);
+ char *chk = cb_strdup_printf("assert(__j_%s>=0);", list);
+ cb_push_line_indent(out, out_size, depth, chk);
+ free(chk);
+ char *mv = cb_strdup_printf("memmove(%s+__j_%s, %s+__j_%s+1, "
+ "(size_t)(%s_len-__j_%s-1)*sizeof(*%s));",
+ list, list, list, list, list, list, list);
+ cb_push_line_indent(out, out_size, depth, mv);
+ free(mv);
+ char *dec = cb_strdup_printf("%s_len--;", list);
+ cb_push_line_indent(out, out_size, depth, dec);
+ free(dec);
+ goto done;
+ }
+ // extend(other)
+ if (strcmp(method, "extend") == 0) {
+ const char *other = inside[0] ? inside : "NULL";
+ emit_list_ensure_capacity(out, out_size, depth, list,
+ cb_strdup_printf("%s_len", other));
+ char *cpy =
+ cb_strdup_printf("memcpy(%s+%s_len, %s, (size_t)%s_len*sizeof(*%s));",
+ list, list, other, other, list);
+ cb_push_line_indent(out, out_size, depth, cpy);
+ free(cpy);
+ char *inc = cb_strdup_printf("%s_len += %s_len;", list, other);
+ cb_push_line_indent(out, out_size, depth, inc);
+ free(inc);
+ goto done;
+ }
+
+done:
+ free(inside);
+}
+
+// If RHS is exactly "<list>.pop(...)" and LHS is scalar, expand and set LHS.
+static bool emit_assignment_pop_expr(char ***out, int *out_size, int depth,
+ const char *lhs, const char *rhs) {
+ char list[128], method[64];
+ char *inside = NULL;
+ if (!parse_list_method_call(rhs, list, sizeof(list), method, sizeof(method),
+ &inside))
+ return false;
+ if (strcmp(method, "pop") != 0) {
+ free(inside);
+ return false;
+ }
+
+ if (inside[0] == '\0') {
+ char *pre = cb_strdup_printf("assert(%s_len>0);", list);
+ cb_push_line_indent(out, out_size, depth, pre);
+ free(pre);
+ char *as = cb_strdup_printf("%s = %s[%s_len-1];", lhs, list, list);
+ cb_push_line_indent(out, out_size, depth, as);
+ free(as);
+ char *dec = cb_strdup_printf("%s_len--;", list);
+ cb_push_line_indent(out, out_size, depth, dec);
+ free(dec);
+ } else {
+ char *idx = cb_strdup_printf(
+ "int __i_%s = (%s); assert(__i_%s>=0 && __i_%s<%s_len);", list, inside,
+ list, list, list);
+ cb_push_line_indent(out, out_size, depth, idx);
+ free(idx);
+ char *as = cb_strdup_printf("%s = %s[__i_%s];", lhs, list, list);
+ cb_push_line_indent(out, out_size, depth, as);
+ free(as);
+ char *mv = cb_strdup_printf("memmove(%s+__i_%s, %s+__i_%s+1, "
+ "(size_t)(%s_len-__i_%s-1)*sizeof(*%s));",
+ list, list, list, list, list, list, list);
+ cb_push_line_indent(out, out_size, depth, mv);
+ free(mv);
+ char *dec = cb_strdup_printf("%s_len--;", list);
+ cb_push_line_indent(out, out_size, depth, dec);
+ free(dec);
+ }
+ free(inside);
+ return true;
+}
+
+static char **transpile_py_block(char **lines, int line_count, int *out_size) {
+ char **out = NULL;
+ *out_size = 0;
+ Symbol *symbols = NULL;
+ int sym_n = 0;
+
+ int indent_stack[256];
+ int depth = 0;
+
+ for (int i = 0; i < line_count; i++) {
+ const char *raw = lines[i];
+
+ int k = 0;
+ while (raw[k] == ' ' || raw[k] == '\t')
+ k++;
+ if (raw[k] == '\0') {
+ cb_push_line(&out, out_size, "");
+ continue;
+ }
+
+ int indent = cb_count_indent(raw);
+ const char *stmt = raw + indent;
+
+ while (depth > 0 && indent <= indent_stack[depth - 1]) {
+ depth--;
+ cb_push_line_indent(&out, out_size, depth, "}");
+ }
+
+ size_t L = strlen(stmt);
+ while (L > 0 && isspace((unsigned char)stmt[L - 1]))
+ L--;
+ int ends_colon = (L > 0 && stmt[L - 1] == ':');
+
+ if (ends_colon) {
+ char *head = str_dup_trim(stmt, (int)L - 1);
+
+ if (starts_with(head, "if ")) {
+ char *cond_py = strdup(head + 3);
+ char *cond = normalize_condition_expr(cond_py);
+ emit_division_asserts(&out, out_size, depth, cond);
+ emit_index_bounds_asserts(&out, out_size, depth, cond, symbols, sym_n);
+ char *line = cb_strdup_printf("if (%s) {", cond);
+ cb_push_line_indent(&out, out_size, depth, line);
+ free(line);
+ indent_stack[depth++] = indent;
+ free(cond_py);
+ free(cond);
+ free(head);
+ continue;
+ }
+
+ if (starts_with(head, "elif ")) {
+ char *cond_py = strdup(head + 5);
+ char *cond = normalize_condition_expr(cond_py);
+ emit_division_asserts(&out, out_size, depth, cond);
+ emit_index_bounds_asserts(&out, out_size, depth, cond, symbols, sym_n);
+ char *line = cb_strdup_printf("else if (%s) {", cond);
+ cb_push_line_indent(&out, out_size, depth, line);
+ free(line);
+ indent_stack[depth++] = indent;
+ free(cond_py);
+ free(cond);
+ free(head);
+ continue;
+ }
+
+ if (strcmp(head, "else") == 0) {
+ cb_push_line_indent(&out, out_size, depth, "else {");
+ indent_stack[depth++] = indent;
+ free(head);
+ continue;
+ }
+
+ if (starts_with(head, "while ")) {
+ char *cond_py = strdup(head + 6);
+ char *cond = normalize_condition_expr(cond_py);
+ emit_division_asserts(&out, out_size, depth, cond);
+ emit_index_bounds_asserts(&out, out_size, depth, cond, symbols, sym_n);
+ char *line = cb_strdup_printf("while (%s) {", cond);
+ cb_push_line_indent(&out, out_size, depth, line);
+ free(line);
+ indent_stack[depth++] = indent;
+ free(cond_py);
+ free(cond);
+ free(head);
+ continue;
+ }
+
+ if (starts_with(head, "for ")) {
+ handle_for_header(&out, out_size, &depth, indent, head, &symbols,
+ &sym_n);
+ indent_stack[depth++] = indent;
+ if (__pending_for_bind_line) {
+ cb_push_line_indent(&out, out_size, depth, __pending_for_bind_line);
+ free(__pending_for_bind_line);
+ __pending_for_bind_line = NULL;
+ }
+ free(head);
+ continue;
+ }
+
+ if (strcmp(head, "pass") == 0) {
+ cb_push_line_indent(&out, out_size, depth, "{ /* pass */");
+ indent_stack[depth++] = indent;
+ free(head);
+ continue;
+ }
+
+ char *err =
+ cb_strdup_printf("assert(0 && \"Unhandled header: %s\");", head);
+ cb_push_line_indent(&out, out_size, depth, err);
+ free(err);
+ free(head);
+ continue;
+ }
+
+ // simple statements
+ if (strcmp(stmt, "pass") == 0) {
+ cb_push_line_indent(&out, out_size, depth, ";");
+ continue;
+ }
+ if (strcmp(stmt, "break") == 0) {
+ cb_push_line_indent(&out, out_size, depth, "break;");
+ continue;
+ }
+ if (strcmp(stmt, "continue") == 0) {
+ cb_push_line_indent(&out, out_size, depth, "continue;");
+ continue;
+ }
+
+ if (starts_with(stmt, "print(")) {
+ const char *rp = strrchr(stmt, ')');
+ if (!rp) {
+ char *err =
+ cb_strdup_printf("assert(0 && \"Malformed print(): %s\");", stmt);
+ cb_push_line_indent(&out, out_size, depth, err);
+ free(err);
+ continue;
+ }
+ char *inside = str_dup_trim(stmt + 6, (int)(rp - (stmt + 6)));
+ emit_division_asserts(&out, out_size, depth, inside);
+ emit_index_bounds_asserts(&out, out_size, depth, inside, symbols, sym_n);
+ emit_printf_from_print(&out, out_size, depth, inside, symbols, sym_n);
+ free(inside);
+ continue;
+ }
+
+ // bare list method statements (append/insert/remove/extend/pop)
+ if (stmt_is_bare_list_call(stmt)) {
+ emit_list_method_stmt(&out, out_size, depth, stmt, symbols, sym_n);
+ continue;
+ }
+
+ // assignment
+ const char *eq = strchr(stmt, '=');
+ if (eq) {
+ int lhs_len = (int)(eq - stmt);
+ char *lhs = str_dup_trim(stmt, lhs_len);
+ const char *rhs = eq + 1;
+ while (*rhs == ' ' || *rhs == '\t')
+ rhs++;
+
+ emit_division_asserts(&out, out_size, depth, rhs);
+ emit_index_bounds_asserts(&out, out_size, depth, rhs, symbols, sym_n);
+
+ // Special: x = lst.pop(...)
+ if (emit_assignment_pop_expr(&out, out_size, depth, lhs, rhs)) {
+ // infer type for lhs from list element if we know it; else int
+ int siL = sym_find(symbols, sym_n, lhs);
+ if (siL < 0) {
+ // try to find list element type from rhs
+ const char *elem_t = "int";
+ char list[128], method[64];
+ char *inside2 = NULL;
+ if (parse_list_method_call(rhs, list, sizeof(list), method,
+ sizeof(method), &inside2)) {
+ int siX = sym_find(symbols, sym_n, list);
+ if (siX >= 0 && symbols[siX].ctype &&
+ ends_with(symbols[siX].ctype, "[]")) {
+ static char buf[64];
+ snprintf(buf, sizeof(buf), "%s", symbols[siX].ctype);
+ size_t Lb = strlen(buf);
+ if (Lb >= 2)
+ buf[Lb - 2] = '\0';
+ elem_t = buf;
+ }
+ free(inside2);
+ }
+ symbols =
+ (Symbol *)realloc(symbols, (size_t)(sym_n + 1) * sizeof(Symbol));
+ symbols[sym_n].name = strdup(lhs);
+ symbols[sym_n].ctype = strdup(elem_t);
+ char *decl = cb_strdup_printf("%s %s; /* from pop */", elem_t, lhs);
+ cb_push_line_indent(&out, out_size, depth, decl);
+ free(decl);
+ sym_n++;
+ }
+ free(lhs);
+ continue;
+ }
+
+ if (is_list_literal(rhs)) {
+ char *inside = strip_brackets(rhs);
+ int n = 0;
+ char **elems = split_args(inside, &n);
+ const char *base = deduce_list_base_ctype(elems, n, symbols, sym_n);
+ if (!base) {
+ char *err = cb_strdup_printf(
+ "assert(0 && \"Heterogeneous list literal for %s\");", lhs);
+ cb_push_line_indent(&out, out_size, depth, err);
+ free(err);
+ base = "int";
+ }
+ int si = sym_find(symbols, sym_n, lhs);
+ bool existed = (si >= 0);
+ if (!existed) {
+ symbols =
+ (Symbol *)realloc(symbols, (size_t)(sym_n + 1) * sizeof(Symbol));
+ symbols[sym_n].name = strdup(lhs);
+ char *ctype = cb_strdup_printf("%s[]", base);
+ symbols[sym_n].ctype = strdup(ctype);
+ free(ctype);
+ sym_n++;
+ }
+ emit_list_set_from_literal(&out, out_size, depth, lhs, elems, n, base,
+ existed);
+ free_split(elems, n);
+ free(inside);
+ free(lhs);
+ continue;
+ }
+
+ const char *new_t = infer_c_type_from_expr(rhs, symbols, sym_n);
+ int si = sym_find(symbols, sym_n, lhs);
+ if (si < 0) {
+ symbols =
+ (Symbol *)realloc(symbols, (size_t)(sym_n + 1) * sizeof(Symbol));
+ symbols[sym_n].name = strdup(lhs);
+ symbols[sym_n].ctype = strdup(new_t);
+ char *cl = cb_strdup_printf("%s %s = %s;", new_t, lhs, rhs);
+ cb_push_line_indent(&out, out_size, depth, cl);
+ free(cl);
+ sym_n++;
+ } else {
+ char *cl = cb_strdup_printf("%s = %s;", lhs, rhs);
+ cb_push_line_indent(&out, out_size, depth, cl);
+ free(cl);
+ }
+ free(lhs);
+ continue;
+ }
+
+ // fallback
+ {
+ char *cl = cb_strdup_printf("assert(0 && \"Unhandled stmt: %s\");", stmt);
+ cb_push_line_indent(&out, out_size, depth, cl);
+ free(cl);
+ }
+ }
+
+ while (depth > 0) {
+ depth--;
+ cb_push_line_indent(&out, out_size, depth, "}");
+ }
+
+ for (int i = 0; i < sym_n; i++)
+ free(symbols[i].name);
+ free(symbols);
+
+ return out;
+}
+
+#endif // CB_PY_H
diff --git a/src/lib/cbuild.h b/src/lib/cbuild.h
new file mode 100644
index 0000000..6002cfb
--- /dev/null
+++ b/src/lib/cbuild.h
@@ -0,0 +1,679 @@
+#ifndef _CBUILD_H
+#define _CBUILD_H
+// #define CB_DEBUG
+// #define _CB_LOG_TO_FILE
+
+#include "arglist.h"
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#if defined(_WIN32)
+#define OS_WIN 1
+#include <process.h>
+#include <windows.h>
+#else
+#define OS_UNIX 1
+#include <sys/wait.h>
+#include <unistd.h>
+#endif
+
+#if defined(__APPLE__)
+#define OS_MACOS 1
+#endif
+
+#ifdef CB_DEBUG
+#define CB_DEBUG_LOG(fmt, ...) \
+ fprintf(stderr, "\x1b[36m[DEBUG] %s:%d: " fmt "\x1b[0m\n", __FILE__, \
+ __LINE__, ##__VA_ARGS__)
+#else
+#define CB_DEBUG_LOG(fmt, ...)
+#endif
+
+#if OS_UNIX
+#include <sys/types.h>
+#endif
+
+#if OS_UNIX
+#define COMPILER_NAME "cc"
+#elif OS_WIN
+#define COMPILER_NAME "cl"
+#elif OS_MACOS
+#define COMPILER_NAME "clang"
+#endif
+
+#define COLOR_RESET "\x1b[0m"
+#define COLOR_RED "\x1b[31m"
+#define COLOR_GREEN "\x1b[32m"
+#define COLOR_YELLOW "\x1b[33m"
+#define COLOR_CYAN "\x1b[36m"
+
+typedef struct _CB_PROJECT {
+ char *name;
+ char *output;
+ CB_ARGLIST *files;
+ CB_ARGLIST *buildflags;
+ CB_ARGLIST *flags;
+ char *compile_command;
+ int is_rebuild;
+} _CB_PROJECT;
+
+typedef struct {
+ _CB_PROJECT **projects;
+ int run;
+ int run_if_skipped; // New: run even if build skipped
+ int parallel; // Number of parallel jobs
+} CB_PROJECT_BUILD_CONFIG;
+
+#define CB_STRLIST(...) ((const char *[]){__VA_ARGS__, NULL})
+#define CB_PROJECT_LIST(...) ((_CB_PROJECT *[]){__VA_ARGS__, NULL})
+
+#define _CB_CREATE_PROJECT(var, ...) \
+ _CB_PROJECT *var = malloc(sizeof(_CB_PROJECT)); \
+ memset(var, 0, sizeof(_CB_PROJECT)); \
+ struct { \
+ char *name; \
+ char *output; \
+ const char **files; \
+ const char **buildflags; \
+ const char **flags; \
+ int is_rebuild; \
+ } var##_init = {__VA_ARGS__}; \
+ var->name = var##_init.name; \
+ var->output = var##_init.output; \
+ var->files = arglist_new(); \
+ var->buildflags = arglist_new(); \
+ var->flags = arglist_new(); \
+ var->is_rebuild = var##_init.is_rebuild; \
+ if (var##_init.files) \
+ arglist_append_array(var->files, var##_init.files); \
+ if (var##_init.buildflags) \
+ arglist_append_array(var->buildflags, var##_init.buildflags); \
+ if (var##_init.flags) \
+ arglist_append_array(var->flags, var##_init.flags)
+
+#define CB_NEEDED_LIBS "-lssl -lcrypto"
+
+static char *cb_concat_compile_command(_CB_PROJECT *proj) {
+ if (!proj || !proj->files || proj->files->count == 0)
+ return strdup("[error] No source files");
+
+ size_t total_len = strlen(COMPILER_NAME) + 32;
+
+ if (proj->is_rebuild)
+ total_len += strlen(CB_NEEDED_LIBS) + 2;
+ for (int i = 0; i < proj->buildflags->count; i++)
+ total_len += strlen(proj->buildflags->list[i]) + 2;
+ for (int i = 0; i < proj->files->count; i++)
+ total_len += strlen(proj->files->list[i]) + 2;
+ if (proj->output)
+ total_len += strlen("-o ") + strlen(proj->output) + 2;
+ for (int i = 0; i < proj->flags->count; i++)
+ total_len += strlen(proj->flags->list[i]) + 2;
+
+ char *cmd = (char *)malloc(total_len);
+ if (!cmd)
+ return NULL;
+
+ strcpy(cmd, COMPILER_NAME);
+
+ for (int i = 0; i < proj->buildflags->count; i++) {
+ strcat(cmd, " ");
+ strcat(cmd, proj->buildflags->list[i]);
+ }
+
+ if (proj->is_rebuild) {
+ strcat(cmd, " ");
+ strcat(cmd, CB_NEEDED_LIBS);
+ }
+
+ for (int i = 0; i < proj->files->count; i++) {
+ strcat(cmd, " ");
+ strcat(cmd, proj->files->list[i]);
+ }
+
+ if (proj->output) {
+ strcat(cmd, " -o ");
+ strcat(cmd, proj->output);
+ }
+
+ CB_DEBUG_LOG("Generated compile command: %s", cmd);
+ return cmd;
+}
+
+#define _CB_BUILD_COMPILE_COMMAND(proj) \
+ do { \
+ if ((proj)->compile_command) { \
+ CB_DEBUG_LOG("Freeing old compile command for project %s", \
+ (proj)->name); \
+ free((proj)->compile_command); \
+ } \
+ (proj)->compile_command = cb_concat_compile_command(proj); \
+ } while (0)
+
+static void cb_dump_to_console(const _CB_PROJECT *project) {
+ if (!project) {
+ printf("[error] Null project pointer\n");
+ return;
+ }
+
+ printf(COLOR_CYAN "=== Project Info ===\n" COLOR_RESET);
+ printf("Name : %s\n", project->name ? project->name : "(null)");
+ printf("Output : %s\n", project->output ? project->output : "(null)");
+
+ printf("\nFiles [%d]:\n", project->files ? project->files->count : 0);
+ for (int i = 0; i < (project->files ? project->files->count : 0); i++)
+ printf(" [%02d] %s\n", i, project->files->list[i]);
+
+ printf("\nBuild Flags [%d]:\n",
+ project->buildflags ? project->buildflags->count : 0);
+ for (int i = 0; i < (project->buildflags ? project->buildflags->count : 0);
+ i++)
+ printf(" [%02d] %s\n", i, project->buildflags->list[i]);
+
+ printf("\nRuntime Flags [%d]:\n", project->flags ? project->flags->count : 0);
+ for (int i = 0; i < (project->flags ? project->flags->count : 0); i++)
+ printf(" [%02d] %s\n", i, project->flags->list[i]);
+
+ printf("\nCompile Command:\n %s\n",
+ project->compile_command ? project->compile_command : "(null)");
+
+ printf("\nRebuild: %s\n", project->is_rebuild ? "true" : "false");
+
+ printf(COLOR_CYAN "====================\n" COLOR_RESET);
+}
+
+static void cb_free_project(_CB_PROJECT *project) {
+ if (!project)
+ return;
+ if (project->files)
+ arglist_free(project->files);
+ if (project->buildflags)
+ arglist_free(project->buildflags);
+ if (project->flags)
+ arglist_free(project->flags);
+ if (project->compile_command)
+ free(project->compile_command);
+ free(project);
+}
+
+// --- Checksum helpers ---
+
+#include <openssl/md5.h> // Requires OpenSSL
+
+static char *cb_compute_md5(const char *data, size_t len) {
+ unsigned char digest[MD5_DIGEST_LENGTH];
+ MD5((const unsigned char *)data, len, digest);
+ char *out = (char *)malloc(MD5_DIGEST_LENGTH * 2 + 1);
+ if (!out)
+ return NULL;
+ for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {
+ sprintf(out + i * 2, "%02x", digest[i]);
+ }
+ out[MD5_DIGEST_LENGTH * 2] = 0;
+ return out;
+}
+
+static char *cb_read_file_content(const char *filepath, size_t *out_len) {
+ FILE *f = fopen(filepath, "rb");
+ if (!f) {
+ CB_DEBUG_LOG("Failed to open file for checksum: %s", filepath);
+ return NULL;
+ }
+ fseek(f, 0, SEEK_END);
+ long size = ftell(f);
+ if (size < 0) {
+ CB_DEBUG_LOG("ftell failed for file: %s", filepath);
+ fclose(f);
+ return NULL;
+ }
+ rewind(f);
+ char *buffer = (char *)malloc(size);
+ if (!buffer) {
+ CB_DEBUG_LOG("Failed to allocate buffer for file content: %s", filepath);
+ fclose(f);
+ return NULL;
+ }
+ size_t read_len = fread(buffer, 1, size, f);
+ fclose(f);
+ if (out_len)
+ *out_len = read_len;
+ CB_DEBUG_LOG("Read %zu bytes from file %s for checksum", read_len, filepath);
+ return buffer;
+}
+
+static char *cb_compute_project_checksum(_CB_PROJECT *proj) {
+ if (!proj)
+ return NULL;
+
+ CB_DEBUG_LOG("Computing checksum for project %s", proj->name);
+
+ MD5_CTX ctx;
+ MD5_Init(&ctx);
+
+ for (int i = 0; i < proj->files->count; i++) {
+ size_t flen = 0;
+ char *fcontent = cb_read_file_content(proj->files->list[i], &flen);
+ if (fcontent) {
+ MD5_Update(&ctx, fcontent, flen);
+ free(fcontent);
+ } else {
+ CB_DEBUG_LOG("Warning: failed to read file %s for checksum",
+ proj->files->list[i]);
+ }
+ }
+
+ if (proj->compile_command) {
+ MD5_Update(&ctx, proj->compile_command, strlen(proj->compile_command));
+ }
+
+ unsigned char digest[MD5_DIGEST_LENGTH];
+ MD5_Final(digest, &ctx);
+
+ char *checksum = (char *)malloc(MD5_DIGEST_LENGTH * 2 + 1);
+ if (!checksum)
+ return NULL;
+ for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {
+ sprintf(checksum + i * 2, "%02x", digest[i]);
+ }
+ checksum[MD5_DIGEST_LENGTH * 2] = 0;
+
+ CB_DEBUG_LOG("Checksum for project %s: %s", proj->name, checksum);
+ return checksum;
+}
+
+static char *cb_read_checksum(const char *filename) {
+ FILE *f = fopen(filename, "r");
+ if (!f) {
+ CB_DEBUG_LOG("Checksum file not found: %s", filename);
+ return NULL;
+ }
+ char buf[MD5_DIGEST_LENGTH * 2 + 1];
+ size_t r = fread(buf, 1, MD5_DIGEST_LENGTH * 2, f);
+ fclose(f);
+ if (r != MD5_DIGEST_LENGTH * 2) {
+ CB_DEBUG_LOG("Checksum file %s incomplete or corrupted", filename);
+ return NULL;
+ }
+ buf[r] = 0;
+ CB_DEBUG_LOG("Read checksum from file %s: %s", filename, buf);
+ return strdup(buf);
+}
+
+static int cb_write_checksum(const char *filename, const char *checksum) {
+ FILE *f = fopen(filename, "w");
+ if (!f) {
+ CB_DEBUG_LOG("Failed to open checksum file for writing: %s", filename);
+ return -1;
+ }
+ size_t w = fwrite(checksum, 1, strlen(checksum), f);
+ fclose(f);
+ CB_DEBUG_LOG("Wrote checksum to file %s: %s", filename, checksum);
+ return (w == strlen(checksum)) ? 0 : -1;
+}
+
+// --- Process pool for parallel runs ---
+
+typedef struct {
+#if OS_WIN
+ PROCESS_INFORMATION pi;
+#else
+ pid_t pid;
+#endif
+ int running;
+ _CB_PROJECT *project;
+} proc_t;
+
+static int proc_start(proc_t *proc, _CB_PROJECT *proj, char **argv) {
+ CB_DEBUG_LOG("Starting process for project %s with command %s", proj->name,
+ argv[0]);
+#if OS_WIN
+ STARTUPINFO si;
+ ZeroMemory(&si, sizeof(si));
+ si.cb = sizeof(si);
+ ZeroMemory(&proc->pi, sizeof(proc->pi));
+
+ size_t cmdlen = 0;
+ for (int i = 0; argv[i]; i++) {
+ size_t arglen = strlen(argv[i]);
+ int needs_quotes = strchr(argv[i], ' ') != NULL;
+ cmdlen += arglen + (needs_quotes ? 2 : 0) + 1;
+ }
+
+ char *cmdline = malloc(cmdlen + 1);
+ if (!cmdline) {
+ CB_DEBUG_LOG("Failed to allocate memory for cmdline");
+ return -1;
+ }
+
+ cmdline[0] = 0;
+ for (int i = 0; argv[i]; i++) {
+ int needs_quotes = strchr(argv[i], ' ') != NULL;
+ if (i > 0)
+ strcat(cmdline, " ");
+ if (needs_quotes)
+ strcat(cmdline, "\"");
+ strcat(cmdline, argv[i]);
+ if (needs_quotes)
+ strcat(cmdline, "\"");
+ }
+
+ BOOL success = CreateProcess(NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL,
+ &si, &proc->pi);
+ free(cmdline);
+
+ if (!success) {
+ fprintf(stderr,
+ COLOR_RED "[error] Failed to start process %s\n" COLOR_RESET,
+ argv[0]);
+ proc->running = 0;
+ return -1;
+ }
+ proc->running = 1;
+ proc->project = proj;
+ CB_DEBUG_LOG("Process started successfully for project %s", proj->name);
+ return 0;
+#else
+ pid_t pid = fork();
+ if (pid == 0) {
+ execvp(argv[0], argv);
+ perror("execvp");
+ exit(1);
+ } else if (pid > 0) {
+ proc->pid = pid;
+ proc->running = 1;
+ proc->project = proj;
+ CB_DEBUG_LOG("Forked child process %d for project %s", pid, proj->name);
+ return 0;
+ } else {
+ perror("fork");
+ proc->running = 0;
+ return -1;
+ }
+#endif
+}
+
+static int proc_poll(proc_t *proc) {
+ if (!proc->running)
+ return -1;
+
+#if OS_WIN
+ DWORD res = WaitForSingleObject(proc->pi.hProcess, 0);
+ if (res == WAIT_OBJECT_0) {
+ DWORD code;
+ GetExitCodeProcess(proc->pi.hProcess, &code);
+ CloseHandle(proc->pi.hProcess);
+ CloseHandle(proc->pi.hThread);
+ proc->running = 0;
+ CB_DEBUG_LOG("Process finished for project %s with exit code %d",
+ proc->project->name, (int)code);
+ return (int)code;
+ }
+ return -1;
+#else
+ int status;
+ pid_t ret = waitpid(proc->pid, &status, WNOHANG);
+ if (ret == 0)
+ return -1; // Still running
+ else if (ret == proc->pid) {
+ proc->running = 0;
+ if (WIFEXITED(status)) {
+ int exit_code = WEXITSTATUS(status);
+ CB_DEBUG_LOG("Process finished for project %s with exit code %d",
+ proc->project->name, exit_code);
+ return exit_code;
+ } else {
+ CB_DEBUG_LOG("Process for project %s ended abnormally",
+ proc->project->name);
+ return -1;
+ }
+ }
+ return -1;
+#endif
+}
+
+static void proc_wait_all(proc_t *procs, int count) {
+ CB_DEBUG_LOG("Waiting for all parallel processes to finish (%d procs)",
+ count);
+ int running = count;
+ while (running > 0) {
+ running = 0;
+ for (int i = 0; i < count; i++) {
+ if (procs[i].running) {
+ int ret = proc_poll(&procs[i]);
+ if (ret >= 0) {
+ // Process finished
+ } else {
+ running++;
+ }
+ }
+ }
+#if OS_WIN
+ Sleep(10);
+#else
+ usleep(10000);
+#endif
+ }
+ CB_DEBUG_LOG("All parallel processes have finished");
+}
+
+// --- Main build function ---
+
+#define _CB_PROJECT_BUILD(...) \
+ _cb_project_build_internal((CB_PROJECT_BUILD_CONFIG){__VA_ARGS__})
+
+static void _cb_project_build_internal(CB_PROJECT_BUILD_CONFIG config) {
+ if (!config.projects) {
+ fprintf(stderr, COLOR_RED "[error] No projects to build.\n" COLOR_RESET);
+ return;
+ }
+
+#ifdef _CB_LOG_TO_FILE
+ FILE *log = fopen(".cb_build.out", "a");
+ if (!log) {
+ perror("fopen");
+ return;
+ }
+ time_t now = time(NULL);
+ fprintf(log, "\n=== Build Started: %s", ctime(&now));
+#endif
+
+ int max_parallel = config.parallel > 0 ? config.parallel : 1;
+ proc_t *proc_pool = NULL;
+ if (config.parallel > 0) {
+ proc_pool = (proc_t *)calloc(max_parallel, sizeof(proc_t));
+ CB_DEBUG_LOG("Initialized process pool for %d parallel jobs", max_parallel);
+ }
+
+ for (int i = 0; config.projects[i]; i++) {
+ _CB_PROJECT *proj = config.projects[i];
+ _CB_BUILD_COMPILE_COMMAND(proj);
+
+ char checksum_file[512];
+ snprintf(checksum_file, sizeof(checksum_file), ".cb_checksum_%s",
+ proj->name);
+
+ char *new_checksum = cb_compute_project_checksum(proj);
+ char *old_checksum = cb_read_checksum(checksum_file);
+
+ int should_build = 1;
+ if (new_checksum && old_checksum &&
+ strcmp(new_checksum, old_checksum) == 0) {
+ should_build = 0;
+ CB_DEBUG_LOG("No changes detected for project %s, skipping build",
+ proj->name);
+ }
+
+ if (!should_build) {
+ printf(COLOR_YELLOW
+ "[build] Skipping %s (no changes detected)\n" COLOR_RESET,
+ proj->name);
+#ifdef _CB_LOG_TO_FILE
+ fprintf(log, "[build] Skipped project: %s\n", proj->name);
+#endif
+ } else {
+ printf(COLOR_YELLOW "[build] Building %s\n" COLOR_RESET, proj->name);
+ printf(" %s\n", proj->compile_command);
+#ifdef _CB_LOG_TO_FILE
+ fprintf(log, "[build] Project: %s\nCommand: %s\n", proj->name,
+ proj->compile_command);
+#endif
+
+ clock_t start = clock();
+
+ int ret = system(proj->compile_command);
+
+ clock_t end = clock();
+
+ CB_DEBUG_LOG("Build command exited with code %d for project %s", ret,
+ proj->name);
+
+ if (ret != 0) {
+ fprintf(stderr, COLOR_RED "[error] Build failed for %s\n" COLOR_RESET,
+ proj->name);
+#ifdef _CB_LOG_TO_FILE
+ fprintf(log, "[error] Build failed for %s\n", proj->name);
+#endif
+ free(new_checksum);
+ free(old_checksum);
+ continue;
+ }
+
+ double duration = (double)(end - start) / CLOCKS_PER_SEC;
+ printf(COLOR_GREEN "[success] Built in %.2fs\n" COLOR_RESET, duration);
+#ifdef _CB_LOG_TO_FILE
+ fprintf(log, "[success] Built in %.2fs\n", duration);
+#endif
+
+ cb_write_checksum(checksum_file, new_checksum);
+ CB_DEBUG_LOG("Checksum updated for project %s", proj->name);
+ }
+
+ free(old_checksum);
+ free(new_checksum);
+
+ // Run executable if requested (and output specified)
+ if (config.run && proj->output) {
+ int argc = proj->flags ? proj->flags->count : 0;
+ char **argv = (char **)malloc(sizeof(char *) * (argc + 2));
+ if (!argv) {
+ perror("malloc");
+ continue;
+ }
+
+ char output_path[512];
+#if OS_WIN
+ snprintf(output_path, sizeof(output_path), "%s", proj->output);
+#else
+ snprintf(output_path, sizeof(output_path), "./%s", proj->output);
+#endif
+ argv[0] = strdup(output_path);
+ for (int j = 0; j < argc; j++)
+ argv[j + 1] = proj->flags->list[j];
+ argv[argc + 1] = NULL;
+
+ CB_DEBUG_LOG("Preparing to run project %s with executable %s", proj->name,
+ argv[0]);
+
+ if (should_build || config.run_if_skipped) {
+ if (config.parallel > 0) {
+ int running_count = 0;
+ for (int k = 0; k < max_parallel; k++) {
+ if (proc_pool[k].running)
+ running_count++;
+ }
+ CB_DEBUG_LOG("Currently running %d parallel processes",
+ running_count);
+
+ while (running_count >= max_parallel) {
+ for (int k = 0; k < max_parallel; k++) {
+ int ret = proc_poll(&proc_pool[k]);
+ if (ret >= 0) {
+ CB_DEBUG_LOG("Parallel process slot freed");
+ running_count--;
+ }
+ }
+#if OS_WIN
+ Sleep(10);
+#else
+ usleep(10000);
+#endif
+ }
+
+ for (int k = 0; k < max_parallel; k++) {
+ if (!proc_pool[k].running) {
+ if (proc_start(&proc_pool[k], proj, argv) == 0) {
+ running_count++;
+ CB_DEBUG_LOG("Started parallel process for project %s",
+ proj->name);
+ } else {
+ CB_DEBUG_LOG("Failed to start parallel process for project %s",
+ proj->name);
+ }
+ break;
+ }
+ }
+ } else {
+ CB_DEBUG_LOG("Running project %s serially", proj->name);
+#if OS_WIN
+ PROCESS_INFORMATION pi;
+ STARTUPINFO si;
+ ZeroMemory(&si, sizeof(si));
+ si.cb = sizeof(si);
+ if (!CreateProcess(NULL, argv[0], NULL, NULL, FALSE, 0, NULL, NULL,
+ &si, &pi)) {
+ fprintf(stderr, COLOR_RED "[error] Failed to run %s\n" COLOR_RESET,
+ argv[0]);
+ CB_DEBUG_LOG("Failed to CreateProcess for %s", argv[0]);
+ } else {
+ WaitForSingleObject(pi.hProcess, INFINITE);
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ CB_DEBUG_LOG("Finished running process %s", argv[0]);
+ }
+#else
+ pid_t pid = fork();
+ if (pid == 0) {
+ execvp(argv[0], argv);
+ perror("execvp");
+ exit(1);
+ } else if (pid > 0) {
+ waitpid(pid, NULL, 0);
+ CB_DEBUG_LOG("Finished running process %s", argv[0]);
+ if (proj->is_rebuild) {
+ CB_DEBUG_LOG("%s is just a rebuild. Therefore not continuing in "
+ "the program. If this blocks something you have to "
+ "run this as the last or rewrite ur logic.",
+ proj->name);
+ exit(1);
+ }
+ } else {
+ perror("fork");
+ CB_DEBUG_LOG("Failed to fork for running process %s", argv[0]);
+ }
+#endif
+ }
+ } else {
+ CB_DEBUG_LOG("Skipping run for project %s because build was skipped "
+ "and run_if_skipped not set",
+ proj->name);
+ }
+
+ free(argv[0]);
+ free(argv);
+ }
+ }
+
+ if (config.parallel > 0 && proc_pool) {
+ proc_wait_all(proc_pool, max_parallel);
+ free(proc_pool);
+ }
+
+#ifdef _CB_LOG_TO_FILE
+ fclose(log);
+#endif
+}
+
+#endif // _CBUILD_H
diff --git a/src/lib/stb_cbuild.h b/src/lib/stb_cbuild.h
new file mode 100644
index 0000000..3714437
--- /dev/null
+++ b/src/lib/stb_cbuild.h
@@ -0,0 +1,1368 @@
+#ifndef _STB_CB_H
+#define _STB_CB_H
+#include <openssl/md5.h> // Requires OpenSSL
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+//============================= arglist.h
+//=======================================
+typedef struct {
+ char **list;
+ int count;
+ int capacity;
+} CB_ARGLIST;
+
+static inline CB_ARGLIST *arglist_new();
+static inline void arglist_append(CB_ARGLIST *arglist, ...); // sentinel: NULL
+static inline void arglist_append_array(CB_ARGLIST *arglist, const char **arr);
+static inline void arglist_free(CB_ARGLIST *arglist);
+
+//============================= platform & macros
+//===============================
+#if defined(_WIN32)
+#define OS_WIN 1
+#include <process.h>
+#include <windows.h>
+#else
+#define OS_UNIX 1
+#include <sys/wait.h>
+#include <unistd.h>
+#endif
+
+#if defined(__APPLE__)
+#define OS_MACOS 1
+#endif
+
+#ifdef CB_DEBUG
+#define CB_DEBUG_LOG(fmt, ...) \
+ fprintf(stderr, "\x1b[36m[DEBUG] %s:%d: " fmt "\x1b[0m\n", __FILE__, \
+ __LINE__, ##__VA_ARGS__)
+#else
+#define CB_DEBUG_LOG(fmt, ...)
+#endif
+
+#if OS_UNIX
+#include <ctype.h>
+#include <errno.h>
+#include <glob.h>
+#include <limits.h>
+#include <sys/types.h>
+#endif
+
+// Allow override by macro or environment
+#ifndef CB_COMPILER
+#if OS_WIN
+#define COMPILER_NAME_DEFAULT "cl"
+#elif defined(OS_MACOS)
+#define COMPILER_NAME_DEFAULT "clang"
+#else
+#define COMPILER_NAME_DEFAULT "cc"
+#endif
+#else
+#define COMPILER_NAME_DEFAULT CB_COMPILER
+#endif
+
+#define COLOR_RESET "\x1b[0m"
+#define COLOR_RED "\x1b[31m"
+#define COLOR_GREEN "\x1b[32m"
+#define COLOR_YELLOW "\x1b[33m"
+#define COLOR_CYAN "\x1b[36m"
+
+typedef enum { BUILD_EXEC, BUILD_STATIC, BUILD_SHARED } _CB_BUILD_TYPE;
+
+typedef struct _CB_PROJECT {
+ char *name;
+ char *output;
+ CB_ARGLIST *files; // may contain wildcard patterns; expanded before build
+ CB_ARGLIST *buildflags;
+ CB_ARGLIST *flags;
+ char *compile_command;
+ int is_rebuild;
+ _CB_BUILD_TYPE build_type;
+ int CB_PYTHON;
+} _CB_PROJECT;
+
+typedef struct {
+ _CB_PROJECT **projects;
+ int run; // run produced outputs?
+ int run_if_skipped; // run even if build skipped (cache hit)
+ int parallel_build; // NEW: number of parallel build jobs
+ int parallel_run; // NEW: number of parallel run jobs
+ int parallel; // DEPRECATED: kept for back-compat; treated as parallel_run if
+ // >0
+} CB_PROJECT_BUILD_CONFIG;
+
+#define CB_STRLIST(...) ((const char *[]){__VA_ARGS__, NULL})
+#define CB_PROJECT_LIST(...) ((_CB_PROJECT *[]){__VA_ARGS__, NULL})
+
+#define _CB_CREATE_PROJECT(var, ...) \
+ _CB_PROJECT *var = (_CB_PROJECT *)malloc(sizeof(_CB_PROJECT)); \
+ if (!(var)) { \
+ fprintf(stderr, COLOR_RED "[error] OOM\n" COLOR_RESET); \
+ exit(1); \
+ } \
+ memset(var, 0, sizeof(_CB_PROJECT)); \
+ struct { \
+ char *name; \
+ char *output; \
+ const char **files; \
+ const char **buildflags; \
+ const char **flags; \
+ int is_rebuild; \
+ _CB_BUILD_TYPE build_type; \
+ int CB_PYTHON; \
+ } var##_init = {__VA_ARGS__}; \
+ var->name = var##_init.name; \
+ var->output = var##_init.output; \
+ var->files = arglist_new(); \
+ var->buildflags = arglist_new(); \
+ var->flags = arglist_new(); \
+ var->is_rebuild = var##_init.is_rebuild; \
+ var->build_type = var##_init.build_type; \
+ var->CB_PYTHON = var##_init.CB_PYTHON; \
+ if (var##_init.files) \
+ arglist_append_array(var->files, var##_init.files); \
+ if (var##_init.buildflags) \
+ arglist_append_array(var->buildflags, var##_init.buildflags); \
+ if (var##_init.flags) \
+ arglist_append_array(var->flags, var##_init.flags)
+
+#define CB_NEEDED_LIBS "-lssl -lcrypto"
+
+// — forward decls —
+static char *cb_concat_compile_command(_CB_PROJECT *proj);
+#define _CB_BUILD_COMPILE_COMMAND(proj) \
+ do { \
+ if ((proj)->compile_command) { \
+ free((proj)->compile_command); \
+ } \
+ (proj)->compile_command = cb_concat_compile_command(proj); \
+ } while (0)
+
+static void cb_dump_to_console(const _CB_PROJECT *project);
+static void cb_free_project(_CB_PROJECT *project);
+static char *cb_compute_md5(const char *data, size_t len);
+static char *cb_read_file_content(const char *filepath, size_t *out_len);
+static char *cb_compute_project_checksum(_CB_PROJECT *proj);
+static char *cb_read_checksum(const char *filename);
+static int cb_write_checksum(const char *filename, const char *checksum);
+static void cb_expand_project_wildcards(_CB_PROJECT *proj); // NEW
+
+typedef struct {
+#if OS_WIN
+ PROCESS_INFORMATION pi;
+#else
+ pid_t pid;
+#endif
+ int running;
+ _CB_PROJECT *project;
+ int is_build;
+} proc_t;
+
+static int proc_start_run(proc_t *proc, _CB_PROJECT *proj, char **argv);
+static int proc_start_build_cmd(proc_t *proc, _CB_PROJECT *proj,
+ const char *cmdline);
+static int proc_poll(proc_t *proc);
+static void proc_wait_all(proc_t *procs, int count);
+
+#define _CB_PROJECT_BUILD(...) \
+ _cb_project_build_internal((CB_PROJECT_BUILD_CONFIG){__VA_ARGS__})
+static void _cb_project_build_internal(CB_PROJECT_BUILD_CONFIG config);
+
+#define _CB_IMPLEMENTATION
+//============================= implementation
+//==================================
+#ifdef _CB_IMPLEMENTATION
+
+//---------- small utils ----------
+static char *cb_strdup(const char *s) {
+ if (!s)
+ return NULL;
+ size_t n = strlen(s) + 1;
+ char *p = (char *)malloc(n);
+ if (!p) {
+ fprintf(stderr, COLOR_RED "[error] OOM\n" COLOR_RESET);
+ exit(1);
+ }
+ memcpy(p, s, n);
+ return p;
+}
+static void *cb_realloc(void *p, size_t sz) {
+ void *q = realloc(p, sz);
+ if (!q) {
+ fprintf(stderr, COLOR_RED "[error] OOM\n" COLOR_RESET);
+ exit(1);
+ }
+ return q;
+}
+static int cb_str_has_wildcards(const char *s) {
+ if (!s)
+ return 0;
+ for (; *s; ++s) {
+ if (*s == '*' || *s == '?' || *s == '[')
+ return 1;
+#if OS_WIN
+ // Windows doesn't support [...] in FindFirstFile, but treat it as pattern
+ // anyway
+#endif
+ }
+ return 0;
+}
+
+// Growable string builder append
+static void cb_strcatf(char **dst, size_t *cap, size_t *len, const char *fmt,
+ ...) {
+ va_list ap;
+ for (;;) {
+ va_start(ap, fmt);
+ int need = vsnprintf((*dst) ? *dst + *len : NULL, (*dst) ? *cap - *len : 0,
+ fmt, ap);
+ va_end(ap);
+ if (need < 0) { // encoding error
+ return;
+ }
+ size_t req = (size_t)need;
+ if ((*dst) && *len + req < *cap) {
+ *len += req;
+ return;
+ }
+ size_t newcap = (*cap ? *cap : 64);
+ while (newcap <= *len + req)
+ newcap *= 2;
+ *dst = (char *)cb_realloc(*dst, newcap);
+ if (*cap == 0) {
+ (*dst)[0] = '\0';
+ }
+ *cap = newcap;
+ }
+}
+
+//---------- arglist ----------
+static inline CB_ARGLIST *arglist_new() {
+ CB_ARGLIST *a = (CB_ARGLIST *)malloc(sizeof(CB_ARGLIST));
+ if (!a) {
+ fprintf(stderr, COLOR_RED "[error] OOM\n" COLOR_RESET);
+ exit(1);
+ }
+ a->count = 0;
+ a->capacity = 8;
+ a->list = (char **)malloc(sizeof(char *) * a->capacity);
+ if (!a->list) {
+ fprintf(stderr, COLOR_RED "[error] OOM\n" COLOR_RESET);
+ exit(1);
+ }
+ return a;
+}
+static void arglist__ensure(CB_ARGLIST *a, int need) {
+ if (a->count + need <= a->capacity)
+ return;
+ while (a->count + need > a->capacity)
+ a->capacity *= 2;
+ a->list = (char **)cb_realloc(a->list, sizeof(char *) * a->capacity);
+}
+static inline void arglist_append(CB_ARGLIST *arglist, ...) {
+ if (!arglist)
+ return;
+ va_list args;
+ va_start(args, arglist);
+ for (;;) {
+ char *arg = va_arg(args, char *);
+ if (!arg)
+ break;
+ arglist__ensure(arglist, 1);
+ arglist->list[arglist->count++] = cb_strdup(arg);
+ }
+ va_end(args);
+}
+static inline void arglist_append_array(CB_ARGLIST *arglist, const char **arr) {
+ if (!arglist || !arr)
+ return;
+ for (int i = 0; arr[i] != NULL; i++) {
+ arglist_append(arglist, arr[i], NULL);
+ }
+}
+static inline void arglist_free(CB_ARGLIST *arglist) {
+ if (!arglist)
+ return;
+ for (int i = 0; i < arglist->count; i++)
+ free(arglist->list[i]);
+ free(arglist->list);
+ free(arglist);
+}
+
+//---------- compiler selection ----------
+static const char *cb_pick_compiler_name(void) {
+ const char *env = getenv("CB_CC");
+ if (env && env[0])
+ return env;
+ return COMPILER_NAME_DEFAULT;
+}
+
+//---------- wildcard expansion ----------
+static void cb_files_add(CB_ARGLIST *files, const char *s) {
+ arglist_append(files, s, NULL);
+}
+
+#if OS_WIN
+static void cb_expand_pattern_win(CB_ARGLIST *out, const char *pattern) {
+ // Split directory part to preserve prefix
+ char dir[MAX_PATH];
+ const char *slash = strrchr(pattern, '\\');
+ const char *slash2 = strrchr(pattern, '/');
+ const char *cut = slash ? slash : slash2;
+ size_t dlen = 0;
+ if (cut) {
+ dlen = (size_t)(cut - pattern + 1);
+ if (dlen >= sizeof(dir))
+ dlen = sizeof(dir) - 1;
+ memcpy(dir, pattern, dlen);
+ dir[dlen] = '\0';
+ } else {
+ dir[0] = '\0';
+ }
+
+ WIN32_FIND_DATAA fd;
+ HANDLE h = FindFirstFileA(pattern, &fd);
+ if (h == INVALID_HANDLE_VALUE) {
+ // no match: keep literal to let compiler report it
+ cb_files_add(out, pattern);
+ return;
+ }
+ do {
+ if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
+ char path[MAX_PATH * 2];
+ snprintf(path, sizeof(path), "%s%s", dir, fd.cFileName);
+ cb_files_add(out, path);
+ }
+ } while (FindNextFileA(h, &fd));
+ FindClose(h);
+}
+#else
+static void cb_expand_pattern_unix(CB_ARGLIST *out, const char *pattern) {
+ glob_t gl;
+ memset(&gl, 0, sizeof(gl));
+ int r = glob(pattern, GLOB_TILDE, NULL, &gl);
+ if (r == 0) {
+ for (size_t i = 0; i < gl.gl_pathc; i++) {
+ cb_files_add(out, gl.gl_pathv[i]);
+ }
+ globfree(&gl);
+ return;
+ }
+ // No matches or error — keep as literal so the compiler can error out if
+ // needed.
+ cb_files_add(out, pattern);
+}
+#endif
+
+static void cb_expand_project_wildcards(_CB_PROJECT *proj) {
+ if (!proj || !proj->files)
+ return;
+ CB_ARGLIST *expanded = arglist_new();
+ for (int i = 0; i < proj->files->count; i++) {
+ const char *f = proj->files->list[i];
+ if (cb_str_has_wildcards(f)) {
+#if OS_WIN
+ cb_expand_pattern_win(expanded, f);
+#else
+ cb_expand_pattern_unix(expanded, f);
+#endif
+ } else {
+ cb_files_add(expanded, f);
+ }
+ }
+ // swap lists
+ arglist_free(proj->files);
+ proj->files = expanded;
+ CB_DEBUG_LOG("Wildcard expansion for %s produced %d file(s)", proj->name,
+ proj->files->count);
+}
+
+#ifdef _CB_PY
+
+#include "cb_py.h"
+
+
+
+typedef struct {
+ const char *name;
+
+ char *file_data;
+ int file_size;
+
+ char ***code_blocks;
+ int code_block_count;
+ int *code_block_sizes;
+ int *code_block_starts;
+
+} _CB_PROJECT_TRANSPILE;
+
+static void _cb_project_transpile_free(_CB_PROJECT_TRANSPILE *transpile) {
+ for (int i = 0; i < transpile->code_block_count; i++) {
+ for (int j = 0; j < transpile->code_block_sizes[i]; j++) {
+ free(transpile->code_blocks[i][j]); // free each line
+ }
+ free(transpile->code_blocks[i]); // free line array
+ }
+ free(transpile->code_blocks);
+ free(transpile->code_block_sizes);
+ free(transpile->code_block_starts);
+ free(transpile->file_data);
+ free(transpile);
+}
+
+static void _cb_project_transpile_append(_CB_PROJECT_TRANSPILE *transpile,
+ char **code_block, int code_block_size,
+ int start_line) {
+ transpile->code_blocks =
+ (char ***)realloc(transpile->code_blocks,
+ (transpile->code_block_count + 1) * sizeof(char **));
+ transpile->code_block_sizes =
+ (int *)realloc(transpile->code_block_sizes,
+ (transpile->code_block_count + 1) * sizeof(int));
+ transpile->code_block_starts =
+ (int *)realloc(transpile->code_block_starts,
+ (transpile->code_block_count + 1) * sizeof(int));
+
+ transpile->code_blocks[transpile->code_block_count] = code_block;
+ transpile->code_block_sizes[transpile->code_block_count] = code_block_size;
+ transpile->code_block_starts[transpile->code_block_count] = start_line;
+ transpile->code_block_count++;
+
+ // Debug print
+ CB_DEBUG_LOG("Captured code block #%d starting at line %d (%d lines):",
+ transpile->code_block_count, start_line, code_block_size);
+}
+
+static char *cb_strip_comment_prefix(const char *line) {
+ const char *p = line;
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ if (p[0] == '/' && p[1] == '/') {
+ p += 2;
+ if (*p == ' ')
+ p++;
+ }
+
+ return strdup(p); // return cleaned line
+}
+static int _py_to_c99(_CB_PROJECT *proj) {
+ for (int i = 0; i < proj->files->count; i++) {
+ const char *f = proj->files->list[i];
+ FILE *fp = fopen(f, "r");
+ if (!fp) {
+ CB_DEBUG_LOG("Failed to open file %s", f);
+ return -1;
+ }
+
+ // read file into memory
+ fseek(fp, 0, SEEK_END);
+ int file_size = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+ char *file_data = (char *)malloc(file_size + 1);
+ if (!file_data) {
+ CB_DEBUG_LOG("Failed to allocate memory for file %s", f);
+ fclose(fp);
+ return -1;
+ }
+ fread(file_data, 1, file_size, fp);
+ file_data[file_size] = '\0';
+ fclose(fp);
+
+ // split into lines
+ int line_count = 0;
+ char **lines = NULL;
+ char *saveptr;
+ char *tok = strtok_r(file_data, "\n", &saveptr);
+ while (tok) {
+ lines = (char **)realloc(lines, (line_count + 1) * sizeof(char *));
+ lines[line_count++] = strdup(tok);
+ tok = strtok_r(NULL, "\n", &saveptr);
+ }
+
+ // process file: detect blocks
+ int in_block = 0;
+ char **current_block = NULL;
+ int current_size = 0;
+ int start_line = 0;
+
+ // new file buffer (lines replaced)
+ char **new_lines = NULL;
+ int new_count = 0;
+
+ for (int j = 0; j < line_count; j++) {
+ const char *line = lines[j];
+ if (!in_block && strstr(line, "__CB_PY_BEGIN")) {
+ in_block = 1;
+ start_line = j;
+ // keep a marker comment in output
+ new_lines =
+ (char **)realloc(new_lines, (new_count + 1) * sizeof(char *));
+ new_lines[new_count++] = strdup("// transpiled block begin");
+ continue;
+ }
+
+ if (in_block && strstr(line, "__CB_PY_END")) {
+ in_block = 0;
+
+ CB_DEBUG_LOG("Captured code block #%d starting at line %d (%d lines):",
+ new_count, start_line, current_size);
+ for (int k = 0; k < current_size; k++) {
+ CB_DEBUG_LOG(" %s", current_block[k]);
+ }
+ // transpile captured block
+ int out_size = 0;
+ char **c_lines =
+ transpile_py_block(current_block, current_size, &out_size);
+
+ for (int k = 0; k < out_size; k++) {
+ new_lines =
+ (char **)realloc(new_lines, (new_count + 1) * sizeof(char *));
+ new_lines[new_count++] = c_lines[k]; // already malloc’d
+ }
+
+ // cleanup block
+ for (int k = 0; k < current_size; k++)
+ free(current_block[k]);
+ free(current_block);
+ current_block = NULL;
+ current_size = 0;
+
+ new_lines =
+ (char **)realloc(new_lines, (new_count + 1) * sizeof(char *));
+ new_lines[new_count++] = strdup("// transpiled block end");
+ continue;
+ }
+
+ if (in_block) {
+ // capture block lines (strip // prefix)
+ current_block = (char **)realloc(current_block,
+ (current_size + 1) * sizeof(char *));
+ current_block[current_size++] = cb_strip_comment_prefix(line);
+ } else {
+ // just copy normal line
+ new_lines =
+ (char **)realloc(new_lines, (new_count + 1) * sizeof(char *));
+ new_lines[new_count++] = strdup(line);
+ }
+ }
+
+ // write file back
+ fp = fopen(f, "w");
+ if (!fp) {
+ CB_DEBUG_LOG("Failed to write file %s", f);
+ return -1;
+ }
+ for (int j = 0; j < new_count; j++) {
+ fprintf(fp, "%s\n", new_lines[j]);
+ free(new_lines[j]);
+ }
+ fclose(fp);
+
+ // cleanup
+ for (int j = 0; j < line_count; j++)
+ free(lines[j]);
+ free(lines);
+ free(new_lines);
+ free(file_data);
+
+ CB_DEBUG_LOG("Transpiled %s", f);
+ }
+ return 0;
+}
+
+#define _CB_PYTHON_TRANSPILE(proj) int ret = _py_to_c99(proj)
+
+#endif // _CB_PY
+//---------- compile command ----------
+static char *cb_concat_compile_command(_CB_PROJECT *proj) {
+ if (!proj || !proj->files || proj->files->count == 0)
+ return cb_strdup("[error] No source files");
+
+#ifdef _CB_PY
+ if (proj->CB_PYTHON) {
+ CB_DEBUG_LOG("Project is set to have _PY() blocks: %s", proj->output);
+ CB_DEBUG_LOG("Will try to transpile into c99.");
+ _CB_PYTHON_TRANSPILE(proj);
+ if (ret != 0) {
+ return cb_strdup("[error] Failed to transpile project");
+ }
+ CB_DEBUG_LOG("Transpilation successful for %s.", proj->output);
+ }
+#endif // _CB_PY
+ const char *cc = cb_pick_compiler_name();
+ char *cmd = NULL;
+ size_t cap = 0, len = 0;
+
+#if OS_WIN
+ switch (proj->build_type) {
+ case BUILD_EXEC:
+ cb_strcatf(&cmd, &cap, &len, "%s", cc);
+ break;
+ case BUILD_STATIC:
+ // compile to .obj files
+ for (int i = 0; i < proj->files->count; i++)
+ cb_strcatf(&cmd, &cap, &len, "%s /c %s && ", cc, proj->files->list[i]);
+ cb_strcatf(&cmd, &cap, &len, "lib /OUT:%s", proj->output);
+ return cmd;
+ case BUILD_SHARED:
+ cb_strcatf(&cmd, &cap, &len, "%s /LD", cc);
+ break;
+ }
+#else // Unix
+ switch (proj->build_type) {
+ case BUILD_EXEC:
+ cb_strcatf(&cmd, &cap, &len, "%s", cc);
+ break;
+ case BUILD_STATIC:
+ // compile all to .o then archive
+ for (int i = 0; i < proj->files->count; i++)
+ cb_strcatf(&cmd, &cap, &len, "%s -c %s && ", cc, proj->files->list[i]);
+ cb_strcatf(&cmd, &cap, &len, "ar rcs %s *.o", proj->output);
+ return cmd;
+ case BUILD_SHARED:
+ cb_strcatf(&cmd, &cap, &len, "%s -fPIC -shared", cc);
+ break;
+ }
+#endif
+
+ // Common flags
+ for (int i = 0; i < proj->buildflags->count; i++)
+ cb_strcatf(&cmd, &cap, &len, " %s", proj->buildflags->list[i]);
+
+ if (proj->is_rebuild)
+ cb_strcatf(&cmd, &cap, &len, " %s", CB_NEEDED_LIBS);
+
+ // Add sources
+ for (int i = 0; i < proj->files->count; i++)
+ cb_strcatf(&cmd, &cap, &len, " %s", proj->files->list[i]);
+
+ // Output handling
+ if (proj->output) {
+#if OS_WIN
+ if (proj->build_type == BUILD_EXEC)
+ cb_strcatf(&cmd, &cap, &len, " /Fe%s", proj->output);
+ else if (proj->build_type == BUILD_SHARED)
+ cb_strcatf(&cmd, &cap, &len, " /Fe%s", proj->output);
+#else
+ if (proj->build_type == BUILD_EXEC)
+ cb_strcatf(&cmd, &cap, &len, " -o %s", proj->output);
+ else if (proj->build_type == BUILD_SHARED)
+ cb_strcatf(&cmd, &cap, &len, " -o %s", proj->output);
+#endif
+ }
+
+ CB_DEBUG_LOG("Generated compile command: %s", cmd);
+ return cmd;
+}
+
+//---------- info & free ----------
+static void _cb_project_dump(_CB_PROJECT *proj) {
+ if (!proj)
+ return;
+
+ printf("== Project Dump ==\n");
+ printf("Name: %s\n", proj->name ? proj->name : "(unnamed)");
+ printf("Output: %s\n", proj->output ? proj->output : "(none)");
+
+ // Build type
+ const char *type_str = "EXEC";
+ if (proj->build_type == BUILD_STATIC)
+ type_str = "STATIC";
+ else if (proj->build_type == BUILD_SHARED)
+ type_str = "SHARED";
+ printf("Build type: %s\n", type_str);
+
+ // Files
+ printf("Files (%d):\n", proj->files ? proj->files->count : 0);
+ if (proj->files)
+ for (int i = 0; i < proj->files->count; i++)
+ printf(" %s\n", proj->files->list[i]);
+
+ // Build Flags
+ printf("Build flags (%d):\n", proj->buildflags ? proj->buildflags->count : 0);
+ if (proj->buildflags)
+ for (int i = 0; i < proj->buildflags->count; i++)
+ printf(" %s\n", proj->buildflags->list[i]);
+
+ // Flags
+ printf("Flags (%d):\n", proj->flags ? proj->flags->count : 0);
+ if (proj->flags)
+ for (int i = 0; i < proj->flags->count; i++)
+ printf(" %s\n", proj->flags->list[i]);
+
+ // Command
+ printf("Compile command: %s\n",
+ proj->compile_command ? proj->compile_command : "(none)");
+}
+
+static void cb_free_project(_CB_PROJECT *project) {
+ if (!project)
+ return;
+ if (project->files)
+ arglist_free(project->files);
+ if (project->buildflags)
+ arglist_free(project->buildflags);
+ if (project->flags)
+ arglist_free(project->flags);
+ if (project->compile_command)
+ free(project->compile_command);
+ free(project);
+}
+
+//---------- checksum ----------
+static char *cb_compute_md5(const char *data, size_t len) {
+ unsigned char digest[MD5_DIGEST_LENGTH];
+ MD5((const unsigned char *)data, len, digest);
+ char *out = (char *)malloc(MD5_DIGEST_LENGTH * 2 + 1);
+ if (!out) {
+ fprintf(stderr, COLOR_RED "[error] OOM\n" COLOR_RESET);
+ exit(1);
+ }
+ for (int i = 0; i < MD5_DIGEST_LENGTH; i++)
+ sprintf(out + i * 2, "%02x", digest[i]);
+ out[MD5_DIGEST_LENGTH * 2] = 0;
+ return out;
+}
+
+static char *cb_read_file_content(const char *filepath, size_t *out_len) {
+ FILE *f = fopen(filepath, "rb");
+ if (!f)
+ return NULL;
+ if (fseek(f, 0, SEEK_END) != 0) {
+ fclose(f);
+ return NULL;
+ }
+ long size = ftell(f);
+ if (size < 0) {
+ fclose(f);
+ return NULL;
+ }
+ if (fseek(f, 0, SEEK_SET) != 0) {
+ fclose(f);
+ return NULL;
+ }
+ char *buffer = (char *)malloc((size_t)size);
+ if (!buffer) {
+ fclose(f);
+ return NULL;
+ }
+ size_t read_len = fread(buffer, 1, (size_t)size, f);
+ fclose(f);
+ if (out_len)
+ *out_len = read_len;
+ return buffer;
+}
+
+static char *cb_compute_project_checksum(_CB_PROJECT *proj) {
+ if (!proj)
+ return NULL;
+ MD5_CTX ctx;
+ MD5_Init(&ctx);
+
+ // include compile command, which includes flags & file list
+ if (proj->compile_command)
+ MD5_Update(&ctx, proj->compile_command, strlen(proj->compile_command));
+
+ // include file contents
+ for (int i = 0; i < proj->files->count; i++) {
+ size_t flen = 0;
+ char *fcontent = cb_read_file_content(proj->files->list[i], &flen);
+ if (fcontent) {
+ MD5_Update(&ctx, fcontent, flen);
+ free(fcontent);
+ }
+ }
+
+ unsigned char digest[MD5_DIGEST_LENGTH];
+ MD5_Final(digest, &ctx);
+
+ char *checksum = (char *)malloc(MD5_DIGEST_LENGTH * 2 + 1);
+ if (!checksum) {
+ fprintf(stderr, COLOR_RED "[error] OOM\n" COLOR_RESET);
+ exit(1);
+ }
+ for (int i = 0; i < MD5_DIGEST_LENGTH; i++)
+ sprintf(checksum + i * 2, "%02x", digest[i]);
+ checksum[MD5_DIGEST_LENGTH * 2] = 0;
+ return checksum;
+}
+
+static char *cb_read_checksum(const char *filename) {
+ FILE *f = fopen(filename, "r");
+ if (!f)
+ return NULL;
+ char buf[MD5_DIGEST_LENGTH * 2 + 1];
+ size_t r = fread(buf, 1, MD5_DIGEST_LENGTH * 2, f);
+ fclose(f);
+ if (r != MD5_DIGEST_LENGTH * 2)
+ return NULL;
+ buf[r] = 0;
+ return cb_strdup(buf);
+}
+
+static int cb_write_checksum(const char *filename, const char *checksum) {
+ FILE *f = fopen(filename, "w");
+ if (!f)
+ return -1;
+ size_t w = fwrite(checksum, 1, strlen(checksum), f);
+ fclose(f);
+ return (w == strlen(checksum)) ? 0 : -1;
+}
+
+//---------- process helpers ----------
+static int proc_start_build_cmd(proc_t *proc, _CB_PROJECT *proj,
+ const char *cmdline) {
+ if (!cmdline)
+ return -1;
+#if OS_WIN
+ STARTUPINFOA si;
+ ZeroMemory(&si, sizeof(si));
+ si.cb = sizeof(si);
+ ZeroMemory(&proc->pi, sizeof(proc->pi));
+
+ // run through cmd.exe to avoid manual argument parsing
+ char *full = NULL;
+ size_t cap = 0, len = 0;
+ cb_strcatf(&full, &cap, &len, "cmd.exe /C %s", cmdline);
+
+ BOOL ok = CreateProcessA(NULL, full, NULL, NULL, FALSE, 0, NULL, NULL, &si,
+ &proc->pi);
+ free(full);
+ if (!ok) {
+ fprintf(stderr, COLOR_RED "[error] Failed to start build: %s\n" COLOR_RESET,
+ cmdline);
+ proc->running = 0;
+ return -1;
+ }
+ proc->running = 1;
+ proc->project = proj;
+ proc->is_build = 1;
+ return 0;
+#else
+ pid_t pid = fork();
+ if (pid == 0) {
+ // child: run via /bin/sh -c "cmdline"
+ execl("/bin/sh", "sh", "-c", cmdline, (char *)NULL);
+ perror("execl");
+ _exit(127);
+ } else if (pid > 0) {
+ proc->pid = pid;
+ proc->running = 1;
+ proc->project = proj;
+ proc->is_build = 1;
+ return 0;
+ } else {
+ perror("fork");
+ proc->running = 0;
+ return -1;
+ }
+#endif
+}
+
+static int proc_start_run(proc_t *proc, _CB_PROJECT *proj, char **argv) {
+#if OS_WIN
+ STARTUPINFOA si;
+ ZeroMemory(&si, sizeof(si));
+ si.cb = sizeof(si);
+ ZeroMemory(&proc->pi, sizeof(proc->pi));
+
+ // Build a quoted commandline
+ size_t cmdlen = 0;
+ for (int i = 0; argv[i]; i++) {
+ size_t arglen = strlen(argv[i]);
+ int needs_quotes = strchr(argv[i], ' ') != NULL;
+ cmdlen += arglen + (needs_quotes ? 2 : 0) + 1;
+ }
+ char *cmdline = (char *)malloc(cmdlen + 1);
+ if (!cmdline)
+ return -1;
+ cmdline[0] = 0;
+ for (int i = 0; argv[i]; i++) {
+ int needs_quotes = strchr(argv[i], ' ') != NULL;
+ if (i > 0)
+ strcat(cmdline, " ");
+ if (needs_quotes)
+ strcat(cmdline, "\"");
+ strcat(cmdline, argv[i]);
+ if (needs_quotes)
+ strcat(cmdline, "\"");
+ }
+ BOOL success = CreateProcessA(NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL,
+ &si, &proc->pi);
+ free(cmdline);
+ if (!success) {
+ fprintf(stderr, COLOR_RED "[error] Failed to run %s\n" COLOR_RESET,
+ argv[0]);
+ proc->running = 0;
+ return -1;
+ }
+ proc->running = 1;
+ proc->project = proj;
+ proc->is_build = 0;
+ return 0;
+#else
+ pid_t pid = fork();
+ if (pid == 0) {
+ execvp(argv[0], argv);
+ perror("execvp");
+ _exit(127);
+ } else if (pid > 0) {
+ proc->pid = pid;
+ proc->running = 1;
+ proc->project = proj;
+ proc->is_build = 0;
+ return 0;
+ } else {
+ perror("fork");
+ proc->running = 0;
+ return -1;
+ }
+#endif
+}
+
+static int proc_poll(proc_t *proc) {
+ if (!proc->running)
+ return -1;
+#if OS_WIN
+ DWORD res = WaitForSingleObject(proc->pi.hProcess, 0);
+ if (res == WAIT_OBJECT_0) {
+ DWORD code;
+ GetExitCodeProcess(proc->pi.hProcess, &code);
+ CloseHandle(proc->pi.hProcess);
+ CloseHandle(proc->pi.hThread);
+ proc->running = 0;
+ return (int)code;
+ }
+ return -1;
+#else
+ int status;
+ pid_t ret = waitpid(proc->pid, &status, WNOHANG);
+ if (ret == 0)
+ return -1; // still running
+ else if (ret == proc->pid) {
+ proc->running = 0;
+ if (WIFEXITED(status))
+ return WEXITSTATUS(status);
+ else
+ return -1;
+ }
+ return -1;
+#endif
+}
+
+static void proc_wait_all(proc_t *procs, int count) {
+ int running = count;
+ while (running > 0) {
+ running = 0;
+ for (int i = 0; i < count; i++) {
+ if (procs[i].running) {
+ int ret = proc_poll(&procs[i]);
+ if (ret < 0)
+ running++;
+ }
+ }
+#if OS_WIN
+ Sleep(10);
+#else
+ struct timespec ts;
+ ts.tv_sec = 0;
+ ts.tv_nsec = 10000000;
+ nanosleep(&ts, NULL);
+#endif
+ }
+}
+
+//---------- main build driver ----------
+static void _cb_project_build_internal(CB_PROJECT_BUILD_CONFIG config) {
+ if (!config.projects) {
+ fprintf(stderr, COLOR_RED "[error] No projects to build.\n" COLOR_RESET);
+ return;
+ }
+
+ // Back-compat: old .parallel means parallel_run if caller used it
+ if (config.parallel_run <= 0 && config.parallel > 0)
+ config.parallel_run = config.parallel;
+ if (config.parallel_build <= 0)
+ config.parallel_build = 1;
+ if (config.parallel_run <= 0)
+ config.parallel_run = 1;
+
+#ifdef _CB_LOG_TO_FILE
+ FILE *log = fopen(".cb_build.out", "a");
+ if (!log) {
+ perror("fopen");
+ return;
+ }
+ time_t now = time(NULL);
+ fprintf(log, "\n=== Build Started: %s", ctime(&now));
+#endif
+
+ // 1) Prepare: expand wildcards, build commands, compute checksums, decide
+ // build/no-build
+ typedef struct {
+ _CB_PROJECT *proj;
+ int should_build;
+ char checksum_file[512];
+ char *new_checksum;
+ char *old_checksum;
+ } item_t;
+
+ int nproj = 0;
+ while (config.projects[nproj])
+ nproj++;
+ item_t *items = (item_t *)calloc(nproj, sizeof(item_t));
+ if (!items) {
+ fprintf(stderr, COLOR_RED "[error] OOM\n" COLOR_RESET);
+ return;
+ }
+
+ for (int i = 0; i < nproj; i++) {
+ _CB_PROJECT *proj = config.projects[i];
+
+ // Expand wildcards BEFORE command & checksum
+ cb_expand_project_wildcards(proj);
+ _CB_BUILD_COMPILE_COMMAND(proj);
+
+ // checksum filename (safe-ish)
+ snprintf(items[i].checksum_file, sizeof(items[i].checksum_file),
+ ".cb_checksum_%s", proj->name ? proj->name : "noname");
+ items[i].proj = proj;
+
+ // compute checksums
+ items[i].new_checksum = cb_compute_project_checksum(proj);
+ items[i].old_checksum = cb_read_checksum(items[i].checksum_file);
+ items[i].should_build = 1;
+ if (items[i].new_checksum && items[i].old_checksum &&
+ strcmp(items[i].new_checksum, items[i].old_checksum) == 0) {
+ items[i].should_build = 0;
+ }
+ }
+
+ // 2) Build stage (parallel if requested)
+ int build_jobs = 0;
+ for (int i = 0; i < nproj; i++)
+ if (items[i].should_build)
+ build_jobs++;
+
+ if (build_jobs == 0) {
+ for (int i = 0; i < nproj; i++) {
+ printf(COLOR_YELLOW
+ "[build] Skipping %s (no changes detected)\n\n" COLOR_RESET,
+ items[i].proj->name);
+#ifdef _CB_LOG_TO_FILE
+ if (log)
+ fprintf(log, "[build] Skipped project: %s\n", items[i].proj->name);
+#endif
+ }
+ } else if (config.parallel_build <= 1) {
+ for (int i = 0; i < nproj; i++) {
+ if (!items[i].should_build) {
+ printf(COLOR_YELLOW
+ "[build] Skipping %s (no changes detected)\n\n" COLOR_RESET,
+ items[i].proj->name);
+ continue;
+ }
+ _CB_PROJECT *proj = items[i].proj;
+ printf(COLOR_YELLOW "[build] Building %s\n" COLOR_RESET, proj->name);
+ printf(" %s\n", proj->compile_command);
+#ifdef _CB_LOG_TO_FILE
+ if (log)
+ fprintf(log, "[build] Project: %s\nCommand: %s\n", proj->name,
+ proj->compile_command);
+#endif
+ clock_t start = clock();
+ // Run synchronously (serial mode)
+#if OS_WIN
+ // Use system here; it will invoke cmd.exe
+ int ret = system(proj->compile_command);
+#else
+ int ret = system(proj->compile_command);
+#endif
+ clock_t end = clock();
+ if (ret != 0) {
+ fprintf(stderr, COLOR_RED "[error] Build failed for %s\n" COLOR_RESET,
+ proj->name);
+#ifdef _CB_LOG_TO_FILE
+ if (log)
+ fprintf(log, "[error] Build failed for %s\n", proj->name);
+#endif
+ continue;
+ }
+ double duration = (double)(end - start) / CLOCKS_PER_SEC;
+ printf(COLOR_GREEN "[success] Built in %.2fs\n" COLOR_RESET, duration);
+#ifdef _CB_LOG_TO_FILE
+ if (log)
+ fprintf(log, "[success] Built in %.2fs\n", duration);
+#endif
+ cb_write_checksum(items[i].checksum_file, items[i].new_checksum);
+ }
+ } else {
+ // Parallel build pool
+ int maxp = config.parallel_build;
+ proc_t *pool = (proc_t *)calloc(maxp, sizeof(proc_t));
+ if (!pool) {
+ fprintf(stderr, COLOR_RED "[error] OOM\n" COLOR_RESET);
+ goto after_builds;
+ }
+ int active = 0, next = 0;
+
+ while (1) {
+ // launch while capacity
+ while (active < maxp && next < nproj) {
+ if (!items[next].should_build) {
+ next++;
+ continue;
+ }
+ _CB_PROJECT *proj = items[next].proj;
+ printf(COLOR_YELLOW "[build] Building %s (parallel)\n" COLOR_RESET,
+ proj->name);
+ printf(" %s\n", proj->compile_command);
+#ifdef _CB_LOG_TO_FILE
+ if (log)
+ fprintf(log, "[build] Project: %s\nCommand: %s\n", proj->name,
+ proj->compile_command);
+#endif
+ // find free slot
+ int k = -1;
+ for (int t = 0; t < maxp; t++)
+ if (!pool[t].running) {
+ k = t;
+ break;
+ }
+ if (k == -1)
+ break; // shouldn't happen due to active<maxp
+ if (proc_start_build_cmd(&pool[k], proj, proj->compile_command) == 0) {
+ active++;
+ } else {
+ fprintf(stderr,
+ COLOR_RED
+ "[error] Failed to start build for %s\n" COLOR_RESET,
+ proj->name);
+ }
+ next++;
+ }
+
+ if (active == 0) {
+ // maybe there are only skipped projects left
+ int remaining = 0;
+ for (int j = next; j < nproj; j++)
+ if (items[j].should_build)
+ remaining++;
+ if (remaining == 0)
+ break;
+ }
+
+ // poll
+ for (int t = 0; t < maxp; t++) {
+ if (pool[t].running) {
+ int ret = proc_poll(&pool[t]);
+ if (ret >= 0) {
+ active--;
+ if (ret != 0) {
+ fprintf(stderr,
+ COLOR_RED
+ "[error] Build failed for %s (exit %d)\n" COLOR_RESET,
+ pool[t].project ? pool[t].project->name : "(unknown)",
+ ret);
+#ifdef _CB_LOG_TO_FILE
+ if (log)
+ fprintf(log, "[error] Build failed for %s\n",
+ pool[t].project ? pool[t].project->name : "(unknown)");
+#endif
+ } else {
+ // success → write checksum
+ for (int i = 0; i < nproj; i++)
+ if (items[i].proj == pool[t].project)
+ cb_write_checksum(items[i].checksum_file,
+ items[i].new_checksum);
+ printf(COLOR_GREEN "[success] Built %s\n" COLOR_RESET,
+ pool[t].project ? pool[t].project->name : "");
+ }
+ }
+ }
+ }
+#if OS_WIN
+ Sleep(10);
+#else
+ struct timespec ts;
+ ts.tv_sec = 0;
+ ts.tv_nsec = 10000000;
+ nanosleep(&ts, NULL);
+
+#endif
+ if (next >= nproj && active == 0)
+ break;
+ }
+ free(pool);
+ after_builds:;
+ // Print skips that never printed
+ for (int i = 0; i < nproj; i++)
+ if (!items[i].should_build)
+ printf(COLOR_YELLOW
+ "[build] Skipping %s (no changes detected)\n" COLOR_RESET,
+ items[i].proj->name);
+ }
+
+ // 3) Optional run stage (may be parallel)
+ if (config.run) {
+ int maxp = (config.parallel_run > 0 ? config.parallel_run : 1);
+ proc_t *pool = (proc_t *)calloc(maxp, sizeof(proc_t));
+ if (!pool) {
+ fprintf(stderr, COLOR_RED "[error] OOM\n" COLOR_RESET);
+ goto cleanup;
+ }
+ int active = 0;
+ for (int i = 0; i < nproj; i++) {
+ _CB_PROJECT *proj = items[i].proj;
+
+ int should_run = proj->output != NULL &&
+ (items[i].should_build || config.run_if_skipped);
+ if (!should_run)
+ continue;
+
+ int argc = proj->flags ? proj->flags->count : 0;
+ char **argv = (char **)malloc(sizeof(char *) * (argc + 2));
+ if (!argv) {
+ fprintf(stderr, COLOR_RED "[error] OOM\n" COLOR_RESET);
+ continue;
+ }
+
+ char output_path[512];
+#if OS_WIN
+ snprintf(output_path, sizeof(output_path), "%s", proj->output);
+#else
+ snprintf(output_path, sizeof(output_path), "./%s", proj->output);
+#endif
+ argv[0] = cb_strdup(output_path);
+ for (int j = 0; j < argc; j++)
+ argv[j + 1] = proj->flags->list[j];
+ argv[argc + 1] = NULL;
+
+ if (maxp <= 1) {
+ // run serially and wait
+ printf(COLOR_CYAN "[run] %s\n" COLOR_RESET, argv[0]);
+#if OS_WIN
+ STARTUPINFOA si;
+ ZeroMemory(&si, sizeof(si));
+ si.cb = sizeof(si);
+ PROCESS_INFORMATION pi;
+ ZeroMemory(&pi, sizeof(pi));
+ // Build cmdline
+ size_t cmdlen = 0;
+ for (int z = 0; argv[z]; z++) {
+ size_t L = strlen(argv[z]);
+ int q = strchr(argv[z], ' ') != NULL;
+ cmdlen += L + (q ? 2 : 0) + 1;
+ }
+ char *cmdline = (char *)malloc(cmdlen + 1);
+ cmdline[0] = 0;
+ for (int z = 0; argv[z]; z++) {
+ int q = strchr(argv[z], ' ') != NULL;
+ if (z > 0)
+ strcat(cmdline, " ");
+ if (q)
+ strcat(cmdline, "\"");
+ strcat(cmdline, argv[z]);
+ if (q)
+ strcat(cmdline, "\"");
+ }
+ if (CreateProcessA(NULL, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si,
+ &pi)) {
+ WaitForSingleObject(pi.hProcess, INFINITE);
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ } else {
+ fprintf(stderr, COLOR_RED "[error] Failed to run %s\n" COLOR_RESET,
+ argv[0]);
+ }
+ free(cmdline);
+#else
+ pid_t pid = fork();
+ if (pid == 0) {
+ execvp(argv[0], argv);
+ perror("execvp");
+ _exit(127);
+ } else if (pid > 0) {
+ waitpid(pid, NULL, 0);
+ } else {
+ perror("fork");
+ }
+#endif
+ free(argv[0]);
+ free(argv);
+ if (proj->is_rebuild) {
+ // as per your debug note: stop the program if it's just a rebuild
+ exit(1);
+ }
+ } else {
+ // run in parallel pool
+ // ensure slot
+ for (;;) {
+ int used = 0;
+ for (int k = 0; k < maxp; k++)
+ if (pool[k].running)
+ used++;
+ if (used < maxp)
+ break;
+ for (int k = 0; k < maxp; k++) {
+ if (pool[k].running) {
+ int ret = proc_poll(&pool[k]);
+ (void)ret;
+ }
+ }
+#if OS_WIN
+ Sleep(10);
+#else
+ struct timespec ts;
+ ts.tv_sec = 0;
+ ts.tv_nsec = 10000000;
+ nanosleep(&ts, NULL);
+#endif
+ }
+ // find free slot
+ int k = -1;
+ for (int t = 0; t < maxp; t++)
+ if (!pool[t].running) {
+ k = t;
+ break;
+ }
+ if (k >= 0) {
+ printf(COLOR_CYAN "[run] %s (parallel)\n" COLOR_RESET, argv[0]);
+ if (proc_start_run(&pool[k], proj, argv) != 0) {
+ fprintf(stderr,
+ COLOR_RED "[error] Failed to start %s\n" COLOR_RESET,
+ argv[0]);
+ }
+ }
+ // argv memory: argv[0] was strdup'ed; the child process duplicates
+ // memory; we can free now
+ free(argv[0]);
+ free(argv);
+ }
+ }
+ if (maxp > 1) {
+ proc_wait_all(pool, maxp);
+ }
+ free(pool);
+ }
+
+cleanup:
+#ifdef _CB_LOG_TO_FILE
+ if (log)
+ fclose(log);
+#endif
+ for (int i = 0; i < nproj; i++) {
+ free(items[i].old_checksum);
+ free(items[i].new_checksum);
+ }
+ free(items);
+}
+
+#endif // _CB_IMPLEMENTATION
+#endif // _STB_CB_H
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..237bca3
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,50 @@
+#define _CB_LOG_TO_FILE
+#define CB_DEBUG
+#define _CB_IMPLEMENTATION
+#include "./lib/stb_cbuild.h"
+
+
+
+
+
+
+
+static _CB_PROJECT *rebuild = {0};
+static _CB_PROJECT *pjt = {0};
+
+int main(int argc, char **argv) {
+ _CB_CREATE_PROJECT(pjt,
+ .name = "test",
+ .output = "./bin/test",
+ .files = CB_STRLIST("main.c"),
+ .buildflags = CB_STRLIST("-Wall"),
+ .is_rebuild = 1,
+ .flags = CB_STRLIST("C Build")
+ );
+ _CB_CREATE_PROJECT(rebuild,
+ .name = "cb_rebuild",
+ .output = "./bin/cbuild",
+ .files = CB_STRLIST("./src/main.c"),
+ .is_rebuild = 1,
+ );
+
+
+ _CB_PROJECT_BUILD(
+ .projects = CB_PROJECT_LIST(rebuild),
+ .parallel = 0,
+ .run = 0,
+ );
+
+ _CB_PROJECT_BUILD(
+ .projects = CB_PROJECT_LIST(pjt),
+ .run = 1,
+ .parallel = 0,
+ .run_if_skipped = 1
+ );
+
+
+ cb_free_project(pjt);
+ cb_free_project(rebuild);
+
+ return EXIT_SUCCESS;
+}