#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 ENGINE "internal/engine.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 *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; } // 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; } static int print_tree_errors(cJSON *root) { if (!root) return 0; cJSON *errors = cJSON_GetObjectItemCaseSensitive(root, "errors"); if (!cJSON_IsArray(errors) || cJSON_GetArraySize(errors) == 0) return 0; const char *filename = ""; cJSON *fname = cJSON_GetObjectItemCaseSensitive(root, "filename"); if (cJSON_IsString(fname)) filename = fname->valuestring; int prev_line = -1; const char *prev_msg = NULL; cJSON *e; cJSON_ArrayForEach(e, errors) { const char *msg = cJSON_GetStringValue( cJSON_GetObjectItemCaseSensitive(e, "message")); cJSON *line = cJSON_GetObjectItemCaseSensitive(e, "line"); cJSON *col = cJSON_GetObjectItemCaseSensitive(e, "column"); int cur_line = cJSON_IsNumber(line) ? (int)line->valuedouble : -1; if (prev_msg && msg && cur_line == prev_line && strcmp(msg, prev_msg) == 0) continue; prev_line = cur_line; prev_msg = msg; if (msg && cJSON_IsNumber(line) && cJSON_IsNumber(col)) fprintf(stderr, "%s:%d:%d: error: %s\n", filename, (int)line->valuedouble, (int)col->valuedouble, msg); else if (msg) fprintf(stderr, "%s: error: %s\n", filename, msg); } return 1; } static int print_json_errors(const char *json) { if (!json) return 0; cJSON *root = cJSON_Parse(json); if (!root) return 0; int result = print_tree_errors(root); cJSON_Delete(root); return result; } // 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 and parse engine.cm to AST 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; } cJSON *ast = JS_ASTTree(data, engine_size, ENGINE); free(data); if (!ast) { printf("ERROR: Failed to parse %s\n", ENGINE); return; } if (print_tree_errors(ast)) { cJSON_Delete(ast); 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); } if (core_path) { JS_SetPropertyStr(js, hidden_env, "core_path", JS_NewString(js, core_path)); } // Stone the environment hidden_env = JS_Stone(js, hidden_env); // Run through MACH VM crt->state = ACTOR_RUNNING; JSValue 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; } /* Run an immediate script string */ static void print_usage(const char *prog) { printf("Usage: %s [options]