#include "lang/syscmd.h"
#include "core/poll.h"
#include "core/ipc.h"
#include "core/profile.h"
#include "lang/env.h"
#include "table/sym.h"
void* ray_runtime_get_poll(void);
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static inline int ray_is_atom_local(ray_t* x) { return x && !RAY_IS_ERR(x) && x->type < 0; }
static int arg_is_null(const ray_t* arg) {
return !arg || RAY_IS_NULL(arg);
}
static int64_t arg_as_i64(ray_t* arg, int* err) {
*err = 0;
if (arg_is_null(arg)) { *err = 1; return 0; }
if (arg->type == -RAY_I64) return arg->i64;
if (arg->type == -RAY_I32) return (int64_t)arg->i32;
if (arg->type == -RAY_I16) return (int64_t)arg->i16;
if (arg->type == -RAY_U8) return (int64_t)arg->u8;
if (arg->type == -RAY_BOOL) return (int64_t)arg->b8;
if (arg->type == -RAY_STR) {
const char* p = ray_str_ptr(arg);
size_t len = ray_str_len(arg);
size_t i = 0;
while (i < len && (p[i] == ' ' || p[i] == '\t')) i++;
int sign = 1;
if (i < len && (p[i] == '+' || p[i] == '-')) { if (p[i] == '-') sign = -1; i++; }
if (i >= len || p[i] < '0' || p[i] > '9') { *err = 1; return 0; }
int64_t v = 0;
while (i < len && p[i] >= '0' && p[i] <= '9') { v = v * 10 + (p[i] - '0'); i++; }
return sign * v;
}
*err = 1;
return 0;
}
static ray_t* h_timeit(ray_t* arg, ray_syscmd_ctx_t* ctx) {
bool active;
if (arg_is_null(arg)) {
active = !g_ray_profile.active;
} else {
int err = 0;
int64_t v = arg_as_i64(arg, &err);
if (err) return ray_error("type", ":t expects an integer (0 = off, 1 = on)");
active = (v != 0);
}
g_ray_profile.active = active;
if (ctx && ctx->repl) {
if (ctx->color) fprintf(stdout, "\033[1;33m");
fprintf(stdout, ". Timeit is %s.", active ? "on" : "off");
if (ctx->color) fprintf(stdout, "\033[0m");
fprintf(stdout, "\n");
return NULL;
}
return ray_i64(active ? 1 : 0);
}
static ray_t* h_listen(ray_t* arg, ray_syscmd_ctx_t* ctx) {
(void)ctx;
int err = 0;
int64_t port = arg_as_i64(arg, &err);
if (err) return ray_error("type", "listen expects a port number");
if (port <= 0 || port > 65535) return ray_error("domain", "listen: port out of range (1..65535)");
ray_poll_t* poll = (ray_poll_t*)ray_runtime_get_poll();
if (!poll) return ray_error("nyi", "listen: no main event loop attached");
int64_t id = ray_ipc_listen(poll, (uint16_t)port);
if (id < 0) {
int e = errno;
return ray_error("io", "listen: bind to port %lld failed: %s",
(long long)port, strerror(e ? e : EADDRINUSE));
}
return ray_i64(id);
}
static const char* type_label_short(ray_t* v) {
if (!v) return "null";
switch (v->type) {
case RAY_LAMBDA: return "lambda";
case RAY_UNARY:
case RAY_BINARY:
case RAY_VARY: return "fn";
case RAY_TABLE: return "table";
case RAY_DICT: return "dict";
case RAY_LIST: return "list";
default:
if (v->type < 0) return "atom";
if (v->type > 0) return "vec";
return "?";
}
}
static ray_t* h_env(ray_t* arg, ray_syscmd_ctx_t* ctx) {
(void)arg;
int64_t sym_ids[512];
ray_t* vals[512];
int32_t n = ray_env_list(sym_ids, vals, 512);
if (ctx && ctx->repl) {
for (int32_t i = 0; i < n; i++) {
ray_t* s = ray_sym_str(sym_ids[i]);
const char* name = s ? ray_str_ptr(s) : "?";
fprintf(stdout, " %-20s %s\n", name, type_label_short(vals[i]));
}
fprintf(stdout, "(%d entries)\n", n);
return NULL;
}
return ray_i64(n);
}
static ray_t* h_clear(ray_t* arg, ray_syscmd_ctx_t* ctx) {
(void)arg;
if (ctx && ctx->repl && ctx->color) {
fprintf(stdout, "\033[2J\033[H");
fflush(stdout);
}
return NULL;
}
static ray_t* h_help(ray_t* arg, ray_syscmd_ctx_t* ctx) {
(void)arg;
if (!ctx || !ctx->repl) return RAY_NULL_OBJ;
bool color = ctx->color;
if (color) fprintf(stdout, "\033[1;33m");
fprintf(stdout, ". Commands list:");
if (color) fprintf(stdout, "\033[0m");
fprintf(stdout, "\n");
if (color) fprintf(stdout, "\033[90m");
size_t n = 0;
const ray_syscmd_t* tbl = ray_syscmd_table(&n);
for (size_t i = 0; i < n; i++) {
char tag[32];
if (tbl[i].alias) snprintf(tag, sizeof(tag), ":%s/:%s", tbl[i].name, tbl[i].alias);
else snprintf(tag, sizeof(tag), ":%s", tbl[i].name);
fprintf(stdout, " %-12s - %s\n", tag, tbl[i].help ? tbl[i].help : "");
}
if (color) fprintf(stdout, "\033[0m");
fprintf(stdout, "\n");
return NULL;
}
static ray_t* h_quit(ray_t* arg, ray_syscmd_ctx_t* ctx) {
(void)arg; (void)ctx;
exit(0);
return NULL;
}
static const ray_syscmd_t TABLE[] = {
{ "help", "?", h_help, RAY_SYSCMD_REPL_ONLY, "Display this help." },
{ "timeit", "t", h_timeit, 0, "Toggle profiling on/off (or :t 0|1)." },
{ "env", NULL, h_env, 0, "List defined globals." },
{ "clear", NULL, h_clear, RAY_SYSCMD_REPL_ONLY, "Clear the screen." },
{ "listen", NULL, h_listen, RAY_SYSCMD_RESTRICTED, "Start IPC listener on PORT." },
{ "q", NULL, h_quit, RAY_SYSCMD_REPL_ONLY, "Exit the REPL." },
{ "quit", NULL, h_quit, RAY_SYSCMD_REPL_ONLY, "Exit the REPL." },
};
static const size_t TABLE_LEN = sizeof(TABLE) / sizeof(TABLE[0]);
const ray_syscmd_t* ray_syscmd_lookup(const char* name, size_t name_len) {
if (!name || name_len == 0) return NULL;
for (size_t i = 0; i < TABLE_LEN; i++) {
const ray_syscmd_t* e = &TABLE[i];
if (e->name && strlen(e->name) == name_len && memcmp(e->name, name, name_len) == 0)
return e;
if (e->alias && strlen(e->alias) == name_len && memcmp(e->alias, name, name_len) == 0)
return e;
}
return NULL;
}
const ray_syscmd_t* ray_syscmd_table(size_t* out_count) {
if (out_count) *out_count = TABLE_LEN;
return TABLE;
}
ray_t* ray_syscmd_dispatch(const char* str, size_t len,
ray_syscmd_ctx_t* ctx, bool allow_shell) {
size_t i = 0;
while (i < len && (str[i] == ' ' || str[i] == '\t')) i++;
if (i >= len) return ray_error("domain", "empty command");
size_t name_start = i;
while (i < len && str[i] != ' ' && str[i] != '\t') i++;
size_t name_len = i - name_start;
while (i < len && (str[i] == ' ' || str[i] == '\t')) i++;
const char* args_p = str + i;
size_t args_len = len - i;
const ray_syscmd_t* e = ray_syscmd_lookup(str + name_start, name_len);
if (!e) {
if (!allow_shell)
return ray_error("domain", "unknown command");
char stackbuf[1024];
char* cmd;
ray_t* cmd_block = NULL;
if (len + 1 <= sizeof(stackbuf)) {
cmd = stackbuf;
} else {
cmd_block = ray_alloc(len + 1);
if (!cmd_block) return ray_error("oom", NULL);
cmd = (char*)ray_data(cmd_block);
}
memcpy(cmd, str, len);
cmd[len] = '\0';
int rc = system(cmd);
if (cmd_block) ray_free(cmd_block);
return ray_i64(rc);
}
if (!e->fn) return ray_error("nyi", "command has no handler");
if ((e->flags & RAY_SYSCMD_REPL_ONLY) && (!ctx || !ctx->repl))
return ray_error("domain", "command is REPL-only");
ray_t* arg = (args_len > 0) ? ray_str(args_p, args_len) : RAY_NULL_OBJ;
ray_t* result = e->fn(arg, ctx);
if (arg && arg != RAY_NULL_OBJ) ray_release(arg);
return result ? result : RAY_NULL_OBJ;
}
ray_t* ray_syscmd_string_dispatch_fn(ray_t* x) {
if (!ray_is_atom_local(x) || x->type != -RAY_STR)
return ray_error("type", ".sys.cmd expects a string");
return ray_syscmd_dispatch(ray_str_ptr(x), ray_str_len(x),
NULL, true);
}
static ray_t* invoke_by_name(const char* name, ray_t* arg) {
const ray_syscmd_t* e = ray_syscmd_lookup(name, strlen(name));
if (!e || !e->fn) return ray_error("nyi", NULL);
if (e->flags & RAY_SYSCMD_REPL_ONLY)
return ray_error("domain", "command is REPL-only");
ray_syscmd_ctx_t ctx = { NULL, false };
ray_t* r = e->fn(arg, &ctx);
return r ? r : RAY_NULL_OBJ;
}
ray_t* ray_sys_listen_fn(ray_t* x) { return invoke_by_name("listen", x); }
ray_t* ray_sys_timeit_fn(ray_t** args, int64_t n) {
return invoke_by_name("timeit", n > 0 ? args[0] : RAY_NULL_OBJ);
}
ray_t* ray_sys_env_fn(ray_t** args, int64_t n) {
(void)args;
return invoke_by_name("env", n > 0 ? args[0] : RAY_NULL_OBJ);
}