#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; // 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; 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"); 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) { JSRuntime *rt = JS_NewRuntime(); JS_SetInterruptHandler(rt, (JSInterruptHandler *)actor_interrupt_cb, prt); JSContext *js = JS_NewContext(rt); JS_SetContextOpaque(js, prt); prt->context = js; cell_rt *crt = JS_GetContextOpaque(js); JS_FreeValue(js, js_blob_use(js)); // Load and compile engine.cm 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; } JSValue bytecode = JS_Compile(js, data, engine_size, ENGINE); free(data); if (JS_IsException(bytecode)) { uncaught_exception(js, bytecode); 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 = JS_NewObject(js); JS_SetPropertyStr(js, hidden_env, "actorsym", JS_DupValue(js, crt->actor_sym)); // 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); // Integrate and run crt->state = ACTOR_RUNNING; JSValue v = JS_Integrate(js, bytecode, hidden_env); 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 int run_eval(const char *script_or_file, int print_bytecode, int use_bootstrap_env) { if (!find_cell_shop()) return 1; /* Check if argument is a file path */ struct stat st; char *script = NULL; char *allocated_script = NULL; const char *filename = ""; if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) { /* It's a file, read its contents */ FILE *f = fopen(script_or_file, "r"); if (!f) { printf("Failed to open file: %s\n", script_or_file); return 1; } allocated_script = malloc(st.st_size + 1); if (!allocated_script) { fclose(f); printf("Failed to allocate memory for script\n"); return 1; } size_t read_size = fread(allocated_script, 1, st.st_size, f); fclose(f); allocated_script[read_size] = '\0'; script = allocated_script; filename = script_or_file; } else { /* Treat as inline script */ script = (char *)script_or_file; } JSRuntime *rt = JS_NewRuntime(); if (!rt) { printf("Failed to create JS runtime\n"); free(allocated_script); return 1; } JSContext *ctx = JS_NewContext(rt); if (!ctx) { printf("Failed to create JS context\n"); JS_FreeRuntime(rt); free(allocated_script); return 1; } int result = 0; JSGCRef bytecode_ref; JS_PushGCRef(ctx, &bytecode_ref); bytecode_ref.val = JS_Compile(ctx, script, strlen(script), filename); if (JS_IsException(bytecode_ref.val)) { uncaught_exception(ctx, bytecode_ref.val); JS_PopGCRef(ctx, &bytecode_ref); result = 1; } else { if (print_bytecode) { printf("=== Compiled Bytecode ===\n"); JS_DumpFunctionBytecode(ctx, bytecode_ref.val); } JSValue env = JS_NULL; if (use_bootstrap_env) { JSGCRef env_ref, json_ref, nota_ref, wota_ref; JS_PushGCRef(ctx, &env_ref); JS_PushGCRef(ctx, &json_ref); JS_PushGCRef(ctx, ¬a_ref); JS_PushGCRef(ctx, &wota_ref); env_ref.val = JS_NewObject(ctx); /* Create modules with GC rooting, then stone them */ json_ref.val = js_json_use(ctx); nota_ref.val = js_nota_use(ctx); wota_ref.val = js_wota_use(ctx); JS_SetPropertyStr(ctx, env_ref.val, "json", JS_Stone(ctx, json_ref.val)); JS_SetPropertyStr(ctx, env_ref.val, "nota", JS_Stone(ctx, nota_ref.val)); JS_SetPropertyStr(ctx, env_ref.val, "wota", JS_Stone(ctx, wota_ref.val)); env = JS_Stone(ctx, env_ref.val); JS_PopGCRef(ctx, &wota_ref); JS_PopGCRef(ctx, ¬a_ref); JS_PopGCRef(ctx, &json_ref); JS_PopGCRef(ctx, &env_ref); } JSValue v = JS_Integrate(ctx, bytecode_ref.val, env); JS_PopGCRef(ctx, &bytecode_ref); if (JS_IsException(v)) { uncaught_exception(ctx, v); result = 1; } else { JS_FreeValue(ctx, v); } } JS_FreeContext(ctx); JS_FreeRuntime(rt); free(allocated_script); return result; } int cell_init(int argc, char **argv) { /* 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); } /* Check for --ast flag to output AST JSON */ if (argc >= 3 && strcmp(argv[1], "--ast") == 0) { const char *script_or_file = argv[2]; char *script = NULL; char *allocated_script = NULL; const char *filename = ""; struct stat st; if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) { FILE *f = fopen(script_or_file, "r"); if (!f) { printf("Failed to open file: %s\n", script_or_file); return 1; } allocated_script = malloc(st.st_size + 1); if (!allocated_script) { fclose(f); printf("Failed to allocate memory for script\n"); return 1; } size_t read_size = fread(allocated_script, 1, st.st_size, f); fclose(f); allocated_script[read_size] = '\0'; script = allocated_script; filename = script_or_file; } else { script = (char *)script_or_file; } cJSON *ast = JS_ASTTree(script, strlen(script), filename); if (ast) { int has_errors = print_tree_errors(ast); char *pretty = cJSON_Print(ast); cJSON_Delete(ast); printf("%s\n", pretty); free(pretty); free(allocated_script); return has_errors ? 1 : 0; } else { printf("Failed to parse AST\n"); free(allocated_script); return 1; } } /* Check for --tokenize flag to output token array JSON */ if (argc >= 3 && strcmp(argv[1], "--tokenize") == 0) { const char *script_or_file = argv[2]; char *script = NULL; char *allocated_script = NULL; const char *filename = ""; struct stat st; if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) { FILE *f = fopen(script_or_file, "r"); if (!f) { printf("Failed to open file: %s\n", script_or_file); return 1; } allocated_script = malloc(st.st_size + 1); if (!allocated_script) { fclose(f); printf("Failed to allocate memory for script\n"); return 1; } size_t read_size = fread(allocated_script, 1, st.st_size, f); fclose(f); allocated_script[read_size] = '\0'; script = allocated_script; filename = script_or_file; } else { script = (char *)script_or_file; } char *json = JS_Tokenize(script, strlen(script), filename); if (json) { int has_errors = print_json_errors(json); cJSON *root = cJSON_Parse(json); free(json); if (root) { char *pretty = cJSON_Print(root); cJSON_Delete(root); printf("%s\n", pretty); free(pretty); } free(allocated_script); return has_errors ? 1 : 0; } else { printf("Failed to tokenize\n"); free(allocated_script); return 1; } } /* Check for --mcode flag to output MCODE JSON IR */ if (argc >= 3 && strcmp(argv[1], "--mcode") == 0) { const char *script_or_file = argv[2]; char *script = NULL; char *allocated_script = NULL; const char *filename = ""; struct stat st; if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) { FILE *f = fopen(script_or_file, "r"); if (!f) { printf("Failed to open file: %s\n", script_or_file); return 1; } allocated_script = malloc(st.st_size + 1); if (!allocated_script) { fclose(f); printf("Failed to allocate memory for script\n"); return 1; } size_t read_size = fread(allocated_script, 1, st.st_size, f); fclose(f); allocated_script[read_size] = '\0'; script = allocated_script; filename = script_or_file; } else { script = (char *)script_or_file; } cJSON *ast = JS_ASTTree(script, strlen(script), filename); if (!ast) { printf("Failed to parse AST\n"); free(allocated_script); return 1; } if (print_tree_errors(ast)) { cJSON_Delete(ast); free(allocated_script); return 1; } cJSON *mcode = JS_McodeTree(ast); cJSON_Delete(ast); if (!mcode) { printf("Failed to generate MCODE\n"); free(allocated_script); return 1; } char *pretty = cJSON_Print(mcode); cJSON_Delete(mcode); printf("%s\n", pretty); free(pretty); free(allocated_script); return 0; } /* Check for --run-mcode flag to execute via MCODE interpreter */ if (argc >= 3 && strcmp(argv[1], "--run-mcode") == 0) { const char *script_or_file = argv[2]; char *script = NULL; char *allocated_script = NULL; const char *filename = ""; struct stat st; if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) { FILE *f = fopen(script_or_file, "r"); if (!f) { printf("Failed to open file: %s\n", script_or_file); return 1; } allocated_script = malloc(st.st_size + 1); if (!allocated_script) { fclose(f); printf("Failed to allocate memory\n"); return 1; } size_t read_size = fread(allocated_script, 1, st.st_size, f); fclose(f); allocated_script[read_size] = '\0'; script = allocated_script; filename = script_or_file; } else { script = (char *)script_or_file; } cJSON *ast = JS_ASTTree(script, strlen(script), filename); if (!ast) { printf("Failed to parse AST\n"); free(allocated_script); return 1; } if (print_tree_errors(ast)) { cJSON_Delete(ast); free(allocated_script); return 1; } cJSON *mcode = JS_McodeTree(ast); cJSON_Delete(ast); if (!mcode) { printf("Failed to generate MCODE\n"); free(allocated_script); return 1; } if (print_tree_errors(mcode)) { cJSON_Delete(mcode); free(allocated_script); return 1; } /* Use a larger heap context for execution */ JSRuntime *rt = JS_NewRuntime(); if (!rt) { printf("Failed to create JS runtime\n"); cJSON_Delete(mcode); free(allocated_script); return 1; } JSContext *ctx = JS_NewContextWithHeapSize(rt, 64 * 1024); if (!ctx) { printf("Failed to create execution context\n"); cJSON_Delete(mcode); JS_FreeRuntime(rt); free(allocated_script); return 1; } JSValue result = JS_CallMcodeTree(ctx, mcode); /* takes ownership of mcode */ if (JS_IsException(result)) { JSValue exc = JS_GetException(ctx); const char *str = JS_ToCString(ctx, exc); if (str) { printf("Error: %s\n", str); JS_FreeCString(ctx, str); } cJSON *stack = JS_GetStack(ctx); if (stack) { int n = cJSON_GetArraySize(stack); for (int i = 0; i < n; i++) { cJSON *fr = cJSON_GetArrayItem(stack, i); const char *fn = cJSON_GetStringValue(cJSON_GetObjectItem(fr, "function")); const char *file = cJSON_GetStringValue(cJSON_GetObjectItem(fr, "file")); int line = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "line")); int col = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "column")); printf(" at %s (%s:%d:%d)\n", fn ? fn : "", file ? file : "", line, col); } cJSON_Delete(stack); } JS_FreeValue(ctx, exc); } else if (!JS_IsNull(result)) { const char *str = JS_ToCString(ctx, result); if (str) { printf("%s\n", str); JS_FreeCString(ctx, str); } } JS_FreeContext(ctx); JS_FreeRuntime(rt); free(allocated_script); return JS_IsException(result) ? 1 : 0; } /* Check for --mach flag to dump MACH bytecode */ if (argc >= 3 && strcmp(argv[1], "--mach") == 0) { const char *script_or_file = argv[2]; char *script = NULL; char *allocated_script = NULL; const char *filename = ""; struct stat st; if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) { FILE *f = fopen(script_or_file, "r"); if (!f) { printf("Failed to open file: %s\n", script_or_file); return 1; } allocated_script = malloc(st.st_size + 1); if (!allocated_script) { fclose(f); printf("Failed to allocate memory for script\n"); return 1; } size_t read_size = fread(allocated_script, 1, st.st_size, f); fclose(f); allocated_script[read_size] = '\0'; script = allocated_script; filename = script_or_file; } else { script = (char *)script_or_file; } cJSON *ast = JS_ASTTree(script, strlen(script), filename); if (!ast) { printf("Failed to parse AST\n"); free(allocated_script); return 1; } if (print_tree_errors(ast)) { cJSON_Delete(ast); free(allocated_script); return 1; } JSRuntime *rt = JS_NewRuntime(); if (!rt) { printf("Failed to create JS runtime\n"); cJSON_Delete(ast); free(allocated_script); return 1; } JSContext *ctx = JS_NewContext(rt); if (!ctx) { printf("Failed to create JS context\n"); cJSON_Delete(ast); JS_FreeRuntime(rt); free(allocated_script); return 1; } JS_DumpMachTree(ctx, ast, JS_NULL); cJSON_Delete(ast); JS_FreeContext(ctx); JS_FreeRuntime(rt); free(allocated_script); return 0; } /* Check for --mach-run flag to compile and run through MACH VM */ if (argc >= 3 && strcmp(argv[1], "--mach-run") == 0) { const char *script_or_file = argv[2]; char *script = NULL; char *allocated_script = NULL; const char *filename = ""; struct stat st; if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) { FILE *f = fopen(script_or_file, "r"); if (!f) { printf("Failed to open file: %s\n", script_or_file); return 1; } allocated_script = malloc(st.st_size + 1); if (!allocated_script) { fclose(f); printf("Failed to allocate memory for script\n"); return 1; } size_t read_size = fread(allocated_script, 1, st.st_size, f); fclose(f); allocated_script[read_size] = '\0'; script = allocated_script; filename = script_or_file; } else { script = (char *)script_or_file; } cJSON *ast = JS_ASTTree(script, strlen(script), filename); if (!ast) { printf("Failed to parse AST\n"); free(allocated_script); return 1; } if (print_tree_errors(ast)) { cJSON_Delete(ast); free(allocated_script); return 1; } JSRuntime *rt = JS_NewRuntime(); if (!rt) { printf("Failed to create JS runtime\n"); cJSON_Delete(ast); free(allocated_script); return 1; } JSContext *ctx = JS_NewContext(rt); if (!ctx) { printf("Failed to create JS context\n"); cJSON_Delete(ast); JS_FreeRuntime(rt); free(allocated_script); return 1; } JSValue result = JS_RunMachTree(ctx, ast, JS_NULL); cJSON_Delete(ast); int exit_code = 0; if (JS_IsException(result)) { JSValue exc = JS_GetException(ctx); const char *err_str = JS_ToCString(ctx, exc); if (err_str) { printf("Error: %s\n", err_str); JS_FreeCString(ctx, err_str); } cJSON *stack = JS_GetStack(ctx); if (stack) { int n = cJSON_GetArraySize(stack); for (int i = 0; i < n; i++) { cJSON *fr = cJSON_GetArrayItem(stack, i); const char *fn = cJSON_GetStringValue(cJSON_GetObjectItem(fr, "function")); const char *file = cJSON_GetStringValue(cJSON_GetObjectItem(fr, "file")); int line = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "line")); int col = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "column")); printf(" at %s (%s:%d:%d)\n", fn ? fn : "", file ? file : "", line, col); } cJSON_Delete(stack); } JS_FreeValue(ctx, exc); 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); } } JS_FreeContext(ctx); JS_FreeRuntime(rt); free(allocated_script); return exit_code; } /* Check for -e or --eval flag to run immediate script */ /* Also check for -p flag to print bytecode */ /* -s / --serializers flag provides json, nota, wota in env */ if (argc >= 3 && (strcmp(argv[1], "-e") == 0 || strcmp(argv[1], "--eval") == 0)) { return run_eval(argv[2], 0, 0); } if (argc >= 3 && (strcmp(argv[1], "-p") == 0 || strcmp(argv[1], "--print-bytecode") == 0)) { return run_eval(argv[2], 1, 0); } if (argc >= 3 && (strcmp(argv[1], "-s") == 0 || strcmp(argv[1], "--serializers") == 0)) { return run_eval(argv[2], 0, 1); } 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) { 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) { cell_rt *rt = JS_GetContextOpaque(js); if (!JS_HasException(js)) { JS_FreeValue(js,v); return 1; } JSValue exp = JS_GetException(js); JSValue message = JS_GetPropertyStr(js, exp, "message"); const char *msg_str = JS_ToCString(js, message); if (msg_str) { printf("Exception: %s\n", msg_str); JS_FreeCString(js, msg_str); } JS_FreeValue(js, message); JSValue stack = JS_GetPropertyStr(js, exp, "stack"); const char *stack_str = JS_ToCString(js, stack); if (stack_str) { printf("Stack:\n%s\n", stack_str); JS_FreeCString(js, stack_str); } JS_FreeValue(js, stack); JS_FreeValue(js, exp); JS_FreeValue(js, v); return 0; }