#include "lang/eval.h"
#include "lang/env.h"
#include "lang/nfo.h"
#include "table/sym.h"
#include "core/block.h"
#include <stdbool.h>
#include <string.h>
typedef struct {
ray_t *code_obj;
uint8_t *code;
int32_t code_len;
int32_t code_cap;
ray_t *consts_obj;
ray_t **consts;
int32_t n_consts;
int32_t consts_cap;
struct { int64_t sym_id; int32_t slot; } locals[256];
int32_t n_locals;
int32_t n_fixed;
bool dynamic_globals;
int32_t max_locals;
bool error;
ray_t *lambda;
ray_t *dbg_obj;
int32_t trap_depth;
int32_t depth;
ray_t *deps;
} compiler_t;
static void compile_expr(compiler_t *c, ray_t *ast);
static bool compile_fn_template(compiler_t *c, ray_t *ast);
static bool compile_lambda(ray_t *lambda, ray_t *capnames, bool dynamic_globals);
static bool compiler_init(compiler_t *c) {
memset(c, 0, sizeof(*c));
c->code_cap = 256;
c->code_obj = ray_alloc(c->code_cap);
if (!c->code_obj) return false;
c->code_obj->type = RAY_U8;
c->code_obj->len = 0;
c->code = (uint8_t *)ray_data(c->code_obj);
c->consts_cap = 16;
c->consts_obj = ray_alloc(c->consts_cap * sizeof(ray_t *));
if (!c->consts_obj) { ray_release(c->code_obj); return false; }
c->consts_obj->type = RAY_LIST;
c->consts_obj->len = 0;
c->consts = (ray_t **)ray_data(c->consts_obj);
memset(c->consts, 0, c->consts_cap * sizeof(ray_t *));
return true;
}
static void compiler_destroy(compiler_t *c) {
for (int32_t i = 0; i < c->n_consts; i++)
if (c->consts[i]) ray_release(c->consts[i]);
ray_release(c->consts_obj);
ray_release(c->code_obj);
if (c->deps) { ray_release(c->deps); c->deps = NULL; }
}
static void dbg_append(compiler_t* c, int32_t offset, int64_t span_id) {
if (!c->dbg_obj) {
c->dbg_obj = ray_vec_new(RAY_I64, 0);
if (!c->dbg_obj) return;
}
int64_t off64 = (int64_t)offset;
c->dbg_obj = ray_vec_append(c->dbg_obj, &off64);
c->dbg_obj = ray_vec_append(c->dbg_obj, &span_id);
}
#define EMIT_DBG(c, ast) do { \
if ((c)->lambda && LAMBDA_NFO((c)->lambda)) { \
ray_span_t _sp = ray_nfo_get(LAMBDA_NFO((c)->lambda), (ast)); \
if (_sp.id != 0) dbg_append(c, (c)->code_len, _sp.id); \
} \
} while(0)
static void emit(compiler_t *c, uint8_t byte) {
if (c->code_len >= c->code_cap) {
int32_t new_cap = c->code_cap * 2;
ray_t *new_obj = ray_alloc(new_cap);
if (!new_obj) { c->error = true; return; }
new_obj->type = RAY_U8;
new_obj->len = 0;
memcpy(ray_data(new_obj), c->code, c->code_len);
ray_release(c->code_obj);
c->code_obj = new_obj;
c->code = (uint8_t *)ray_data(new_obj);
c->code_cap = new_cap;
}
c->code[c->code_len++] = byte;
}
static void emit_const(compiler_t *c, int32_t idx) {
if (idx < 256) {
emit(c, OP_LOADCONST);
emit(c, (uint8_t)idx);
} else {
emit(c, OP_LOADCONST_W);
emit(c, (uint8_t)(idx >> 8));
emit(c, (uint8_t)(idx & 0xFF));
}
}
static int32_t add_constant(compiler_t *c, ray_t *value) {
for (int32_t i = 0; i < c->n_consts; i++) {
ray_t *v = c->consts[i];
if (v == value) return i;
if (v->type == value->type && ray_is_atom(v)) {
if (v->type == -RAY_I64 && v->i64 == value->i64) return i;
if (v->type == -RAY_F64 && v->f64 == value->f64) return i;
if (v->type == -RAY_BOOL && v->b8 == value->b8) return i;
if (v->type == -RAY_SYM && v->i64 == value->i64 &&
v->attrs == value->attrs) return i;
}
}
if (c->n_consts >= c->consts_cap) {
int32_t new_cap = c->consts_cap * 2;
ray_t *new_obj = ray_alloc(new_cap * sizeof(ray_t *));
if (!new_obj || RAY_IS_ERR(new_obj)) { c->error = true; return c->n_consts; }
new_obj->type = RAY_LIST;
new_obj->len = 0;
ray_t **new_arr = (ray_t **)ray_data(new_obj);
memcpy(new_arr, c->consts, c->n_consts * sizeof(ray_t *));
memset(new_arr + c->n_consts, 0, (new_cap - c->n_consts) * sizeof(ray_t *));
ray_release(c->consts_obj);
c->consts_obj = new_obj;
c->consts = new_arr;
c->consts_cap = new_cap;
}
ray_retain(value);
c->consts[c->n_consts] = value;
return c->n_consts++;
}
static int32_t find_local(compiler_t *c, int64_t sym_id) {
for (int32_t i = c->n_locals - 1; i >= 0; i--)
if (c->locals[i].sym_id == sym_id) return c->locals[i].slot;
return -1;
}
static int32_t add_local(compiler_t *c, int64_t sym_id) {
if (c->n_locals >= 256) return -1;
int32_t slot = c->n_locals;
c->locals[c->n_locals].sym_id = sym_id;
c->locals[c->n_locals].slot = slot;
c->n_locals++;
if (c->n_locals > c->max_locals) c->max_locals = c->n_locals;
return slot;
}
static int32_t emit_jump(compiler_t *c, uint8_t opcode) {
emit(c, opcode);
int32_t patch_pos = c->code_len;
emit(c, 0);
emit(c, 0);
return patch_pos;
}
static void write_jump_offset(compiler_t *c, int32_t pos, int32_t target) {
int32_t raw = target - pos - 2;
if (raw > 32767 || raw < -32768) { c->error = true; return; }
int16_t offset = (int16_t)raw;
c->code[pos] = (uint8_t)((uint16_t)offset >> 8);
c->code[pos + 1] = (uint8_t)(offset & 0xFF);
}
static void patch_jump(compiler_t *c, int32_t pos) {
write_jump_offset(c, pos, c->code_len);
}
static void emit_jump_back(compiler_t *c, int32_t target) {
emit(c, OP_JMP);
int32_t pos = c->code_len;
emit(c, 0);
emit(c, 0);
write_jump_offset(c, pos, target);
}
static _Thread_local int64_t sf_set = -1, sf_let = -1, sf_if = -1, sf_do = -1, sf_while = -1, sf_times = -1, sf_fn = -1, sf_self = -1, sf_try = -1, sf_return = -1, sf_null = -1;
static _Thread_local int64_t sf_eval = -1, sf_resolve = -1;
static void init_sf_syms(void) {
if (sf_set >= 0) return;
sf_set = ray_sym_intern("set", 3);
sf_let = ray_sym_intern("let", 3);
sf_if = ray_sym_intern("if", 2);
sf_do = ray_sym_intern("do", 2);
sf_while= ray_sym_intern("while", 5);
sf_times= ray_sym_intern("times", 5);
sf_fn = ray_sym_intern("fn", 2);
sf_self = ray_sym_intern("self", 4);
sf_try = ray_sym_intern("try", 3);
sf_return = ray_sym_intern("return", 6);
sf_null = ray_sym_intern("null", 4);
sf_eval = ray_sym_intern("eval", 4);
sf_resolve = ray_sym_intern("resolve", 7);
}
static bool ast_refs_locals(compiler_t *c, ray_t *ast);
static bool sym_is_local_ref(compiler_t *c, int64_t id) {
if (id == sf_self || id == sf_eval || id == sf_resolve) return true;
for (int32_t i = c->n_locals - 1; i >= 0; i--)
if (c->locals[i].sym_id == id) return true;
return false;
}
static bool ast_refs_locals(compiler_t *c, ray_t *ast) {
if (!ast) return false;
if (ast->type == -RAY_SYM)
return sym_is_local_ref(c, ast->i64);
if (ast->type == RAY_SYM) {
const int64_t *ids = (const int64_t*)ray_data(ast);
for (int64_t i = 0; i < ast->len; i++)
if (sym_is_local_ref(c, ids[i])) return true;
return false;
}
if (ast->type == RAY_LIST) {
ray_t **elems = (ray_t**)ray_data(ast);
for (int64_t i = 0; i < ast->len; i++)
if (ast_refs_locals(c, elems[i])) return true;
return false;
}
if (ast->type == RAY_DICT) {
ray_t **slots = (ray_t**)ray_data(ast);
return ast_refs_locals(c, slots[0]) || ast_refs_locals(c, slots[1]);
}
return false;
}
static void compile_list(compiler_t *c, ray_t *ast) {
if (c->error) return;
EMIT_DBG(c, ast);
int64_t n = ray_len(ast);
if (n == 0) { c->error = true; return; }
ray_t **elems = (ray_t **)ray_data(ast);
ray_t *head = elems[0];
init_sf_syms();
bool head_local = head->type == -RAY_SYM && !(head->attrs & ATTR_QUOTED) &&
find_local(c, head->i64) >= 0;
if (head->type == -RAY_SYM && !(head->attrs & ATTR_QUOTED) && !head_local) {
int64_t sym_id = head->i64;
if (sym_id == sf_set && n == 3) {
ray_t *name_obj = elems[1];
if (name_obj->type != -RAY_SYM) { c->error = true; return; }
compile_expr(c, elems[2]);
emit(c, OP_FORCE);
int32_t idx = add_constant(c, name_obj);
if (idx < 256) {
emit(c, OP_STOREGLOBAL);
emit(c, (uint8_t)idx);
} else {
emit(c, OP_STOREGLOBAL_W);
emit(c, (uint8_t)(idx >> 8));
emit(c, (uint8_t)(idx & 0xFF));
}
return;
}
if (sym_id == sf_let && n == 3) {
ray_t *name_obj = elems[1];
if (name_obj->type != -RAY_SYM ||
(ray_sym_is_reserved(name_obj->i64) &&
!ray_sym_is_ipc_hook(name_obj->i64))) {
c->error = true;
return;
}
compile_expr(c, elems[2]);
emit(c, OP_FORCE);
emit(c, OP_DUP);
int32_t slot = find_local(c, name_obj->i64);
if (slot < 0) slot = add_local(c, name_obj->i64);
if (slot < 0) { c->error = true; return; }
emit(c, OP_STOREENV);
emit(c, (uint8_t)slot);
return;
}
if (sym_id == sf_if && n >= 3) {
compile_expr(c, elems[1]);
emit(c, OP_FORCE);
int32_t jmpf_pos = emit_jump(c, OP_JMPF);
compile_expr(c, elems[2]);
if (n >= 4) {
int32_t jmp_pos = emit_jump(c, OP_JMP);
patch_jump(c, jmpf_pos);
compile_expr(c, elems[3]);
patch_jump(c, jmp_pos);
} else {
int32_t jmp_pos = emit_jump(c, OP_JMP);
patch_jump(c, jmpf_pos);
ray_t *zero = ray_alloc(0);
zero->type = -RAY_I64;
zero->i64 = 0;
int32_t idx = add_constant(c, zero);
ray_release(zero);
emit_const(c, idx);
patch_jump(c, jmp_pos);
}
return;
}
if (sym_id == sf_do && n >= 2) {
for (int64_t i = 1; i < n; i++) {
if (i > 1) emit(c, OP_POP);
compile_expr(c, elems[i]);
}
return;
}
if (sym_id == sf_while && n >= 2) {
int32_t top = c->code_len;
compile_expr(c, elems[1]);
emit(c, OP_FORCE);
int32_t jmpf_pos = emit_jump(c, OP_JMPF);
for (int64_t i = 2; i < n; i++) {
compile_expr(c, elems[i]);
emit(c, OP_POP);
}
emit_jump_back(c, top);
patch_jump(c, jmpf_pos);
int32_t idx = add_constant(c, RAY_NULL_OBJ);
emit_const(c, idx);
return;
}
if (sym_id == sf_times && n >= 2) {
ray_t *norm_fn = ray_fn_unary("times norm", RAY_FN_NONE, ray_times_norm_fn);
ray_t *dec_fn = ray_fn_unary("times dec", RAY_FN_NONE, ray_times_dec_fn);
if (!norm_fn || RAY_IS_ERR(norm_fn) || !dec_fn || RAY_IS_ERR(dec_fn)) {
if (norm_fn && !RAY_IS_ERR(norm_fn)) ray_release(norm_fn);
if (dec_fn && !RAY_IS_ERR(dec_fn)) ray_release(dec_fn);
c->error = true;
return;
}
int32_t norm_idx = add_constant(c, norm_fn);
int32_t dec_idx = add_constant(c, dec_fn);
ray_release(norm_fn);
ray_release(dec_fn);
int32_t cslot = add_local(c, ray_sym_intern("times ctr", 9));
if (cslot < 0 || c->error) { c->error = true; return; }
emit_const(c, norm_idx);
compile_expr(c, elems[1]);
emit(c, OP_CALL1);
emit(c, OP_STOREENV);
emit(c, (uint8_t)cslot);
int32_t top = c->code_len;
emit(c, OP_LOADENV);
emit(c, (uint8_t)cslot);
int32_t jmpf_pos = emit_jump(c, OP_JMPF);
for (int64_t i = 2; i < n; i++) {
compile_expr(c, elems[i]);
emit(c, OP_POP);
}
emit_const(c, dec_idx);
emit(c, OP_LOADENV);
emit(c, (uint8_t)cslot);
emit(c, OP_CALL1);
emit(c, OP_STOREENV);
emit(c, (uint8_t)cslot);
emit_jump_back(c, top);
patch_jump(c, jmpf_pos);
int32_t null_idx = add_constant(c, RAY_NULL_OBJ);
emit_const(c, null_idx);
return;
}
if (sym_id == sf_fn && n >= 3) {
if (compile_fn_template(c, ast)) return;
if (c->error) return;
bool refs = ast_refs_locals(c, ast);
int32_t nsyms = refs ? c->n_locals : 0;
ray_t *syms = ray_alloc((size_t)(nsyms > 0 ? nsyms : 1) * sizeof(int64_t));
if (!syms || RAY_IS_ERR(syms)) { c->error = true; return; }
syms->type = RAY_I64;
syms->len = nsyms;
int64_t *ids = (int64_t*)ray_data(syms);
for (int32_t i = 0; i < nsyms; i++)
ids[i] = c->locals[i].sym_id;
int32_t syms_idx = add_constant(c, syms);
ray_release(syms);
if (c->error) return;
emit_const(c, syms_idx);
emit(c, OP_SCOPE_EVAL);
int32_t idx = add_constant(c, ast);
emit_const(c, idx);
emit(c, OP_CALLD);
emit(c, 0);
emit_const(c, syms_idx);
emit(c, OP_SCOPE_END);
return;
}
if (sym_id == sf_try && n == 3) {
int32_t err_slot = add_local(c, -1);
if (err_slot < 0) { c->error = true; return; }
int32_t trap_pos = emit_jump(c, OP_TRAP);
c->trap_depth++;
compile_expr(c, elems[1]);
c->trap_depth--;
emit(c, OP_TRAP_END);
int32_t jmp_pos = emit_jump(c, OP_JMP);
patch_jump(c, trap_pos);
emit(c, OP_STOREENV);
emit(c, (uint8_t)err_slot);
compile_expr(c, elems[2]);
emit(c, OP_LOADENV);
emit(c, (uint8_t)err_slot);
emit(c, OP_TRYH);
patch_jump(c, jmp_pos);
return;
}
if (sym_id == sf_return && (n == 1 || n == 2)) {
if (n == 2) {
compile_expr(c, elems[1]);
} else {
int32_t idx = add_constant(c, RAY_NULL_OBJ);
emit_const(c, idx);
}
for (int32_t i = 0; i < c->trap_depth; i++)
emit(c, OP_TRAP_END);
emit(c, OP_RET);
return;
}
}
if (head->type == -RAY_SYM && !(head->attrs & ATTR_QUOTED) && !head_local &&
head->i64 == sf_self) {
int64_t argc = n - 1;
if (argc > 64) { c->error = true; return; }
for (int64_t i = 1; i < n; i++)
compile_expr(c, elems[i]);
emit(c, OP_CALLS);
emit(c, (uint8_t)argc);
return;
}
ray_t *fn = NULL;
if (head->type == -RAY_SYM && !(head->attrs & ATTR_QUOTED) && !head_local) {
fn = c->dynamic_globals ? ray_env_get_global(head->i64) : ray_env_get_compiled(head->i64);
if (c->deps && !c->dynamic_globals) {
bool builtin = fn && (fn->type == RAY_UNARY || fn->type == RAY_BINARY || fn->type == RAY_VARY);
int64_t pair[2] = { head->i64, builtin ? (int64_t)(uintptr_t)fn : 0 };
for (int k = 0; k < 2 && !c->error; k++) {
c->deps = ray_vec_append(c->deps, &pair[k]);
if (!c->deps || RAY_IS_ERR(c->deps)) { c->deps = NULL; c->error = true; }
}
if (c->error) return;
}
}
if (fn && (fn->attrs & RAY_FN_SPECIAL_FORM)) {
bool refs = ast_refs_locals(c, ast);
int32_t nsyms = refs ? c->n_locals : 0;
ray_t *syms = ray_alloc((size_t)(nsyms > 0 ? nsyms : 1) * sizeof(int64_t));
if (!syms || RAY_IS_ERR(syms)) { c->error = true; return; }
syms->type = RAY_I64;
syms->len = nsyms;
int64_t *ids = (int64_t*)ray_data(syms);
for (int32_t i = 0; i < nsyms; i++)
ids[i] = c->locals[i].sym_id;
int32_t syms_idx = add_constant(c, syms);
ray_release(syms);
if (c->error) return;
emit_const(c, syms_idx);
emit(c, OP_SCOPE_EVAL);
int32_t idx = add_constant(c, ast);
emit_const(c, idx);
emit(c, OP_CALLD);
emit(c, 0);
emit_const(c, syms_idx);
emit(c, OP_SCOPE_END);
return;
}
int32_t dyn_syms_idx = -1;
if (head->type == -RAY_SYM && !(head->attrs & ATTR_QUOTED) && !head_local &&
(head->i64 == sf_eval || head->i64 == sf_resolve)) {
ray_t *syms = ray_alloc((size_t)(c->n_locals > 0 ? c->n_locals : 1) * sizeof(int64_t));
if (!syms || RAY_IS_ERR(syms)) { c->error = true; return; }
syms->type = RAY_I64;
syms->len = c->n_locals;
int64_t *ids = (int64_t*)ray_data(syms);
for (int32_t i = 0; i < c->n_locals; i++) ids[i] = c->locals[i].sym_id;
dyn_syms_idx = add_constant(c, syms);
ray_release(syms);
if (c->error) return;
}
bool baked = fn && !c->dynamic_globals &&
(fn->type == RAY_UNARY || fn->type == RAY_BINARY || fn->type == RAY_VARY);
if (baked) {
int32_t idx = add_constant(c, fn);
emit_const(c, idx);
} else {
compile_expr(c, head);
}
int64_t argc = n - 1;
if (argc > 64) { c->error = true; return; }
for (int64_t i = 1; i < n; i++)
compile_expr(c, elems[i]);
EMIT_DBG(c, ast);
if (dyn_syms_idx >= 0) {
emit_const(c, dyn_syms_idx);
emit(c, OP_SCOPE_EVAL);
}
bool emitted = false;
if (fn && !baked && c->dynamic_globals) {
if (fn->type == RAY_UNARY && argc == 1) { emit(c, OP_CALL1R); emit(c, 1); emitted = true; }
else if (fn->type == RAY_BINARY && argc == 2) { emit(c, OP_CALL2R); emit(c, 2); emitted = true; }
else if (fn->type == RAY_VARY) { emit(c, OP_CALLNR); emit(c, (uint8_t)argc); emitted = true; }
}
if (!emitted && fn && (baked || fn->type == RAY_LAMBDA)) {
switch (fn->type) {
case RAY_UNARY:
if (argc == 1) { emit(c, OP_CALL1); emitted = true; }
break;
case RAY_BINARY:
if (argc == 2) { emit(c, OP_CALL2); emitted = true; }
break;
case RAY_VARY:
emit(c, OP_CALLN);
emit(c, (uint8_t)argc);
emitted = true;
break;
case RAY_LAMBDA:
emit(c, OP_CALLF);
emit(c, (uint8_t)argc);
emitted = true;
break;
default:
break;
}
}
if (!emitted) {
emit(c, OP_CALLF);
emit(c, (uint8_t)argc);
}
if (dyn_syms_idx >= 0) {
emit_const(c, dyn_syms_idx);
emit(c, OP_SCOPE_END);
}
}
static void compile_expr_at(compiler_t *c, ray_t *ast);
static void compile_expr(compiler_t *c, ray_t *ast) {
if (c->error) return;
if (++c->depth > 2048) { c->error = true; c->depth--; return; }
compile_expr_at(c, ast);
c->depth--;
}
static void compile_expr_at(compiler_t *c, ray_t *ast) {
if (c->error) return;
if (!ast || RAY_IS_ERR(ast)) return;
EMIT_DBG(c, ast);
if (ray_is_atom(ast)) {
if (ast->type == -RAY_SYM && !(ast->attrs & ATTR_QUOTED)) {
init_sf_syms();
if (ast->i64 == sf_null) {
emit_const(c, add_constant(c, RAY_NULL_OBJ));
return;
}
int32_t slot = find_local(c, ast->i64);
if (slot >= 0 && slot < c->n_fixed) {
emit(c, OP_LOADENV);
emit(c, (uint8_t)slot);
} else if (slot >= 0) {
int32_t idx = add_constant(c, ast);
emit(c, OP_LOADENVG);
emit(c, (uint8_t)slot);
emit(c, (uint8_t)(idx >> 8));
emit(c, (uint8_t)(idx & 0xFF));
} else if (ast->i64 == sf_self) {
emit(c, OP_LOADSELF);
} else {
int32_t idx = add_constant(c, ast);
if (idx < 256) {
emit(c, OP_RESOLVE);
emit(c, (uint8_t)idx);
} else {
emit(c, OP_RESOLVE_W);
emit(c, (uint8_t)(idx >> 8));
emit(c, (uint8_t)(idx & 0xFF));
}
}
return;
}
int32_t idx = add_constant(c, ast);
emit_const(c, idx);
return;
}
if (ast->type != RAY_LIST) {
int32_t idx = add_constant(c, ast);
emit_const(c, idx);
return;
}
if (ray_len(ast) == 0) {
int32_t idx = add_constant(c, ast);
emit_const(c, idx);
return;
}
compile_list(c, ast);
}
static bool closure_shape_ok(ray_t *closure) {
if (!closure) return true;
if (closure->type != RAY_DICT) return false;
ray_t *keys = ray_dict_keys(closure);
ray_t *vals = ray_dict_vals(closure);
return keys && vals && keys->type == RAY_SYM && vals->type == RAY_LIST && keys->len == vals->len;
}
static bool ast_collect_syms_at(ray_t *ast, ray_t **out, bool *dynamic, int depth, int64_t *weight,
bool stop, int64_t *nodes);
enum { AST_NAME_NODES = 1 << 16 };
static bool ast_collect_syms(ray_t *ast, ray_t **out, bool *dynamic) {
int64_t weight = 0, nodes = AST_NAME_NODES;
return ast_collect_syms_at(ast, out, dynamic, 0, &weight, false, &nodes);
}
static int64_t ast_node_bytes(ray_t *v, int64_t cap);
static int64_t ast_value_bytes(ray_t *v, int64_t cap) {
if (!v || RAY_IS_ERR(v)) return 0;
if (cap < 0) return 1;
int64_t b;
if (v->type == RAY_LAMBDA) {
b = (int64_t)(sizeof(ray_t) + LAMBDA_SLOTS * sizeof(ray_t*));
ray_t *parts[4] = { LAMBDA_PARAMS(v), LAMBDA_BODY(v), LAMBDA_CLOSURE(v), LAMBDA_CAPROWS(v) };
for (int i = 0; i < 4 && b <= cap; i++) b += ast_value_bytes(parts[i], cap - b);
return b;
}
if (v->type == RAY_TABLE || v->type == RAY_DICT) {
ray_t **slots = (ray_t**)ray_data(v);
b = (int64_t)ray_block_size(v);
for (int i = 0; i < 2 && b <= cap; i++) b += ast_value_bytes(slots[i], cap - b);
return b;
}
if (v->type == RAY_LIST && !(v->attrs & RAY_ATTR_SLICE)) {
ray_t **elems = (ray_t**)ray_data(v);
b = (int64_t)ray_block_size(v);
for (int64_t i = 0; i < v->len && b <= cap; i++) b += ast_value_bytes(elems[i], cap - b);
return b;
}
return ast_node_bytes(v, cap);
}
static int64_t ast_node_bytes(ray_t *v, int64_t cap) {
if (v->type == RAY_TABLE || v->type == RAY_LAMBDA)
return ast_value_bytes(v, cap);
if (ray_is_atom(v)) {
int64_t b = (int64_t)ray_block_size(v);
if (v->type == -RAY_STR && !((v->slen >= 1 && v->slen <= 7) || (v->slen == 0 && !v->obj)) && v->obj)
b += ast_node_bytes(v->obj, cap);
else if (v->type == -RAY_GUID && v->obj)
b += ast_node_bytes(v->obj, cap);
return b;
}
if ((v->attrs & RAY_ATTR_SLICE) && v->slice_parent) return ast_node_bytes(v->slice_parent, cap);
int64_t b = (int64_t)ray_block_size(v);
if (v->type == RAY_STR && v->str_pool) b += (int64_t)ray_block_size(v->str_pool);
return b;
}
static bool ast_collect_syms_at(ray_t *ast, ray_t **out, bool *dynamic, int depth, int64_t *weight,
bool stop, int64_t *nodes) {
if (!ast || !*out) return false;
if (depth > 1024) return false;
if (nodes && --*nodes < 0) return false;
*weight += ast_node_bytes(ast, RAY_CLOSURE_TMPL_MAX_BYTES - *weight);
if (stop && *weight > RAY_CLOSURE_TMPL_MAX_BYTES) return false;
if (ast->type == -RAY_SYM) {
int64_t id = ast->i64;
if (id == sf_eval || id == sf_resolve) *dynamic = true;
const int64_t *have = (const int64_t*)ray_data(*out);
for (int64_t i = 0; i < (*out)->len; i++) if (have[i] == id) return true;
*out = ray_vec_append(*out, &id);
return *out && !RAY_IS_ERR(*out);
}
if (ast->type == RAY_SYM) {
for (int64_t i = 0; i < ast->len; i++) {
int64_t id = ray_read_sym(ray_data(ast), i, RAY_SYM, ast->attrs);
const int64_t *have = (const int64_t*)ray_data(*out);
bool seen = false;
for (int64_t k = 0; k < (*out)->len; k++) if (have[k] == id) { seen = true; break; }
if (seen) continue;
*out = ray_vec_append(*out, &id);
if (!*out || RAY_IS_ERR(*out)) return false;
}
return true;
}
if (ast->type == RAY_LIST) {
ray_t **elems = (ray_t**)ray_data(ast);
for (int64_t i = 0; i < ast->len; i++)
if (!ast_collect_syms_at(elems[i], out, dynamic, depth + 1, weight, stop, nodes)) return false;
return true;
}
if (ast->type == RAY_DICT) {
ray_t **slots = (ray_t**)ray_data(ast);
return ast_collect_syms_at(slots[0], out, dynamic, depth + 1, weight, stop, nodes) &&
ast_collect_syms_at(slots[1], out, dynamic, depth + 1, weight, stop, nodes);
}
return true;
}
static bool syms_contain(ray_t *syms, int64_t id) {
if (!syms) return false;
const int64_t *ids = (const int64_t*)ray_data(syms);
for (int64_t i = 0; i < syms->len; i++) if (ids[i] == id) return true;
return false;
}
static ray_t *closure_capture_names(ray_t *lambda, bool *failed) {
*failed = false;
ray_t *closure = LAMBDA_CLOSURE(lambda);
if (!closure) return NULL;
ray_t *keys = ray_dict_keys(closure);
if (!keys || keys->len == 0) return NULL;
init_sf_syms();
ray_t *mentioned = ray_vec_new(RAY_I64, 16);
if (!mentioned || RAY_IS_ERR(mentioned)) { *failed = true; return NULL; }
bool dynamic = false;
bool ok = ast_collect_syms(LAMBDA_BODY(lambda), &mentioned, &dynamic);
ray_t *names = ok ? ray_sym_vec_new(RAY_SYM_W64, keys->len) : NULL;
if (!names || RAY_IS_ERR(names)) *failed = true;
if (names && !RAY_IS_ERR(names)) {
int64_t *out = (int64_t*)ray_data(names);
names->len = 0;
for (int64_t i = 0; i < keys->len; i++) {
int64_t id = ray_read_sym(ray_data(keys), i, RAY_SYM, keys->attrs);
if (id == sf_self) continue;
if (dynamic || syms_contain(mentioned, id)) out[names->len++] = id;
}
}
if (mentioned) ray_release(mentioned);
if (names && RAY_IS_ERR(names)) names = NULL;
return names;
}
static bool compile_fn_template_at(compiler_t *c, ray_t *ast);
static _Thread_local int g_template_depth = 0;
static bool compile_fn_template(compiler_t *c, ray_t *ast) {
if (g_template_depth >= 32) return false;
g_template_depth++;
bool ok = compile_fn_template_at(c, ast);
g_template_depth--;
return ok;
}
static bool compile_fn_template_at(compiler_t *c, ray_t *ast) {
ray_t **elems = (ray_t**)ray_data(ast);
int64_t n = ray_len(ast);
ray_t *params = elems[1];
if (!params) return false;
bool own_params = false;
if (params->len == 0 && params->type != RAY_SYM && params->type > 0) {
ray_t *pv = ray_sym_vec_new(RAY_SYM_W64, 1);
if (!pv || RAY_IS_ERR(pv)) return false;
pv->len = 0;
params = pv;
own_params = true;
} else if (params->type == RAY_LIST) {
ray_t **pe = (ray_t**)ray_data(params);
for (int64_t i = 0; i < params->len; i++)
if (!pe[i] || pe[i]->type != -RAY_SYM) return false;
ray_t *pv = ray_sym_vec_new(RAY_SYM_W64, params->len > 0 ? params->len : 1);
if (!pv || RAY_IS_ERR(pv)) return false;
for (int64_t i = 0; i < params->len; i++) ((int64_t*)ray_data(pv))[i] = pe[i]->i64;
pv->len = params->len;
params = pv;
own_params = true;
} else if (params->type != RAY_SYM) {
return false;
}
for (int64_t i = 0; i < params->len; i++)
if (ray_sym_is_reserved(ray_read_sym(ray_data(params), i, RAY_SYM, params->attrs))) {
if (own_params) ray_release(params);
return false;
}
ray_t *mentioned = ray_vec_new(RAY_I64, 16);
if (!mentioned || RAY_IS_ERR(mentioned)) { if (own_params) ray_release(params); return false; }
bool dynamic = false, ok = true;
int64_t tweight = 0, tnodes = AST_NAME_NODES;
for (int64_t i = 2; i < n && ok; i++)
ok = ast_collect_syms_at(elems[i], &mentioned, &dynamic, 0, &tweight, false, &tnodes);
if (!ok) { ray_release(mentioned); if (own_params) ray_release(params); return false; }
ray_t *names = ray_sym_vec_new(RAY_SYM_W64, c->n_locals > 0 ? c->n_locals : 1);
ray_t *slots = ray_vec_new(RAY_I64, c->n_locals > 0 ? c->n_locals : 1);
if (!names || RAY_IS_ERR(names) || !slots || RAY_IS_ERR(slots)) {
ray_release(mentioned);
if (names && !RAY_IS_ERR(names)) ray_release(names);
if (slots && !RAY_IS_ERR(slots)) ray_release(slots);
if (own_params) ray_release(params);
return false;
}
names->len = 0; slots->len = 0;
for (int32_t i = 0; i < c->n_locals; i++) {
int64_t id = c->locals[i].sym_id;
if (id < 0 || id == sf_self) continue;
if (!dynamic && !syms_contain(mentioned, id)) continue;
bool dup = false;
for (int64_t k = 0; k < names->len; k++)
if (((int64_t*)ray_data(names))[k] == id) { ((int64_t*)ray_data(slots))[k] = c->locals[i].slot; dup = true; break; }
if (dup) continue;
((int64_t*)ray_data(names))[names->len++] = id;
((int64_t*)ray_data(slots))[slots->len++] = c->locals[i].slot;
}
ray_release(mentioned);
ray_t *body = ray_alloc((size_t)(n - 2) * sizeof(ray_t*));
ray_t *tmpl = ray_alloc(LAMBDA_SLOTS * sizeof(ray_t*));
if (!body || RAY_IS_ERR(body) || !tmpl || RAY_IS_ERR(tmpl)) {
ray_release(names); ray_release(slots);
if (own_params) ray_release(params);
if (body && !RAY_IS_ERR(body)) { body->type = RAY_LIST; body->len = 0; ray_release(body); }
if (tmpl && !RAY_IS_ERR(tmpl)) { tmpl->type = RAY_LAMBDA; memset(ray_data(tmpl), 0, LAMBDA_SLOTS * sizeof(ray_t*)); ray_release(tmpl); }
return false;
}
body->type = RAY_LIST;
body->len = n - 2;
for (int64_t i = 2; i < n; i++) { ray_retain(elems[i]); ((ray_t**)ray_data(body))[i - 2] = elems[i]; }
tmpl->type = RAY_LAMBDA;
tmpl->attrs = 0;
tmpl->len = 0;
memset(ray_data(tmpl), 0, LAMBDA_SLOTS * sizeof(ray_t*));
if (!own_params) ray_retain(params);
LAMBDA_PARAMS(tmpl) = params;
LAMBDA_BODY(tmpl) = body;
if (c->lambda && LAMBDA_NFO(c->lambda)) { ray_retain(LAMBDA_NFO(c->lambda)); LAMBDA_NFO(tmpl) = LAMBDA_NFO(c->lambda); }
if (!compile_lambda(tmpl, names->len ? names : NULL, true)) {
ray_release(tmpl); ray_release(names); ray_release(slots);
return false;
}
int32_t tidx = add_constant(c, tmpl);
int32_t nidx = add_constant(c, names);
int32_t sidx = add_constant(c, slots);
ray_release(tmpl); ray_release(names); ray_release(slots);
if (c->error) return false;
emit_const(c, tidx);
emit_const(c, nidx);
emit_const(c, sidx);
emit(c, OP_MKCLOSURE);
return true;
}
ray_t *ray_closure_capture_names(ray_t *lambda) { bool failed; return closure_capture_names(lambda, &failed); }
ray_t *ray_closure_mentioned_syms(ray_t *body, bool *dynamic, int64_t *weight) {
init_sf_syms();
ray_t *mentioned = ray_vec_new(RAY_I64, 16);
if (!mentioned || RAY_IS_ERR(mentioned)) return NULL;
*dynamic = false;
*weight = 0;
if (!ast_collect_syms_at(body, &mentioned, dynamic, 0, weight, true, NULL)) { if (mentioned) ray_release(mentioned); return NULL; }
return mentioned;
}
ray_t *ray_closure_names_from(ray_t *closure, ray_t *mentioned, bool dynamic) {
if (!closure) return NULL;
ray_t *keys = ray_dict_keys(closure);
if (!keys || keys->len == 0) return NULL;
init_sf_syms();
ray_t *names = ray_sym_vec_new(RAY_SYM_W64, keys->len);
if (!names || RAY_IS_ERR(names)) return NULL;
int64_t *out = (int64_t*)ray_data(names);
names->len = 0;
for (int64_t i = 0; i < keys->len; i++) {
int64_t id = ray_read_sym(ray_data(keys), i, RAY_SYM, keys->attrs);
if (id == sf_self) continue;
if (dynamic || syms_contain(mentioned, id)) out[names->len++] = id;
}
return names;
}
bool ray_closure_shape_ok(ray_t *closure) { return closure_shape_ok(closure); }
bool ray_compile_with_captures(ray_t *lambda, ray_t *capnames, bool closure_body) { return compile_lambda(lambda, capnames, closure_body); }
void ray_compile(ray_t *lambda) {
if (LAMBDA_IS_COMPILED(lambda)) return;
if (!closure_shape_ok(LAMBDA_CLOSURE(lambda))) return;
bool walk_failed = false;
ray_t *names = closure_capture_names(lambda, &walk_failed);
if (walk_failed) {
names = ray_closure_names_from(LAMBDA_CLOSURE(lambda), NULL, true);
if (!names) { LAMBDA_NLOCALS(lambda) = -1; return; }
}
if (!compile_lambda(lambda, names, LAMBDA_CLOSURE(lambda) != NULL))
LAMBDA_NLOCALS(lambda) = -1;
if (names) ray_release(names);
}
static bool compile_lambda_deps(ray_t *lambda, ray_t *capnames, bool dynamic_globals, ray_t **deps);
static bool compile_lambda(ray_t *lambda, ray_t *capnames, bool dynamic_globals) {
return compile_lambda_deps(lambda, capnames, dynamic_globals, NULL);
}
bool ray_compile_definition(ray_t *lambda, ray_t **deps) {
*deps = NULL;
if (LAMBDA_IS_COMPILED(lambda) || LAMBDA_CLOSURE(lambda)) return false;
if (!compile_lambda_deps(lambda, NULL, false, deps)) {
LAMBDA_NLOCALS(lambda) = -1;
return false;
}
return true;
}
static bool compile_lambda_deps(ray_t *lambda, ray_t *capnames, bool dynamic_globals, ray_t **deps) {
compiler_t c;
if (!compiler_init(&c)) return false;
c.lambda = lambda;
c.dynamic_globals = dynamic_globals;
if (deps) {
c.deps = ray_vec_new(RAY_I64, 16);
if (!c.deps || RAY_IS_ERR(c.deps)) { c.deps = NULL; compiler_destroy(&c); return false; }
}
ray_t *params_list = LAMBDA_PARAMS(lambda);
int64_t param_count = ray_len(params_list);
int64_t *param_ids = (int64_t*)ray_data(params_list);
for (int64_t i = 0; i < param_count; i++) {
if (add_local(&c, param_ids[i]) < 0) { c.error = true; break; }
}
c.n_fixed = c.n_locals;
if (!c.error && capnames && capnames->len > 0) {
ray_t *pairs = ray_alloc((size_t)capnames->len * 2 * sizeof(int64_t));
if (!pairs || RAY_IS_ERR(pairs)) { c.error = true; }
else {
pairs->type = RAY_I64;
pairs->len = 0;
int64_t *pv = (int64_t*)ray_data(pairs);
for (int64_t i = 0; i < capnames->len && !c.error; i++) {
int64_t id = ray_read_sym(ray_data(capnames), i, RAY_SYM, capnames->attrs);
if (find_local(&c, id) >= 0) continue;
int32_t slot = add_local(&c, id);
if (slot < 0) { c.error = true; break; }
pv[pairs->len++] = id;
pv[pairs->len++] = slot;
}
if (!c.error && pairs->len > 0) {
int32_t idx = add_constant(&c, pairs);
if (idx < 0 || idx > 255) c.error = true;
else { emit(&c, OP_BINDCAPS); emit(&c, (uint8_t)idx); }
}
ray_release(pairs);
}
}
ray_t *body = LAMBDA_BODY(lambda);
int64_t body_count = ray_len(body);
ray_t **body_exprs = (ray_t **)ray_data(body);
for (int64_t i = 0; i < body_count; i++) {
if (i > 0) emit(&c, OP_POP);
compile_expr(&c, body_exprs[i]);
}
emit(&c, OP_RET);
if (c.error) {
if (c.dbg_obj) ray_release(c.dbg_obj);
compiler_destroy(&c);
return false;
}
ray_t *bc = ray_alloc(c.code_len);
if (!bc) { compiler_destroy(&c); return false; }
bc->type = RAY_U8;
bc->len = c.code_len;
memcpy(ray_data(bc), c.code, c.code_len);
ray_t *consts = ray_alloc(c.n_consts * sizeof(ray_t *));
if (!consts) { ray_release(bc); compiler_destroy(&c); return false; }
consts->type = RAY_LIST;
consts->len = c.n_consts;
ray_t **cpool = (ray_t **)ray_data(consts);
for (int32_t i = 0; i < c.n_consts; i++) {
ray_retain(c.consts[i]);
cpool[i] = c.consts[i];
}
LAMBDA_BC(lambda) = bc;
LAMBDA_CONSTS(lambda) = consts;
LAMBDA_NLOCALS(lambda) = c.max_locals;
lambda->attrs |= RAY_FN_COMPILED;
if (c.dbg_obj) {
LAMBDA_DBG(lambda) = c.dbg_obj;
}
if (deps) { *deps = c.deps; c.deps = NULL; }
compiler_destroy(&c);
return true;
}
ray_span_t ray_bc_dbg_get(ray_t* dbg, int32_t ip) {
ray_span_t span = {0};
if (!dbg || dbg->len == 0) return span;
int64_t* data = (int64_t*)ray_data(dbg);
int64_t n = dbg->len;
int64_t best_offset = -1;
for (int64_t i = 0; i < n; i += 2) {
int64_t offset = data[i];
if (offset <= ip && offset > best_offset) {
best_offset = offset;
span.id = data[i + 1];
}
}
return span;
}
void ray_compile_reset(void) {
sf_set = sf_let = sf_if = sf_do = sf_fn = sf_self = sf_try = sf_return = sf_null = -1;
sf_eval = sf_resolve = -1;
}