#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_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) size_t boot_size; char *boot_data = load_core_file(BOOTSTRAP_MACH, &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 to null for actor spawn (not CLI mode) JS_SetPropertyStr(js, hidden_env, "args", JS_NULL); if (core_path) JS_SetPropertyStr(js, hidden_env, "core_path", JS_NewString(js, core_path)); JS_SetPropertyStr(js, hidden_env, "shop_path", shop_path ? JS_NewString(js, shop_path) : JS_NULL); // Stone the environment hidden_env = JS_Stone(js, hidden_env); // Run through MACH VM crt->state = ACTOR_RUNNING; JSValue v = JS_RunMachBin(js, (const uint8_t *)boot_data, boot_size, hidden_env); free(boot_data); 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] [args...]\n\n", prog); printf("Run a cell program (.ce actor).\n\n"); printf("Options:\n"); printf(" --core Set core path directly (overrides CELL_CORE)\n"); printf(" --shop Set shop path (overrides CELL_SHOP)\n"); printf(" --test [heap_size] Run C test suite\n"); printf(" -h, --help Show this help message\n"); printf("\nEnvironment:\n"); printf(" CELL_CORE Core path (default: /packages/core)\n"); printf(" CELL_SHOP Shop path (default: ~/.cell)\n"); printf("\nRecompile after changes: make\n"); printf("Bootstrap from scratch: make bootstrap\n"); } int cell_init(int argc, char **argv) { /* Check for --help flag */ if (argc >= 2 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) { print_usage(argv[0]); return 0; } /* Check for --test flag to run C test suite */ if (argc >= 2 && strcmp(argv[1], "--test") == 0) { size_t heap_size = 64 * 1024; /* 64KB default */ if (argc >= 3) { heap_size = strtoull(argv[2], NULL, 0); /* Round up to power of 2 for buddy allocator */ size_t p = 1; while (p < heap_size) p <<= 1; heap_size = p; } return run_test_suite(heap_size); } /* Default: run script through bootstrap pipeline */ int arg_start = 1; const char *shop_override = NULL; const char *core_override = NULL; // Parse flags (order-independent) while (arg_start < argc && argv[arg_start][0] == '-') { if (strcmp(argv[arg_start], "--shop") == 0) { if (arg_start + 1 >= argc) { printf("ERROR: --shop requires a path argument\n"); return 1; } shop_override = argv[arg_start + 1]; arg_start += 2; } else if (strcmp(argv[arg_start], "--core") == 0) { if (arg_start + 1 >= argc) { printf("ERROR: --core requires a path argument\n"); return 1; } core_override = argv[arg_start + 1]; arg_start += 2; } else { break; } } if (arg_start >= argc) { print_usage(argv[0]); return 1; } if (!find_cell_shop(shop_override, core_override)) return 1; actor_initialize(); size_t boot_size; char *boot_data = load_core_file(BOOTSTRAP_MACH, &boot_size); if (!boot_data) { printf("ERROR: Could not load bootstrap from %s\n", core_path); return 1; } g_runtime = JS_NewRuntime(); if (!g_runtime) { printf("Failed to create JS runtime\n"); free(boot_data); return 1; } JSContext *ctx = JS_NewContextWithHeapSize(g_runtime, 16 * 1024 * 1024); if (!ctx) { printf("Failed to create JS context\n"); free(boot_data); JS_FreeRuntime(g_runtime); return 1; } /* Create a cell_rt for the CLI context so JS-C bridge functions work */ cell_rt *cli_rt = calloc(sizeof(*cli_rt), 1); cli_rt->mutex = malloc(sizeof(pthread_mutex_t)); pthread_mutexattr_t mattr; pthread_mutexattr_init(&mattr); pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(cli_rt->mutex, &mattr); cli_rt->msg_mutex = malloc(sizeof(pthread_mutex_t)); pthread_mutex_init(cli_rt->msg_mutex, &mattr); pthread_mutexattr_destroy(&mattr); cli_rt->context = ctx; JS_SetContextOpaque(ctx, cli_rt); JS_SetInterruptHandler(ctx, (JSInterruptHandler *)actor_interrupt_cb, cli_rt); JS_AddGCRef(ctx, &cli_rt->idx_buffer_ref); JS_AddGCRef(ctx, &cli_rt->on_exception_ref); JS_AddGCRef(ctx, &cli_rt->message_handle_ref); JS_AddGCRef(ctx, &cli_rt->unneeded_ref); JS_AddGCRef(ctx, &cli_rt->actor_sym_ref); cli_rt->idx_buffer_ref.val = JS_NULL; cli_rt->on_exception_ref.val = JS_NULL; cli_rt->message_handle_ref.val = JS_NULL; cli_rt->unneeded_ref.val = JS_NULL; cli_rt->actor_sym_ref.val = JS_NewObject(ctx); root_cell = cli_rt; JS_FreeValue(ctx, js_blob_use(ctx)); JSValue hidden_env = JS_NewObject(ctx); JS_SetPropertyStr(ctx, hidden_env, "os", js_os_use(ctx)); JS_SetPropertyStr(ctx, hidden_env, "core_path", JS_NewString(ctx, core_path)); JS_SetPropertyStr(ctx, hidden_env, "shop_path", shop_path ? JS_NewString(ctx, shop_path) : JS_NULL); /* TODO: remove after next 'make regen' — old bootstrap.mach reads these */ JS_SetPropertyStr(ctx, hidden_env, "emit_qbe", JS_FALSE); JS_SetPropertyStr(ctx, hidden_env, "dump_mach", JS_FALSE); JS_SetPropertyStr(ctx, hidden_env, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val)); JS_SetPropertyStr(ctx, hidden_env, "json", js_json_use(ctx)); JS_SetPropertyStr(ctx, hidden_env, "nota", js_nota_use(ctx)); JS_SetPropertyStr(ctx, hidden_env, "wota", js_wota_use(ctx)); JS_SetPropertyStr(ctx, hidden_env, "init", JS_NULL); JSValue args_arr = JS_NewArray(ctx); for (int i = arg_start; i < argc; i++) { JSValue str = JS_NewString(ctx, argv[i]); JS_ArrayPush(ctx, &args_arr, str); } JS_SetPropertyStr(ctx, hidden_env, "args", args_arr); hidden_env = JS_Stone(ctx, hidden_env); JSValue result = JS_RunMachBin(ctx, (const uint8_t *)boot_data, boot_size, hidden_env); free(boot_data); int exit_code = 0; if (JS_IsException(result)) { JS_GetException(ctx); exit_code = 1; } else if (!JS_IsNull(result)) { const char *str = JS_ToCString(ctx, result); if (str) { printf("%s\n", str); JS_FreeCString(ctx, str); } } if (scheduler_actor_count() > 0) { scheduler_enable_quiescence(); actor_loop(); exit_handler(); exit(0); } /* No actors spawned — clean up CLI context */ JS_DeleteGCRef(ctx, &cli_rt->idx_buffer_ref); JS_DeleteGCRef(ctx, &cli_rt->on_exception_ref); JS_DeleteGCRef(ctx, &cli_rt->message_handle_ref); JS_DeleteGCRef(ctx, &cli_rt->unneeded_ref); JS_DeleteGCRef(ctx, &cli_rt->actor_sym_ref); JS_SetInterruptHandler(ctx, NULL, NULL); pthread_mutex_destroy(cli_rt->mutex); free(cli_rt->mutex); pthread_mutex_destroy(cli_rt->msg_mutex); free(cli_rt->msg_mutex); free(cli_rt); root_cell = NULL; JS_FreeContext(ctx); JS_FreeRuntime(g_runtime); g_runtime = NULL; exit_handler(); return exit_code; } int JS_ArrayLength(JSContext *js, JSValue a) { int64_t len; JS_GetLength(js, a, &len); return len; } int js2bool(JSContext *js, JSValue v) { return JS_ToBool(js,v); } JSValue bool2js(JSContext *js, int b) { return JS_NewBool(js,b); } JSValue number2js(JSContext *js, double g) { return JS_NewFloat64(js,g); } double js2number(JSContext *js, JSValue v) { double g; JS_ToFloat64(js, &g, v); if (isnan(g)) g = 0; return g; } uint64_t cell_random_fit() { uint64_t buf; randombytes((uint8_t *)&buf, sizeof(buf)); return buf >> 11; } double cell_random() { uint64_t buf = cell_random_fit(); return (double)buf / 9007199254740992.0; } void cell_trace_sethook(cell_hook) { } int uncaught_exception(JSContext *js, JSValue v) { (void)v; if (!JS_HasException(js)) return 1; /* Error message and backtrace were already printed to stderr by JS_ThrowError2 / print_backtrace. Just clear the flag. */ JS_GetException(js); return 0; }