#ifdef _WIN32 #include #endif #define WOTA_IMPLEMENTATION #include "wota.h" #define STB_DS_IMPLEMENTATION #include "stb_ds.h" #include "cell.h" #include "cell_internal.h" #define ENGINE "internal/engine.cm" #define CELL_SHOP_DIR ".cell" #define CELL_CORE_DIR "packages/core" #include #include #include cell_rt *root_cell = NULL; static char *core_path = 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; } // Find and verify the cell shop at ~/.cell int find_cell_shop(void) { const char *home = get_home_dir(); if (!home) { printf("ERROR: Could not determine home directory. Set HOME environment variable.\n"); return 0; } // Build path to ~/.cell/core size_t path_len = strlen(home) + strlen("/" CELL_SHOP_DIR "/" CELL_CORE_DIR) + 1; core_path = malloc(path_len); if (!core_path) { printf("ERROR: Could not allocate memory for core path\n"); return 0; } snprintf(core_path, path_len, "%s/" CELL_SHOP_DIR "/" CELL_CORE_DIR, home); // Check if the core directory exists struct stat st; if (stat(core_path, &st) != 0 || !S_ISDIR(st.st_mode)) { printf("ERROR: Cell shop not found at %s/" CELL_SHOP_DIR "\n", home); printf("Run 'cell install' to set up the cell environment.\n"); free(core_path); core_path = NULL; 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); void script_startup(cell_rt *prt) { JSRuntime *rt; rt = JS_NewRuntime(); JSContext *js = JS_NewContextRaw(rt); JS_SetInterruptHandler(rt, actor_interrupt_cb, prt); JS_AddIntrinsicBaseObjects(js); JS_AddIntrinsicEval(js); JS_AddIntrinsicRegExp(js); JS_AddIntrinsicJSON(js); JS_AddIntrinsicMapSet(js); JS_SetContextOpaque(js, prt); prt->context = js; cell_rt *crt = JS_GetContextOpaque(js); JS_FreeValue(js, js_blob_use(js)); JSValue globalThis = JS_GetGlobalObject(js); JSValue cell = JS_NewObject(js); JS_SetPropertyStr(js,globalThis,"cell", cell); JSValue hidden_fn = JS_NewObject(js); JS_SetPropertyStr(js, cell, "hidden", hidden_fn); JS_SetPropertyStr(js, hidden_fn, "os", js_os_use(js)); const char actorsym_script[] = "Symbol('actordata');"; JSValue actorsym = JS_Eval(js, actorsym_script, sizeof(actorsym_script)-1, "internal", 0); JS_SetPropertyStr(js, hidden_fn, "actorsym", actorsym); crt->actor_sym = JS_ValueToAtom(js, actorsym); if (crt->init_wota) { JS_SetPropertyStr(js, hidden_fn, "init", wota2value(js, crt->init_wota)); // init wota can now be freed free(crt->init_wota); crt->init_wota = NULL; } // Store the core path for scripts to use JSValue js_cell = JS_GetPropertyStr(js, globalThis, "cell"); JSValue hidden = JS_GetPropertyStr(js, js_cell, "hidden"); if (core_path) { JS_SetPropertyStr(js, hidden, "core_path", JS_NewString(js, core_path)); } JS_FreeValue(js, hidden); JS_FreeValue(js, js_cell); JS_FreeValue(js, globalThis); // Load engine.cm from the core directory size_t engine_size; char *data = load_core_file(ENGINE, &engine_size); if (!data) { printf("ERROR: Could not load %s from %s!\n", ENGINE, core_path); return; } crt->state = ACTOR_RUNNING; JSValue v = JS_Eval(js, data, engine_size, ENGINE, 0); free(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(); } int cell_init(int argc, char **argv) { int script_start = 1; /* Find the cell shop at ~/.cell */ int found = find_cell_shop(); if (!found) { return 1; } /* Create the initial actor from the command line */ int actor_argc = argc - script_start; char **actor_argv = argv + script_start; WotaBuffer startwota; wota_buffer_init(&startwota, 5); wota_write_record(&startwota, 2); wota_write_text(&startwota, "program"); wota_write_text(&startwota, actor_argv[0]); wota_write_text(&startwota, "arg"); wota_write_array(&startwota, actor_argc - 1); for (int i = 1; i < actor_argc; i++) wota_write_text(&startwota, actor_argv[i]); /* Initialize synchronization primitives */ actor_initialize(); root_cell = create_actor(startwota.data); #ifndef TARGET_PLAYDATE signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); signal(SIGSEGV, signal_handler); signal(SIGABRT, signal_handler); #endif actor_loop(); return 0; } int JS_ArrayLength(JSContext *js, JSValue a) { JSValue length = JS_GetPropertyStr(js, a, "length"); int len; JS_ToInt32(js,&len,length); JS_FreeValue(js,length); 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) { cell_rt *rt = JS_GetContextOpaque(js); if (!JS_HasException(js)) { JS_FreeValue(js,v); return 1; } JSValue exp = JS_GetException(js); if (JS_IsNull(rt->on_exception)) { const char *str = JS_ToCString(js, exp); if (str) { printf("Uncaught exception: %s\n", str); JS_FreeCString(js, str); } JSValue stack = JS_GetPropertyStr(js, exp, "stack"); if (!JS_IsNull(stack)) { const char *stack_str = JS_ToCString(js, stack); if (stack_str) { printf("Stack trace:\n%s\n", stack_str); JS_FreeCString(js, stack_str); } } JS_FreeValue(js, stack); } else { JSValue ret = JS_Call(js, rt->on_exception, JS_NULL, 1, &exp); JS_FreeValue(js, ret); } JS_FreeValue(js, exp); JS_FreeValue(js, v); return 0; }