// SPDX-License-Identifier: MIT #include "comptime_interpreter.h" #include "../diagnostics/diagnostics.h" #include #include #include // Max comptime execution steps #define MAX_STEPS 1000000 // Max recursion depth for @comptime fn calls #define MAX_RECURSION 64 // Max yield buffer #define MAX_YIELD (1024 * 1024) // Tagged value type typedef enum { VAL_NULL, VAL_INT, VAL_BOOL, VAL_STRING, VAL_ARRAY } CValType; typedef struct CValue { CValType kind; union { int64_t i; int b; char *s; struct { struct CValue *data; size_t len; } arr; } as; } CValue; static const CValue val_null = {VAL_NULL, {0}}; // Scope entry typedef struct CScope { char *name; CValue value; struct CScope *next; } CScope; // Interpreter state typedef struct { ParserContext *pctx; CScope *scope; char *yield_buf; size_t yield_cap; size_t yield_len; const char *source_file; int step_count; int rec_depth; int error_happened; } CInterp; static CScope *scope_push(CScope *head, const char *name, CValue val) { CScope *e = xmalloc(sizeof(CScope)); e->name = xstrdup(name); e->value = val; e->next = head; return e; } static void scope_pop(CScope **head) { if (!*head) { return; } CScope *tmp = *head; *head = (*head)->next; if (tmp->value.kind == VAL_STRING) { zfree(tmp->value.as.s); } zfree(tmp->name); zfree(tmp); } static void scope_pop_all(CScope **head) { while (*head) { scope_pop(head); } } static CValue *scope_find(CScope *head, const char *name) { for (CScope *e = head; e; e = e->next) { if (strcmp(e->name, name) == 0) { return &e->value; } } return NULL; } static CValue val_string(const char *s) { CValue v = {VAL_STRING, {0}}; v.as.s = xstrdup(s ? s : ""); return v; } // The character a one-letter escape (`\n`, `\t`...) stands for, or -1. static int simple_escape(char c) { switch (c) { case 'n': return '\n'; case 't': return '\t'; case 'r': return '\r'; case 'a': return '\a'; case 'b': return '\b'; case 'f': return '\f'; case 'v': return '\v'; case 'e': return 27; default: return -1; } } static int is_hex_digit(char c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } // A string literal's string_val holds its text as written in C (escapes // included), ready for codegen. At compile time we need the characters // themselves: `code("];\n")` must yield a newline, not `\` and `n`. static char *unescape_c_string(const char *s) { char *out = xmalloc(strlen(s) + 1); char *p = out; while (*s) { if (*s != '\\' || !s[1]) { *p++ = *s++; continue; } s++; char c = *s++; int simple = simple_escape(c); if (simple >= 0) { *p++ = (char)simple; } else if (c == 'x') { unsigned int val = 0; for (int digits = 0; digits < 2 && is_hex_digit(*s); digits++, s++) { val = (val << 4) | (unsigned int)(*s <= '9' ? *s - '0' : (*s | 0x20) - 'a' + 10); } *p++ = (char)val; } else if (c >= '0' && c <= '7') { unsigned int val = (unsigned int)(c - '0'); for (int k = 0; k < 2 && *s >= '0' && *s <= '7'; k++) { val = (val << 3) | (unsigned int)(*s++ - '0'); } *p++ = (char)val; } else { // \\, \", \' and anything unknown: the character itself. *p++ = c; } } *p = '\0'; return out; } // The value of a string literal: raw strings already hold their characters. static CValue string_literal_value(ASTNode *node) { const char *text = node->literal.string_val ? node->literal.string_val : ""; if (node->literal.kind == LITERAL_RAW_STRING) { return val_string(text); } CValue v = {VAL_STRING, {0}}; v.as.s = unescape_c_string(text); return v; } static void val_free(CValue *v) { if (!v) { return; } if (v->kind == VAL_STRING) { zfree(v->as.s); v->as.s = NULL; } else if (v->kind == VAL_ARRAY) { for (size_t k = 0; k < v->as.arr.len; k++) { val_free(&v->as.arr.data[k]); } zfree(v->as.arr.data); v->as.arr.data = NULL; v->as.arr.len = 0; } } // Deep copy a CValue so that assigning or reading an array element does not // alias the storage of another value. Scalars copy by value; strings and // arrays allocate fresh storage. static CValue val_deep_copy(const CValue *v) { if (!v) { return val_null; } if (v->kind == VAL_STRING) { return val_string(v->as.s); } if (v->kind == VAL_ARRAY) { CValue out = {VAL_ARRAY, {0}}; out.as.arr.len = v->as.arr.len; out.as.arr.data = xcalloc(v->as.arr.len ? v->as.arr.len : 1, sizeof(CValue)); for (size_t k = 0; k < v->as.arr.len; k++) { out.as.arr.data[k] = val_deep_copy(&v->as.arr.data[k]); } return out; } return *v; } static CValue val_new_array(size_t len) { CValue v = {VAL_ARRAY, {0}}; v.as.arr.len = len; v.as.arr.data = xcalloc(len ? len : 1, sizeof(CValue)); return v; } static void yield_append(CInterp *ci, const char *s) { if (!s) { return; } size_t len = strlen(s); if (ci->yield_len + len + 1 > ci->yield_cap) { size_t new_cap = ci->yield_cap ? ci->yield_cap * 2 : 4096; while (ci->yield_len + len + 1 > new_cap) { new_cap *= 2; } if (new_cap > MAX_YIELD) { zerror_at(TOKEN_UNKNOWN, "comptime yield buffer exceeded max size (1 MiB)"); ci->error_happened = 1; return; } ci->yield_buf = xrealloc(ci->yield_buf, new_cap); ci->yield_cap = new_cap; } memcpy(ci->yield_buf + ci->yield_len, s, (size_t)(len)); ci->yield_len += len; ci->yield_buf[ci->yield_len] = 0; } // Forward declarations static CValue eval_expr(CInterp *ci, ASTNode *node); static void exec_stmt(CInterp *ci, ASTNode *node); static void exec_assign_index(CInterp *ci, ASTNode *node); static CValue call_builtin(CInterp *ci, const char *name, ASTNode *args); static CValue call_comptime_fn(CInterp *ci, ASTNode *fn_node, ASTNode *args); static CValue eval_literal(CInterp *ci, ASTNode *node) { (void)ci; CValue v = {VAL_NULL, {0}}; switch (node->literal.kind) { case LITERAL_INT: v.kind = VAL_INT; v.as.i = (int64_t)node->literal.int_val; break; case LITERAL_FLOAT: v.kind = VAL_INT; v.as.i = (int64_t)node->literal.float_val; break; case LITERAL_STRING: case LITERAL_RAW_STRING: v = string_literal_value(node); break; case LITERAL_CHAR: v.kind = VAL_INT; v.as.i = (int64_t)node->literal.int_val; break; default: v = val_null; break; } return v; } static CValue eval_var(CInterp *ci, ASTNode *node) { const char *name = node->var_ref.name; if (!name) { return val_null; } // Builtin variables if (strcmp(name, "__COMPTIME_TARGET__") == 0) { return val_string(z_get_system_name()); } if (strcmp(name, "__COMPTIME_FILE__") == 0) { return val_string(ci->source_file ? ci->source_file : ""); } if (strcmp(name, "true") == 0) { CValue v = {VAL_BOOL, {.b = 1}}; return v; } if (strcmp(name, "false") == 0) { CValue v = {VAL_BOOL, {.b = 0}}; return v; } CValue *found = scope_find(ci->scope, name); if (!found) { zerror_at(node->token, "comptime: undefined variable '%s'", name); ci->error_happened = 1; return val_null; } CValue v; if (found->kind == VAL_STRING) { v = val_string(found->as.s); } else if (found->kind == VAL_ARRAY) { // Deep copy so reads never alias the scope's array storage. v = val_deep_copy(found); } else { memcpy(&v, found, sizeof(CValue)); } return v; } static int64_t eval_int(CInterp *ci, ASTNode *node) { CValue v = eval_expr(ci, node); if (ci->error_happened) { return 0; } if (v.kind != VAL_INT) { zerror_at(node->token, "comptime: expected integer expression"); ci->error_happened = 1; val_free(&v); return 0; } int64_t r = v.as.i; val_free(&v); return r; } static CValue eval_binary(CInterp *ci, ASTNode *node) { CValue left = eval_expr(ci, node->binary.left); if (ci->error_happened) { return val_null; } const char *op = node->binary.op; if (!op) { val_free(&left); return val_null; } // Handle string concat with + if (left.kind == VAL_STRING && strcmp(op, "+") == 0) { CValue right = eval_expr(ci, node->binary.right); if (ci->error_happened) { val_free(&left); val_free(&right); return val_null; } if (right.kind != VAL_STRING) { zerror_at(node->token, "comptime: cannot concatenate string with non-string"); ci->error_happened = 1; val_free(&left); val_free(&right); return val_null; } size_t sz = strlen(left.as.s) + strlen(right.as.s) + 1; char *buf = xmalloc(sz); snprintf(buf, sz, "%s%s", left.as.s, right.as.s); val_free(&left); val_free(&right); CValue r = {VAL_STRING, {0}}; r.as.s = buf; return r; } CValue right = eval_expr(ci, node->binary.right); if (ci->error_happened) { val_free(&left); val_free(&right); return val_null; } // Logical operators work on bools; arithmetic/comparison on ints int is_logic = (strcmp(op, "&&") == 0 || strcmp(op, "||") == 0); int is_compare = (strcmp(op, "==") == 0 || strcmp(op, "!=") == 0 || strcmp(op, "<") == 0 || strcmp(op, ">") == 0 || strcmp(op, "<=") == 0 || strcmp(op, ">=") == 0); if (is_logic) { int a = 0, b = 0; if (left.kind == VAL_BOOL) { a = left.as.b; } else if (left.kind == VAL_INT) { a = left.as.i != 0; } if (right.kind == VAL_BOOL) { b = right.as.b; } else if (right.kind == VAL_INT) { b = right.as.i != 0; } val_free(&left); val_free(&right); CValue r = {VAL_BOOL, {.b = (strcmp(op, "&&") == 0) ? (a && b) : (a || b)}}; return r; } if (left.kind != VAL_INT) { zerror_at(node->token, "comptime: operator '%s' requires integer operands", op); ci->error_happened = 1; val_free(&left); val_free(&right); return val_null; } if (right.kind != VAL_INT) { zerror_at(node->token, "comptime: operator '%s' requires integer operands", op); ci->error_happened = 1; val_free(&left); val_free(&right); return val_null; } int64_t a = left.as.i, b = right.as.i; CValue r = {VAL_INT, {0}}; if (strcmp(op, "+") == 0) { r.as.i = a + b; } else if (strcmp(op, "-") == 0) { r.as.i = a - b; } else if (strcmp(op, "*") == 0) { r.as.i = a * b; } else if (strcmp(op, "/") == 0) { if (b == 0) { zerror_at(node->token, "comptime: division by zero"); ci->error_happened = 1; val_free(&left); val_free(&right); return val_null; } r.as.i = a / b; } else if (strcmp(op, "%") == 0) { if (b == 0) { zerror_at(node->token, "comptime: modulo by zero"); ci->error_happened = 1; val_free(&left); val_free(&right); return val_null; } r.as.i = a % b; } else if (is_compare) { r.kind = VAL_BOOL; if (strcmp(op, "==") == 0) { r.as.b = (a == b); } else if (strcmp(op, "!=") == 0) { r.as.b = (a != b); } else if (strcmp(op, "<") == 0) { r.as.b = (a < b); } else if (strcmp(op, ">") == 0) { r.as.b = (a > b); } else if (strcmp(op, "<=") == 0) { r.as.b = (a <= b); } else if (strcmp(op, ">=") == 0) { r.as.b = (a >= b); } } else if (strcmp(op, "&&") == 0) { r.kind = VAL_BOOL; r.as.b = (a && b); } else if (strcmp(op, "||") == 0) { r.kind = VAL_BOOL; r.as.b = (a || b); } else { zerror_at(node->token, "comptime: unsupported binary operator '%s'", op); ci->error_happened = 1; } val_free(&left); val_free(&right); return r; } static CValue eval_unary(CInterp *ci, ASTNode *node) { if (!node->unary.op) { return val_null; } if (strcmp(node->unary.op, "-") == 0) { CValue v = eval_expr(ci, node->unary.operand); if (ci->error_happened || v.kind != VAL_INT) { val_free(&v); return val_null; } v.as.i = -v.as.i; return v; } if (strcmp(node->unary.op, "!") == 0) { CValue v = eval_expr(ci, node->unary.operand); if (ci->error_happened) { return val_null; } if (v.kind == VAL_INT) { v.as.b = !v.as.i; v.kind = VAL_BOOL; } else if (v.kind == VAL_BOOL) { v.as.b = !v.as.b; } return v; } zerror_at(node->token, "comptime: unsupported unary operator '%s'", node->unary.op); ci->error_happened = 1; return val_null; } static CValue eval_call(CInterp *ci, ASTNode *node) { const char *name = NULL; if (node->call.callee && node->call.callee->kind == NODE_EXPR_VAR) { name = node->call.callee->var_ref.name; } if (!name) { zerror_at(node->token, "comptime: cannot call indirect expressions"); ci->error_happened = 1; return val_null; } // Try builtins first if (strcmp(name, "yield") == 0 || strcmp(name, "code") == 0 || strcmp(name, "compile_error") == 0 || strcmp(name, "compile_warn") == 0) { return call_builtin(ci, name, node->call.args); } // Look for @comptime function for (StructRef *r = ci->pctx->parsed_funcs_list; r; r = r->next) { if (r->node && r->node->kind == NODE_FUNCTION && r->node->func.is_comptime && strcmp(r->node->func.name, name) == 0) { return call_comptime_fn(ci, r->node, node->call.args); } } zerror_at(node->token, "comptime: undefined function '%s'", name); ci->error_happened = 1; return val_null; } static CValue call_builtin(CInterp *ci, const char *name, ASTNode *args) { // Evaluate first argument CValue arg = {VAL_NULL, {0}}; if (args && args->type_info && args->type_info->kind == TYPE_STRING) { // For string-typed args, we need the compile-time value if (args->kind == NODE_EXPR_LITERAL || args->kind == NODE_EXPR_VAR) { arg = eval_expr(ci, args); } else if (args->kind == NODE_EXPR_BINARY && strcmp(args->binary.op, "+") == 0) { arg = eval_expr(ci, args); // string concat } else if (args->kind == NODE_EXPR_CALL) { arg = eval_expr(ci, args); // function return value } else { zerror_at(args->token, "comptime: argument to '%s' must be a compile-time constant", name); ci->error_happened = 1; return val_null; } } else if (args) { arg = eval_expr(ci, args); } if (ci->error_happened) { val_free(&arg); return val_null; } if (strcmp(name, "yield") == 0 || strcmp(name, "code") == 0) { char int_buf[32]; const char *s = ""; if (arg.kind == VAL_STRING) { s = arg.as.s ? arg.as.s : ""; } else if (arg.kind == VAL_INT) { snprintf(int_buf, sizeof(int_buf), "%lld", (long long)arg.as.i); s = int_buf; } yield_append(ci, s); val_free(&arg); return val_null; } if (strcmp(name, "compile_error") == 0) { const char *msg = (arg.kind == VAL_STRING && arg.as.s) ? arg.as.s : "comptime error"; zerror_at(TOKEN_UNKNOWN, "comptime error: %s", msg); ci->error_happened = 1; val_free(&arg); return val_null; } if (strcmp(name, "compile_warn") == 0) { const char *msg = (arg.kind == VAL_STRING && arg.as.s) ? arg.as.s : "comptime warning"; fprintf(stderr, "comptime warning: %s\n", msg); val_free(&arg); return val_null; } val_free(&arg); return val_null; } static CValue call_comptime_fn(CInterp *ci, ASTNode *fn_node, ASTNode *args) { if (ci->rec_depth >= MAX_RECURSION) { zerror_at(fn_node->token, "comptime: recursion depth limit exceeded"); ci->error_happened = 1; return val_null; } // Save scope CScope *saved_scope = ci->scope; ci->scope = NULL; ci->rec_depth++; // Bind args to params ASTNode *arg = args; for (int i = 0; i < fn_node->func.count && arg; i++, arg = arg->next) { CValue val = eval_expr(ci, arg); if (ci->error_happened) { break; } if (fn_node->func.param_names && fn_node->func.param_names[i]) { ci->scope = scope_push(ci->scope, fn_node->func.param_names[i], val); } else { val_free(&val); } } // Execute body if (!ci->error_happened && fn_node->func.body) { ASTNode *stmt = fn_node->func.body->block.statements; while (stmt && !ci->error_happened) { // Check for return if (stmt->kind == NODE_RETURN) { CValue ret = val_null; if (stmt->ret.value) { ret = eval_expr(ci, stmt->ret.value); } scope_pop_all(&ci->scope); ci->scope = saved_scope; ci->rec_depth--; return ret; } exec_stmt(ci, stmt); if (ci->error_happened) { break; } stmt = stmt->next; } } scope_pop_all(&ci->scope); ci->scope = saved_scope; ci->rec_depth--; return val_null; } static CValue eval_index(CInterp *ci, ASTNode *node) { CValue arr = eval_expr(ci, node->index.array); if (ci->error_happened) { return val_null; } if (arr.kind != VAL_ARRAY) { val_free(&arr); zerror_at(node->token, "comptime: cannot index a non-array value"); ci->error_happened = 1; return val_null; } int64_t idx = eval_int(ci, node->index.index); if (ci->error_happened) { val_free(&arr); return val_null; } if (idx < 0 || (uint64_t)idx >= arr.as.arr.len) { val_free(&arr); zerror_at(node->token, "comptime: array index %lld out of bounds (length %zu)", (long long)idx, arr.as.arr.len); ci->error_happened = 1; return val_null; } CValue elem = val_deep_copy(&arr.as.arr.data[idx]); val_free(&arr); return elem; } static CValue eval_expr(CInterp *ci, ASTNode *node) { if (ci->step_count++ > MAX_STEPS) { zerror_at(node ? node->token : TOKEN_UNKNOWN, "comptime: step limit exceeded (possible infinite loop)"); ci->error_happened = 1; return val_null; } if (!node || ci->error_happened) { return val_null; } switch (node->kind) { case NODE_EXPR_LITERAL: return eval_literal(ci, node); case NODE_EXPR_VAR: return eval_var(ci, node); case NODE_EXPR_BINARY: return eval_binary(ci, node); case NODE_EXPR_UNARY: return eval_unary(ci, node); case NODE_EXPR_CALL: return eval_call(ci, node); case NODE_EXPR_INDEX: return eval_index(ci, node); case NODE_EXPR_CAST: // Casts are transparent in the comptime value model. return eval_expr(ci, node->cast.expr); case NODE_EXPR_MEMBER: // Simple member access not supported yet zerror_at(node->token, "comptime: member access not supported yet"); ci->error_happened = 1; return val_null; default: zerror_at(node->token, "comptime: unsupported expression type %d", (int)node->kind); ci->error_happened = 1; return val_null; } } static void exec_let(CInterp *ci, ASTNode *node) { const char *name = node->var_decl.name; if (!name) { return; } CValue val = val_null; if (node->var_decl.init_expr) { val = eval_expr(ci, node->var_decl.init_expr); if (ci->error_happened) { return; } } else if (node->var_decl.type_info && node->var_decl.type_info->kind == TYPE_ARRAY && node->var_decl.type_info->array_size > 0) { // Materialize a zero-initialized fixed-size array (e.g. let fib: long[20];) val = val_new_array((size_t)node->var_decl.type_info->array_size); } ci->scope = scope_push(ci->scope, name, val); } static void exec_assign(CInterp *ci, ASTNode *node) { if (!node->binary.left) { zerror_at(node->token, "comptime: assignment target must be a variable"); ci->error_happened = 1; return; } if (node->binary.left->kind == NODE_EXPR_INDEX) { exec_assign_index(ci, node); return; } if (node->binary.left->kind != NODE_EXPR_VAR) { zerror_at(node->token, "comptime: assignment target must be a variable"); ci->error_happened = 1; return; } const char *name = node->binary.left->var_ref.name; CValue *existing = scope_find(ci->scope, name); if (!existing) { zerror_at(node->token, "comptime: undefined variable '%s'", name); ci->error_happened = 1; return; } CValue new_val = eval_expr(ci, node->binary.right); if (ci->error_happened) { return; } val_free(existing); *existing = new_val; } // Assign to an array element (left side of '=' is a NODE_EXPR_INDEX). // Writes directly into the scope's array storage so the mutation persists. static void exec_assign_index(CInterp *ci, ASTNode *node) { ASTNode *lhs = node->binary.left; if (!lhs->index.array || lhs->index.array->kind != NODE_EXPR_VAR) { zerror_at(node->token, "comptime: can only assign to an element of an array variable"); ci->error_happened = 1; return; } const char *name = lhs->index.array->var_ref.name; CValue *existing = scope_find(ci->scope, name); if (!existing) { zerror_at(node->token, "comptime: undefined variable '%s'", name); ci->error_happened = 1; return; } if (existing->kind != VAL_ARRAY) { zerror_at(node->token, "comptime: cannot index a non-array value"); ci->error_happened = 1; return; } int64_t idx = eval_int(ci, lhs->index.index); if (ci->error_happened) { return; } if (idx < 0 || (uint64_t)idx >= existing->as.arr.len) { zerror_at(node->token, "comptime: array index %lld out of bounds (length %zu)", (long long)idx, existing->as.arr.len); ci->error_happened = 1; return; } CValue new_val = eval_expr(ci, node->binary.right); if (ci->error_happened) { return; } CValue *slot = existing->as.arr.data + idx; val_free(slot); *slot = new_val; } static void exec_if(CInterp *ci, ASTNode *node) { CValue cond = eval_expr(ci, node->if_stmt.condition); if (ci->error_happened) { val_free(&cond); return; } int truthy = (cond.kind == VAL_INT && cond.as.i) || (cond.kind == VAL_BOOL && cond.as.b); val_free(&cond); if (truthy) { ASTNode *s = node->if_stmt.then_body; if (s && s->kind == NODE_BLOCK) { s = s->block.statements; } while (s && !ci->error_happened) { exec_stmt(ci, s); s = s->next; } } else if (node->if_stmt.else_body) { ASTNode *s = node->if_stmt.else_body; if (s && s->kind == NODE_BLOCK) { s = s->block.statements; } while (s && !ci->error_happened) { exec_stmt(ci, s); s = s->next; } } } static void exec_block(CInterp *ci, ASTNode *node) { ASTNode *s = node->block.statements; while (s && !ci->error_happened) { exec_stmt(ci, s); s = s->next; } } static void exec_stmt(CInterp *ci, ASTNode *node) { if (!node || ci->error_happened) { return; } if (ci->step_count++ > MAX_STEPS) { zerror_at(node->token, "comptime: step limit exceeded (possible infinite loop)"); ci->error_happened = 1; return; } switch (node->kind) { case NODE_ASSERT: case NODE_EXPECT: { CValue cv = eval_expr(ci, node->assert_stmt.condition); int truthy = (cv.kind == VAL_INT && cv.as.i) || (cv.kind == VAL_BOOL && cv.as.b); val_free(&cv); if (!truthy) { const char *msg = node->assert_stmt.message ? node->assert_stmt.message : "comptime assertion failed"; zerror_at(node->token, "comptime: %s", msg); ci->error_happened = 1; } break; } case NODE_VAR_DECL: exec_let(ci, node); break; case NODE_EXPR_BINARY: if (node->binary.op && strcmp(node->binary.op, "=") == 0) { exec_assign(ci, node); } else { eval_expr(ci, node); } break; case NODE_EXPR_CALL: case NODE_EXPR_VAR: case NODE_EXPR_LITERAL: case NODE_EXPR_UNARY: eval_expr(ci, node); break; case NODE_IF: exec_if(ci, node); break; case NODE_BLOCK: exec_block(ci, node); break; case NODE_FOR: { // for (init; cond; step) body if (node->for_stmt.init) { exec_stmt(ci, node->for_stmt.init); } while (!ci->error_happened) { CValue cv = val_null; if (node->for_stmt.condition) { cv = eval_expr(ci, node->for_stmt.condition); int truthy = (cv.kind == VAL_INT && cv.as.i) || (cv.kind == VAL_BOOL && cv.as.b); val_free(&cv); if (!truthy) { break; } } if (node->for_stmt.body) { exec_stmt(ci, node->for_stmt.body); } if (ci->error_happened) { break; } if (node->for_stmt.step) { exec_stmt(ci, node->for_stmt.step); } } break; } case NODE_FOR_RANGE: { int64_t start = node->for_range.start ? eval_int(ci, node->for_range.start) : 0; int64_t end = node->for_range.end ? eval_int(ci, node->for_range.end) : 0; if (ci->error_happened) { break; } int inclusive = node->for_range.is_inclusive; for (int64_t i = start; inclusive ? i <= end : i < end; i++) { if (ci->step_count++ > MAX_STEPS) { zerror_at(node->token, "comptime: step limit exceeded in for-range loop"); ci->error_happened = 1; break; } // Bind loop variable if (node->for_range.var_name) { CValue iv = {VAL_INT, {.i = i}}; ci->scope = scope_push(ci->scope, node->for_range.var_name, iv); } if (node->for_range.body) { exec_stmt(ci, node->for_range.body); } if (node->for_range.var_name) { scope_pop(&ci->scope); } if (ci->error_happened) { break; } } break; } case NODE_WHILE: { while (!ci->error_happened) { CValue cv = eval_expr(ci, node->while_stmt.condition); int truthy = (cv.kind == VAL_INT && cv.as.i) || (cv.kind == VAL_BOOL && cv.as.b); val_free(&cv); if (!truthy) { break; } if (node->while_stmt.body) { exec_stmt(ci, node->while_stmt.body); } } break; } case NODE_LOOP: { while (!ci->error_happened) { if (ci->step_count++ > MAX_STEPS) { break; } if (node->loop_stmt.body) { exec_stmt(ci, node->loop_stmt.body); } } break; } case NODE_BREAK: case NODE_CONTINUE: // Not directly supported in comptime loops; ignore break; default: // Skip unsupported statement types silently (they may be comptime-only constructs) break; } } char *interpret_comptime(ParserContext *ctx, ASTNode *body, const char *source_file) { CInterp ci; memset(&ci, 0, sizeof(ci)); ci.pctx = ctx; ci.source_file = source_file; ci.yield_buf = NULL; ci.yield_cap = 0; ci.yield_len = 0; // Execute body statements ASTNode *stmt = body; while (stmt && !ci.error_happened) { exec_stmt(&ci, stmt); stmt = stmt->next; } // Finalize yield buffer char *result = ci.yield_buf ? ci.yield_buf : xstrdup(""); // Cleanup scope_pop_all(&ci.scope); if (ci.error_happened) { if (result != ci.yield_buf) { zfree(result); } zfree(ci.yield_buf); return NULL; } return result; }