lumbda/c/builtins.c
russell@unturf.com 6e9d3ea52f isqrt: add integer square root builtin to all three impls
Semantics: (isqrt n) → floor(sqrt(n)). Negative argument errors.
Matches Python 3.8+ math.isqrt and R7RS exact-integer-sqrt contract.

- Python: wraps math.isqrt via lambda registration
- C: hand-rolled bit-by-bit algorithm in bi_isqrt (O(log n), no FPU)
- asm: new BI_ISQRT=109, bit-by-bit algorithm in integer registers
       (%r8/%r9/%r10). Negative input → stderr + exit(1) like other
       errors. GC builtin constants bumped to 110-114.

Tests:
- tests/functional.lsp: 7 shared tests (0, 1, perfect squares, floor
  cases, large values). Python + C now 196 each (was 189).
- asm/test.sh: 5 asm-local tests. asm suite now 142 (was 137).

MOAD: all three implementations O(log n), no O(N²) hazards.
2026-04-20 08:50:24 -04:00

1953 lines
71 KiB
C

/*
* builtins.c — All built-in functions for the Scheme interpreter
*/
#include "lumbda.h"
/* ═══════════════════════════════════════════════════════════════════════════
* Helper macros
* ═══════════════════════════════════════════════════════════════════════════ */
#define CHECK_ARITY(name, expected) \
if (n != (expected)) lisp_error(name ": need %d args, got %d", (expected), n)
#define CHECK_MIN_ARITY(name, expected) \
if (n < (expected)) lisp_error(name ": need at least %d args, got %d", (expected), n)
#define AS_NUM(v) \
(is_number(v) ? (v) : (lisp_error("not a number: %s", show(v, false)), VAL_NIL))
static void check_string(Value v) {
if (!IS_STRING(v)) lisp_error("not a string: %s", show(v, false));
}
static void check_sym(Value v) {
if (!IS_SYM(v)) lisp_error("not a symbol: %s", show(v, false));
}
static void check_pair(Value v) {
if (!IS_PAIR(v)) lisp_error("not a pair: %s", show(v, false));
}
/* ═══════════════════════════════════════════════════════════════════════════
* Arithmetic builtins
* ═══════════════════════════════════════════════════════════════════════════ */
static Value bi_add(Value *a, int n, Env *e) {
(void)e;
if (n == 0) return VAL_INT(0);
Value result = AS_NUM(a[0]);
for (int i = 1; i < n; i++) result = num_add(result, AS_NUM(a[i]));
return result;
}
static Value bi_sub(Value *a, int n, Env *e) {
(void)e;
if (n == 0) lisp_error("-: no args");
if (n == 1) return num_neg(AS_NUM(a[0]));
Value result = AS_NUM(a[0]);
for (int i = 1; i < n; i++) result = num_sub(result, AS_NUM(a[i]));
return result;
}
static Value bi_mul(Value *a, int n, Env *e) {
(void)e;
Value result = VAL_INT(1);
for (int i = 0; i < n; i++) result = num_mul(result, AS_NUM(a[i]));
return result;
}
static Value bi_div(Value *a, int n, Env *e) {
(void)e;
if (n == 0) lisp_error("/: no args");
if (n == 1) return num_div(VAL_INT(1), AS_NUM(a[0]));
Value result = AS_NUM(a[0]);
for (int i = 1; i < n; i++) result = num_div(result, AS_NUM(a[i]));
return result;
}
#define MATH_1(name, fn) \
static Value bi_##name(Value *a, int n, Env *e) { \
(void)e; CHECK_ARITY(#name, 1); \
return make_double(fn(as_number_double(AS_NUM(a[0])))); \
}
MATH_1(sqrt, sqrt)
static Value bi_isqrt(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("isqrt", 1);
int64_t v = as_number_int(AS_NUM(a[0]));
if (v < 0) lisp_error("isqrt: negative argument: %lld", (long long)v);
if (v < 2) return VAL_INT(v);
uint64_t x = (uint64_t)v;
uint64_t res = 0;
uint64_t bit = (uint64_t)1 << 62;
while (bit > x) bit >>= 2;
while (bit != 0) {
if (x >= res + bit) { x -= res + bit; res = (res >> 1) + bit; }
else { res >>= 1; }
bit >>= 2;
}
return VAL_INT((int64_t)res);
}
MATH_1(sin, sin)
MATH_1(cos, cos)
MATH_1(tan, tan)
MATH_1(asin, asin)
MATH_1(acos, acos)
MATH_1(exp, exp)
static Value bi_log(Value *a, int n, Env *e) {
(void)e;
if (n == 1) return make_double(log(as_number_double(a[0])));
if (n == 2) return make_double(log(as_number_double(a[0])) / log(as_number_double(a[1])));
lisp_error("log: need 1-2 args");
return VAL_NIL;
}
static Value bi_atan(Value *a, int n, Env *e) {
(void)e;
if (n == 1) return make_double(atan(as_number_double(a[0])));
if (n == 2) return make_double(atan2(as_number_double(a[0]), as_number_double(a[1])));
lisp_error("atan: need 1-2 args");
return VAL_NIL;
}
static Value bi_quotient(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("quotient", 2);
return VAL_INT(as_number_int(a[0]) / as_number_int(a[1]));
}
static Value bi_remainder(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("remainder", 2);
int64_t x = as_number_int(a[0]), y = as_number_int(a[1]);
int64_t r = x % y;
/* Remainder has sign of dividend */
return VAL_INT(r);
}
static Value bi_modulo(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("modulo", 2);
int64_t x = as_number_int(a[0]), y = as_number_int(a[1]);
int64_t r = x % y;
if ((r > 0 && y < 0) || (r < 0 && y > 0)) r += y;
return VAL_INT(r);
}
static Value bi_expt(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("expt", 2);
if (IS_INT(a[0]) && IS_INT(a[1]) && as_int(a[1]) >= 0) {
int64_t base = as_int(a[0]), exp = as_int(a[1]);
int64_t result = 1;
for (int64_t i = 0; i < exp; i++) result *= base;
return VAL_INT(result);
}
return make_double(pow(as_number_double(a[0]), as_number_double(a[1])));
}
static Value bi_abs(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("abs", 1);
if (IS_INT(a[0])) { int64_t v = as_int(a[0]); return VAL_INT(v < 0 ? -v : v); }
return make_double(fabs(as_number_double(a[0])));
}
static Value bi_floor(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("floor", 1);
return VAL_INT((int64_t)floor(as_number_double(a[0])));
}
static Value bi_ceiling(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("ceiling", 1);
return VAL_INT((int64_t)ceil(as_number_double(a[0])));
}
static Value bi_round(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("round", 1);
return VAL_INT((int64_t)round(as_number_double(a[0])));
}
static Value bi_truncate(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("truncate", 1);
return VAL_INT((int64_t)trunc(as_number_double(a[0])));
}
static Value bi_min(Value *a, int n, Env *e) {
(void)e; CHECK_MIN_ARITY("min", 1);
Value r = a[0];
for (int i = 1; i < n; i++) if (num_lt(a[i], r)) r = a[i];
return r;
}
static Value bi_max(Value *a, int n, Env *e) {
(void)e; CHECK_MIN_ARITY("max", 1);
Value r = a[0];
for (int i = 1; i < n; i++) if (num_gt(a[i], r)) r = a[i];
return r;
}
static Value bi_gcd(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("gcd", 2);
int64_t x = as_number_int(a[0]), y = as_number_int(a[1]);
if (x < 0) x = -x; if (y < 0) y = -y;
while (y) { int64_t t = y; y = x % y; x = t; }
return VAL_INT(x);
}
static Value bi_lcm(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("lcm", 2);
int64_t x = as_number_int(a[0]), y = as_number_int(a[1]);
if (x < 0) x = -x; if (y < 0) y = -y;
int64_t g = x;
{ int64_t b = y; while (b) { int64_t t = b; b = g % b; g = t; } }
return VAL_INT(g ? (x / g) * y : 0);
}
static Value bi_exact(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("exact", 1);
if (IS_INT(a[0]) || IS_RATIONAL(a[0])) return a[0];
/* Approximate double → fraction */
double d = as_double(a[0]);
return rational_normalize((int64_t)(d * 1000000), 1000000);
}
static Value bi_inexact(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("inexact", 1);
return make_double(as_number_double(a[0]));
}
static Value bi_numerator(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("numerator", 1);
if (IS_RATIONAL(a[0])) return VAL_INT(AS_RATIONAL(a[0])->num);
if (IS_INT(a[0])) return a[0];
return a[0];
}
static Value bi_denominator(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("denominator", 1);
if (IS_RATIONAL(a[0])) return VAL_INT(AS_RATIONAL(a[0])->den);
return VAL_INT(1);
}
static Value bi_number_to_string(Value *a, int n, Env *e) {
(void)e; CHECK_MIN_ARITY("number->string", 1);
char buf[128];
if (n > 1) {
int base = (int)as_number_int(a[1]);
int64_t val = as_number_int(a[0]);
switch (base) {
case 2: {
char *p = buf + 127; *p = '\0';
uint64_t uv = val < 0 ? -val : val;
if (uv == 0) { *--p = '0'; }
else { while (uv) { *--p = '0' + (uv & 1); uv >>= 1; } }
if (val < 0) *--p = '-';
return make_string_from_cstr(p);
}
case 8: snprintf(buf, sizeof(buf), "%llo", (long long)val); break;
case 16: snprintf(buf, sizeof(buf), "%llx", (long long)val); break;
default: snprintf(buf, sizeof(buf), "%lld", (long long)val); break;
}
} else {
char *s = show(a[0], false);
Value r = make_string_from_cstr(s);
ul_free(s);
return r;
}
return make_string_from_cstr(buf);
}
/* Numeric comparison builtins */
#define NUM_CMP(name, op) \
static Value bi_##name(Value *a, int n, Env *e) { \
(void)e; CHECK_MIN_ARITY(#name, 2); \
for (int i = 0; i < n - 1; i++) { \
if (!(op(AS_NUM(a[i]), AS_NUM(a[i+1])))) return VAL_FALSE; \
} \
return VAL_TRUE; \
}
NUM_CMP(num_eq, num_eq)
NUM_CMP(num_lt, num_lt)
NUM_CMP(num_gt, num_gt)
NUM_CMP(num_le, num_le)
NUM_CMP(num_ge, num_ge)
/* Numeric predicates */
#define NUM_PRED(name, check) \
static Value bi_##name(Value *a, int n, Env *e) { \
(void)e; CHECK_ARITY(#name, 1); \
return VAL_BOOL(check); \
}
NUM_PRED(zero_p, is_number(a[0]) && num_eq(a[0], VAL_INT(0)))
NUM_PRED(positive_p, is_number(a[0]) && num_gt(a[0], VAL_INT(0)))
NUM_PRED(negative_p, is_number(a[0]) && num_lt(a[0], VAL_INT(0)))
NUM_PRED(odd_p, IS_INT(a[0]) && (as_int(a[0]) % 2 != 0))
NUM_PRED(even_p, IS_INT(a[0]) && (as_int(a[0]) % 2 == 0))
static Value bi_nan_p(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("nan?", 1);
return VAL_BOOL(IS_DOUBLE(a[0]) && isnan(as_double(a[0])));
}
static Value bi_infinite_p(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("infinite?", 1);
return VAL_BOOL(IS_DOUBLE(a[0]) && isinf(as_double(a[0])));
}
static Value bi_finite_p(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("finite?", 1);
return VAL_BOOL(is_number(a[0]) && (IS_INT(a[0]) || (IS_DOUBLE(a[0]) && isfinite(as_double(a[0])))));
}
static Value bi_square(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("square", 1);
return num_mul(a[0], a[0]);
}
static Value bi_exact_integer_p(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("exact-integer?", 1);
return VAL_BOOL(IS_INT(a[0]));
}
/* ═══════════════════════════════════════════════════════════════════════════
* Booleans & Equality
* ═══════════════════════════════════════════════════════════════════════════ */
static Value bi_not(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("not", 1);
return VAL_BOOL(!IS_TRUTHY(a[0]));
}
static Value bi_boolean_p(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("boolean?", 1);
return VAL_BOOL(IS_TRUE(a[0]) || IS_FALSE(a[0]));
}
static Value bi_eq_p(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("eq?", 2);
if (a[0] == a[1]) return VAL_TRUE;
/* eq? on integers, booleans, symbols is value comparison */
if (IS_INT(a[0]) && IS_INT(a[1])) return VAL_BOOL(as_int(a[0]) == as_int(a[1]));
return VAL_FALSE;
}
static Value bi_eqv_p(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("eqv?", 2);
if (a[0] == a[1]) return VAL_TRUE;
if (IS_INT(a[0]) && IS_INT(a[1])) return VAL_BOOL(as_int(a[0]) == as_int(a[1]));
if (IS_DOUBLE(a[0]) && IS_DOUBLE(a[1])) return VAL_BOOL(as_double(a[0]) == as_double(a[1]));
if (IS_CHAR(a[0]) && IS_CHAR(a[1])) return VAL_BOOL(AS_CHAR(a[0]) == AS_CHAR(a[1]));
if (IS_STRING(a[0]) && IS_STRING(a[1])) {
ULString *s1 = AS_STRING(a[0]), *s2 = AS_STRING(a[1]);
return VAL_BOOL(s1->len == s2->len && memcmp(s1->data, s2->data, s1->len) == 0);
}
return VAL_FALSE;
}
static Value bi_equal_p(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("equal?", 2);
return VAL_BOOL(values_equal(a[0], a[1]));
}
/* ═══════════════════════════════════════════════════════════════════════════
* Type predicates
* ═══════════════════════════════════════════════════════════════════════════ */
#define TYPE_PRED(name, check) \
static Value bi_##name(Value *a, int n, Env *e) { \
(void)e; CHECK_ARITY(#name, 1); \
return VAL_BOOL(check); \
}
TYPE_PRED(number_p, is_number(a[0]))
TYPE_PRED(integer_p, IS_INT(a[0]) || (IS_DOUBLE(a[0]) && as_double(a[0]) == floor(as_double(a[0]))))
TYPE_PRED(real_p, is_number(a[0]))
TYPE_PRED(rational_p, IS_INT(a[0]) || IS_RATIONAL(a[0]) || (IS_DOUBLE(a[0]) && isfinite(as_double(a[0]))))
TYPE_PRED(exact_p, IS_INT(a[0]) || IS_RATIONAL(a[0]))
TYPE_PRED(inexact_p, IS_DOUBLE(a[0]))
TYPE_PRED(pair_p, IS_PAIR(a[0]))
TYPE_PRED(null_p, IS_NIL(a[0]))
TYPE_PRED(list_p, is_proper_list(a[0]))
TYPE_PRED(symbol_p, IS_SYM(a[0]))
TYPE_PRED(string_p, IS_STRING(a[0]))
TYPE_PRED(char_p, IS_CHAR(a[0]))
TYPE_PRED(vector_p, IS_VECTOR(a[0]))
TYPE_PRED(procedure_p, is_callable(a[0]))
TYPE_PRED(void_p, IS_VOID(a[0]))
TYPE_PRED(eof_object_p, IS_EOF(a[0]))
TYPE_PRED(port_p, IS_PORT(a[0]))
TYPE_PRED(input_port_p, IS_PORT(a[0]) && AS_PORT(a[0])->dir == PORT_INPUT)
TYPE_PRED(output_port_p, IS_PORT(a[0]) && AS_PORT(a[0])->dir == PORT_OUTPUT)
TYPE_PRED(hash_table_p, IS_HASHTABLE(a[0]))
/* ═══════════════════════════════════════════════════════════════════════════
* Pairs & Lists
* ═══════════════════════════════════════════════════════════════════════════ */
static Value bi_cons(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("cons", 2);
return cons(a[0], a[1]);
}
static Value bi_car(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("car", 1); check_pair(a[0]);
return CAR(a[0]);
}
static Value bi_cdr(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("cdr", 1); check_pair(a[0]);
return CDR(a[0]);
}
static Value bi_set_car(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("set-car!", 2); check_pair(a[0]);
AS_PAIR(a[0])->car = a[1];
return VAL_VOID;
}
static Value bi_set_cdr(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("set-cdr!", 2); check_pair(a[0]);
AS_PAIR(a[0])->cdr = a[1];
return VAL_VOID;
}
static Value bi_list(Value *a, int n, Env *e) {
(void)e;
return list_to_value(a, n);
}
static Value bi_length(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("length", 1);
Value *items; int count = value_to_list(a[0], &items);
ul_free(items);
return VAL_INT(count);
}
static Value bi_append(Value *a, int n, Env *e) {
(void)e;
if (n == 0) return VAL_NIL;
Value result = a[n - 1];
for (int i = n - 2; i >= 0; i--) {
Value *items; int ni = value_to_list(a[i], &items);
for (int j = ni - 1; j >= 0; j--) result = cons(items[j], result);
ul_free(items);
}
return result;
}
static Value bi_reverse(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("reverse", 1);
Value *items; int ni = value_to_list(a[0], &items);
Value result = VAL_NIL;
for (int i = 0; i < ni; i++) result = cons(items[i], result);
ul_free(items);
return result;
}
static Value bi_list_tail(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("list-tail", 2);
int k = (int)as_number_int(a[1]);
Value lst = a[0];
for (int i = 0; i < k; i++) { check_pair(lst); lst = CDR(lst); }
return lst;
}
static Value bi_list_ref(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("list-ref", 2);
int k = (int)as_number_int(a[1]);
Value lst = a[0];
for (int i = 0; i < k; i++) { check_pair(lst); lst = CDR(lst); }
check_pair(lst);
return CAR(lst);
}
static Value bi_list_set(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("list-set!", 3);
int k = (int)as_number_int(a[1]);
Value lst = a[0];
for (int i = 0; i < k; i++) { check_pair(lst); lst = CDR(lst); }
check_pair(lst);
AS_PAIR(lst)->car = a[2];
return VAL_VOID;
}
static Value bi_list_copy(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("list-copy", 1);
Value *items; int ni = value_to_list(a[0], &items);
Value r = list_to_value(items, ni);
ul_free(items);
return r;
}
static Value bi_make_list(Value *a, int n, Env *e) {
(void)e; CHECK_MIN_ARITY("make-list", 1);
int len = (int)as_number_int(a[0]);
Value fill = n > 1 ? a[1] : VAL_FALSE;
Value r = VAL_NIL;
for (int i = 0; i < len; i++) r = cons(fill, r);
return r;
}
static Value bi_iota(Value *a, int n, Env *e) {
(void)e; CHECK_MIN_ARITY("iota", 1);
int count = (int)as_number_int(a[0]);
int start = n > 1 ? (int)as_number_int(a[1]) : 0;
int step = n > 2 ? (int)as_number_int(a[2]) : 1;
Value r = VAL_NIL;
for (int i = count - 1; i >= 0; i--) r = cons(VAL_INT(start + i * step), r);
return r;
}
static Value bi_memq(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("memq", 2);
Value lst = a[1];
while (IS_PAIR(lst)) {
if (CAR(lst) == a[0] || (IS_INT(CAR(lst)) && IS_INT(a[0]) && as_int(CAR(lst)) == as_int(a[0])))
return lst;
lst = CDR(lst);
}
return VAL_FALSE;
}
static Value bi_memv(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("memv", 2);
Value lst = a[1];
while (IS_PAIR(lst)) {
if (values_equal(CAR(lst), a[0])) return lst;
lst = CDR(lst);
}
return VAL_FALSE;
}
static Value bi_member(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("member", 2);
Value lst = a[1];
while (IS_PAIR(lst)) {
if (values_equal(CAR(lst), a[0])) return lst;
lst = CDR(lst);
}
return VAL_FALSE;
}
static Value bi_assq(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("assq", 2);
Value lst = a[1];
while (IS_PAIR(lst)) {
Value pair = CAR(lst);
if (IS_PAIR(pair) && CAR(pair) == a[0]) return pair;
lst = CDR(lst);
}
return VAL_FALSE;
}
static Value bi_assoc(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("assoc", 2);
Value lst = a[1];
while (IS_PAIR(lst)) {
Value pair = CAR(lst);
if (IS_PAIR(pair) && values_equal(CAR(pair), a[0])) return pair;
lst = CDR(lst);
}
return VAL_FALSE;
}
/* ═══════════════════════════════════════════════════════════════════════════
* Higher-order functions
* ═══════════════════════════════════════════════════════════════════════════ */
static Value bi_map(Value *a, int n, Env *e) {
CHECK_MIN_ARITY("map", 2);
Value proc = a[0];
/* Collect all lists */
int nlists = n - 1;
Value *lists = a + 1;
Value **items_arr = (Value **)ul_malloc(sizeof(Value *) * nlists);
int min_len = INT32_MAX;
for (int i = 0; i < nlists; i++) {
int ni;
items_arr[i] = NULL;
ni = value_to_list(lists[i], &items_arr[i]);
if (ni < min_len) min_len = ni;
}
Value *results = (Value *)ul_malloc(sizeof(Value) * min_len);
Value *call_args = (Value *)ul_malloc(sizeof(Value) * nlists);
for (int j = 0; j < min_len; j++) {
for (int i = 0; i < nlists; i++) call_args[i] = items_arr[i][j];
results[j] = call_proc(proc, call_args, nlists, e);
}
Value r = list_to_value(results, min_len);
for (int i = 0; i < nlists; i++) ul_free(items_arr[i]);
ul_free(items_arr); ul_free(results); ul_free(call_args);
return r;
}
static Value bi_for_each(Value *a, int n, Env *e) {
CHECK_MIN_ARITY("for-each", 2);
Value proc = a[0];
int nlists = n - 1;
Value *lists = a + 1;
Value **items_arr = (Value **)ul_malloc(sizeof(Value *) * nlists);
int min_len = INT32_MAX;
for (int i = 0; i < nlists; i++) {
int ni;
ni = value_to_list(lists[i], &items_arr[i]);
if (ni < min_len) min_len = ni;
}
Value *call_args = (Value *)ul_malloc(sizeof(Value) * nlists);
for (int j = 0; j < min_len; j++) {
for (int i = 0; i < nlists; i++) call_args[i] = items_arr[i][j];
call_proc(proc, call_args, nlists, e);
}
for (int i = 0; i < nlists; i++) ul_free(items_arr[i]);
ul_free(items_arr); ul_free(call_args);
return VAL_VOID;
}
static Value bi_filter(Value *a, int n, Env *e) {
CHECK_ARITY("filter", 2);
Value proc = a[0];
Value *items; int ni = value_to_list(a[1], &items);
Value *results = (Value *)ul_malloc(sizeof(Value) * ni);
int nr = 0;
for (int i = 0; i < ni; i++) {
Value v = call_proc(proc, &items[i], 1, e);
if (IS_TRUTHY(v)) results[nr++] = items[i];
}
Value r = list_to_value(results, nr);
ul_free(items); ul_free(results);
return r;
}
static Value bi_fold_left(Value *a, int n, Env *e) {
CHECK_ARITY("fold-left", 3);
Value proc = a[0];
Value acc = a[1];
Value *items; int ni = value_to_list(a[2], &items);
for (int i = 0; i < ni; i++) {
Value args[2] = {items[i], acc};
acc = call_proc(proc, args, 2, e);
}
ul_free(items);
return acc;
}
static Value bi_fold_right(Value *a, int n, Env *e) {
CHECK_ARITY("fold-right", 3);
Value proc = a[0];
Value acc = a[1];
Value *items; int ni = value_to_list(a[2], &items);
for (int i = ni - 1; i >= 0; i--) {
Value args[2] = {items[i], acc};
acc = call_proc(proc, args, 2, e);
}
ul_free(items);
return acc;
}
static Value bi_any(Value *a, int n, Env *e) {
CHECK_ARITY("any", 2);
Value *items; int ni = value_to_list(a[1], &items);
for (int i = 0; i < ni; i++) {
Value v = call_proc(a[0], &items[i], 1, e);
if (IS_TRUTHY(v)) { ul_free(items); return v; }
}
ul_free(items); return VAL_FALSE;
}
static Value bi_every(Value *a, int n, Env *e) {
CHECK_ARITY("every", 2);
Value *items; int ni = value_to_list(a[1], &items);
for (int i = 0; i < ni; i++) {
Value v = call_proc(a[0], &items[i], 1, e);
if (!IS_TRUTHY(v)) { ul_free(items); return VAL_FALSE; }
}
ul_free(items); return VAL_TRUE;
}
static Value bi_find(Value *a, int n, Env *e) {
CHECK_ARITY("find", 2);
Value *items; int ni = value_to_list(a[1], &items);
for (int i = 0; i < ni; i++) {
Value v = call_proc(a[0], &items[i], 1, e);
if (IS_TRUTHY(v)) { Value r = items[i]; ul_free(items); return r; }
}
ul_free(items); return VAL_FALSE;
}
static Value bi_sort(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("sort", 1);
Value *items; int ni = value_to_list(a[0], &items);
/* Simple insertion sort */
for (int i = 1; i < ni; i++) {
Value key = items[i];
int j = i - 1;
while (j >= 0 && num_gt(items[j], key)) {
items[j + 1] = items[j]; j--;
}
items[j + 1] = key;
}
Value r = list_to_value(items, ni);
ul_free(items);
return r;
}
static Value bi_count(Value *a, int n, Env *e) {
CHECK_ARITY("count", 2);
Value *items; int ni = value_to_list(a[1], &items);
int c = 0;
for (int i = 0; i < ni; i++) {
if (IS_TRUTHY(call_proc(a[0], &items[i], 1, e))) c++;
}
ul_free(items);
return VAL_INT(c);
}
static Value bi_apply(Value *a, int n, Env *e) {
CHECK_MIN_ARITY("apply", 2);
Value proc = a[0];
int npre = n - 2;
Value last = a[n - 1];
Value *lst; int nlst = value_to_list(last, &lst);
int total = npre + nlst;
Value *all = (Value *)ul_malloc(sizeof(Value) * total);
for (int i = 0; i < npre; i++) all[i] = a[i + 1];
memcpy(all + npre, lst, sizeof(Value) * nlst);
Value r = call_proc(proc, all, total, e);
ul_free(lst); ul_free(all);
return r;
}
/* ═══════════════════════════════════════════════════════════════════════════
* Strings
* ═══════════════════════════════════════════════════════════════════════════ */
static Value bi_make_string(Value *a, int n, Env *e) {
(void)e; CHECK_MIN_ARITY("make-string", 1);
int len = (int)as_number_int(a[0]);
char fill = n > 1 && IS_CHAR(a[1]) ? (char)AS_CHAR(a[1]) : ' ';
char *buf = (char *)ul_malloc(len + 1);
memset(buf, fill, len); buf[len] = '\0';
Value r = make_string(buf, len, true);
ul_free(buf);
return r;
}
static Value bi_string_length(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("string-length", 1); check_string(a[0]);
return VAL_INT(AS_STRING(a[0])->len);
}
static Value bi_string_ref(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("string-ref", 2); check_string(a[0]);
int idx = (int)as_number_int(a[1]);
ULString *s = AS_STRING(a[0]);
if (idx < 0 || idx >= (int)s->len) lisp_error("string-ref: index out of range");
return VAL_CHAR(s->data[idx]);
}
static Value bi_substring(Value *a, int n, Env *e) {
(void)e; CHECK_MIN_ARITY("substring", 2); check_string(a[0]);
ULString *s = AS_STRING(a[0]);
int start = (int)as_number_int(a[1]);
int end = n > 2 ? (int)as_number_int(a[2]) : (int)s->len;
if (start < 0) start = 0;
if (end > (int)s->len) end = (int)s->len;
return make_string(s->data + start, end - start, false);
}
static Value bi_string_append(Value *a, int n, Env *e) {
(void)e;
size_t total = 0;
for (int i = 0; i < n; i++) {
check_string(a[i]);
total += AS_STRING(a[i])->len;
}
char *buf = (char *)ul_malloc(total + 1);
size_t pos = 0;
for (int i = 0; i < n; i++) {
ULString *s = AS_STRING(a[i]);
memcpy(buf + pos, s->data, s->len);
pos += s->len;
}
buf[pos] = '\0';
Value r = make_string(buf, pos, false);
ul_free(buf);
return r;
}
static Value bi_string_to_list(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("string->list", 1); check_string(a[0]);
ULString *s = AS_STRING(a[0]);
Value r = VAL_NIL;
for (int i = (int)s->len - 1; i >= 0; i--) r = cons(VAL_CHAR(s->data[i]), r);
return r;
}
static Value bi_list_to_string(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("list->string", 1);
Value *items; int ni = value_to_list(a[0], &items);
char *buf = (char *)ul_malloc(ni + 1);
for (int i = 0; i < ni; i++) buf[i] = IS_CHAR(items[i]) ? (char)AS_CHAR(items[i]) : '?';
buf[ni] = '\0';
ul_free(items);
Value r = make_string(buf, ni, false);
ul_free(buf);
return r;
}
static Value bi_string_to_symbol(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("string->symbol", 1); check_string(a[0]);
return intern(AS_STRING(a[0])->data);
}
static Value bi_symbol_to_string(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("symbol->string", 1); check_sym(a[0]);
return make_string_from_cstr(sym_name(a[0]));
}
static Value bi_string_to_number(Value *a, int n, Env *e) {
(void)e; CHECK_MIN_ARITY("string->number", 1); check_string(a[0]);
const char *s = AS_STRING(a[0])->data;
int base = n > 1 ? (int)as_number_int(a[1]) : 10;
char *end;
errno = 0;
long long val = strtoll(s, &end, base);
if (*end == '\0' && errno == 0) return VAL_INT(val);
if (base == 10) {
double d = strtod(s, &end);
if (*end == '\0' && errno == 0) return make_double(d);
/* Try rational */
const char *slash = strchr(s, '/');
if (slash) {
int64_t num = strtoll(s, NULL, 10);
int64_t den = strtoll(slash + 1, NULL, 10);
if (den != 0) return rational_normalize(num, den);
}
}
return VAL_FALSE;
}
static Value bi_string_upcase(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("string-upcase", 1); check_string(a[0]);
ULString *s = AS_STRING(a[0]);
char *buf = (char *)ul_malloc(s->len + 1);
for (size_t i = 0; i < s->len; i++) buf[i] = toupper(s->data[i]);
buf[s->len] = '\0';
Value r = make_string(buf, s->len, false);
ul_free(buf);
return r;
}
static Value bi_string_downcase(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("string-downcase", 1); check_string(a[0]);
ULString *s = AS_STRING(a[0]);
char *buf = (char *)ul_malloc(s->len + 1);
for (size_t i = 0; i < s->len; i++) buf[i] = tolower(s->data[i]);
buf[s->len] = '\0';
Value r = make_string(buf, s->len, false);
ul_free(buf);
return r;
}
static Value bi_string_eq(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("string=?", 2); check_string(a[0]); check_string(a[1]);
ULString *s1 = AS_STRING(a[0]), *s2 = AS_STRING(a[1]);
return VAL_BOOL(s1->len == s2->len && memcmp(s1->data, s2->data, s1->len) == 0);
}
static Value bi_string_lt(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("string<?", 2); check_string(a[0]); check_string(a[1]);
return VAL_BOOL(strcmp(AS_STRING(a[0])->data, AS_STRING(a[1])->data) < 0);
}
static Value bi_format(Value *a, int n, Env *e) {
(void)e; CHECK_MIN_ARITY("format", 1);
check_string(a[0]);
const char *fmt = AS_STRING(a[0])->data;
size_t fmtlen = AS_STRING(a[0])->len;
char buf[4096];
int pos = 0, ai = 1;
for (size_t i = 0; i < fmtlen && pos < 4090; i++) {
if (fmt[i] == '~' && i + 1 < fmtlen) {
i++;
switch (fmt[i]) {
case 'a': { char *s = show(a[ai++], true); pos += snprintf(buf + pos, sizeof(buf) - pos, "%s", s); ul_free(s); break; }
case 's': { char *s = show(a[ai++], false); pos += snprintf(buf + pos, sizeof(buf) - pos, "%s", s); ul_free(s); break; }
case '%': buf[pos++] = '\n'; break;
case '~': buf[pos++] = '~'; break;
default: buf[pos++] = '~'; buf[pos++] = fmt[i]; break;
}
} else {
buf[pos++] = fmt[i];
}
}
buf[pos] = '\0';
return make_string_from_cstr(buf);
}
/* ═══════════════════════════════════════════════════════════════════════════
* Characters
* ═══════════════════════════════════════════════════════════════════════════ */
static Value bi_char_to_integer(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("char->integer", 1);
return VAL_INT(AS_CHAR(a[0]));
}
static Value bi_integer_to_char(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("integer->char", 1);
return VAL_CHAR((int)as_number_int(a[0]));
}
static Value bi_char_alphabetic(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("char-alphabetic?", 1);
return VAL_BOOL(isalpha(AS_CHAR(a[0])));
}
static Value bi_char_numeric(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("char-numeric?", 1);
return VAL_BOOL(isdigit(AS_CHAR(a[0])));
}
static Value bi_char_whitespace(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("char-whitespace?", 1);
return VAL_BOOL(isspace(AS_CHAR(a[0])));
}
static Value bi_char_eq(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("char=?", 2);
return VAL_BOOL(AS_CHAR(a[0]) == AS_CHAR(a[1]));
}
/* ═══════════════════════════════════════════════════════════════════════════
* Vectors
* ═══════════════════════════════════════════════════════════════════════════ */
static Value bi_make_vector(Value *a, int n, Env *e) {
(void)e; CHECK_MIN_ARITY("make-vector", 1);
int len = (int)as_number_int(a[0]);
Value fill = n > 1 ? a[1] : VAL_INT(0);
return make_vector(len, fill);
}
static Value bi_vector(Value *a, int n, Env *e) {
(void)e;
return make_vector_from(a, n);
}
static Value bi_vector_length(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("vector-length", 1);
return VAL_INT(AS_VECTOR(a[0])->len);
}
static Value bi_vector_ref(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("vector-ref", 2);
int idx = (int)as_number_int(a[1]);
return AS_VECTOR(a[0])->data[idx];
}
static Value bi_vector_set(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("vector-set!", 3);
int idx = (int)as_number_int(a[1]);
AS_VECTOR(a[0])->data[idx] = a[2];
return VAL_VOID;
}
static Value bi_vector_to_list(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("vector->list", 1);
ULVector *v = AS_VECTOR(a[0]);
return list_to_value(v->data, (int)v->len);
}
static Value bi_list_to_vector(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("list->vector", 1);
Value *items; int ni = value_to_list(a[0], &items);
Value r = make_vector_from(items, ni);
ul_free(items);
return r;
}
/* ═══════════════════════════════════════════════════════════════════════════
* Hash tables
* ═══════════════════════════════════════════════════════════════════════════ */
static Value bi_make_hash_table(Value *a, int n, Env *e) {
(void)e;
return make_hashtable();
}
static Value bi_hash_table_set(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("hash-table-set!", 3);
ht_set(AS_HASHTABLE(a[0]), a[1], a[2]);
return VAL_VOID;
}
static Value bi_hash_table_ref(Value *a, int n, Env *e) {
CHECK_MIN_ARITY("hash-table-ref", 2);
bool found;
Value v = ht_ref(AS_HASHTABLE(a[0]), a[1], &found);
if (!found) {
if (n > 2) return call_proc(a[2], NULL, 0, e);
lisp_error("hash-table-ref: missing key");
}
return v;
}
static Value bi_hash_table_ref_default(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("hash-table-ref/default", 3);
bool found;
Value v = ht_ref(AS_HASHTABLE(a[0]), a[1], &found);
return found ? v : a[2];
}
static Value bi_hash_table_delete(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("hash-table-delete!", 2);
ht_delete(AS_HASHTABLE(a[0]), a[1]);
return VAL_VOID;
}
static Value bi_hash_table_exists(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("hash-table-exists?", 2);
bool found;
ht_ref(AS_HASHTABLE(a[0]), a[1], &found);
return VAL_BOOL(found);
}
static Value bi_hash_table_size(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("hash-table-size", 1);
return VAL_INT(ht_count(AS_HASHTABLE(a[0])));
}
static Value bi_hash_table_keys(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("hash-table-keys", 1);
ULHashTable *ht = AS_HASHTABLE(a[0]);
Value r = VAL_NIL;
for (size_t i = 0; i < ht->nbuckets; i++) {
HTEntry *entry = ht->buckets[i];
while (entry) { r = cons(entry->key, r); entry = entry->next; }
}
return r;
}
static Value bi_hash_table_values(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("hash-table-values", 1);
ULHashTable *ht = AS_HASHTABLE(a[0]);
Value r = VAL_NIL;
for (size_t i = 0; i < ht->nbuckets; i++) {
HTEntry *entry = ht->buckets[i];
while (entry) { r = cons(entry->value, r); entry = entry->next; }
}
return r;
}
/* ═══════════════════════════════════════════════════════════════════════════
* I/O
* ═══════════════════════════════════════════════════════════════════════════ */
static Value bi_display(Value *a, int n, Env *e) {
(void)e; CHECK_MIN_ARITY("display", 1);
FILE *out = n > 1 && IS_PORT(a[1]) ? AS_PORT(a[1])->fp : stdout;
if (!out) out = stdout;
char *s = show(a[0], true);
if (n > 1 && IS_PORT(a[1]) && AS_PORT(a[1])->kind == PORT_STRING) {
port_write_str(AS_PORT(a[1]), s, strlen(s));
} else {
fputs(s, out);
fflush(out);
}
ul_free(s);
return VAL_VOID;
}
static Value bi_write(Value *a, int n, Env *e) {
(void)e; CHECK_MIN_ARITY("write", 1);
FILE *out = n > 1 && IS_PORT(a[1]) ? AS_PORT(a[1])->fp : stdout;
if (!out) out = stdout;
char *s = show(a[0], false);
if (n > 1 && IS_PORT(a[1]) && AS_PORT(a[1])->kind == PORT_STRING) {
port_write_str(AS_PORT(a[1]), s, strlen(s));
} else {
fputs(s, out);
fflush(out);
}
ul_free(s);
return VAL_VOID;
}
static Value bi_newline(Value *a, int n, Env *e) {
(void)e;
FILE *out = n > 0 && IS_PORT(a[0]) ? AS_PORT(a[0])->fp : stdout;
if (!out) out = stdout;
fputc('\n', out);
fflush(out);
return VAL_VOID;
}
static Value bi_print(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("print", 1);
char *s = show(a[0], true);
printf("%s\n", s);
fflush(stdout);
ul_free(s);
return VAL_VOID;
}
static Value bi_read_line(Value *a, int n, Env *e) {
(void)e;
if (n > 0 && IS_PORT(a[0])) {
char *line = port_read_line(AS_PORT(a[0]));
if (!line) return VAL_EOF;
Value r = make_string_from_cstr(line);
ul_free(line);
return r;
}
char buf[4096];
if (!fgets(buf, sizeof(buf), stdin)) return VAL_EOF;
size_t len = strlen(buf);
if (len > 0 && buf[len-1] == '\n') buf[--len] = '\0';
return make_string(buf, len, false);
}
static Value bi_read_char(Value *a, int n, Env *e) {
(void)e;
int ch;
if (n > 0 && IS_PORT(a[0])) {
ch = port_read_char(AS_PORT(a[0]));
} else {
ch = fgetc(stdin);
}
if (ch == EOF) return VAL_EOF;
return VAL_CHAR(ch);
}
static Value bi_open_input_file(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("open-input-file", 1); check_string(a[0]);
FILE *f = fopen(AS_STRING(a[0])->data, "r");
if (!f) lisp_error("open-input-file: cannot open: %s", AS_STRING(a[0])->data);
return make_file_port(f, PORT_INPUT);
}
static Value bi_open_output_file(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("open-output-file", 1); check_string(a[0]);
FILE *f = fopen(AS_STRING(a[0])->data, "w");
if (!f) lisp_error("open-output-file: cannot open: %s", AS_STRING(a[0])->data);
return make_file_port(f, PORT_OUTPUT);
}
static Value bi_write_file(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("write-file", 2); check_string(a[0]); check_string(a[1]);
FILE *f = fopen(AS_STRING(a[0])->data, "w");
if (!f) return VAL_FALSE;
ULString *s = AS_STRING(a[1]);
size_t wrote = fwrite(s->data, 1, s->len, f);
fclose(f);
return wrote == (size_t)s->len ? VAL_TRUE : VAL_FALSE;
}
static Value bi_file_to_string(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("file->string", 1); check_string(a[0]);
FILE *f = fopen(AS_STRING(a[0])->data, "rb");
if (!f) return VAL_FALSE;
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
char *buf = ul_malloc(size + 1);
size_t got = fread(buf, 1, size, f);
fclose(f);
buf[got] = '\0';
Value r = make_string(buf, got, false);
ul_free(buf);
return r;
}
static Value bi_open_input_string(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("open-input-string", 1); check_string(a[0]);
ULString *s = AS_STRING(a[0]);
return make_string_input_port(s->data, s->len);
}
/* ── TCP sockets: fd wrapped in a FILE* via fdopen, unbuffered ─ */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
static Value wrap_fd_as_port(int fd, PortDir dir) {
FILE *fp = fdopen(fd, dir == PORT_INPUT ? "rb" : "r+b");
if (!fp) { close(fd); return VAL_FALSE; }
setvbuf(fp, NULL, _IONBF, 0);
return make_file_port(fp, dir);
}
static Value bi_tcp_listen(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("tcp-listen", 1);
int port = (int)as_int(a[0]);
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) return VAL_FALSE;
int one = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons((uint16_t)port);
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { close(fd); return VAL_FALSE; }
if (listen(fd, 128) < 0) { close(fd); return VAL_FALSE; }
return wrap_fd_as_port(fd, PORT_INPUT);
}
static Value bi_tcp_accept(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("tcp-accept", 1);
ULPort *p = AS_PORT(a[0]);
int server_fd = fileno(p->fp);
struct sockaddr_in caddr;
socklen_t clen = sizeof(caddr);
int cfd = accept(server_fd, (struct sockaddr *)&caddr, &clen);
if (cfd < 0) return VAL_FALSE;
return wrap_fd_as_port(cfd, PORT_OUTPUT);
}
static Value bi_tcp_connect(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("tcp-connect", 2); check_string(a[0]);
const char *host = AS_STRING(a[0])->data;
int port = (int)as_int(a[1]);
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) return VAL_FALSE;
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons((uint16_t)port);
if (inet_pton(AF_INET, host, &addr.sin_addr) <= 0) {
struct hostent *he = gethostbyname(host);
if (!he) { close(fd); return VAL_FALSE; }
memcpy(&addr.sin_addr, he->h_addr_list[0], he->h_length);
}
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { close(fd); return VAL_FALSE; }
return wrap_fd_as_port(fd, PORT_OUTPUT);
}
static Value bi_tcp_recv(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("tcp-recv", 2);
ULPort *p = AS_PORT(a[0]);
size_t cap = (size_t)as_int(a[1]);
if (cap > (1u << 20)) cap = (1u << 20);
char *buf = ul_malloc(cap + 1);
int fd = fileno(p->fp);
ssize_t got = read(fd, buf, cap);
if (got < 0) { ul_free(buf); return VAL_FALSE; }
buf[got] = '\0';
Value r = make_string(buf, got, false);
ul_free(buf);
return r;
}
static Value bi_tcp_send(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("tcp-send", 2); check_string(a[1]);
ULPort *p = AS_PORT(a[0]);
ULString *s = AS_STRING(a[1]);
int fd = fileno(p->fp);
ssize_t wrote = write(fd, s->data, s->len);
if (wrote < 0) return VAL_FALSE;
return VAL_INT(wrote);
}
static Value bi_tcp_close(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("tcp-close", 1);
ULPort *p = AS_PORT(a[0]);
if (p->fp && p->fp != stdin && p->fp != stdout && p->fp != stderr)
fclose(p->fp);
p->closed = true;
return VAL_VOID;
}
/* heap-snapshot / heap-restore: asm-only arena primitives. The asm impl
* has no GC; these let a server rewind its bump allocator between
* requests. Python + C have real GCs — no-ops here so portable .lsp
* code can call them unconditionally. */
static Value bi_heap_snapshot(Value *a, int n, Env *e) {
(void)a; (void)n; (void)e;
return VAL_FALSE;
}
static Value bi_heap_restore(Value *a, int n, Env *e) {
(void)a; (void)n; (void)e;
return VAL_VOID;
}
#include <time.h>
static Value bi_current_time_ms(Value *a, int n, Env *e) {
(void)a; (void)n; (void)e;
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
int64_t ms = (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
return VAL_INT(ms);
}
static Value bi_read_from_string(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("read-from-string", 1); check_string(a[0]);
int count = 0;
Value *exprs = read_all(AS_STRING(a[0])->data, &count, false);
Value r = (count > 0) ? exprs[0] : VAL_FALSE;
ul_free(exprs);
return r;
}
static Value bi_open_output_string(Value *a, int n, Env *e) {
(void)e;
return make_string_output_port();
}
static Value bi_get_output_string(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("get-output-string", 1);
char *s = port_get_output_string(AS_PORT(a[0]));
Value r = make_string_from_cstr(s);
ul_free(s);
return r;
}
static Value bi_close_port(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("close-port", 1);
ULPort *p = AS_PORT(a[0]);
if (p->fp && p->fp != stdin && p->fp != stdout && p->fp != stderr)
fclose(p->fp);
p->closed = true;
return VAL_VOID;
}
static Value bi_eof_object(Value *a, int n, Env *e) {
(void)e; return VAL_EOF;
}
static Value bi_void(Value *a, int n, Env *e) {
(void)e; return VAL_VOID;
}
static Value bi_current_input_port(Value *a, int n, Env *e) {
(void)e; return make_file_port(stdin, PORT_INPUT);
}
static Value bi_current_output_port(Value *a, int n, Env *e) {
(void)e; return make_file_port(stdout, PORT_OUTPUT);
}
static Value bi_current_error_port(Value *a, int n, Env *e) {
(void)e; return make_file_port(stderr, PORT_OUTPUT);
}
static Value bi_flush_output_port(Value *a, int n, Env *e) {
(void)e;
if (n > 0 && IS_PORT(a[0]) && AS_PORT(a[0])->fp) fflush(AS_PORT(a[0])->fp);
else fflush(stdout);
return VAL_VOID;
}
/* ═══════════════════════════════════════════════════════════════════════════
* File system
* ═══════════════════════════════════════════════════════════════════════════ */
static Value bi_file_exists(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("file-exists?", 1); check_string(a[0]);
struct stat st;
return VAL_BOOL(stat(AS_STRING(a[0])->data, &st) == 0);
}
static Value bi_current_directory(Value *a, int n, Env *e) {
(void)e;
char buf[4096];
if (!getcwd(buf, sizeof(buf))) return make_string_from_cstr(".");
return make_string_from_cstr(buf);
}
/* ═══════════════════════════════════════════════════════════════════════════
* System
* ═══════════════════════════════════════════════════════════════════════════ */
static Value bi_exit(Value *a, int n, Env *e) {
(void)e;
exit(n > 0 ? (int)as_number_int(a[0]) : 0);
return VAL_VOID;
}
static Value bi_error(Value *a, int n, Env *e) {
(void)e; CHECK_MIN_ARITY("error", 1);
char *msg = show(a[0], true);
Value *irr = n > 1 ? a + 1 : NULL;
int nirr = n - 1;
Value obj = make_error_object(msg, irr, nirr);
ErrorObject *eo = AS_ERROR(obj);
char full_msg[MAX_ERROR_MSG];
int off = snprintf(full_msg, sizeof(full_msg), "%s", eo->message);
for (int i = 0; i < nirr && off < MAX_ERROR_MSG - 2; i++) {
char *s = show(irr[i], false);
off += snprintf(full_msg + off, sizeof(full_msg) - off, ": %s", s);
ul_free(s);
}
ul_free(msg);
lisp_error_with_obj(obj, "%s", full_msg);
return VAL_NIL;
}
static Value bi_error_object_p(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("error-object?", 1);
return VAL_BOOL(IS_ERROR_OBJ(a[0]));
}
static Value bi_error_object_message(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("error-object-message", 1);
if (IS_ERROR_OBJ(a[0])) return make_string_from_cstr(AS_ERROR(a[0])->message);
char *s = show(a[0], true);
Value r = make_string_from_cstr(s);
ul_free(s);
return r;
}
static Value bi_error_object_irritants(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("error-object-irritants", 1);
if (IS_ERROR_OBJ(a[0])) {
ErrorObject *eo = AS_ERROR(a[0]);
return list_to_value(eo->irritants, eo->nirritants);
}
return VAL_NIL;
}
static Value bi_current_time(Value *a, int n, Env *e) {
(void)e;
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
return make_double(ts.tv_sec + ts.tv_nsec / 1e9);
}
static Value bi_current_jiffy(Value *a, int n, Env *e) {
(void)e;
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return VAL_INT(ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
}
static Value bi_jiffies_per_second(Value *a, int n, Env *e) {
(void)e; return VAL_INT(1000);
}
static Value bi_command_line(Value *a, int n, Env *e) {
(void)a; (void)n; (void)e;
return VAL_NIL; /* will be set by main */
}
static Value bi_get_environment_variable(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("get-environment-variable", 1); check_string(a[0]);
const char *val = getenv(AS_STRING(a[0])->data);
if (!val) return VAL_FALSE;
return make_string_from_cstr(val);
}
static Value bi_gensym(Value *a, int n, Env *e) {
(void)e;
static int counter = 0;
char buf[32];
snprintf(buf, sizeof(buf), "g%d", counter++);
return intern(buf);
}
static Value bi_make_parameter(Value *a, int n, Env *e) {
(void)e; CHECK_MIN_ARITY("make-parameter", 1);
/* We store the parameter value in a vector of size 1 */
Value box = make_vector(1, a[0]);
/* If converter provided, apply it */
if (n > 1) {
Value args[1] = {a[0]};
AS_VECTOR(box)->data[0] = call_proc(a[1], args, 1, e);
}
/* Return a closure: 0 args → get, 1 arg → set */
/* We need to create a Scheme closure that captures the box */
/* Use a builtin with captured state via a trick: store box in an env */
Env *closure_env = make_env(e);
env_define(closure_env, intern("__param_box__"), box);
if (n > 1) env_define(closure_env, intern("__param_converter__"), a[1]);
else env_define(closure_env, intern("__param_converter__"), VAL_FALSE);
char src[256];
snprintf(src, sizeof(src),
"(lambda args "
" (if (null? args) "
" (vector-ref __param_box__ 0) "
" (begin (vector-set! __param_box__ 0 "
" (if __param_converter__ (__param_converter__ (car args)) (car args))) "
" (void))))");
int nc;
Value *exprs = read_all(src, &nc, false);
Value proc = leval(exprs[0], closure_env);
ul_free(exprs);
return proc;
}
static Value bi_values(Value *a, int n, Env *e) {
(void)e;
if (n == 1) return a[0];
return make_vector_from(a, n);
}
static Value bi_call_with_values(Value *a, int n, Env *e) {
CHECK_ARITY("call-with-values", 2);
Value r = call_proc(a[0], NULL, 0, e);
if (IS_VECTOR(r)) {
ULVector *v = AS_VECTOR(r);
return call_proc(a[1], v->data, (int)v->len, e);
}
Value args[1] = {r};
return call_proc(a[1], args, 1, e);
}
static Value bi_object_to_string(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("object->string", 1);
char *s = show(a[0], false);
Value r = make_string_from_cstr(s);
ul_free(s);
return r;
}
static Value bi_display_to_string(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("display-to-string", 1);
char *s = show(a[0], true);
Value r = make_string_from_cstr(s);
ul_free(s);
return r;
}
static Value bi_string_copy(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("string-copy", 1); check_string(a[0]);
ULString *s = AS_STRING(a[0]);
return make_string(s->data, s->len, true);
}
static Value bi_string_set(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("string-set!", 3); check_string(a[0]);
ULString *s = AS_STRING(a[0]);
if (!s->mutable) lisp_error("string-set!: immutable string");
int idx = (int)as_number_int(a[1]);
s->data[idx] = (char)AS_CHAR(a[2]);
return VAL_VOID;
}
static Value bi_string_contains(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("string-contains", 2);
check_string(a[0]); check_string(a[1]);
return VAL_BOOL(strstr(AS_STRING(a[0])->data, AS_STRING(a[1])->data) != NULL);
}
static Value bi_string_join(Value *a, int n, Env *e) {
(void)e; CHECK_MIN_ARITY("string-join", 1);
Value *items; int ni = value_to_list(a[0], &items);
const char *sep = n > 1 ? AS_STRING(a[1])->data : " ";
size_t seplen = strlen(sep);
size_t total = 0;
for (int i = 0; i < ni; i++) {
check_string(items[i]);
total += AS_STRING(items[i])->len;
if (i > 0) total += seplen;
}
char *buf = (char *)ul_malloc(total + 1);
size_t pos = 0;
for (int i = 0; i < ni; i++) {
if (i > 0) { memcpy(buf + pos, sep, seplen); pos += seplen; }
ULString *s = AS_STRING(items[i]);
memcpy(buf + pos, s->data, s->len);
pos += s->len;
}
buf[pos] = '\0';
ul_free(items);
Value r = make_string(buf, pos, false);
ul_free(buf);
return r;
}
static Value bi_string_split(Value *a, int n, Env *e) {
(void)e; CHECK_MIN_ARITY("string-split", 1);
check_string(a[0]);
const char *str = AS_STRING(a[0])->data;
const char *sep = n > 1 ? AS_STRING(a[1])->data : " ";
size_t seplen = strlen(sep);
Value result = VAL_NIL;
Value *items = NULL;
int nitems = 0, cap = 16;
items = (Value *)ul_malloc(sizeof(Value) * cap);
const char *p = str;
while (*p) {
const char *found = strstr(p, sep);
if (!found) {
if (nitems >= cap) { cap *= 2; items = (Value *)ul_realloc(items, sizeof(Value) * cap); }
items[nitems++] = make_string(p, strlen(p), false);
break;
}
if (nitems >= cap) { cap *= 2; items = (Value *)ul_realloc(items, sizeof(Value) * cap); }
items[nitems++] = make_string(p, found - p, false);
p = found + seplen;
}
result = list_to_value(items, nitems);
ul_free(items);
return result;
}
/* Record type subtype check — used by define-record-type predicates */
static Value bi_is_subtype_p(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("__is-subtype?__", 2);
check_string(a[0]); check_string(a[1]);
return VAL_BOOL(is_subtype(AS_STRING(a[0])->data, AS_STRING(a[1])->data));
}
static Value bi_identity(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("identity", 1);
return a[0];
}
static Value bi_string_trim(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("string-trim", 1); check_string(a[0]);
const char *s = AS_STRING(a[0])->data;
size_t len = AS_STRING(a[0])->len;
while (len > 0 && isspace(s[0])) { s++; len--; }
while (len > 0 && isspace(s[len-1])) { len--; }
return make_string(s, len, false);
}
static Value bi_string_index(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("string-index", 2); check_string(a[0]); check_string(a[1]);
const char *found = strstr(AS_STRING(a[0])->data, AS_STRING(a[1])->data);
if (!found) return VAL_INT(-1);
return VAL_INT(found - AS_STRING(a[0])->data);
}
static Value bi_string_replace(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("string-replace", 3);
check_string(a[0]); check_string(a[1]); check_string(a[2]);
const char *src = AS_STRING(a[0])->data;
const char *from = AS_STRING(a[1])->data;
const char *to = AS_STRING(a[2])->data;
size_t from_len = strlen(from), to_len = strlen(to);
if (from_len == 0) return a[0];
/* Use strstr (libc-tuned, often Boyer-Moore-Horspool) to skip the
* O(N*k) hand-rolled strncmp-at-every-position sediment. */
char buf[8192];
size_t pos = 0;
const char *cur = src;
const char *hit;
while ((hit = strstr(cur, from)) != NULL) {
size_t chunk = (size_t)(hit - cur);
if (pos + chunk + to_len >= sizeof(buf) - 1) break;
memcpy(buf + pos, cur, chunk); pos += chunk;
memcpy(buf + pos, to, to_len); pos += to_len;
cur = hit + from_len;
}
size_t tail = strlen(cur);
if (pos + tail >= sizeof(buf)) tail = sizeof(buf) - pos - 1;
memcpy(buf + pos, cur, tail); pos += tail;
buf[pos] = '\0';
return make_string_from_cstr(buf);
}
static Value bi_auto_compile(Value *a, int n, Env *e) {
(void)e;
if (n == 0) return VAL_BOOL(g_auto_compile);
g_auto_compile = IS_TRUTHY(a[0]);
return VAL_VOID;
}
static Value bi_compile(Value *a, int n, Env *e) {
CHECK_ARITY("compile", 1);
if (!IS_PROC(a[0])) lisp_error("compile: not a procedure");
CompiledProc *cp = compile_proc(AS_PROC(a[0]), e);
return VAL_PTR(cp);
}
static Value bi_compiled_p(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("compiled?", 1);
return VAL_BOOL(IS_COMPILED_PROC(a[0]));
}
static Value bi_procedure_name(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("procedure-name", 1);
if (IS_PROC(a[0])) {
const char *name = AS_PROC(a[0])->name;
return name ? make_string_from_cstr(name) : VAL_FALSE;
}
if (IS_COMPILED_PROC(a[0])) {
const char *name = AS_COMPILED_PROC(a[0])->name;
return name ? make_string_from_cstr(name) : VAL_FALSE;
}
return VAL_FALSE;
}
static Value bi_write_string(Value *a, int n, Env *e) {
(void)e; CHECK_MIN_ARITY("write-string", 1); check_string(a[0]);
FILE *out = n > 1 && IS_PORT(a[1]) ? AS_PORT(a[1])->fp : stdout;
if (n > 1 && IS_PORT(a[1]) && AS_PORT(a[1])->kind == PORT_STRING) {
ULString *s = AS_STRING(a[0]);
port_write_str(AS_PORT(a[1]), s->data, s->len);
} else {
fputs(AS_STRING(a[0])->data, out ? out : stdout);
fflush(out ? out : stdout);
}
return VAL_VOID;
}
static Value bi_write_char(Value *a, int n, Env *e) {
(void)e; CHECK_MIN_ARITY("write-char", 1);
FILE *out = n > 1 && IS_PORT(a[1]) ? AS_PORT(a[1])->fp : stdout;
fputc(AS_CHAR(a[0]), out ? out : stdout);
fflush(out ? out : stdout);
return VAL_VOID;
}
/* ═══════════════════════════════════════════════════════════════════════════
* Prelude — standard macros defined in Scheme
* ═══════════════════════════════════════════════════════════════════════════ */
const char *PRELUDE =
"(define-macro (when test . body)\n"
" `(if ,test (begin ,@body) (void)))\n"
"\n"
"(define-macro (unless test . body)\n"
" `(if ,test (void) (begin ,@body)))\n"
"\n"
"(define-macro (and . args)\n"
" (cond ((null? args) #t)\n"
" ((null? (cdr args)) (car args))\n"
" (else `(if ,(car args) (and ,@(cdr args)) #f))))\n"
"\n"
"(define-macro (or . args)\n"
" (cond ((null? args) #f)\n"
" ((null? (cdr args)) (car args))\n"
" (else (let ((v (gensym)))\n"
" `(let ((,v ,(car args)))\n"
" (if ,v ,v (or ,@(cdr args))))))))\n"
"\n"
"(define-macro (case key . clauses)\n"
" (let ((k (gensym)))\n"
" `(let ((,k ,key))\n"
" (cond ,@(map (lambda (c)\n"
" (if (eq? (car c) 'else)\n"
" c\n"
" `((memv ,k ',(car c)) ,@(cdr c))))\n"
" clauses)))))\n"
"\n"
"(define-macro (while test . body)\n"
" (let ((loop (gensym)))\n"
" `(let ,loop ()\n"
" (when ,test ,@body (,loop)))))\n"
"\n"
"(define-macro (for var lst . body)\n"
" `(for-each (lambda (,var) ,@body) ,lst))\n"
"\n"
"(define (1+ n) (+ n 1))\n"
"(define (1- n) (- n 1))\n"
"(define (-1+ n) (- n 1))\n"
"(define (add1 n) (+ n 1))\n"
"(define (sub1 n) (- n 1))\n"
"\n"
"(define (square x) (* x x))\n"
"(define (cube x) (* x x x))\n"
"\n"
"(define (atom? x) (not (pair? x)))\n"
"\n"
"(define (flatten lst)\n"
" (cond ((null? lst) '())\n"
" ((pair? (car lst)) (append (flatten (car lst)) (flatten (cdr lst))))\n"
" (else (cons (car lst) (flatten (cdr lst))))))\n"
"\n"
"(define (range . args)\n"
" (cond ((= (length args) 1) (iota (car args)))\n"
" ((= (length args) 2) (iota (- (cadr args) (car args)) (car args)))\n"
" ((= (length args) 3) (iota (ceiling (/ (- (cadr args) (car args)) (caddr args)))\n"
" (car args) (caddr args)))\n"
" (else (error \"range: wrong number of args\"))))\n"
"\n"
"(define (char-list->string chars) (apply string chars))\n"
"(define (string-for-each f s) (for-each f (string->list s)))\n"
"(define (string-map f s) (list->string (map f (string->list s))))\n"
"(define (char->string c) (string c))\n"
"(define (boolean->string b) (if b \"#t\" \"#f\"))\n"
"\n"
"(define (caar x) (car (car x)))\n"
"(define (cadr x) (car (cdr x)))\n"
"(define (cdar x) (cdr (car x)))\n"
"(define (cddr x) (cdr (cdr x)))\n"
"(define (caaar x) (car (car (car x))))\n"
"(define (caadr x) (car (car (cdr x))))\n"
"(define (cadar x) (car (cdr (car x))))\n"
"(define (caddr x) (car (cdr (cdr x))))\n"
"(define (cdaar x) (cdr (car (car x))))\n"
"(define (cdadr x) (cdr (car (cdr x))))\n"
"(define (cddar x) (cdr (cdr (car x))))\n"
"(define (cdddr x) (cdr (cdr (cdr x))))\n"
"(define (caaaar x) (car (car (car (car x)))))\n"
"(define (caaadr x) (car (car (car (cdr x)))))\n"
"(define (caaddr x) (car (car (cdr (cdr x)))))\n"
"(define (caddar x) (car (cdr (cdr (car x)))))\n"
"(define (cadddr x) (car (cdr (cdr (cdr x)))))\n"
"\n"
"(define (list* . args)\n"
" (if (null? (cdr args)) (car args)\n"
" (cons (car args) (apply list* (cdr args)))))\n"
"(define cons* list*)\n"
"\n"
"(define (last-pair lst)\n"
" (if (pair? (cdr lst)) (last-pair (cdr lst)) lst))\n"
"\n"
"(define-macro (trace name)\n"
" `(set! ,name (make-traced ,name ',name)))\n"
"\n"
"(define-macro (untrace name)\n"
" `(set! ,name (untrace-proc ',name)))\n"
;
/* ═══════════════════════════════════════════════════════════════════════════
* Build the global environment
* ═══════════════════════════════════════════════════════════════════════════ */
#define DEF(name, fn) env_define(g, intern(name), VAL_BUILTIN(fn))
Env *make_global_env(void) {
Env *g = make_env(NULL);
g->global = g;
/* Arithmetic */
DEF("+", bi_add); DEF("-", bi_sub); DEF("*", bi_mul); DEF("/", bi_div);
DEF("quotient", bi_quotient); DEF("remainder", bi_remainder); DEF("modulo", bi_modulo);
DEF("expt", bi_expt); DEF("abs", bi_abs);
DEF("floor", bi_floor); DEF("ceiling", bi_ceiling); DEF("round", bi_round);
DEF("truncate", bi_truncate);
DEF("sqrt", bi_sqrt); DEF("isqrt", bi_isqrt); DEF("log", bi_log); DEF("exp", bi_exp);
DEF("sin", bi_sin); DEF("cos", bi_cos); DEF("tan", bi_tan);
DEF("asin", bi_asin); DEF("acos", bi_acos); DEF("atan", bi_atan);
DEF("min", bi_min); DEF("max", bi_max);
DEF("gcd", bi_gcd); DEF("lcm", bi_lcm);
DEF("exact", bi_exact); DEF("inexact", bi_inexact);
DEF("exact->inexact", bi_inexact); DEF("inexact->exact", bi_exact);
DEF("numerator", bi_numerator); DEF("denominator", bi_denominator);
DEF("number->string", bi_number_to_string);
DEF("square", bi_square); DEF("exact-integer?", bi_exact_integer_p);
/* Numeric comparison */
DEF("=", bi_num_eq); DEF("<", bi_num_lt); DEF(">", bi_num_gt);
DEF("<=", bi_num_le); DEF(">=", bi_num_ge);
/* Numeric predicates */
DEF("zero?", bi_zero_p); DEF("positive?", bi_positive_p); DEF("negative?", bi_negative_p);
DEF("odd?", bi_odd_p); DEF("even?", bi_even_p);
DEF("nan?", bi_nan_p); DEF("infinite?", bi_infinite_p); DEF("finite?", bi_finite_p);
/* Booleans & equality */
DEF("not", bi_not); DEF("boolean?", bi_boolean_p);
DEF("eq?", bi_eq_p); DEF("eqv?", bi_eqv_p); DEF("equal?", bi_equal_p);
/* Type predicates */
DEF("number?", bi_number_p); DEF("integer?", bi_integer_p);
DEF("real?", bi_real_p); DEF("rational?", bi_rational_p);
DEF("exact?", bi_exact_p); DEF("inexact?", bi_inexact_p);
DEF("pair?", bi_pair_p); DEF("null?", bi_null_p); DEF("list?", bi_list_p);
DEF("symbol?", bi_symbol_p); DEF("string?", bi_string_p);
DEF("char?", bi_char_p); DEF("vector?", bi_vector_p);
DEF("procedure?", bi_procedure_p);
DEF("void?", bi_void_p); DEF("eof-object?", bi_eof_object_p);
DEF("port?", bi_port_p); DEF("input-port?", bi_input_port_p);
DEF("output-port?", bi_output_port_p);
DEF("hash-table?", bi_hash_table_p);
/* Pairs & lists */
DEF("cons", bi_cons); DEF("car", bi_car); DEF("cdr", bi_cdr);
DEF("set-car!", bi_set_car); DEF("set-cdr!", bi_set_cdr);
DEF("list", bi_list); DEF("length", bi_length);
DEF("append", bi_append); DEF("reverse", bi_reverse);
DEF("list-tail", bi_list_tail); DEF("list-ref", bi_list_ref);
DEF("list-set!", bi_list_set); DEF("list-copy", bi_list_copy);
DEF("make-list", bi_make_list); DEF("iota", bi_iota);
DEF("memq", bi_memq); DEF("memv", bi_memv); DEF("member", bi_member);
DEF("assq", bi_assq); DEF("assoc", bi_assoc); DEF("assv", bi_assoc);
/* Higher-order */
DEF("map", bi_map); DEF("for-each", bi_for_each);
DEF("filter", bi_filter);
DEF("fold-left", bi_fold_left); DEF("fold-right", bi_fold_right);
DEF("foldl", bi_fold_left); DEF("foldr", bi_fold_right);
DEF("reduce", bi_fold_left);
DEF("any", bi_any); DEF("every", bi_every);
DEF("find", bi_find); DEF("sort", bi_sort); DEF("count", bi_count);
DEF("apply", bi_apply);
DEF("filter-map", bi_filter); /* simplified */
DEF("identity", bi_identity);
/* Strings */
DEF("make-string", bi_make_string);
DEF("string-length", bi_string_length); DEF("string-ref", bi_string_ref);
DEF("substring", bi_substring); DEF("string-append", bi_string_append);
DEF("string-copy", bi_string_copy); DEF("string-set!", bi_string_set);
DEF("string->list", bi_string_to_list); DEF("list->string", bi_list_to_string);
DEF("string->symbol", bi_string_to_symbol); DEF("symbol->string", bi_symbol_to_string);
DEF("string->number", bi_string_to_number);
DEF("string-upcase", bi_string_upcase); DEF("string-downcase", bi_string_downcase);
DEF("string=?", bi_string_eq); DEF("string<?", bi_string_lt);
DEF("string-contains", bi_string_contains);
DEF("string-join", bi_string_join); DEF("string-split", bi_string_split);
DEF("string-trim", bi_string_trim); DEF("string-index", bi_string_index);
DEF("string-replace", bi_string_replace);
DEF("format", bi_format); DEF("string-format", bi_format);
/* Characters */
DEF("char->integer", bi_char_to_integer); DEF("integer->char", bi_integer_to_char);
DEF("char-alphabetic?", bi_char_alphabetic);
DEF("char-numeric?", bi_char_numeric);
DEF("char-whitespace?", bi_char_whitespace);
DEF("char=?", bi_char_eq);
/* Vectors */
DEF("make-vector", bi_make_vector); DEF("vector", bi_vector);
DEF("vector-length", bi_vector_length);
DEF("vector-ref", bi_vector_ref); DEF("vector-set!", bi_vector_set);
DEF("vector->list", bi_vector_to_list); DEF("list->vector", bi_list_to_vector);
/* Hash tables */
DEF("make-hash-table", bi_make_hash_table);
DEF("make-equal-hash-table", bi_make_hash_table);
DEF("hash-table-set!", bi_hash_table_set);
DEF("hash-table/put!", bi_hash_table_set);
DEF("hash-table-ref", bi_hash_table_ref);
DEF("hash-table-ref/default", bi_hash_table_ref_default);
DEF("hash-table/get", bi_hash_table_ref_default);
DEF("hash-table-delete!", bi_hash_table_delete);
DEF("hash-table-exists?", bi_hash_table_exists);
DEF("hash-table-size", bi_hash_table_size);
DEF("hash-table/count", bi_hash_table_size);
DEF("hash-table-keys", bi_hash_table_keys);
DEF("hash-table-values", bi_hash_table_values);
/* I/O */
DEF("display", bi_display); DEF("write", bi_write);
DEF("newline", bi_newline); DEF("print", bi_print);
DEF("println", bi_print); DEF("writeln", bi_write);
DEF("write-string", bi_write_string); DEF("write-char", bi_write_char);
DEF("read-line", bi_read_line); DEF("read-char", bi_read_char);
DEF("open-input-file", bi_open_input_file);
DEF("open-output-file", bi_open_output_file);
DEF("write-file", bi_write_file);
DEF("file->string", bi_file_to_string);
DEF("tcp-listen", bi_tcp_listen);
DEF("tcp-accept", bi_tcp_accept);
DEF("tcp-connect", bi_tcp_connect);
DEF("tcp-recv", bi_tcp_recv);
DEF("tcp-send", bi_tcp_send);
DEF("tcp-close", bi_tcp_close);
DEF("heap-snapshot", bi_heap_snapshot);
DEF("heap-restore", bi_heap_restore);
DEF("current-time-ms", bi_current_time_ms);
DEF("read-from-string", bi_read_from_string);
DEF("open-input-string", bi_open_input_string);
DEF("open-output-string", bi_open_output_string);
DEF("get-output-string", bi_get_output_string);
DEF("close-port", bi_close_port);
DEF("close-input-port", bi_close_port);
DEF("close-output-port", bi_close_port);
DEF("current-input-port", bi_current_input_port);
DEF("current-output-port", bi_current_output_port);
DEF("current-error-port", bi_current_error_port);
DEF("flush-output-port", bi_flush_output_port);
DEF("eof-object", bi_eof_object); DEF("void", bi_void);
/* File system */
DEF("file-exists?", bi_file_exists);
DEF("current-directory", bi_current_directory);
/* System */
DEF("exit", bi_exit); DEF("error", bi_error);
DEF("error-object?", bi_error_object_p);
DEF("error?", bi_error_object_p);
DEF("error-object-message", bi_error_object_message);
DEF("error-object-irritants", bi_error_object_irritants);
DEF("error-message", bi_error_object_message);
DEF("current-time", bi_current_time);
DEF("current-jiffy", bi_current_jiffy);
DEF("jiffies-per-second", bi_jiffies_per_second);
DEF("command-line", bi_command_line);
DEF("get-environment-variable", bi_get_environment_variable);
DEF("gensym", bi_gensym);
DEF("make-parameter", bi_make_parameter);
DEF("values", bi_values);
DEF("call-with-values", bi_call_with_values);
DEF("object->string", bi_object_to_string);
DEF("write-to-string", bi_object_to_string);
DEF("display-to-string", bi_display_to_string);
DEF("raise", bi_error);
DEF("procedure-name", bi_procedure_name);
DEF("auto-compile!", bi_auto_compile);
DEF("compile", bi_compile);
DEF("compiled?", bi_compiled_p);
/* Internal */
DEF("__is-subtype?__", bi_is_subtype_p);
/* Constants */
env_define(g, intern("pi"), make_double(M_PI));
env_define(g, intern("e"), make_double(M_E));
env_define(g, intern("else"), VAL_TRUE);
env_define(g, intern("..."), intern("..."));
env_define(g, intern("*version*"), make_string_from_cstr("1.0.0"));
env_define(g, intern("*name*"), make_string_from_cstr("lumbda"));
/* Portal builtins */
register_portal_builtins(g);
return g;
}