Files
cell/source/cell.c

529 lines
15 KiB
C

#ifdef _WIN32
#include <windows.h>
#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 <math.h>
#include <signal.h>
#include <unistd.h>
#include <sys/stat.h>
/* 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 <path> 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] <script> [args...]\n\n", prog);
printf("Run a cell script (.ce actor or .cm module).\n\n");
printf("Options:\n");
printf(" --core <path> Set core path directly (overrides CELL_CORE)\n");
printf(" --shop <path> Set shop path (overrides CELL_SHOP)\n");
printf(" --mcode <script> [args] Run through mcode compilation pipeline\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: <shop>/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 use_mcode = 0;
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], "--mcode") == 0) {
use_mcode = 1;
arg_start++;
} else 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 (!find_cell_shop(shop_override, core_override)) return 1;
actor_initialize();
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 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));
if (shop_path)
JS_SetPropertyStr(ctx, hidden_env, "shop_path", JS_NewString(ctx, shop_path));
JS_SetPropertyStr(ctx, hidden_env, "use_mcode", JS_NewBool(ctx, use_mcode));
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;
if (boot_is_bin) {
result = JS_RunMachBin(ctx, (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("Failed to parse bootstrap AST\n"); JS_FreeContext(ctx); JS_FreeRuntime(g_runtime); return 1; }
result = JS_RunMachTree(ctx, ast, hidden_env);
cJSON_Delete(ast);
}
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) {
actor_loop();
exit_handler();
return exit_code;
}
/* 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;
}