#ifdef _WIN32 #include #endif #include "wota.h" #define STB_DS_IMPLEMENTATION #include "stb_ds.h" #include "cell.h" #include "cell_internal.h" #include "cJSON.h" #define BOOTSTRAP_MACH "internal/bootstrap.mach" #define BOOTSTRAP_AST "internal/bootstrap.ast.json" #define BOOTSTRAP_SRC "internal/bootstrap.cm" #define CELL_SHOP_DIR ".cell" #define CELL_CORE_DIR "packages/core" #include #include #include #include /* Test suite declarations */ int run_c_test_suite(JSContext *ctx); static int run_test_suite(size_t heap_size); cell_rt *root_cell = NULL; static char *shop_path = NULL; static char *core_path = NULL; static JSRuntime *g_runtime = NULL; // Get the home directory static const char* get_home_dir(void) { const char *home = getenv("HOME"); if (!home) { home = getenv("USERPROFILE"); // Windows fallback } return home; } // Resolve shop_path and core_path // core: --core flag > CELL_CORE env > derived from shop // shop: --shop flag > CELL_SHOP env > ~/.cell int find_cell_shop(const char *shop_override, const char *core_override) { // Resolve shop_path if (shop_override) { shop_path = strdup(shop_override); } else { const char *env = getenv("CELL_SHOP"); if (env) { shop_path = strdup(env); } else { const char *home = get_home_dir(); if (!home && !core_override && !getenv("CELL_CORE")) { printf("ERROR: Could not determine home directory. Set HOME environment variable.\n"); return 0; } if (home) { size_t path_len = strlen(home) + strlen("/" CELL_SHOP_DIR) + 1; shop_path = malloc(path_len); if (shop_path) snprintf(shop_path, path_len, "%s/" CELL_SHOP_DIR, home); } } } // Resolve core_path if (core_override) { core_path = strdup(core_override); } else { const char *env = getenv("CELL_CORE"); if (env) { core_path = strdup(env); } else if (shop_path) { size_t core_len = strlen(shop_path) + strlen("/" CELL_CORE_DIR) + 1; core_path = malloc(core_len); if (core_path) snprintf(core_path, core_len, "%s/" CELL_CORE_DIR, shop_path); } } if (!core_path) { printf("ERROR: No core path. Use --core or set CELL_CORE.\n"); return 0; } // Check if the core directory exists struct stat st; if (stat(core_path, &st) != 0 || !S_ISDIR(st.st_mode)) { printf("ERROR: Core not found at %s\n", core_path); return 0; } return 1; } // Load a file from the core directory static char* load_core_file(const char *filename, size_t *out_size) { if (!core_path) return NULL; size_t path_len = strlen(core_path) + 1 + strlen(filename) + 1; char *full_path = malloc(path_len); if (!full_path) return NULL; snprintf(full_path, path_len, "%s/%s", core_path, filename); FILE *fh = fopen(full_path, "rb"); free(full_path); if (!fh) return NULL; fseek(fh, 0, SEEK_END); long file_size = ftell(fh); fseek(fh, 0, SEEK_SET); char *data = malloc(file_size + 1); if (!data) { fclose(fh); return NULL; } if (fread(data, 1, file_size, fh) != (size_t)file_size) { free(data); fclose(fh); return NULL; } fclose(fh); data[file_size] = 0; if (out_size) *out_size = file_size; return data; } // Get the core path for use by scripts const char* cell_get_core_path(void) { return core_path; } void actor_disrupt(cell_rt *crt) { crt->disrupt = 1; if (crt->state != ACTOR_RUNNING) actor_free(crt); } JSValue js_os_use(JSContext *js); JSValue js_math_use(JSContext *js); JSValue js_json_use(JSContext *js); JSValue js_nota_use(JSContext *js); JSValue js_wota_use(JSContext *js); void script_startup(cell_rt *prt) { if (!g_runtime) { g_runtime = JS_NewRuntime(); } JSContext *js = JS_NewContext(g_runtime); JS_SetInterruptHandler(js, (JSInterruptHandler *)actor_interrupt_cb, prt); JS_SetContextOpaque(js, prt); prt->context = js; /* Register all GCRef fields so the Cheney GC can relocate them. */ JS_AddGCRef(js, &prt->idx_buffer_ref); JS_AddGCRef(js, &prt->on_exception_ref); JS_AddGCRef(js, &prt->message_handle_ref); JS_AddGCRef(js, &prt->unneeded_ref); JS_AddGCRef(js, &prt->actor_sym_ref); prt->idx_buffer_ref.val = JS_NULL; prt->on_exception_ref.val = JS_NULL; prt->message_handle_ref.val = JS_NULL; prt->unneeded_ref.val = JS_NULL; prt->actor_sym_ref.val = JS_NULL; cell_rt *crt = JS_GetContextOpaque(js); JS_FreeValue(js, js_blob_use(js)); // Load pre-compiled bootstrap bytecode (.mach), fall back to AST JSON size_t boot_size; int boot_is_bin = 1; char *boot_data = load_core_file(BOOTSTRAP_MACH, &boot_size); if (!boot_data) { boot_is_bin = 0; boot_data = load_core_file(BOOTSTRAP_AST, &boot_size); } if (!boot_data) { printf("ERROR: Could not load bootstrap from %s!\n", core_path); return; } // Create hidden environment JSValue hidden_env = JS_NewObject(js); JS_SetPropertyStr(js, hidden_env, "os", js_os_use(js)); JS_SetPropertyStr(js, hidden_env, "json", js_json_use(js)); JS_SetPropertyStr(js, hidden_env, "nota", js_nota_use(js)); JS_SetPropertyStr(js, hidden_env, "wota", js_wota_use(js)); crt->actor_sym_ref.val = JS_NewObject(js); JS_SetPropertyStr(js, hidden_env, "actorsym", JS_DupValue(js, crt->actor_sym_ref.val)); // Always set init (even if null) if (crt->init_wota) { JS_SetPropertyStr(js, hidden_env, "init", wota2value(js, crt->init_wota)); free(crt->init_wota); crt->init_wota = NULL; } else { JS_SetPropertyStr(js, hidden_env, "init", JS_NULL); } // Set args and use_mcode to null/false for actor spawn (not CLI mode) JS_SetPropertyStr(js, hidden_env, "args", JS_NULL); JS_SetPropertyStr(js, hidden_env, "use_mcode", JS_NewBool(js, 0)); if (core_path) JS_SetPropertyStr(js, hidden_env, "core_path", JS_NewString(js, core_path)); if (shop_path) JS_SetPropertyStr(js, hidden_env, "shop_path", JS_NewString(js, shop_path)); // Stone the environment hidden_env = JS_Stone(js, hidden_env); // Run through MACH VM crt->state = ACTOR_RUNNING; JSValue v; if (boot_is_bin) { v = JS_RunMachBin(js, (const uint8_t *)boot_data, boot_size, hidden_env); free(boot_data); } else { cJSON *ast = cJSON_Parse(boot_data); free(boot_data); if (!ast) { printf("ERROR: Failed to parse bootstrap AST\n"); return; } v = JS_RunMachTree(js, ast, hidden_env); cJSON_Delete(ast); } uncaught_exception(js, v); crt->state = ACTOR_IDLE; set_actor_state(crt); } static void signal_handler(int sig) { const char *str = NULL; #ifndef TARGET_PLAYDATE switch (sig) { case SIGABRT: str = "SIGABRT"; break; case SIGFPE: str = "SIGFPE"; break; case SIGILL: str = "SIGILL"; break; case SIGINT: str = "SIGINT"; break; case SIGSEGV: str = "SIGSEGV"; break; case SIGTERM: str = "SIGTERM"; break; } #endif if (!str) return; exit_handler(); } /* Run the C test suite with minimal runtime setup */ static int run_test_suite(size_t heap_size) { JSRuntime *rt = JS_NewRuntime(); if (!rt) { printf("Failed to create JS runtime\n"); return 1; } JSContext *ctx = JS_NewContextWithHeapSize(rt, heap_size); if (!ctx) { printf("Failed to create JS context\n"); JS_FreeRuntime(rt); return 1; } int result = run_c_test_suite(ctx); JS_FreeContext(ctx); JS_FreeRuntime(rt); return result; } static void print_usage(const char *prog) { printf("Usage: %s [options]