diff --git a/_test_closure/build/closure_test b/_test_closure/build/closure_test new file mode 100755 index 0000000..4637bfd Binary files /dev/null and b/_test_closure/build/closure_test differ diff --git a/_test_closure/build/io.c b/_test_closure/build/io.c new file mode 100644 index 0000000..a13bbb7 --- /dev/null +++ b/_test_closure/build/io.c @@ -0,0 +1,54 @@ +/* Bux Standard Library - I/O functions */ + +#include +#include +#include + +/* PrintLine - print string with newline */ +void PrintLine(const char* s) { + if (s != NULL) { + puts(s); + } else { + puts(""); + } +} + +/* Print - print string without newline */ +void Print(const char* s) { + if (s != NULL) { + printf("%s", s); + } +} + +/* PrintInt - print integer */ +void PrintInt(int n) { + printf("%d", n); +} + +/* PrintInt64 - print 64-bit integer */ +void PrintInt64(int64_t n) { + printf("%lld", (long long)n); +} + +/* PrintFloat - print float */ +void PrintFloat(double f) { + printf("%g", f); +} + +/* PrintBool - print boolean */ +void PrintBool(int b) { + printf("%s", b ? "true" : "false"); +} + +/* ReadLine - read line from stdin (simplified) */ +const char* ReadLine(void) { + static char buffer[1024]; + if (fgets(buffer, sizeof(buffer), stdin) != NULL) { + size_t len = strlen(buffer); + if (len > 0 && buffer[len-1] == '\n') { + buffer[len-1] = '\0'; + } + return buffer; + } + return ""; +} diff --git a/_test_closure/build/main b/_test_closure/build/main new file mode 100755 index 0000000..91e1ee8 Binary files /dev/null and b/_test_closure/build/main differ diff --git a/_test_closure/build/main.c b/_test_closure/build/main.c new file mode 100644 index 0000000..456eed2 --- /dev/null +++ b/_test_closure/build/main.c @@ -0,0 +1,67 @@ +// Generated by Bux C Backend v2 +#include +#include +#include +#include +#include + +typedef const char* String; +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; +typedef unsigned long long uint64; +typedef signed char int8; +typedef short int16; +typedef long long int64; +typedef float float32; +typedef double float64; +typedef char char8; + +void* bux_alloc(unsigned int size); +void bux_free(void* ptr); + + +int Apply(int x, int (*op)(int)); +int Double(int x); +int __closure_2(int a, int b); +int __closure_3(int x); +int main(); + +void PrintInt(int x); +void PrintLine(String msg); + +int Apply(int x, int (*op)(int)) { + return op(x); + +} + +int Double(int x) { + return x * 2; + +} + +int __closure_2(int a, int b) { + return a + b; + +} + +int __closure_3(int x) { + return x * 3; + +} + +int main() { + int (*add)(int, int) = &__closure_2; + int sum = add(3, 4); + PrintInt(sum); + PrintLine(""); + int result = Apply(5, &__closure_3); + PrintInt(result); + PrintLine(""); + int (*d)(int) = &Double; + PrintInt(d(7)); + PrintLine(""); + return 0; + +} + diff --git a/_test_closure/build/runtime.c b/_test_closure/build/runtime.c new file mode 100644 index 0000000..b46be93 --- /dev/null +++ b/_test_closure/build/runtime.c @@ -0,0 +1,1760 @@ +/* Bux Runtime - Minimal C runtime for Bux programs */ +/* This is linked with every Bux program compiled via the C backend */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Command-line argument storage */ +int g_argc = 0; +char** g_argv = NULL; + +int bux_argc(void) { return g_argc; } +char* bux_argv(int index) { + if (index < 0 || index >= g_argc) return ""; + return g_argv[index]; +} + +/* Memory allocation */ +void* bux_alloc(size_t size) { + void* ptr = calloc(1, size); + if (ptr == NULL) { + fprintf(stderr, "bux runtime: out of memory (alloc %zu bytes)\n", size); + abort(); + } + return ptr; +} + +void* bux_realloc(void* ptr, size_t size) { + void* new_ptr = realloc(ptr, size); + if (new_ptr == NULL && size > 0) { + fprintf(stderr, "bux runtime: out of memory (realloc %zu bytes)\n", size); + abort(); + } + return new_ptr; +} + +void bux_free(void* ptr) { + free(ptr); +} + +/* Escape a string for C output (newlines -> \n, quotes -> \", etc.) */ +/* Returns escaped content WITHOUT surrounding quotes */ +char* bux_escape_c_string(const char* s, int len) { + if (s == NULL || len <= 0) { + char* empty = (char*)bux_alloc(1); + empty[0] = '\0'; + return empty; + } + /* Worst case: every char needs escaping (e.g., all newlines -> 2 chars) */ + char* buf = (char*)bux_alloc(len * 2 + 1); + int j = 0; + for (int i = 0; i < len; i++) { + char c = s[i]; + switch (c) { + case '\n': buf[j++] = '\\'; buf[j++] = 'n'; break; + case '\r': buf[j++] = '\\'; buf[j++] = 'r'; break; + case '\t': buf[j++] = '\\'; buf[j++] = 't'; break; + case '\\': buf[j++] = '\\'; buf[j++] = '\\'; break; + case '"': buf[j++] = '\\'; buf[j++] = '"'; break; + default: buf[j++] = c; break; + } + } + buf[j] = '\0'; + return buf; +} + +int bux_run_nim(const char* nim_file, const char* out_bin) { + char cmd[4096]; + snprintf(cmd, sizeof(cmd), "nim c -o:%s -d:release --gc:orc %s 2>&1", out_bin, nim_file); + return system(cmd); +} + +int bux_system(const char* cmd) { + if (cmd == NULL) return -1; + return system(cmd); +} + +int bux_strlen_c(const char* s) { + if (s == NULL) return 0; + const char* p = s; + while (*p) p++; + return (int)(p - s); +} + +/* I/O */ +void bux_print(const char* s) { + if (s != NULL) { + fputs(s, stdout); + } +} + +void bux_println(const char* s) { + if (s != NULL) { + puts(s); + } else { + puts(""); + } +} + +void bux_print_int(int64_t n) { + printf("%lld", (long long)n); +} + +void bux_print_float(double f) { + printf("%g", f); +} + +void bux_print_bool(bool b) { + printf("%s", b ? "true" : "false"); +} + +void bux_print_char(char c) { + putchar(c); +} + +/* Panic */ +void bux_panic(const char* msg) { + fprintf(stderr, "bux panic: %s\n", msg ? msg : "unknown error"); + abort(); +} + +/* Division by zero check */ +int64_t bux_div_i64(int64_t a, int64_t b) { + if (b == 0) { + bux_panic("division by zero"); + } + return a / b; +} + +int64_t bux_mod_i64(int64_t a, int64_t b) { + if (b == 0) { + bux_panic("modulo by zero"); + } + return a % b; +} + +/* String operations */ +typedef struct { + const char* data; + size_t len; +} BuxString; + +BuxString bux_string_from_cstr(const char* s) { + BuxString result; + result.data = s; + result.len = s ? strlen(s) : 0; + return result; +} + +BuxString bux_string_concat(BuxString a, BuxString b) { + BuxString result; + result.len = a.len + b.len; + char* buf = (char*)bux_alloc(result.len + 1); + if (a.data && a.len > 0) memcpy(buf, a.data, a.len); + if (b.data && b.len > 0) memcpy(buf + a.len, b.data, b.len); + buf[result.len] = '\0'; + result.data = buf; + return result; +} + +/* Slice operations */ +typedef struct { + void* data; + size_t len; + size_t cap; +} BuxSlice; + +BuxSlice bux_slice_new(size_t elem_size, size_t len) { + BuxSlice result; + result.len = len; + result.cap = len; + result.data = bux_alloc(elem_size * len); + return result; +} + +void bux_bounds_check(size_t index, size_t len) { + if (index >= len) { + fprintf(stderr, "bux panic: index out of bounds (index %zu, len %zu)\n", index, len); + abort(); + } +} + +/* String wrappers with Bux-compatible signatures */ +unsigned int bux_strlen(const char* s) { + return (unsigned int)strlen(s); +} + +int bux_strcmp(const char* a, const char* b) { + if (a == b) return 0; + if (!a) return -1; + if (!b) return 1; + return strcmp(a, b); +} + +int bux_strncmp(const char* a, const char* b, unsigned int n) { + if (a == b) return 0; + if (!a) return -1; + if (!b) return 1; + return strncmp(a, b, (size_t)n); +} + +char* bux_strcpy(char* dest, const char* src) { + if (!dest || !src) return dest; + return strcpy(dest, src); +} + +char* bux_strcat(char* dest, const char* src) { + return strcat(dest, src); +} + +char* bux_strncpy(char* dest, const char* src, unsigned int n) { + return strncpy(dest, src, (size_t)n); +} + +double bux_str_to_float(const char* s) { + if (!s) return 0.0; + return strtod(s, NULL); +} + +/* String find: returns pointer to first occurrence of needle in haystack, or NULL */ +const char* bux_strstr(const char* haystack, const char* needle) { + if (!haystack || !needle) return NULL; + return strstr(haystack, needle); +} + +/* String offset: byte offset of pos within base (both must point into same string) */ +unsigned int bux_str_offset(const char* pos, const char* base) { + if (!pos || !base) return 0; + return (unsigned int)(pos - base); +} + +/* String contains: returns 1 if haystack contains needle, 0 otherwise */ +int bux_str_contains(const char* haystack, const char* needle) { + return bux_strstr(haystack, needle) != NULL; +} + +/* String is null: returns 1 if string is NULL */ +int bux_str_is_null(const char* s) { + return s == NULL; +} + +/* String slice: extract substring from start, length len */ +char* bux_str_slice(const char* s, unsigned int start, unsigned int len) { + if (!s) return NULL; + unsigned int s_len = (unsigned int)strlen(s); + if (start >= s_len) { + char* empty = (char*)bux_alloc(1); + empty[0] = '\0'; + return empty; + } + unsigned int avail = s_len - start; + if (len > avail) len = avail; + char* result = (char*)bux_alloc(len + 1); + memcpy(result, s + start, len); + result[len] = '\0'; + return result; +} + +/* String trim left: remove leading whitespace */ +char* bux_str_trim_left(const char* s) { + if (!s) return NULL; + while (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r') s++; + unsigned int len = (unsigned int)strlen(s); + char* result = (char*)bux_alloc(len + 1); + memcpy(result, s, len + 1); + return result; +} + +/* String trim right: remove trailing whitespace */ +char* bux_str_trim_right(const char* s) { + if (!s) return NULL; + unsigned int len = (unsigned int)strlen(s); + while (len > 0 && (s[len-1] == ' ' || s[len-1] == '\t' || s[len-1] == '\n' || s[len-1] == '\r')) { + len--; + } + char* result = (char*)bux_alloc(len + 1); + memcpy(result, s, len); + result[len] = '\0'; + return result; +} + +/* String trim: remove both leading and trailing whitespace */ +char* bux_str_trim(const char* s) { + char* left = bux_str_trim_left(s); + char* result = bux_str_trim_right(left); + bux_free(left); + return result; +} + +/* Int to string conversion */ +char* bux_int_to_str(int64_t n) { + char* result = (char*)bux_alloc(32); + snprintf(result, 32, "%lld", (long long)n); + return result; +} + +/* String to int conversion */ +int64_t bux_str_to_int(const char* s) { + if (!s) return 0; + return (int64_t)atoll(s); +} + +/* String builder */ +typedef struct { + char* buf; + unsigned int len; + unsigned int cap; +} BuxStringBuilder; + +BuxStringBuilder* bux_sb_new(unsigned int initial_cap) { + BuxStringBuilder* sb = (BuxStringBuilder*)bux_alloc(sizeof(BuxStringBuilder)); + sb->cap = initial_cap > 0 ? initial_cap : 64; + sb->buf = (char*)bux_alloc(sb->cap); + sb->buf[0] = '\0'; + sb->len = 0; + return sb; +} + +void bux_sb_append(BuxStringBuilder* sb, const char* s) { + if (!sb || !s) return; + unsigned int s_len = (unsigned int)strlen(s); + unsigned int new_len = sb->len + s_len; + if (new_len + 1 > sb->cap) { + while (sb->cap < new_len + 1) sb->cap *= 2; + sb->buf = (char*)bux_realloc(sb->buf, sb->cap); + } + memcpy(sb->buf + sb->len, s, s_len); + sb->len = new_len; + sb->buf[sb->len] = '\0'; +} + +void bux_sb_append_int(BuxStringBuilder* sb, int64_t n) { + char tmp[32]; + snprintf(tmp, sizeof(tmp), "%lld", (long long)n); + bux_sb_append(sb, tmp); +} + +void bux_sb_append_float(BuxStringBuilder* sb, double f) { + char tmp[32]; + snprintf(tmp, sizeof(tmp), "%g", f); + bux_sb_append(sb, tmp); +} + +void bux_sb_append_char(BuxStringBuilder* sb, char c) { + if (!sb) return; + if (sb->len + 2 > sb->cap) { + sb->cap *= 2; + sb->buf = (char*)bux_realloc(sb->buf, sb->cap); + } + sb->buf[sb->len++] = c; + sb->buf[sb->len] = '\0'; +} + +const char* bux_sb_build(BuxStringBuilder* sb) { + if (!sb) return ""; + const char* buf = sb->buf; + sb->buf = NULL; // detach — ownership transferred to caller + return buf; +} + +void bux_sb_free(BuxStringBuilder* sb) { + if (!sb) return; + bux_free(sb->buf); // safe: NULL if bux_sb_build already detached + bux_free(sb); +} + +/* String split: count parts separated by delimiter */ +unsigned int bux_str_split_count(const char* s, const char* delim) { + if (!s || !delim || !*delim) return 1; + unsigned int count = 1; + size_t delim_len = strlen(delim); + const char* p = s; + while ((p = strstr(p, delim)) != NULL) { + count++; + p += delim_len; + } + return count; +} + +/* String split: get the n-th part (0-indexed) */ +char* bux_str_split_part(const char* s, const char* delim, unsigned int index) { + if (!s || !delim || !*delim) { + if (index == 0) { + unsigned int len = s ? (unsigned int)strlen(s) : 0; + char* result = (char*)bux_alloc(len + 1); + if (s) memcpy(result, s, len); + result[len] = '\0'; + return result; + } + char* empty = (char*)bux_alloc(1); + empty[0] = '\0'; + return empty; + } + size_t delim_len = strlen(delim); + const char* start = s; + const char* end; + unsigned int current = 0; + while (current < index) { + end = strstr(start, delim); + if (!end) { + char* empty = (char*)bux_alloc(1); + empty[0] = '\0'; + return empty; + } + start = end + delim_len; + current++; + } + end = strstr(start, delim); + size_t part_len; + if (end) { + part_len = (size_t)(end - start); + } else { + part_len = strlen(start); + } + char* result = (char*)bux_alloc(part_len + 1); + memcpy(result, start, part_len); + result[part_len] = '\0'; + return result; +} + +/* String join: join two strings with separator */ +char* bux_str_join2(const char* a, const char* b, const char* sep) { + if (!a && !b) { + char* empty = (char*)bux_alloc(1); + empty[0] = '\0'; + return empty; + } + unsigned int len_a = a ? (unsigned int)strlen(a) : 0; + unsigned int len_b = b ? (unsigned int)strlen(b) : 0; + unsigned int len_sep = sep ? (unsigned int)strlen(sep) : 0; + unsigned int total = len_a + len_sep + len_b; + char* result = (char*)bux_alloc(total + 1); + if (a) memcpy(result, a, len_a); + if (sep && len_a > 0 && len_b > 0) memcpy(result + len_a, sep, len_sep); + if (b) memcpy(result + len_a + (len_a > 0 && len_b > 0 ? len_sep : 0), b, len_b); + result[total] = '\0'; + return result; +} + +/* Simple string format: replace {0}, {1}, ... with string arguments. + Returns formatted string. Supports up to 8 arguments. */ +char* bux_float_to_string(double f) { + char* buf = (char*)bux_alloc(64); + snprintf(buf, 64, "%g", f); + return buf; +} + +char* bux_str_format(const char* pattern, + const char* a0, const char* a1, const char* a2, const char* a3, + const char* a4, const char* a5, const char* a6, const char* a7) { + if (!pattern) { + char* empty = (char*)bux_alloc(1); + empty[0] = '\0'; + return empty; + } + const char* args[8] = { a0, a1, a2, a3, a4, a5, a6, a7 }; + + /* Calculate result size */ + size_t total = 0; + const char* p = pattern; + while (*p) { + if (*p == '{' && p[1] >= '0' && p[1] <= '7' && p[2] == '}') { + int idx = p[1] - '0'; + if (args[idx]) total += strlen(args[idx]); + p += 3; + } else { + total++; + p++; + } + } + + char* result = (char*)bux_alloc(total + 1); + char* w = result; + p = pattern; + while (*p) { + if (*p == '{' && p[1] >= '0' && p[1] <= '7' && p[2] == '}') { + int idx = p[1] - '0'; + if (args[idx]) { + size_t len = strlen(args[idx]); + memcpy(w, args[idx], len); + w += len; + } + p += 3; + } else { + *w++ = *p++; + } + } + *w = '\0'; + return result; +} + +/* File I/O — read entire file into string */ +char* bux_read_file(const char* path) { + if (!path) return NULL; + FILE* f = fopen(path, "rb"); + if (!f) return NULL; + fseek(f, 0, SEEK_END); + long size = ftell(f); + fseek(f, 0, SEEK_SET); + char* buf = (char*)bux_alloc((size_t)size + 1); + if (!buf) { fclose(f); return NULL; } + size_t read = fread(buf, 1, (size_t)size, f); + fclose(f); + buf[read] = '\0'; + return buf; +} + +/* File I/O — write string to file */ +int bux_write_file(const char* path, const char* content) { + if (!path || !content) return 0; + FILE* f = fopen(path, "wb"); + if (!f) return 0; + size_t len = strlen(content); + size_t written = fwrite(content, 1, len, f); + fclose(f); + return written > 0 ? 1 : 0; +} + +/* File I/O — check if file exists */ +int bux_file_exists(const char* path) { + if (!path) return 0; + FILE* f = fopen(path, "rb"); + if (f) { + fclose(f); + return 1; + } + return 0; +} + +/* Path operations */ +char* bux_path_join(const char* a, const char* b) { + if (!a && !b) { + char* empty = (char*)bux_alloc(1); + empty[0] = '\0'; + return empty; + } + if (!a) { + unsigned int len = (unsigned int)strlen(b); + char* result = (char*)bux_alloc(len + 1); + memcpy(result, b, len + 1); + return result; + } + if (!b) { + unsigned int len = (unsigned int)strlen(a); + char* result = (char*)bux_alloc(len + 1); + memcpy(result, a, len + 1); + return result; + } + unsigned int len_a = (unsigned int)strlen(a); + unsigned int len_b = (unsigned int)strlen(b); + int need_sep = (len_a > 0 && a[len_a-1] != '/') ? 1 : 0; + unsigned int total = len_a + (need_sep ? 1 : 0) + len_b; + char* result = (char*)bux_alloc(total + 1); + memcpy(result, a, len_a); + if (need_sep) result[len_a] = '/'; + memcpy(result + len_a + (need_sep ? 1 : 0), b, len_b); + result[total] = '\0'; + return result; +} + +char* bux_path_parent(const char* path) { + if (!path) { + char* empty = (char*)bux_alloc(1); + empty[0] = '\0'; + return empty; + } + int len = (int)strlen(path); + while (len > 0 && path[len-1] == '/') len--; + while (len > 0 && path[len-1] != '/') len--; + while (len > 0 && path[len-1] == '/') len--; + if (len == 0) { + char* dot = (char*)bux_alloc(2); + dot[0] = '.'; + dot[1] = '\0'; + return dot; + } + char* result = (char*)bux_alloc((unsigned int)len + 1); + memcpy(result, path, (unsigned int)len); + result[len] = '\0'; + return result; +} + +char* bux_path_ext(const char* path) { + if (!path) { + char* empty = (char*)bux_alloc(1); + empty[0] = '\0'; + return empty; + } + const char* dot = strrchr(path, '.'); + if (!dot) { + char* empty = (char*)bux_alloc(1); + empty[0] = '\0'; + return empty; + } + const char* slash = strrchr(path, '/'); + if (slash && slash > dot) { + char* empty = (char*)bux_alloc(1); + empty[0] = '\0'; + return empty; + } + unsigned int len = (unsigned int)strlen(dot); + char* result = (char*)bux_alloc(len + 1); + memcpy(result, dot, len + 1); + return result; +} + +/* Math functions — wrap C math.h */ +#include + +double bux_sqrt(double x) { return sqrt(x); } +double bux_pow(double x, double y) { return pow(x, y); } +int64_t bux_abs_i64(int64_t x) { return x < 0 ? -x : x; } +double bux_abs_f64(double x) { return x < 0 ? -x : x; } +int64_t bux_min_i64(int64_t a, int64_t b) { return a < b ? a : b; } +int64_t bux_max_i64(int64_t a, int64_t b) { return a > b ? a : b; } +double bux_min_f64(double a, double b) { return a < b ? a : b; } +double bux_max_f64(double a, double b) { return a > b ? a : b; } + +/* Hash function (djb2) over raw bytes — for generic key types */ +unsigned int bux_hash_bytes(const void* ptr, size_t size) { + if (!ptr) return 0; + unsigned int hash = 5381; + const unsigned char* bytes = (const unsigned char*)ptr; + for (size_t i = 0; i < size; i++) { + hash = ((hash << 5) + hash) + bytes[i]; /* hash * 33 + byte */ + } + return hash; +} + +/* Byte equality check — for generic key comparison */ +int bux_mem_eq(const void* a, const void* b, size_t size) { + if (a == b) return 1; + if (!a || !b) return 0; + return memcmp(a, b, size) == 0; +} + +/* Hash function (djb2) for string keys */ +unsigned int bux_hash_string(const char* s) { + unsigned int hash = 5381; + int c; + while ((c = *s++)) { + hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ + } + return hash; +} + +/* --------------------------------------------------------------------------- + * Directory listing and build tools (for self-hosting compiler) + * --------------------------------------------------------------------------- */ + +#include +#include + +/* bux_list_dir: returns array of file paths in dir (recursively) matching ext suffix. + * Result is malloc'd array of malloc'd strings. Caller must free. + * Sets *out_count to number of files found. */ +static int bux_count_files_recursive(const char* dir, const char* ext, size_t ext_len) { + DIR* d = opendir(dir); + if (!d) return 0; + int count = 0; + struct dirent* entry; + while ((entry = readdir(d)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; + size_t path_len = strlen(dir) + 1 + strlen(entry->d_name) + 1; + char* path = (char*)malloc(path_len); + if (!path) continue; + snprintf(path, path_len, "%s/%s", dir, entry->d_name); + struct stat st; + if (stat(path, &st) == 0) { + if (S_ISDIR(st.st_mode)) { + count += bux_count_files_recursive(path, ext, ext_len); + } else { + size_t name_len = strlen(entry->d_name); + if (name_len > ext_len && strcmp(entry->d_name + name_len - ext_len, ext) == 0) { + count++; + } + } + } + free(path); + } + closedir(d); + return count; +} + +static int bux_collect_files_recursive(const char* dir, const char* ext, size_t ext_len, char** result, int idx) { + DIR* d = opendir(dir); + if (!d) return idx; + struct dirent* entry; + while ((entry = readdir(d)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; + size_t path_len = strlen(dir) + 1 + strlen(entry->d_name) + 1; + char* path = (char*)malloc(path_len); + if (!path) continue; + snprintf(path, path_len, "%s/%s", dir, entry->d_name); + struct stat st; + if (stat(path, &st) == 0) { + if (S_ISDIR(st.st_mode)) { + idx = bux_collect_files_recursive(path, ext, ext_len, result, idx); + free(path); + } else { + size_t name_len = strlen(entry->d_name); + if (name_len > ext_len && strcmp(entry->d_name + name_len - ext_len, ext) == 0) { + result[idx++] = path; + } else { + free(path); + } + } + } else { + free(path); + } + } + closedir(d); + return idx; +} + +char** bux_list_dir(const char* dir, const char* ext, int* out_count) { + size_t ext_len = strlen(ext); + int count = bux_count_files_recursive(dir, ext, ext_len); + if (count == 0) { *out_count = 0; return NULL; } + char** result = (char**)malloc(count * sizeof(char*)); + if (!result) { *out_count = 0; return NULL; } + int idx = bux_collect_files_recursive(dir, ext, ext_len, result, 0); + *out_count = idx; + return result; +} + +/* bux_mkdir_if_needed: create directory if it doesn't exist */ +int bux_mkdir_if_needed(const char* path) { + struct stat st; + if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) return 0; + return mkdir(path, 0755); +} + +/* bux_run_cc: invoke C compiler to compile c_file into out_bin, + * linking runtime.c and io.c. Returns exit code. */ +int bux_run_cc(const char* c_file, const char* out_bin, + const char* runtime_c, const char* io_c, + const char* math_lib) { + char cmd[4096]; + snprintf(cmd, sizeof(cmd), + "cc %s %s %s -o %s %s 2>&1", + c_file, + runtime_c ? runtime_c : "", + io_c ? io_c : "", + out_bin, + math_lib ? "-lm" : ""); + return system(cmd); +} + +/* bux_dir_exists: check if directory exists */ +int bux_dir_exists(const char* path) { + struct stat st; + return (stat(path, &st) == 0 && S_ISDIR(st.st_mode)); +} + +/* ============================================================================ + * Concurrency primitives (Phase 8.3) + * ============================================================================ */ + +typedef struct { + pthread_t thread; +} BuxTask; + +typedef struct { + uint8_t* buffer; + size_t capacity; + size_t elem_size; + size_t head; + size_t tail; + size_t count; + pthread_mutex_t mutex; + pthread_cond_t not_empty; + pthread_cond_t not_full; + int closed; +} BuxChannel; + +/* Task / thread spawning */ +void* bux_task_spawn(void* (*func)(void*), void* arg) { + BuxTask* task = (BuxTask*)malloc(sizeof(BuxTask)); + if (!task) { + fprintf(stderr, "bux runtime: out of memory (task spawn)\n"); + abort(); + } + int rc = pthread_create(&task->thread, NULL, func, arg); + if (rc != 0) { + fprintf(stderr, "bux runtime: pthread_create failed (%d)\n", rc); + free(task); + return NULL; + } + return task; +} + +void bux_task_join(void* handle) { + if (!handle) return; + BuxTask* task = (BuxTask*)handle; + pthread_join(task->thread, NULL); + free(task); +} + +void bux_task_sleep(int64_t ms) { + if (ms <= 0) return; + struct timespec ts; + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000; + nanosleep(&ts, NULL); +} + +/* Channel implementation */ +void* bux_channel_new(int64_t capacity, int64_t elem_size) { + if (capacity <= 0) capacity = 1; + if (elem_size <= 0) elem_size = 1; + BuxChannel* ch = (BuxChannel*)malloc(sizeof(BuxChannel)); + if (!ch) { + fprintf(stderr, "bux runtime: out of memory (channel new)\n"); + abort(); + } + ch->buffer = (uint8_t*)malloc((size_t)capacity * (size_t)elem_size); + if (!ch->buffer) { + fprintf(stderr, "bux runtime: out of memory (channel buffer)\n"); + free(ch); + abort(); + } + ch->capacity = (size_t)capacity; + ch->elem_size = (size_t)elem_size; + ch->head = 0; + ch->tail = 0; + ch->count = 0; + ch->closed = 0; + pthread_mutex_init(&ch->mutex, NULL); + pthread_cond_init(&ch->not_empty, NULL); + pthread_cond_init(&ch->not_full, NULL); + return ch; +} + +void bux_channel_send(void* handle, void* elem) { + if (!handle || !elem) return; + BuxChannel* ch = (BuxChannel*)handle; + pthread_mutex_lock(&ch->mutex); + while (ch->count >= ch->capacity && !ch->closed) { + pthread_cond_wait(&ch->not_full, &ch->mutex); + } + if (ch->closed) { + pthread_mutex_unlock(&ch->mutex); + return; + } + uint8_t* dst = ch->buffer + ch->tail * ch->elem_size; + memcpy(dst, elem, ch->elem_size); + ch->tail = (ch->tail + 1) % ch->capacity; + ch->count++; + pthread_cond_signal(&ch->not_empty); + pthread_mutex_unlock(&ch->mutex); +} + +int bux_channel_recv(void* handle, void* out) { + if (!handle || !out) return 0; + BuxChannel* ch = (BuxChannel*)handle; + pthread_mutex_lock(&ch->mutex); + while (ch->count == 0 && !ch->closed) { + pthread_cond_wait(&ch->not_empty, &ch->mutex); + } + if (ch->count == 0) { + pthread_mutex_unlock(&ch->mutex); + return 0; /* channel empty and closed */ + } + uint8_t* src = ch->buffer + ch->head * ch->elem_size; + memcpy(out, src, ch->elem_size); + ch->head = (ch->head + 1) % ch->capacity; + ch->count--; + pthread_cond_signal(&ch->not_full); + pthread_mutex_unlock(&ch->mutex); + return 1; +} + +void bux_channel_close(void* handle) { + if (!handle) return; + BuxChannel* ch = (BuxChannel*)handle; + pthread_mutex_lock(&ch->mutex); + ch->closed = 1; + pthread_cond_broadcast(&ch->not_empty); + pthread_cond_broadcast(&ch->not_full); + pthread_mutex_unlock(&ch->mutex); +} + +void bux_channel_free(void* handle) { + if (!handle) return; + BuxChannel* ch = (BuxChannel*)handle; + pthread_mutex_destroy(&ch->mutex); + pthread_cond_destroy(&ch->not_empty); + pthread_cond_destroy(&ch->not_full); + free(ch->buffer); + free(ch); +} + +/* ============================================================================ + * Synchronization primitives (Mutex, RwLock) + * ============================================================================ */ + +typedef struct bux_mutex { + pthread_mutex_t mtx; +} bux_mutex_t; + +typedef struct bux_rwlock { + pthread_rwlock_t rwl; +} bux_rwlock_t; + +void* bux_mutex_new(void) { + bux_mutex_t* m = (bux_mutex_t*)malloc(sizeof(bux_mutex_t)); + if (!m) return NULL; + pthread_mutex_init(&m->mtx, NULL); + return m; +} + +void bux_mutex_lock(void* handle) { + if (!handle) return; + bux_mutex_t* m = (bux_mutex_t*)handle; + pthread_mutex_lock(&m->mtx); +} + +void bux_mutex_unlock(void* handle) { + if (!handle) return; + bux_mutex_t* m = (bux_mutex_t*)handle; + pthread_mutex_unlock(&m->mtx); +} + +void bux_mutex_free(void* handle) { + if (!handle) return; + bux_mutex_t* m = (bux_mutex_t*)handle; + pthread_mutex_destroy(&m->mtx); + free(m); +} + +void* bux_rwlock_new(void) { + bux_rwlock_t* rw = (bux_rwlock_t*)malloc(sizeof(bux_rwlock_t)); + if (!rw) return NULL; + pthread_rwlock_init(&rw->rwl, NULL); + return rw; +} + +void bux_rwlock_rdlock(void* handle) { + if (!handle) return; + bux_rwlock_t* rw = (bux_rwlock_t*)handle; + pthread_rwlock_rdlock(&rw->rwl); +} + +void bux_rwlock_wrlock(void* handle) { + if (!handle) return; + bux_rwlock_t* rw = (bux_rwlock_t*)handle; + pthread_rwlock_wrlock(&rw->rwl); +} + +void bux_rwlock_unlock(void* handle) { + if (!handle) return; + bux_rwlock_t* rw = (bux_rwlock_t*)handle; + pthread_rwlock_unlock(&rw->rwl); +} + +void bux_rwlock_free(void* handle) { + if (!handle) return; + bux_rwlock_t* rw = (bux_rwlock_t*)handle; + pthread_rwlock_destroy(&rw->rwl); + free(rw); +} + +/* ============================================================================ + * Stackful Coroutines + Async Scheduler (Phase 8.3 true async) + * ============================================================================ */ + +#define BUX_CORO_STACK_SIZE (64 * 1024) + +typedef struct bux_async_task { + ucontext_t ctx; + ucontext_t* caller_ctx; + uint8_t* stack; + int state; /* 0 = ready, 1 = running, 2 = done */ + void (*entry)(void); + void* result; /* pointer to heap-allocated result */ + int64_t sleep_until_ms; + struct bux_async_task* next; +} bux_async_task_t; + +static bux_async_task_t* bux_ready_head = NULL; +static bux_async_task_t* bux_ready_tail = NULL; +static bux_async_task_t* bux_current_task = NULL; +static ucontext_t bux_scheduler_ctx; +static int bux_scheduler_running = 0; + +static void bux_enqueue_ready(bux_async_task_t* task) { + task->next = NULL; + if (bux_ready_tail) { + bux_ready_tail->next = task; + } else { + bux_ready_head = task; + } + bux_ready_tail = task; +} + +static bux_async_task_t* bux_dequeue_ready(void) { + bux_async_task_t* task = bux_ready_head; + if (task) { + bux_ready_head = task->next; + if (!bux_ready_head) bux_ready_tail = NULL; + } + return task; +} + +static void bux_remove_from_ready(bux_async_task_t* target) { + bux_async_task_t* prev = NULL; + bux_async_task_t* curr = bux_ready_head; + while (curr) { + if (curr == target) { + if (prev) { + prev->next = curr->next; + } else { + bux_ready_head = curr->next; + } + if (bux_ready_tail == curr) { + bux_ready_tail = prev; + } + return; + } + prev = curr; + curr = curr->next; + } +} + +static void bux_coro_trampoline(void) { + bux_async_task_t* self = bux_current_task; + if (self != NULL && self->entry != NULL) { + self->entry(); + } + if (self != NULL) { + self->state = 2; /* done */ + swapcontext(&self->ctx, &bux_scheduler_ctx); + } +} + +void* bux_async_spawn(void (*func)(void)) { + bux_async_task_t* task = (bux_async_task_t*)malloc(sizeof(bux_async_task_t)); + if (!task) { + fprintf(stderr, "bux runtime: out of memory (async spawn)\n"); + abort(); + } + task->stack = (uint8_t*)malloc(BUX_CORO_STACK_SIZE); + if (!task->stack) { + fprintf(stderr, "bux runtime: out of memory (coro stack)\n"); + free(task); + abort(); + } + task->state = 0; + task->next = NULL; + task->caller_ctx = NULL; + task->entry = func; + task->sleep_until_ms = 0; + + getcontext(&task->ctx); + task->ctx.uc_stack.ss_sp = task->stack; + task->ctx.uc_stack.ss_size = BUX_CORO_STACK_SIZE; + task->ctx.uc_link = &bux_scheduler_ctx; + makecontext(&task->ctx, bux_coro_trampoline, 0); + + bux_enqueue_ready(task); + return task; +} + +void bux_async_yield(void) { + if (bux_current_task != NULL) { + bux_async_task_t* task = bux_current_task; + bux_current_task = NULL; + bux_enqueue_ready(task); + swapcontext(&task->ctx, &bux_scheduler_ctx); + } +} + + +void bux_async_run(void); + +void* bux_async_await(void* handle) { + if (!handle) return NULL; + bux_async_task_t* target = (bux_async_task_t*)handle; + while (target->state != 2) { + if (bux_current_task != NULL) { + bux_async_yield(); + } else { + if (!bux_scheduler_running) { + bux_async_run(); + } + } + } + void* result = target->result; + bux_remove_from_ready(target); + free(target->stack); + free(target); + return result; +} +static int64_t bux_now_ms(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} + +void bux_async_run(void) { + if (bux_scheduler_running) return; + bux_scheduler_running = 1; + getcontext(&bux_scheduler_ctx); + while (bux_ready_head != NULL) { + bux_async_task_t* task = bux_dequeue_ready(); + if (!task) break; + if (task->state == 2) { + /* Leave completed tasks in queue for await to clean up */ + bux_enqueue_ready(task); + continue; + } + /* Check if task is still sleeping */ + if (task->sleep_until_ms > 0) { + int64_t now = bux_now_ms(); + if (now < task->sleep_until_ms) { + /* Re-queue and check next task */ + bux_enqueue_ready(task); + /* If all tasks are sleeping, sleep the thread */ + if (bux_ready_head == task && bux_ready_tail == task) { + int64_t delay = task->sleep_until_ms - now; + struct timespec ts; + ts.tv_sec = delay / 1000; + ts.tv_nsec = (delay % 1000) * 1000000; + nanosleep(&ts, NULL); + } + continue; + } + task->sleep_until_ms = 0; + } + task->state = 1; + bux_current_task = task; + swapcontext(&bux_scheduler_ctx, &task->ctx); + bux_current_task = NULL; + } + bux_scheduler_running = 0; +} + +void bux_async_sleep(int64_t ms) { + if (ms > 0 && bux_current_task != NULL) { + bux_current_task->sleep_until_ms = bux_now_ms() + ms; + bux_async_yield(); + } +} + +void bux_async_return(void* value, size_t size) { + if (bux_current_task != NULL && value != NULL && size > 0) { + void* copy = malloc(size); + if (copy) memcpy(copy, value, size); + bux_current_task->result = copy; + } +} + +void* bux_async_result(void* handle) { + if (!handle) return NULL; + bux_async_task_t* task = (bux_async_task_t*)handle; + return task->result; +} + +/* ============================================================================ + * OS primitives + * ============================================================================ */ + +#include + +const char* bux_getenv(const char* name) { + if (!name) return ""; + const char* val = getenv(name); + return val ? val : ""; +} + +int bux_setenv(const char* name, const char* value) { + if (!name || !value) return -1; + return setenv(name, value, 1); +} + +const char* bux_getcwd(void) { + static char buf[4096]; + if (getcwd(buf, sizeof(buf))) { + return buf; + } + return ""; +} + +int bux_chdir(const char* path) { + if (!path) return -1; + return chdir(path); +} + +/* ============================================================================ + * Time primitives + * ============================================================================ */ + +int64_t bux_time_ms(void) { + struct timespec ts; + if (clock_gettime(CLOCK_REALTIME, &ts) == 0) { + return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; + } + return 0; +} + +int64_t bux_time_us(void) { + struct timespec ts; + if (clock_gettime(CLOCK_REALTIME, &ts) == 0) { + return (int64_t)ts.tv_sec * 1000000 + ts.tv_nsec / 1000; + } + return 0; +} + +void bux_sleep_ms(int64_t ms) { + if (ms <= 0) return; + struct timespec ts; + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000; + nanosleep(&ts, NULL); +} + +/* ============================================================================ + * Process primitives + * ============================================================================ */ + +#include + +int bux_process_run(const char* cmd) { + if (!cmd) return -1; + return system(cmd); +} + +char* bux_process_output(const char* cmd) { + if (!cmd) return NULL; + FILE* pipe = popen(cmd, "r"); + if (!pipe) return NULL; + + size_t cap = 1024; + size_t len = 0; + char* buf = (char*)malloc(cap); + if (!buf) { pclose(pipe); return NULL; } + + int c; + while ((c = fgetc(pipe)) != EOF) { + if (len + 1 >= cap) { + cap *= 2; + char* new_buf = (char*)realloc(buf, cap); + if (!new_buf) { free(buf); pclose(pipe); return NULL; } + buf = new_buf; + } + buf[len++] = (char)c; + } + buf[len] = '\0'; + pclose(pipe); + return buf; +} + +/* ============================================================================ + * Network / Socket primitives + * ============================================================================ */ + +int bux_socket_create(void) { + int fd = socket(AF_INET, SOCK_STREAM, 0); + return fd; +} + +int bux_socket_reuse(int fd) { + int opt = 1; + return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); +} + +int bux_socket_bind(int fd, const char* addr, int port) { + struct sockaddr_in sa; + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons((uint16_t)port); + if (addr == NULL || addr[0] == '\0') { + sa.sin_addr.s_addr = INADDR_ANY; + } else { + if (inet_pton(AF_INET, addr, &sa.sin_addr) <= 0) { + return -1; + } + } + return bind(fd, (struct sockaddr*)&sa, sizeof(sa)); +} + +int bux_socket_listen(int fd, int backlog) { + return listen(fd, backlog); +} + +int bux_socket_accept(int fd) { + struct sockaddr_in sa; + socklen_t len = sizeof(sa); + return accept(fd, (struct sockaddr*)&sa, &len); +} + +int bux_socket_connect(int fd, const char* addr, int port) { + struct sockaddr_in sa; + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons((uint16_t)port); + if (inet_pton(AF_INET, addr, &sa.sin_addr) <= 0) { + return -1; + } + return connect(fd, (struct sockaddr*)&sa, sizeof(sa)); +} + +int bux_socket_send(int fd, const char* data, int len) { + if (!data || len <= 0) return 0; + return (int)send(fd, data, (size_t)len, 0); +} + +BuxString bux_socket_recv(int fd, int max_len) { + BuxString result; + if (max_len <= 0) { + result.data = ""; + result.len = 0; + return result; + } + char* buf = (char*)bux_alloc((size_t)max_len + 1); + ssize_t n = recv(fd, buf, (size_t)max_len, 0); + if (n <= 0) { + result.data = ""; + result.len = 0; + return result; + } + buf[n] = '\0'; + result.data = buf; + result.len = (size_t)n; + return result; +} + +int bux_socket_close(int fd) { + return close(fd); +} + +const char* bux_socket_error(void) { + return strerror(errno); +} + +/* ============================================================================ + * Test / Assert primitives + * ============================================================================ */ + +void bux_exit(int code) { + exit(code); +} + +void bux_assert(int cond, const char* file, int line, const char* expr) { + if (!cond) { + fprintf(stderr, "ASSERT FAILED: %s at %s:%d\n", expr, file, line); + exit(1); + } +} + +/* ============================================================================ + * Cryptography primitives (OpenSSL) + * ============================================================================ */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void bux_sha256(const char* data, int len, unsigned char* out) { + EVP_MD_CTX* ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, EVP_sha256(), NULL); + EVP_DigestUpdate(ctx, data, (size_t)len); + EVP_DigestFinal_ex(ctx, out, NULL); + EVP_MD_CTX_free(ctx); +} + +void bux_hmac_sha256(const char* key, int keylen, const char* msg, int msglen, unsigned char* out) { + unsigned int outlen = 32; + HMAC(EVP_sha256(), key, keylen, (const unsigned char*)msg, (size_t)msglen, out, &outlen); +} + +int bux_random_bytes(unsigned char* buf, int len) { + return RAND_bytes(buf, len); +} + +char* bux_base64_encode(const unsigned char* in, int inlen) { + int outlen = 4 * ((inlen + 2) / 3); + char* out = (char*)bux_alloc(outlen + 1); + int elen = EVP_EncodeBlock((unsigned char*)out, in, inlen); + out[elen] = '\0'; + return out; +} + +char* bux_base64_decode(const char* in, int inlen, int* outlen) { + int maxlen = 3 * inlen / 4; + char* out = (char*)bux_alloc(maxlen + 1); + *outlen = EVP_DecodeBlock((unsigned char*)out, (const unsigned char*)in, inlen); + if (*outlen < 0) { + *outlen = 0; + out[0] = '\0'; + } + return out; +} + +char* bux_bytes_to_hex(const unsigned char* data, int len) { + char* out = (char*)bux_alloc((size_t)len * 2 + 1); + const char* hex = "0123456789abcdef"; + for (int i = 0; i < len; i++) { + out[i * 2] = hex[(data[i] >> 4) & 0x0F]; + out[i * 2 + 1] = hex[data[i] & 0x0F]; + } + out[len * 2] = '\0'; + return out; +} + +/* ============================================================================ + * Extended cryptography primitives (OpenSSL) + * ============================================================================ */ + +static void bux_digest(const EVP_MD* md, const char* data, int len, unsigned char* out) { + EVP_MD_CTX* ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(ctx, md, NULL); + EVP_DigestUpdate(ctx, data, (size_t)len); + EVP_DigestFinal_ex(ctx, out, NULL); + EVP_MD_CTX_free(ctx); +} + +void bux_sha1(const char* data, int len, unsigned char* out) { + bux_digest(EVP_sha1(), data, len, out); +} + +void bux_sha384(const char* data, int len, unsigned char* out) { + bux_digest(EVP_sha384(), data, len, out); +} + +void bux_sha512(const char* data, int len, unsigned char* out) { + bux_digest(EVP_sha512(), data, len, out); +} + +/* --- HMAC --- */ + +void bux_hmac_sha384(const char* key, int keylen, const char* msg, int msglen, unsigned char* out) { + unsigned int outlen = 48; + HMAC(EVP_sha384(), key, keylen, (const unsigned char*)msg, (size_t)msglen, out, &outlen); +} + +void bux_hmac_sha512(const char* key, int keylen, const char* msg, int msglen, unsigned char* out) { + unsigned int outlen = 64; + HMAC(EVP_sha512(), key, keylen, (const unsigned char*)msg, (size_t)msglen, out, &outlen); +} + +/* --- Base64URL --- */ + +static const char b64url_table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +char* bux_base64url_encode(const unsigned char* in, int inlen) { + int outlen = 4 * ((inlen + 2) / 3); + char* out = (char*)bux_alloc(outlen + 1); + int j = 0; + for (int i = 0; i < inlen; i += 3) { + int a = in[i]; + int b = (i + 1 < inlen) ? in[i + 1] : 0; + int c = (i + 2 < inlen) ? in[i + 2] : 0; + out[j++] = b64url_table[(a >> 2) & 0x3F]; + out[j++] = b64url_table[((a << 4) | (b >> 4)) & 0x3F]; + if (i + 1 < inlen) + out[j++] = b64url_table[((b << 2) | (c >> 6)) & 0x3F]; + if (i + 2 < inlen) + out[j++] = b64url_table[c & 0x3F]; + } + out[j] = '\0'; + return out; +} + +static int b64url_char_val(char c) { + if (c >= 'A' && c <= 'Z') return c - 'A'; + if (c >= 'a' && c <= 'z') return c - 'a' + 26; + if (c >= '0' && c <= '9') return c - '0' + 52; + if (c == '-') return 62; + if (c == '_') return 63; + return -1; +} + +char* bux_base64url_decode(const char* in, int inlen, int* outlen) { + int maxlen = 3 * inlen / 4 + 1; + char* out = (char*)bux_alloc(maxlen); + int j = 0; + int buf = 0, bits = 0; + for (int i = 0; i < inlen; i++) { + int val = b64url_char_val(in[i]); + if (val < 0) continue; + buf = (buf << 6) | val; + bits += 6; + if (bits >= 8) { + bits -= 8; + out[j++] = (buf >> bits) & 0xFF; + } + } + out[j] = '\0'; + *outlen = j; + return out; +} + +/* --- AES-256-CBC --- */ + +static int aes_cbc_ctx_encrypt(const unsigned char* in, int inlen, + unsigned char* out, const unsigned char* key, + const unsigned char* iv, int encrypt) { + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + EVP_CipherInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv, encrypt); + int outlen = 0, tmplen = 0; + EVP_CipherUpdate(ctx, out, &tmplen, in, inlen); + outlen = tmplen; + EVP_CipherFinal_ex(ctx, out + outlen, &tmplen); + outlen += tmplen; + EVP_CIPHER_CTX_free(ctx); + return outlen; +} + +char* bux_aes_256_cbc_encrypt(const char* plain, int plainlen, + const char* key, const char* iv, int* outlen) { + int maxlen = plainlen + EVP_MAX_BLOCK_LENGTH; + char* out = (char*)bux_alloc(maxlen + 1); + *outlen = aes_cbc_ctx_encrypt((const unsigned char*)plain, plainlen, + (unsigned char*)out, + (const unsigned char*)key, + (const unsigned char*)iv, 1); + out[*outlen] = '\0'; + return out; +} + +char* bux_aes_256_cbc_decrypt(const char* cipher, int cipherlen, + const char* key, const char* iv, int* outlen) { + char* out = (char*)bux_alloc(cipherlen + 1); + *outlen = aes_cbc_ctx_encrypt((const unsigned char*)cipher, cipherlen, + (unsigned char*)out, + (const unsigned char*)key, + (const unsigned char*)iv, 0); + out[*outlen] = '\0'; + return out; +} + +/* --- AES-256-GCM --- */ + +char* bux_aes_256_gcm_encrypt(const char* plain, int plainlen, + const char* key, const char* iv, + unsigned char* tag, int* outlen) { + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, (const unsigned char*)key, + (const unsigned char*)iv); + int maxlen = plainlen + EVP_MAX_BLOCK_LENGTH; + char* out = (char*)bux_alloc(maxlen); + int tmplen = 0; + EVP_EncryptUpdate(ctx, (unsigned char*)out, &tmplen, + (const unsigned char*)plain, plainlen); + *outlen = tmplen; + EVP_EncryptFinal_ex(ctx, (unsigned char*)out + *outlen, &tmplen); + *outlen += tmplen; + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag); + EVP_CIPHER_CTX_free(ctx); + return out; +} + +char* bux_aes_256_gcm_decrypt(const char* cipher, int cipherlen, + const char* key, const char* iv, + const char* tag, int* outlen) { + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, (const unsigned char*)key, + (const unsigned char*)iv); + char* out = (char*)bux_alloc(cipherlen); + int tmplen = 0; + EVP_DecryptUpdate(ctx, (unsigned char*)out, &tmplen, + (const unsigned char*)cipher, cipherlen); + *outlen = tmplen; + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (void*)tag); + int ret = EVP_DecryptFinal_ex(ctx, (unsigned char*)out + *outlen, &tmplen); + EVP_CIPHER_CTX_free(ctx); + if (ret <= 0) { *outlen = 0; out[0] = '\0'; return out; } + *outlen += tmplen; + out[*outlen] = '\0'; + return out; +} + +/* --- RSA PKCS#1 v1.5 sign / verify --- */ + +static EVP_PKEY* bux_load_private_key(const char* pem, int len) { + BIO* bio = BIO_new_mem_buf(pem, len); + if (!bio) return NULL; + EVP_PKEY* pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); + BIO_free(bio); + return pkey; +} + +static EVP_PKEY* bux_load_public_key(const char* pem, int len) { + BIO* bio = BIO_new_mem_buf(pem, len); + if (!bio) return NULL; + EVP_PKEY* pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); + BIO_free(bio); + return pkey; +} + +static char* bux_rsa_sign_evp(const EVP_MD* md, const char* pem_key, int keylen, + const char* data, int datalen, int* siglen) { + EVP_PKEY* pkey = bux_load_private_key(pem_key, keylen); + if (!pkey) { *siglen = 0; return (char*)bux_alloc(1); } + EVP_MD_CTX* ctx = EVP_MD_CTX_new(); + EVP_SignInit_ex(ctx, md, NULL); + EVP_SignUpdate(ctx, data, (size_t)datalen); + unsigned int slen = (unsigned int)EVP_PKEY_size(pkey); + unsigned char* sig = (unsigned char*)bux_alloc(slen + 1); + EVP_SignFinal(ctx, sig, &slen, pkey); + *siglen = (int)slen; + sig[*siglen] = '\0'; + EVP_MD_CTX_free(ctx); + EVP_PKEY_free(pkey); + return (char*)sig; +} + +static int bux_rsa_verify_evp(const EVP_MD* md, const char* pem_key, int keylen, + const char* data, int datalen, + const char* sig, int siglen) { + EVP_PKEY* pkey = bux_load_public_key(pem_key, keylen); + if (!pkey) return 0; + EVP_MD_CTX* ctx = EVP_MD_CTX_new(); + EVP_VerifyInit_ex(ctx, md, NULL); + EVP_VerifyUpdate(ctx, data, (size_t)datalen); + int ret = EVP_VerifyFinal(ctx, (const unsigned char*)sig, (size_t)siglen, pkey); + EVP_MD_CTX_free(ctx); + EVP_PKEY_free(pkey); + return ret; +} + +char* bux_rsa_sign_sha256(const char* pem, int keylen, const char* data, int datalen, int* siglen) { + return bux_rsa_sign_evp(EVP_sha256(), pem, keylen, data, datalen, siglen); +} +char* bux_rsa_sign_sha384(const char* pem, int keylen, const char* data, int datalen, int* siglen) { + return bux_rsa_sign_evp(EVP_sha384(), pem, keylen, data, datalen, siglen); +} +char* bux_rsa_sign_sha512(const char* pem, int keylen, const char* data, int datalen, int* siglen) { + return bux_rsa_sign_evp(EVP_sha512(), pem, keylen, data, datalen, siglen); +} + +int bux_rsa_verify_sha256(const char* pem, int keylen, const char* data, int datalen, + const char* sig, int siglen) { + return bux_rsa_verify_evp(EVP_sha256(), pem, keylen, data, datalen, sig, siglen); +} +int bux_rsa_verify_sha384(const char* pem, int keylen, const char* data, int datalen, + const char* sig, int siglen) { + return bux_rsa_verify_evp(EVP_sha384(), pem, keylen, data, datalen, sig, siglen); +} +int bux_rsa_verify_sha512(const char* pem, int keylen, const char* data, int datalen, + const char* sig, int siglen) { + return bux_rsa_verify_evp(EVP_sha512(), pem, keylen, data, datalen, sig, siglen); +} + +/* --- ECDSA P-256 / P-384 --- */ + +static char* bux_ecdsa_sign_evp(const EVP_MD* md, const char* pem, int keylen, + const char* data, int datalen, int* siglen) { + EVP_PKEY* pkey = bux_load_private_key(pem, keylen); + if (!pkey) { *siglen = 0; return (char*)bux_alloc(1); } + EVP_MD_CTX* ctx = EVP_MD_CTX_new(); + EVP_SignInit_ex(ctx, md, NULL); + EVP_SignUpdate(ctx, data, (size_t)datalen); + unsigned int slen = (unsigned int)EVP_PKEY_size(pkey); + unsigned char* sig = (unsigned char*)bux_alloc(slen + 1); + EVP_SignFinal(ctx, sig, &slen, pkey); + *siglen = (int)slen; + sig[*siglen] = '\0'; + EVP_MD_CTX_free(ctx); + EVP_PKEY_free(pkey); + return (char*)sig; +} + +static int bux_ecdsa_verify_evp(const EVP_MD* md, const char* pem, int keylen, + const char* data, int datalen, + const char* sig, int siglen) { + EVP_PKEY* pkey = bux_load_public_key(pem, keylen); + if (!pkey) return 0; + EVP_MD_CTX* ctx = EVP_MD_CTX_new(); + EVP_VerifyInit_ex(ctx, md, NULL); + EVP_VerifyUpdate(ctx, data, (size_t)datalen); + int ret = EVP_VerifyFinal(ctx, (const unsigned char*)sig, (size_t)siglen, pkey); + EVP_MD_CTX_free(ctx); + EVP_PKEY_free(pkey); + return ret; +} + +char* bux_ecdsa_sign_p256(const char* pem, int keylen, const char* data, int datalen, int* siglen) { + return bux_ecdsa_sign_evp(EVP_sha256(), pem, keylen, data, datalen, siglen); +} +char* bux_ecdsa_sign_p384(const char* pem, int keylen, const char* data, int datalen, int* siglen) { + return bux_ecdsa_sign_evp(EVP_sha384(), pem, keylen, data, datalen, siglen); +} +int bux_ecdsa_verify_p256(const char* pem, int keylen, const char* data, int datalen, + const char* sig, int siglen) { + return bux_ecdsa_verify_evp(EVP_sha256(), pem, keylen, data, datalen, sig, siglen); +} +int bux_ecdsa_verify_p384(const char* pem, int keylen, const char* data, int datalen, + const char* sig, int siglen) { + return bux_ecdsa_verify_evp(EVP_sha384(), pem, keylen, data, datalen, sig, siglen); +} + +/* --- Ed25519 (OpenSSL 1.1.1+) --- */ + +int bux_ed25519_keypair(unsigned char* pub, unsigned char* priv) { + EVP_PKEY* pkey = NULL; + EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL); + if (!pctx) return 0; + if (EVP_PKEY_keygen_init(pctx) <= 0 || + EVP_PKEY_keygen(pctx, &pkey) <= 0) { + EVP_PKEY_CTX_free(pctx); + return 0; + } + EVP_PKEY_CTX_free(pctx); + size_t pub_len = 32, priv_len = 32; + EVP_PKEY_get_raw_public_key(pkey, pub, &pub_len); + EVP_PKEY_get_raw_private_key(pkey, priv, &priv_len); + EVP_PKEY_free(pkey); + return 1; +} + +int bux_ed25519_sign(const char* priv, const char* data, int datalen, unsigned char* sig) { + EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, + (const unsigned char*)priv, 32); + if (!pkey) return 0; + EVP_MD_CTX* ctx = EVP_MD_CTX_new(); + int ret = 0; + if (EVP_DigestSignInit(ctx, NULL, NULL, NULL, pkey) > 0) { + size_t siglen = 64; + EVP_DigestSign(ctx, sig, &siglen, (const unsigned char*)data, (size_t)datalen); + ret = 1; + } + EVP_MD_CTX_free(ctx); + EVP_PKEY_free(pkey); + return ret; +} + +int bux_ed25519_verify(const char* pub, const char* sig, const char* data, int datalen) { + EVP_PKEY* pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, + (const unsigned char*)pub, 32); + if (!pkey) return 0; + EVP_MD_CTX* ctx = EVP_MD_CTX_new(); + int ret = 0; + if (EVP_DigestVerifyInit(ctx, NULL, NULL, NULL, pkey) > 0) { + ret = EVP_DigestVerify(ctx, (const unsigned char*)sig, 64, + (const unsigned char*)data, (size_t)datalen); + ret = (ret == 1) ? 1 : 0; + } + EVP_MD_CTX_free(ctx); + EVP_PKEY_free(pkey); + return ret; +} diff --git a/_test_closure/bux.toml b/_test_closure/bux.toml new file mode 100644 index 0000000..8f016f2 --- /dev/null +++ b/_test_closure/bux.toml @@ -0,0 +1,7 @@ +[Package] +Name = "closure_test" +Version = "0.1.0" +Type = "bin" + +[Build] +Output = "Bin" diff --git a/_test_closure/src/Main.bux b/_test_closure/src/Main.bux new file mode 100644 index 0000000..b8b4f73 --- /dev/null +++ b/_test_closure/src/Main.bux @@ -0,0 +1,34 @@ +module Main { + +extern func PrintInt(x: int); +extern func PrintLine(msg: String); + +func Apply(x: int, op: func(int) -> int) -> int { + return op(x); +} + +func Double(x: int) -> int { + return x * 2; +} + +func main() -> int { + // Closure as variable + let add: func(int, int) -> int = |a: int, b: int| -> int { return a + b; }; + let sum: int = add(3, 4); + PrintInt(sum); + PrintLine(""); + + // Closure passed to function + let result: int = Apply(5, |x: int| -> int { return x * 3; }); + PrintInt(result); + PrintLine(""); + + // Address of named function as function pointer + let d: func(int) -> int = &Double; + PrintInt(d(7)); + PrintLine(""); + + return 0; +} + +} diff --git a/bootstrap/ast.nim b/bootstrap/ast.nim index 4fe20c5..4e512bb 100644 --- a/bootstrap/ast.nim +++ b/bootstrap/ast.nim @@ -132,6 +132,7 @@ type ekBlock ekMatch ekStringInterp + ekClosure MatchArm* = object loc*: SourceLocation @@ -228,6 +229,10 @@ type of ekStringInterp: exprInterpTexts*: seq[string] exprInterpExprs*: seq[Expr] + of ekClosure: + exprClosureParams*: seq[Param] + exprClosureBody*: Block + exprClosureReturnType*: TypeExpr # --------------------------------------------------------------------------- # Statements diff --git a/bootstrap/hir_lower.nim b/bootstrap/hir_lower.nim index 187641d..6d6feee 100644 --- a/bootstrap/hir_lower.nim +++ b/bootstrap/hir_lower.nim @@ -276,6 +276,7 @@ proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type = proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode proc lowerBlock(ctx: var LowerCtx, blk: Block): HirNode +proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type = if expr == nil: return makeUnknown() @@ -1139,6 +1140,10 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode = resultNode = hirCall("String_Concat", @[resultNode, lastTextNode], makeStr(), loc) return resultNode + of ekClosure: + let f = ctx.lowerClosureFunc(expr) + return hirUnary(tkAmp, hirVar(f.name, makeFunc(@[], makeVoid()), loc), typ, loc) + else: return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc), typ: makeVoid(), loc: loc) @@ -1167,6 +1172,12 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode = of tekSlice: let elemType = ctx.resolveTypeExpr(stmt.stmtLetType.sliceElement) makeSlice(elemType) + of tekFunc: + var params: seq[Type] = @[] + for p in stmt.stmtLetType.funcParams: + params.add(ctx.resolveTypeExpr(p)) + let ret = if stmt.stmtLetType.funcRet != nil: ctx.resolveTypeExpr(stmt.stmtLetType.funcRet) else: makeVoid() + makeFunc(params, ret) else: makeUnknown() elif stmt.stmtLetInit != nil: ctx.resolveExprType(stmt.stmtLetInit) @@ -1475,6 +1486,25 @@ proc generateMethodInstance(ctx: var LowerCtx, baseMethodName: string, typeArgs: ctx.generatedFuncInsts[mangledName] = true return mangledName +proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc = + let loc = expr.loc + let name = "__closure_" & $ctx.varCounter + inc ctx.varCounter + var f = HirFunc(name: name, isPublic: false) + # Params + for p in expr.exprClosureParams: + f.params.add((name: p.name, typ: if p.ptype != nil: ctx.resolveTypeExpr(p.ptype) else: makeUnknown())) + # Return type + if expr.exprClosureReturnType != nil: + f.retType = ctx.resolveTypeExpr(expr.exprClosureReturnType) + else: + f.retType = makeVoid() + # Body + if expr.exprClosureBody != nil: + f.body = ctx.lowerBlock(expr.exprClosureBody) + ctx.extraFuncs.add(f) + return f + proc lowerModule*(module: Module, sema: Sema): HirModule = var ctx = initLowerCtx(module, sema) var funcs: seq[HirFunc] = @[] diff --git a/bootstrap/lir_c_backend.nim b/bootstrap/lir_c_backend.nim index af16321..2141cd9 100644 --- a/bootstrap/lir_c_backend.nim +++ b/bootstrap/lir_c_backend.nim @@ -58,6 +58,13 @@ proc typeFromValue(be: var LirCBackend, v: LirValue): string = proc setTempType(be: var LirCBackend, temp: string, cType: string) = be.tempTypes[temp] = cType +proc cParamDecl(cType, name: string): string = + ## Emit a C parameter declaration, handling function-pointer syntax. + if cType.contains("(*)"): + return cType.replace("(*)", "(*" & name & ")") + else: + return cType & " " & name + # ── Per-instruction emission ── proc emitInstr(be: var LirCBackend, instr: LirInstr) = @@ -183,7 +190,7 @@ proc emitInstr(be: var LirCBackend, instr: LirInstr) = let inferred = be.tempTypes[instr.dst.strVal] if inferred != "" and inferred != ct: ct = inferred - be.emitLine(&"{ct} {v(instr.dst)};") + be.emitLine(cParamDecl(ct, v(instr.dst)) & ";") # ── Pointers ── of lirAddrOf: @@ -241,13 +248,6 @@ proc emitInstr(be: var LirCBackend, instr: LirInstr) = # ── Function emission ── -proc cParamDecl(cType, name: string): string = - ## Emit a C parameter declaration, handling function-pointer syntax. - if cType.contains("(*)"): - return cType.replace("(*)", "(*" & name & ")") - else: - return cType & " " & name - proc emitFunc(be: var LirCBackend, f: LirFunc, funcRetTypes: Table[string, string], funcPtrTypes: Table[string, string]) = var paramsStr = "" for i, p in f.params: diff --git a/bootstrap/parser.nim b/bootstrap/parser.nim index ad34094..c661479 100644 --- a/bootstrap/parser.nim +++ b/bootstrap/parser.nim @@ -587,6 +587,26 @@ proc parsePrimary(p: var Parser): Expr = of tkNull: discard p.advance() return newLiteralExpr(Token(kind: tkNull, text: "null", loc: loc)) + of tkPipe: + # Closure: |params| -> Ret { body } + discard p.advance() # | + var params: seq[Param] = @[] + while not p.check(tkPipe) and not p.isAtEnd: + let nameTok = p.expect(tkIdent, "expected parameter name in closure") + discard p.expect(tkColon, "expected ':' in closure parameter") + let ptype = p.parseType() + params.add(Param(name: nameTok.text, ptype: ptype)) + if p.check(tkComma): + discard p.advance() + else: + break + discard p.expect(tkPipe, "expected '|' to close closure params") + var retType: TypeExpr = nil + if p.check(tkArrow): + discard p.advance() # -> + retType = p.parseType() + let body = p.parseBlock() + return Expr(kind: ekClosure, loc: loc, exprClosureParams: params, exprClosureBody: body, exprClosureReturnType: retType) else: p.emitError(loc, "expected expression") discard p.advance() diff --git a/bootstrap/sema.nim b/bootstrap/sema.nim index 29825b5..84ad050 100644 --- a/bootstrap/sema.nim +++ b/bootstrap/sema.nim @@ -44,6 +44,7 @@ type checkedFunc*: bool ## true inside @[Checked] function currentFuncIsAsync*: bool ## true inside async func movedVars*: seq[string] ## variables moved in current checked function + currentRetType*: Type ## return type of the function being checked # --------------------------------------------------------------------------- # Helpers @@ -1337,6 +1338,25 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type = for e in expr.exprInterpExprs: discard sema.checkExpr(e, scope) return makeStr() + of ekClosure: + let savedRetType = sema.currentRetType + let childScope = Scope(parent: scope) + sema.currentRetType = if expr.exprClosureReturnType != nil: sema.resolveType(expr.exprClosureReturnType) else: makeUnknown() + # Register params + for p in expr.exprClosureParams: + let ptype = if p.ptype != nil: sema.resolveType(p.ptype) else: makeUnknown() + discard childScope.define(Symbol(kind: skVar, name: p.name, typ: ptype)) + # Check body + if expr.exprClosureBody != nil: + for stmt in expr.exprClosureBody.stmts: + discard sema.checkStmt(stmt, childScope) + sema.currentRetType = savedRetType + # Build function type + var params: seq[Type] = @[] + for p in expr.exprClosureParams: + params.add(if p.ptype != nil: sema.resolveType(p.ptype) else: makeUnknown()) + let retType = if expr.exprClosureReturnType != nil: sema.resolveType(expr.exprClosureReturnType) else: makeVoid() + return makeFunc(params, retType) # --------------------------------------------------------------------------- # Statement type checking diff --git a/src/ast.bux b/src/ast.bux index ccbe55d..cb1c092 100644 --- a/src/ast.bux +++ b/src/ast.bux @@ -107,6 +107,7 @@ const ekMatch: int = 22; const ekSpawn: int = 24; const ekAwait: int = 25; const ekStringInterp: int = 26; +const ekClosure: int = 27; struct ExprList { expr: *Expr, @@ -139,6 +140,8 @@ struct Expr { // Struct init fields structName: String, structFieldCount: int, + // Closure params (for ekClosure) + closureParams: *Decl, // Call arguments (linked list for multi-arg support) callArgs: *ExprList, callArgCount: int, diff --git a/src/hir_lower.bux b/src/hir_lower.bux index f6e687c..2c25ece 100644 --- a/src/hir_lower.bux +++ b/src/hir_lower.bux @@ -384,6 +384,23 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode { return n; } + // Closure: generate function and return address-of + if kind == ekClosure { + let f: *HirFunc = Lcx_LowerClosureFunc(ctx, expr); + n.kind = hUnary; + n.intValue = tkAmp; + let varNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode; + varNode.kind = hVar; + varNode.strValue = f.name; + varNode.typeKind = tyFunc; + n.child1 = varNode; + n.typeKind = tyFunc; + if expr.refType != null as *TypeExpr { + n.typeName = Lcx_BuildFuncTypeName(expr.refType); + } + return n; + } + // Cast if kind == ekCast { n.kind = hCast; @@ -779,6 +796,93 @@ func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc { return f; } +// --------------------------------------------------------------------------- +// Closure lowering — generate a global function for a closure expression +// --------------------------------------------------------------------------- + +func Lcx_LowerClosureFunc(ctx: *LowerCtx, expr: *Expr) -> *HirFunc { + let f: *HirFunc = bux_alloc(sizeof(HirFunc)) as *HirFunc; + + // Generate unique name + let numStr: String = String_FromInt(ctx.funcCount); + f.name = String_Concat("__closure_", numStr); + f.isPublic = false; + + let params: *Decl = expr.closureParams; + if params != null as *Decl { + f.paramCount = params.paramCount; + if params.paramCount > 0 { f.param0 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param0, ¶ms.param0); } + if params.paramCount > 1 { f.param1 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param1, ¶ms.param1); } + if params.paramCount > 2 { f.param2 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param2, ¶ms.param2); } + if params.paramCount > 3 { f.param3 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param3, ¶ms.param3); } + if params.paramCount > 4 { f.param4 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param4, ¶ms.param4); } + if params.paramCount > 5 { f.param5 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param5, ¶ms.param5); } + if params.paramCount > 6 { f.param6 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param6, ¶ms.param6); } + if params.paramCount > 7 { f.param7 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param7, ¶ms.param7); } + if params.paramCount > 8 { f.param8 = bux_alloc(sizeof(HirParam)) as *HirParam; Lcx_LowerParam(f.param8, ¶ms.param8); } + } + + if expr.refType != null as *TypeExpr && expr.refType.kind == tekFunc && expr.refType.funcRet != null as *TypeExpr { + f.retTypeName = expr.refType.funcRet.typeName; + f.retTypeKind = Lcx_ResolveTypeKind(expr.refType.funcRet); + } else { + f.retTypeName = ""; + f.retTypeKind = 0; + } + + // Create function scope + var funcScope: Scope = Scope_NewChild(ctx.scope); + var pi: int = 0; + while pi < params.paramCount { + var p: *Param = null as *Param; + if pi == 0 { p = ¶ms.param0; } + else if pi == 1 { p = ¶ms.param1; } + else if pi == 2 { p = ¶ms.param2; } + else if pi == 3 { p = ¶ms.param3; } + else if pi == 4 { p = ¶ms.param4; } + else if pi == 5 { p = ¶ms.param5; } + else if pi == 6 { p = ¶ms.param6; } + else if pi == 7 { p = ¶ms.param7; } + else if pi == 8 { p = ¶ms.param8; } + if p != null as *Param && p.refParamType != null as *TypeExpr { + var sym: Symbol; + sym.kind = skVar; + sym.name = p.name; + sym.typeKind = Lcx_ResolveTypeKind(p.refParamType); + sym.refType = p.refParamType; + if p.refParamType.kind == tekFunc { + sym.typeName = Lcx_BuildFuncTypeName(p.refParamType); + } else if !String_Eq(p.refParamType.typeName, "") { + sym.typeName = p.refParamType.typeName; + } else if p.refParamType.kind == tekPointer && p.refParamType.pointerPointee != null as *TypeExpr { + sym.typeName = String_Concat(p.refParamType.pointerPointee.typeName, "*"); + } else { + sym.typeName = ""; + } + sym.isMutable = false; + sym.isPublic = false; + sym.decl = null as *Decl; + discard Scope_Define(&funcScope, sym); + } + pi = pi + 1; + } + + let prevScope: *Scope = ctx.scope; + ctx.scope = &funcScope; + if expr.refBlock != null as *Block { + f.body = Lcx_LowerBlock(ctx, expr.refBlock, -1); + } else { + f.body = null as *HirNode; + } + ctx.scope = prevScope; + + // Add to module functions + ctx.funcs[ctx.funcCount] = *f; + ctx.funcCount = ctx.funcCount + 1; + + return f; +} + // --------------------------------------------------------------------------- // Module lowering — main entry point // --------------------------------------------------------------------------- diff --git a/src/parser.bux b/src/parser.bux index 40e204c..439e9a4 100644 --- a/src/parser.bux +++ b/src/parser.bux @@ -384,10 +384,89 @@ func parserParsePrimary(p: *Parser) -> *Expr { return e; } + // Closure: |params| -> Ret { body } + if kind == tkPipe { + return parserParseClosure(p); + } + parserEmitDiag(p, line, col, "expected expression"); return parserMakeExpr(ekLiteral, line, col); } +// --------------------------------------------------------------------------- +// Closure: |params| -> Ret { body } +// --------------------------------------------------------------------------- + +func parserParseClosure(p: *Parser) -> *Expr { + let line: uint32 = parserCurToken(p).line; + let col: uint32 = parserCurToken(p).column; + discard parserExpect(p, tkPipe, "expected '|' to start closure params"); + + let e: *Expr = parserMakeExpr(ekClosure, line, col); + let params: *Decl = bux_alloc(sizeof(Decl)) as *Decl; + params.kind = dkFunc; + params.paramCount = 0; + + // Parse params: name: Type + while !parserCheck(p, tkPipe) && parserPeek(p, 0) != tkEndOfFile { + while parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) { + discard parserAdvance(p); + } + if parserCheck(p, tkPipe) || parserPeek(p, 0) == tkEndOfFile { + break; + } + if params.paramCount >= 9 { break; } + let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected parameter name in closure"); + discard parserExpect(p, tkColon, "expected ':' in closure parameter"); + let ptype: *TypeExpr = parserParseType(p); + + let idx: int = params.paramCount; + if idx == 0 { + params.param0.name = nameTok.text; + params.param0.refParamType = ptype; + } else if idx == 1 { + params.param1.name = nameTok.text; + params.param1.refParamType = ptype; + } else if idx == 2 { + params.param2.name = nameTok.text; + params.param2.refParamType = ptype; + } else if idx == 3 { + params.param3.name = nameTok.text; + params.param3.refParamType = ptype; + } else if idx == 4 { + params.param4.name = nameTok.text; + params.param4.refParamType = ptype; + } else if idx == 5 { + params.param5.name = nameTok.text; + params.param5.refParamType = ptype; + } else if idx == 6 { + params.param6.name = nameTok.text; + params.param6.refParamType = ptype; + } else if idx == 7 { + params.param7.name = nameTok.text; + params.param7.refParamType = ptype; + } else if idx == 8 { + params.param8.name = nameTok.text; + params.param8.refParamType = ptype; + } + params.paramCount = params.paramCount + 1; + if parserMatch(p, tkComma) { continue; } + break; + } + + discard parserExpect(p, tkPipe, "expected '|' to close closure params"); + e.closureParams = params; + + // Optional return type: -> Type + if parserMatch(p, tkArrow) { + e.refType = parserParseType(p); + } + + // Body: { ... } + e.refBlock = parserParseBlock(p); + return e; +} + // --------------------------------------------------------------------------- // Postfix: call, index, field access, as, is, ? // --------------------------------------------------------------------------- diff --git a/src/sema.bux b/src/sema.bux index 40ba0c6..6b6c312 100644 --- a/src/sema.bux +++ b/src/sema.bux @@ -564,6 +564,105 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int { return tyVoid; } + // Closure: |params| -> Ret { body } + if kind == ekClosure { + let savedRetType: int = sema.currentRetType; + let savedScope: *Scope = sema.scope; + let childScope: Scope = Scope_NewChild(sema.scope); + sema.scope = &childScope; + + // Set return type from annotation, or unknown for inference + var closureRetType: int = tyUnknown; + if expr.refType != null as *TypeExpr { + closureRetType = Sema_ResolveType(sema, expr.refType); + } + sema.currentRetType = closureRetType; + + // Register params in child scope + let params: *Decl = expr.closureParams; + if params != null as *Decl { + var i: int = 0; + while i < params.paramCount { + var p: Param; + if i == 0 { p = params.param0; } + else if i == 1 { p = params.param1; } + else if i == 2 { p = params.param2; } + else if i == 3 { p = params.param3; } + else if i == 4 { p = params.param4; } + else if i == 5 { p = params.param5; } + else if i == 6 { p = params.param6; } + else if i == 7 { p = params.param7; } + else if i == 8 { p = params.param8; } + + if !String_Eq(p.name, "") { + var sym: Symbol; + sym.kind = skVar; + sym.name = p.name; + sym.typeKind = tyUnknown; + if p.refParamType != null as *TypeExpr { + sym.typeKind = Sema_ResolveType(sema, p.refParamType); + sym.refType = p.refParamType; + } + sym.typeName = ""; + sym.isMutable = false; + sym.isPublic = false; + sym.decl = null as *Decl; + discard Scope_Define(sema.scope, sym); + } + i = i + 1; + } + } + + // Check body + if expr.refBlock != null as *Block { + Sema_CheckBlock(sema, expr.refBlock); + } + + // Restore + sema.scope = savedScope; + sema.currentRetType = savedRetType; + + // Build function type expression for later use + let funcType: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr; + funcType.kind = tekFunc; + funcType.funcRet = expr.refType; + funcType.funcParamCount = params.paramCount; + // Build param type list + if params.paramCount > 0 { + var head: *TypeExprList = null as *TypeExprList; + var tail: *TypeExprList = null as *TypeExprList; + var i: int = 0; + while i < params.paramCount { + var p: Param; + if i == 0 { p = params.param0; } + else if i == 1 { p = params.param1; } + else if i == 2 { p = params.param2; } + else if i == 3 { p = params.param3; } + else if i == 4 { p = params.param4; } + else if i == 5 { p = params.param5; } + else if i == 6 { p = params.param6; } + else if i == 7 { p = params.param7; } + else if i == 8 { p = params.param8; } + + let node: *TypeExprList = bux_alloc(sizeof(TypeExprList)) as *TypeExprList; + node.te = p.refParamType; + node.next = null as *TypeExprList; + if head == null as *TypeExprList { + head = node; + tail = node; + } else { + tail.next = node; + tail = node; + } + i = i + 1; + } + funcType.funcParams = head; + } + expr.refType = funcType; + + return tyFunc; + } + return tyUnknown; }