334 lines
7.8 KiB
C
334 lines
7.8 KiB
C
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#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 <signal.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
|
|
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_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));
|
|
|
|
crt->actor_sym = JS_NewObject(js);
|
|
JS_SetPropertyStr(js, hidden_fn, "actorsym", JS_DupValue(js,crt->actor_sym));
|
|
|
|
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);
|
|
|
|
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;
|
|
|
|
|
|
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;
|
|
} |