diff options
| -rw-r--r-- | Makefile | 14 | ||||
| -rw-r--r-- | README.md | 353 | ||||
| -rw-r--r-- | main.c | 13 | ||||
| -rwxr-xr-x | setup.sh | 6 | ||||
| -rw-r--r-- | src/lib/arglist.h | 51 | ||||
| -rw-r--r-- | src/lib/cb_py.h | 1404 | ||||
| -rw-r--r-- | src/lib/cbuild.h | 679 | ||||
| -rw-r--r-- | src/lib/stb_cbuild.h | 1368 | ||||
| -rw-r--r-- | src/main.c | 50 |
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. @@ -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; +} |
