Adds tagged bignum support alongside the existing 48-bit fixnum on the C tier. Tag 6 = bignum, heap struct sign-magnitude with u64 little-endian limbs. Reader emits bignums for any literal past the fixnum range; +, -, *, quotient, remainder, modulo, expt, =, <, >, abs, odd?, even?, integer?, exact?, number->string, string->number all promote fixnum → bignum on overflow & demote back when results fit. Boehm GC owns every allocation. Schoolbook O(n²) mul + shift-subtract divmod is sufficient at our 4-limb / 256-bit scale. Before: (expt 2 48) = 0, (expt 2 256) = 0, secp256k1-p = -4294968273. After: all three return their exact arbitrary-precision values, matching Python tier byte-for-byte. Validated: - c/test.c — 85/85 pass (+2 new bignum unit tests). - tests/functional.lsp — 205/205 pass on both C & Python tiers. - tests/bignum-cross-tier.lsp — 33/33 pass byte-identical on both tiers (diff produces no output). - ecdsa/runs/lumbda-sweep-003/c-tier-bignum-probe.lsp — all four assertions now match the Python oracle. - ecdsa Phase B byte-identity sweep inside QEMU guest: n+1=9 p=251 sha256 c668bbe3... — matches Python oracle. n+1=18 p=131071 sha256 8a031f96... — matches Python oracle. n+1=33 p=2³²-5 sha256 0bc56905... — matches Python oracle. Previously the n+1=33 C tier emitted sha256 b024d6d9... (26,078 fewer Toffolis due to silent fixnum wrap). Bignums close that gate. secp256k1 production-width emit (n+1=257) is now structurally unblocked on C tier; downstream agent (#55) drives that next-step on the ecdsa side. Asm tier inherits in a follow-up port.
840 lines
32 KiB
C
840 lines
32 KiB
C
/*
|
|
* lumbda.h — A Scheme interpreter in C (NaN-boxed values, Boehm-style GC)
|
|
*
|
|
* Complete port of lumbda.py.
|
|
* Part of the permacomputer platform — code outlasts authors.
|
|
*/
|
|
#ifndef LUMBDA_H
|
|
#define LUMBDA_H
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <math.h>
|
|
#include <ctype.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <dirent.h>
|
|
#include <setjmp.h>
|
|
#include <stdarg.h>
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
* Memory allocation — simple arena/malloc wrapper
|
|
* When libgc is available, compile with -DUSE_BOEHM_GC and link -lgc
|
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
|
|
#ifdef USE_BOEHM_GC
|
|
#include <gc.h>
|
|
#define ul_malloc(sz) GC_MALLOC(sz)
|
|
#define ul_realloc(p,sz) GC_REALLOC(p,sz)
|
|
#define ul_free(p) ((void)0)
|
|
#define ul_strdup(s) GC_STRDUP(s)
|
|
#else
|
|
/* Fallback: plain malloc (no collection — acceptable for batch scripts) */
|
|
#define ul_malloc(sz) malloc(sz)
|
|
#define ul_realloc(p,sz) realloc(p,sz)
|
|
#define ul_free(p) free(p)
|
|
static inline char *ul_strdup(const char *s) {
|
|
size_t n = strlen(s) + 1;
|
|
char *d = (char *)ul_malloc(n);
|
|
if (d) memcpy(d, s, n);
|
|
return d;
|
|
}
|
|
#endif
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
* NaN-boxed Value type
|
|
*
|
|
* IEEE 754 double: if exponent bits are all 1 and mantissa != 0, it's NaN.
|
|
* We use quiet NaN with tag bits in the upper mantissa.
|
|
*
|
|
* Layout (64 bits):
|
|
* [sign:1][exponent:11][quiet:1][tag:3][payload:48]
|
|
*
|
|
* Tag 0 = pointer (Pair, String, Vector, etc — distinguished by pointed-to type tag)
|
|
* Tag 1 = integer (48-bit signed)
|
|
* Tag 2 = symbol (pointer to interned string)
|
|
* Tag 3 = special (NIL, VOID, TRUE, FALSE, EOF, char in payload)
|
|
* Tag 4 = builtin function pointer
|
|
* Tag 5 = rational (pointer to Rational struct)
|
|
* Tag 6 = bignum (pointer to Bignum struct) — arbitrary-precision integer.
|
|
* Used for any integer outside the 48-bit fixnum range. Arithmetic
|
|
* promotes fixnum to bignum on overflow & demotes bignum back to
|
|
* fixnum when result fits, so callers never need to distinguish.
|
|
*
|
|
* Plain doubles (no NaN payload) are floating-point numbers.
|
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
|
|
typedef uint64_t Value;
|
|
|
|
/* NaN box constants */
|
|
#define QNAN ((uint64_t)0x7FF8000000000000ULL)
|
|
#define TAG_MASK ((uint64_t)0x0007000000000000ULL)
|
|
#define TAG_SHIFT 48
|
|
#define PAYLOAD_MASK ((uint64_t)0x0000FFFFFFFFFFFFULL)
|
|
#define SIGN_BIT ((uint64_t)0x8000000000000000ULL)
|
|
|
|
#define TAG_PTR 0ULL
|
|
#define TAG_INT 1ULL
|
|
#define TAG_SYM 2ULL
|
|
#define TAG_SPECIAL 3ULL
|
|
#define TAG_BUILTIN 4ULL
|
|
#define TAG_RATIONAL 5ULL
|
|
#define TAG_BIGNUM 6ULL
|
|
|
|
/* Fixnum range: signed 48-bit (low 48 bits of payload). */
|
|
#define FIXNUM_MIN ((int64_t)(-(1LL << 47)))
|
|
#define FIXNUM_MAX ((int64_t)((1LL << 47) - 1))
|
|
#define FITS_FIXNUM(v) ((v) >= FIXNUM_MIN && (v) <= FIXNUM_MAX)
|
|
|
|
/* Construct a NaN-boxed value */
|
|
#define NANBOX(tag, payload) (QNAN | ((uint64_t)(tag) << TAG_SHIFT) | ((uint64_t)(payload) & PAYLOAD_MASK))
|
|
|
|
/* Extract tag and payload */
|
|
#define IS_DOUBLE(v) (((v) & QNAN) != QNAN)
|
|
#define GET_TAG(v) (((v) >> TAG_SHIFT) & 7ULL)
|
|
#define GET_PAYLOAD(v) ((v) & PAYLOAD_MASK)
|
|
#define GET_PTR(v) ((void *)(uintptr_t)GET_PAYLOAD(v))
|
|
|
|
/* Value constructors */
|
|
static inline Value make_double(double d) {
|
|
Value v;
|
|
memcpy(&v, &d, sizeof(v));
|
|
return v;
|
|
}
|
|
static inline double as_double(Value v) {
|
|
double d;
|
|
memcpy(&d, &v, sizeof(d));
|
|
return d;
|
|
}
|
|
|
|
/* Integer: 48-bit signed */
|
|
#define VAL_INT(n) NANBOX(TAG_INT, (uint64_t)(int64_t)(n) & PAYLOAD_MASK)
|
|
static inline int64_t as_int(Value v) {
|
|
int64_t raw = (int64_t)(GET_PAYLOAD(v));
|
|
/* Sign-extend from 48 bits */
|
|
if (raw & (1ULL << 47)) raw |= ~PAYLOAD_MASK;
|
|
return raw;
|
|
}
|
|
|
|
/* Special values */
|
|
#define SPECIAL_NIL 0ULL
|
|
#define SPECIAL_VOID 1ULL
|
|
#define SPECIAL_TRUE 2ULL
|
|
#define SPECIAL_FALSE 3ULL
|
|
#define SPECIAL_EOF 4ULL
|
|
#define SPECIAL_CHAR_BASE 256ULL /* char = CHAR_BASE + codepoint */
|
|
|
|
#define VAL_NIL NANBOX(TAG_SPECIAL, SPECIAL_NIL)
|
|
#define VAL_VOID NANBOX(TAG_SPECIAL, SPECIAL_VOID)
|
|
#define VAL_TRUE NANBOX(TAG_SPECIAL, SPECIAL_TRUE)
|
|
#define VAL_FALSE NANBOX(TAG_SPECIAL, SPECIAL_FALSE)
|
|
#define VAL_EOF NANBOX(TAG_SPECIAL, SPECIAL_EOF)
|
|
|
|
#define VAL_CHAR(c) NANBOX(TAG_SPECIAL, SPECIAL_CHAR_BASE + (uint32_t)(c))
|
|
|
|
#define VAL_BOOL(b) ((b) ? VAL_TRUE : VAL_FALSE)
|
|
|
|
/* Type checks */
|
|
#define IS_INT(v) (!IS_DOUBLE(v) && GET_TAG(v) == TAG_INT)
|
|
#define IS_SYM(v) (!IS_DOUBLE(v) && GET_TAG(v) == TAG_SYM)
|
|
#define IS_PTR(v) (!IS_DOUBLE(v) && GET_TAG(v) == TAG_PTR)
|
|
#define IS_SPECIAL(v) (!IS_DOUBLE(v) && GET_TAG(v) == TAG_SPECIAL)
|
|
#define IS_BUILTIN(v) (!IS_DOUBLE(v) && GET_TAG(v) == TAG_BUILTIN)
|
|
#define IS_RATIONAL(v) (!IS_DOUBLE(v) && GET_TAG(v) == TAG_RATIONAL)
|
|
#define IS_BIGNUM(v) (!IS_DOUBLE(v) && GET_TAG(v) == TAG_BIGNUM)
|
|
/* Integer-shaped: either fixnum or bignum. */
|
|
#define IS_INTEGER(v) (IS_INT(v) || IS_BIGNUM(v))
|
|
|
|
#define IS_NIL(v) ((v) == VAL_NIL)
|
|
#define IS_VOID(v) ((v) == VAL_VOID)
|
|
#define IS_TRUE(v) ((v) == VAL_TRUE)
|
|
#define IS_FALSE(v) ((v) == VAL_FALSE)
|
|
#define IS_EOF(v) ((v) == VAL_EOF)
|
|
#define IS_CHAR(v) (IS_SPECIAL(v) && GET_PAYLOAD(v) >= SPECIAL_CHAR_BASE)
|
|
|
|
#define AS_CHAR(v) ((int)(GET_PAYLOAD(v) - SPECIAL_CHAR_BASE))
|
|
|
|
/* Truthiness: everything is truthy except #f */
|
|
#define IS_TRUTHY(v) (!IS_FALSE(v))
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
* Heap object types — all heap objects have a type tag as first field
|
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
|
|
typedef enum {
|
|
OBJ_PAIR,
|
|
OBJ_STRING,
|
|
OBJ_MUTABLE_STRING,
|
|
OBJ_VECTOR,
|
|
OBJ_HASHTABLE,
|
|
OBJ_ENV,
|
|
OBJ_PROC,
|
|
OBJ_COMPILED_PROC,
|
|
OBJ_MACRO,
|
|
OBJ_CODE,
|
|
OBJ_CONTINUATION,
|
|
OBJ_PORT,
|
|
OBJ_ERROR,
|
|
OBJ_SYNTAX_TRANSFORMER,
|
|
OBJ_RATIONAL,
|
|
OBJ_BIGNUM,
|
|
} ObjType;
|
|
|
|
typedef struct ObjHeader {
|
|
ObjType type;
|
|
} ObjHeader;
|
|
|
|
/* Get the heap object type */
|
|
static inline ObjType obj_type(Value v) {
|
|
return ((ObjHeader *)GET_PTR(v))->type;
|
|
}
|
|
|
|
/* Wrap a pointer as a Value */
|
|
#define VAL_PTR(p) NANBOX(TAG_PTR, (uintptr_t)(p))
|
|
|
|
/* ── Pair ────────────────────────────────────────────────────────────────── */
|
|
|
|
typedef struct Pair {
|
|
ObjHeader hdr;
|
|
Value car;
|
|
Value cdr;
|
|
int line; /* source line for error reporting, 0 = unknown */
|
|
} Pair;
|
|
|
|
#define IS_PAIR(v) (IS_PTR(v) && obj_type(v) == OBJ_PAIR)
|
|
#define AS_PAIR(v) ((Pair *)GET_PTR(v))
|
|
|
|
Pair *make_pair(Value car, Value cdr);
|
|
Value cons(Value car, Value cdr);
|
|
|
|
/* ── Symbol interning ────────────────────────────────────────────────────── */
|
|
|
|
typedef struct SymbolEntry {
|
|
char *name;
|
|
Value value; /* the NaN-boxed symbol value */
|
|
struct SymbolEntry *next;
|
|
} SymbolEntry;
|
|
|
|
#define SYMBOL_TABLE_SIZE 4096
|
|
|
|
typedef struct {
|
|
SymbolEntry *buckets[SYMBOL_TABLE_SIZE];
|
|
} SymbolTable;
|
|
|
|
extern SymbolTable g_symbols;
|
|
|
|
Value intern(const char *name);
|
|
const char *sym_name(Value sym);
|
|
|
|
#define VAL_SYM_RAW(p) NANBOX(TAG_SYM, (uintptr_t)(p))
|
|
|
|
/* ── String ──────────────────────────────────────────────────────────────── */
|
|
|
|
typedef struct ULString {
|
|
ObjHeader hdr;
|
|
char *data;
|
|
size_t len;
|
|
bool mutable;
|
|
} ULString;
|
|
|
|
#define IS_STRING(v) (IS_PTR(v) && (obj_type(v) == OBJ_STRING || obj_type(v) == OBJ_MUTABLE_STRING))
|
|
#define AS_STRING(v) ((ULString *)GET_PTR(v))
|
|
|
|
Value make_string(const char *s, size_t len, bool mutable);
|
|
Value make_string_from_cstr(const char *s); /* immutable */
|
|
|
|
/* ── Vector ──────────────────────────────────────────────────────────────── */
|
|
|
|
typedef struct ULVector {
|
|
ObjHeader hdr;
|
|
Value *data;
|
|
size_t len;
|
|
size_t cap;
|
|
} ULVector;
|
|
|
|
#define IS_VECTOR(v) (IS_PTR(v) && obj_type(v) == OBJ_VECTOR)
|
|
#define AS_VECTOR(v) ((ULVector *)GET_PTR(v))
|
|
|
|
Value make_vector(size_t len, Value fill);
|
|
Value make_vector_from(Value *items, size_t len);
|
|
|
|
/* ── Hash Table ──────────────────────────────────────────────────────────── */
|
|
|
|
typedef struct HTEntry {
|
|
Value key;
|
|
Value value;
|
|
struct HTEntry *next;
|
|
} HTEntry;
|
|
|
|
typedef struct ULHashTable {
|
|
ObjHeader hdr;
|
|
HTEntry **buckets;
|
|
size_t nbuckets;
|
|
size_t count;
|
|
} ULHashTable;
|
|
|
|
#define IS_HASHTABLE(v) (IS_PTR(v) && obj_type(v) == OBJ_HASHTABLE)
|
|
#define AS_HASHTABLE(v) ((ULHashTable *)GET_PTR(v))
|
|
|
|
Value make_hashtable(void);
|
|
void ht_set(ULHashTable *ht, Value key, Value val);
|
|
Value ht_ref(ULHashTable *ht, Value key, bool *found);
|
|
bool ht_delete(ULHashTable *ht, Value key);
|
|
size_t ht_count(ULHashTable *ht);
|
|
|
|
/* ── Rational ────────────────────────────────────────────────────────────── */
|
|
|
|
typedef struct Rational {
|
|
ObjHeader hdr;
|
|
int64_t num;
|
|
int64_t den;
|
|
} Rational;
|
|
|
|
#define AS_RATIONAL(v) ((Rational *)(uintptr_t)GET_PAYLOAD(v))
|
|
|
|
Value make_rational(int64_t num, int64_t den);
|
|
/* Returns integer Value if den==1, otherwise rational */
|
|
Value rational_normalize(int64_t num, int64_t den);
|
|
|
|
/* ── Bignum — arbitrary-precision integer ────────────────────────────────── */
|
|
|
|
/*
|
|
* Bignum representation: sign-magnitude.
|
|
* sign: -1 (negative), 0 (zero — must have n_limbs == 0), +1 (positive)
|
|
* n_limbs: number of u64 magnitude limbs (always trimmed of leading zeros)
|
|
* limbs[i]: little-endian magnitude (limb 0 = least-significant 64 bits)
|
|
*
|
|
* Boehm GC owns every Bignum struct & its limbs array. Construction goes
|
|
* through bignum_normalize() which trims leading-zero limbs & forces zero
|
|
* to canonical form (sign=0, n_limbs=0).
|
|
*
|
|
* Result-producing helpers ALWAYS return a Value: if the magnitude fits in
|
|
* 48 signed bits, a fixnum Value is returned; otherwise a bignum Value.
|
|
* Callers therefore never branch on which one they get back.
|
|
*/
|
|
typedef struct Bignum {
|
|
ObjHeader hdr;
|
|
int32_t sign; /* -1, 0, +1 */
|
|
uint32_t n_limbs; /* magnitude limb count after trim */
|
|
uint64_t *limbs; /* little-endian magnitude */
|
|
} Bignum;
|
|
|
|
#define AS_BIGNUM(v) ((Bignum *)(uintptr_t)GET_PAYLOAD(v))
|
|
|
|
/* Constructors */
|
|
Value make_bignum_from_i64(int64_t v);
|
|
Value make_bignum_from_str(const char *s, int base); /* base 10 or 16 */
|
|
Value bignum_from_limbs(int32_t sign, uint64_t *limbs, uint32_t n);
|
|
|
|
/* Demote a bignum to fixnum when magnitude fits 48 signed bits. Caller passes
|
|
* a (sign, limbs, n_limbs) triple; result is fixnum Value or bignum Value. */
|
|
Value bignum_normalize(int32_t sign, uint64_t *limbs, uint32_t n);
|
|
|
|
/* Promote a fixnum Value to a bignum Value (no-op for already-bignum). */
|
|
Value to_bignum(Value v);
|
|
|
|
/* Decimal-string render (caller frees via ul_free). */
|
|
char *bignum_to_str(Bignum *b);
|
|
|
|
/* Arithmetic — accept fixnum or bignum, return fixnum-or-bignum
|
|
* (demoted when possible). Sign/magnitude internal. */
|
|
Value big_add(Value a, Value b);
|
|
Value big_sub(Value a, Value b);
|
|
Value big_mul(Value a, Value b);
|
|
Value big_neg(Value a);
|
|
Value big_quotient(Value a, Value b);
|
|
Value big_remainder(Value a, Value b); /* sign of dividend */
|
|
Value big_modulo(Value a, Value b); /* sign of divisor */
|
|
Value big_expt(Value base, Value exp); /* exp >= 0, integer */
|
|
int big_cmp(Value a, Value b); /* -1, 0, +1 */
|
|
bool big_is_zero(Value v);
|
|
bool big_is_odd(Value v);
|
|
Value big_abs(Value v);
|
|
|
|
/* Coerce any number Value to a fresh Bignum struct (caller owns) for ops
|
|
* that want explicit bignum arguments. */
|
|
Bignum *as_bignum(Value v);
|
|
|
|
/* ── Environment ─────────────────────────────────────────────────────────── */
|
|
|
|
typedef struct EnvBinding {
|
|
Value sym;
|
|
Value val;
|
|
struct EnvBinding *next;
|
|
} EnvBinding;
|
|
|
|
typedef struct Env {
|
|
ObjHeader hdr;
|
|
EnvBinding **buckets;
|
|
size_t nbuckets;
|
|
size_t count;
|
|
struct Env *parent;
|
|
struct Env *global; /* shortcut to global env */
|
|
} Env;
|
|
|
|
#define IS_ENV(v) (IS_PTR(v) && obj_type(v) == OBJ_ENV)
|
|
#define AS_ENV(v) ((Env *)GET_PTR(v))
|
|
|
|
Env *make_env(Env *parent);
|
|
void env_define(Env *e, Value sym, Value val);
|
|
Value env_lookup(Env *e, Value sym);
|
|
bool env_set(Env *e, Value sym, Value val);
|
|
Env *env_child(Env *parent, Value *params, int nparams, Value rest_param, Value *args, int nargs);
|
|
Env *deep_copy_env(Env *env);
|
|
|
|
/* ── Procedure ───────────────────────────────────────────────────────────── */
|
|
|
|
/* Parsed formals */
|
|
typedef struct {
|
|
Value *params;
|
|
int nparams;
|
|
Value rest; /* VAL_NIL if no rest param */
|
|
} Formals;
|
|
|
|
/* Expression list (body) */
|
|
typedef struct {
|
|
Value *exprs;
|
|
int count;
|
|
} ExprList;
|
|
|
|
typedef struct Proc {
|
|
ObjHeader hdr;
|
|
Value *params;
|
|
int nparams;
|
|
Value rest; /* rest param symbol, or VAL_NIL */
|
|
ExprList body;
|
|
Env *env;
|
|
const char *name;
|
|
bool has_defs; /* body starts with define? */
|
|
void *jit_block; /* cached JitBlock*, or NULL */
|
|
} Proc;
|
|
|
|
#define IS_PROC(v) (IS_PTR(v) && obj_type(v) == OBJ_PROC)
|
|
#define AS_PROC(v) ((Proc *)GET_PTR(v))
|
|
|
|
Proc *make_proc(Value *params, int nparams, Value rest, ExprList body, Env *env, const char *name);
|
|
|
|
/* ── Builtin function ────────────────────────────────────────────────────── */
|
|
|
|
typedef Value (*BuiltinFn)(Value *args, int nargs, Env *env);
|
|
|
|
/* Wrap a function pointer */
|
|
#define VAL_BUILTIN(fn) NANBOX(TAG_BUILTIN, (uintptr_t)(fn))
|
|
#define AS_BUILTIN(v) ((BuiltinFn)(uintptr_t)GET_PAYLOAD(v))
|
|
|
|
/* ── Macro ───────────────────────────────────────────────────────────────── */
|
|
|
|
typedef struct ULMacro {
|
|
ObjHeader hdr;
|
|
Value transformer; /* Proc, CompiledProc, or SyntaxTransformer */
|
|
} ULMacro;
|
|
|
|
#define IS_MACRO(v) (IS_PTR(v) && obj_type(v) == OBJ_MACRO)
|
|
#define AS_MACRO(v) ((ULMacro *)GET_PTR(v))
|
|
|
|
Value make_macro(Value transformer);
|
|
|
|
/* ── Error object ────────────────────────────────────────────────────────── */
|
|
|
|
typedef struct ErrorObject {
|
|
ObjHeader hdr;
|
|
char *message;
|
|
Value *irritants;
|
|
int nirritants;
|
|
} ErrorObject;
|
|
|
|
#define IS_ERROR_OBJ(v) (IS_PTR(v) && obj_type(v) == OBJ_ERROR)
|
|
#define AS_ERROR(v) ((ErrorObject *)GET_PTR(v))
|
|
|
|
Value make_error_object(const char *msg, Value *irritants, int nirr);
|
|
|
|
/* ── Port ────────────────────────────────────────────────────────────────── */
|
|
|
|
typedef enum { PORT_INPUT, PORT_OUTPUT } PortDir;
|
|
typedef enum { PORT_FILE, PORT_STRING } PortKind;
|
|
|
|
typedef struct ULPort {
|
|
ObjHeader hdr;
|
|
PortDir dir;
|
|
PortKind kind;
|
|
FILE *fp; /* for file ports */
|
|
char *str_buf; /* for string ports */
|
|
size_t str_len;
|
|
size_t str_pos;
|
|
size_t str_cap;
|
|
bool closed;
|
|
} ULPort;
|
|
|
|
#define IS_PORT(v) (IS_PTR(v) && obj_type(v) == OBJ_PORT)
|
|
#define AS_PORT(v) ((ULPort *)GET_PTR(v))
|
|
|
|
Value make_file_port(FILE *fp, PortDir dir);
|
|
Value make_string_input_port(const char *s, size_t len);
|
|
Value make_string_output_port(void);
|
|
void port_write_char(ULPort *p, int ch);
|
|
void port_write_str(ULPort *p, const char *s, size_t len);
|
|
int port_read_char(ULPort *p);
|
|
int port_peek_char(ULPort *p);
|
|
char *port_read_line(ULPort *p);
|
|
char *port_get_output_string(ULPort *p);
|
|
|
|
/* ── Bytecode ────────────────────────────────────────────────────────────── */
|
|
|
|
typedef enum {
|
|
OP_CONST = 0, OP_LOOKUP = 1, OP_SET = 2, OP_DEFINE = 3,
|
|
OP_POP = 4, OP_DUP = 5, OP_VOID = 6,
|
|
OP_JUMP = 10, OP_JUMP_IF_FALSE = 11,
|
|
OP_JUMP_IF_FALSE_KEEP = 12, OP_JUMP_IF_TRUE_KEEP = 13,
|
|
OP_CALL = 20, OP_TAIL_CALL = 21, OP_RETURN = 22,
|
|
OP_MAKE_CLOSURE = 30,
|
|
OP_PUSH_ENV = 40, OP_POP_ENV = 41, OP_BIND = 42,
|
|
OP_EVAL = 50, OP_CALL_CC = 51,
|
|
OP_ADD = 60, OP_SUB = 61, OP_MUL = 62, OP_NEG = 63,
|
|
OP_NUM_EQ = 64, OP_LT = 65, OP_GT = 66, OP_LE = 67, OP_GE = 68,
|
|
OP_ADD1 = 69, OP_SUB1 = 70,
|
|
OP_CAR = 71, OP_CDR = 72, OP_CONS = 73,
|
|
OP_NULL_P = 74, OP_PAIR_P = 75, OP_NOT = 76, OP_ZERO_P = 77,
|
|
OP_VEC_REF = 78, OP_VEC_SET = 79,
|
|
OP_LOOK_LOOK = 80, OP_LOOK_ADD1 = 81, OP_LOOK_SUB1 = 82,
|
|
OP_CONST_EQ_JF = 83, OP_LOOK_CONST_CALL2 = 84,
|
|
OP_SELF_TAIL_CALL = 85,
|
|
} Opcode;
|
|
|
|
typedef struct {
|
|
Opcode op;
|
|
Value arg; /* operand — meaning depends on op */
|
|
int arg2; /* secondary operand (for some superinstructions) */
|
|
} Instruction;
|
|
|
|
typedef struct CodeObj {
|
|
ObjHeader hdr;
|
|
Instruction *instrs;
|
|
int count;
|
|
int cap;
|
|
int *source_map; /* line numbers, parallel to instrs */
|
|
const char *name;
|
|
/* Self-tail-call optimization info */
|
|
const char *self_name;
|
|
Value *self_params;
|
|
int self_nparams;
|
|
} CodeObj;
|
|
|
|
#define IS_CODE(v) (IS_PTR(v) && obj_type(v) == OBJ_CODE)
|
|
#define AS_CODE(v) ((CodeObj *)GET_PTR(v))
|
|
|
|
typedef struct CompiledProc {
|
|
ObjHeader hdr;
|
|
CodeObj *code;
|
|
Value *params;
|
|
int nparams;
|
|
Value rest;
|
|
Env *env;
|
|
const char *name;
|
|
} CompiledProc;
|
|
|
|
#define IS_COMPILED_PROC(v) (IS_PTR(v) && obj_type(v) == OBJ_COMPILED_PROC)
|
|
#define AS_COMPILED_PROC(v) ((CompiledProc *)GET_PTR(v))
|
|
|
|
/* ── Continuation ────────────────────────────────────────────────────────── */
|
|
|
|
typedef struct VMFrame {
|
|
Instruction *instrs;
|
|
int ip;
|
|
int n_instrs;
|
|
Env *env;
|
|
Value *stack;
|
|
int stack_len;
|
|
int stack_cap;
|
|
CodeObj *cur_code; /* saved so RET restores it — needed for SELF_TAIL_CALL after a nested call */
|
|
} VMFrame;
|
|
|
|
typedef struct FullCont {
|
|
ObjHeader hdr;
|
|
VMFrame *frames;
|
|
int nframes;
|
|
Value *stack;
|
|
int stack_len;
|
|
int ip;
|
|
Instruction *instrs;
|
|
int n_instrs;
|
|
Env *env;
|
|
void *vm_id;
|
|
} FullCont;
|
|
|
|
#define IS_CONTINUATION(v) (IS_PTR(v) && obj_type(v) == OBJ_CONTINUATION)
|
|
#define AS_CONTINUATION(v) ((FullCont *)GET_PTR(v))
|
|
|
|
/* Continuation invocation signaling — used by full continuations in the VM.
|
|
* When a FullCont is invoked (as a callable), it sets these thread-locals
|
|
* and longjmps to the VM's trampoline. Similar to Python's _ContInvoked. */
|
|
typedef struct {
|
|
jmp_buf jmp;
|
|
bool active;
|
|
} ContTrampoline;
|
|
|
|
extern __thread ContTrampoline *g_cont_trampoline;
|
|
extern __thread FullCont *g_cont_invoked;
|
|
extern __thread Value g_cont_invoked_val;
|
|
|
|
FullCont *make_full_cont(VMFrame *frames, int nframes, Value *stack, int stack_len,
|
|
int ip, Instruction *instrs, int n_instrs, Env *env, void *vm_id);
|
|
|
|
/* Deep-copy a VMFrame array for multi-shot continuations */
|
|
VMFrame *deep_copy_frames(VMFrame *frames, int nframes);
|
|
|
|
/* ── Syntax Transformer ──────────────────────────────────────────────────── */
|
|
|
|
typedef struct SyntaxRule {
|
|
Value pattern;
|
|
Value tmpl;
|
|
} SyntaxRule;
|
|
|
|
typedef struct SyntaxTransformer {
|
|
ObjHeader hdr;
|
|
char **literals;
|
|
int nliterals;
|
|
SyntaxRule *rules;
|
|
int nrules;
|
|
Env *def_env;
|
|
} SyntaxTransformer;
|
|
|
|
#define IS_SYNTAX_TRANSFORMER(v) (IS_PTR(v) && obj_type(v) == OBJ_SYNTAX_TRANSFORMER)
|
|
#define AS_SYNTAX_TRANSFORMER(v) ((SyntaxTransformer *)GET_PTR(v))
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
* Error handling — longjmp-based exception system
|
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
|
|
#define MAX_ERROR_MSG 1024
|
|
#define MAX_CALL_STACK 256
|
|
|
|
typedef struct {
|
|
jmp_buf jmp;
|
|
char message[MAX_ERROR_MSG];
|
|
Value error_obj; /* ErrorObject Value or VAL_NIL */
|
|
const char *call_stack[MAX_CALL_STACK];
|
|
int call_stack_depth;
|
|
int source_line;
|
|
} ErrorContext;
|
|
|
|
extern __thread ErrorContext *g_error_ctx;
|
|
|
|
void lisp_error(const char *fmt, ...) __attribute__((noreturn));
|
|
void lisp_error_with_obj(Value obj, const char *fmt, ...) __attribute__((noreturn));
|
|
|
|
/* Push/pop error handler */
|
|
#define TRY(ctx) \
|
|
do { \
|
|
ErrorContext *_prev = g_error_ctx; \
|
|
ErrorContext ctx; \
|
|
ctx.call_stack_depth = _prev ? _prev->call_stack_depth : 0; \
|
|
if (_prev) memcpy(ctx.call_stack, _prev->call_stack, sizeof(char*) * ctx.call_stack_depth); \
|
|
ctx.error_obj = VAL_NIL; \
|
|
ctx.source_line = 0; \
|
|
g_error_ctx = &ctx; \
|
|
if (setjmp(ctx.jmp) == 0) {
|
|
|
|
#define CATCH \
|
|
} else {
|
|
|
|
#define ENDTRY \
|
|
} \
|
|
g_error_ctx = _prev; \
|
|
} while(0)
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
* Value stack (dynamic array)
|
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
|
|
typedef struct {
|
|
Value *data;
|
|
int len;
|
|
int cap;
|
|
} ValueStack;
|
|
|
|
void vs_init(ValueStack *s, int cap);
|
|
void vs_push(ValueStack *s, Value v);
|
|
Value vs_pop(ValueStack *s);
|
|
Value vs_peek(ValueStack *s);
|
|
void vs_clear(ValueStack *s);
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
* Function declarations — reader.c
|
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
|
|
typedef struct {
|
|
char **tokens;
|
|
int *lines;
|
|
int count;
|
|
int cap;
|
|
} TokenList;
|
|
|
|
void tokenize(const char *src, TokenList *out, bool track_lines);
|
|
Value parse_one(TokenList *tl, int *pos);
|
|
Value parse_atom(const char *tok);
|
|
Value *read_all(const char *src, int *count, bool track_lines);
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
* Function declarations — printer.c
|
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
|
|
/* Returns a malloc'd string. Caller must free. */
|
|
char *show(Value v, bool display);
|
|
/* Print to stdout */
|
|
void print_value(Value v, bool display, FILE *out);
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
* Function declarations — eval.c
|
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
|
|
Value leval(Value expr, Env *env);
|
|
Value call_proc(Value proc, Value *args, int nargs, Env *env);
|
|
void load_file(const char *path, Env *env);
|
|
|
|
/* Quasiquote */
|
|
Value qq_expand(Value tmpl, Env *env, int depth);
|
|
|
|
/* Syntax transformer */
|
|
Value syntax_transform_value(SyntaxTransformer *st, Value form);
|
|
|
|
/* Helpers */
|
|
Value list_to_value(Value *items, int count); /* Python list → Lisp list */
|
|
int value_to_list(Value v, Value **out); /* Lisp list → C array, returns count */
|
|
Formals parse_formals(Value f);
|
|
bool has_internal_defines(ExprList body);
|
|
ExprList body_with_env(Value *forms, int count, Env *env);
|
|
bool is_proper_list(Value v);
|
|
bool values_equal(Value a, Value b);
|
|
|
|
/* Global state */
|
|
extern bool g_auto_compile;
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
* Function declarations — builtins.c
|
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
|
|
Env *make_global_env(void);
|
|
extern const char *PRELUDE;
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
* Function declarations — vm.c
|
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
|
|
CodeObj *make_code(const char *name);
|
|
int code_emit(CodeObj *c, Opcode op, Value arg);
|
|
void code_emit2(CodeObj *c, Opcode op, Value arg, int arg2);
|
|
void code_patch(CodeObj *c, int addr, Value arg);
|
|
|
|
CompiledProc *compile_proc(Proc *p, Env *env);
|
|
Value vm_exec(CodeObj *code, Env *env);
|
|
void bc_compile(Value expr, CodeObj *code, Env *env, bool tail);
|
|
CodeObj *bc_lambda(Value *body, int nbody, Value *params, int nparams,
|
|
Value rest, Env *env, const char *name,
|
|
const char *self_name, Value *self_params, int self_nparams);
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
* Function declarations — portal.c
|
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
|
|
void portal_save(Env *env, const char *path, FullCont *continuation);
|
|
bool portal_resume(const char *path, Env *base_env, Env **out_env, FullCont **out_cont);
|
|
void register_portal_builtins(Env *env);
|
|
|
|
/* Portal checkpoint signal — set to path to trigger save at next VM safe point */
|
|
extern __thread const char *g_portal_checkpoint_path;
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
* Commonly used interned symbols — cached for fast comparison
|
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
|
|
extern Value SYM_QUOTE, SYM_IF, SYM_COND, SYM_AND, SYM_OR;
|
|
extern Value SYM_WHEN, SYM_UNLESS, SYM_BEGIN, SYM_DEFINE, SYM_SET;
|
|
extern Value SYM_LAMBDA, SYM_LAMBDA_UC, SYM_LET, SYM_LET_STAR, SYM_LETREC;
|
|
extern Value SYM_LETREC_STAR, SYM_DO, SYM_QUASIQUOTE, SYM_UNQUOTE;
|
|
extern Value SYM_UNQUOTE_SPLICING, SYM_DEFINE_MACRO, SYM_DEFMACRO;
|
|
extern Value SYM_DEFINE_SYNTAX, SYM_LET_SYNTAX, SYM_LETREC_SYNTAX;
|
|
extern Value SYM_SYNTAX_RULES, SYM_VALUES, SYM_CALL_WITH_VALUES;
|
|
extern Value SYM_CALL_CC, SYM_CALL_CC2, SYM_APPLY, SYM_EVAL;
|
|
extern Value SYM_ERROR, SYM_DEFINE_RECORD_TYPE, SYM_MODULE, SYM_IMPORT;
|
|
extern Value SYM_LOAD, SYM_INCLUDE, SYM_PARAMETERIZE, SYM_DYNAMIC_WIND;
|
|
extern Value SYM_WITH_EXCEPTION_HANDLER, SYM_GUARD, SYM_DEFINE_VALUES;
|
|
extern Value SYM_LET_VALUES, SYM_LET_STAR_VALUES, SYM_CASE;
|
|
extern Value SYM_ELSE, SYM_ARROW, SYM_DOT, SYM_ELLIPSIS, SYM_UNDERSCORE;
|
|
extern Value SYM_EXPORT;
|
|
|
|
void init_symbols(void);
|
|
|
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
* Utility macros
|
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
|
|
#define CAR(v) (AS_PAIR(v)->car)
|
|
#define CDR(v) (AS_PAIR(v)->cdr)
|
|
#define CAAR(v) CAR(CAR(v))
|
|
#define CADR(v) CAR(CDR(v))
|
|
#define CDAR(v) CDR(CAR(v))
|
|
#define CDDR(v) CDR(CDR(v))
|
|
#define CADDR(v) CAR(CDDR(v))
|
|
|
|
/* Is v callable? */
|
|
static inline bool is_callable(Value v) {
|
|
if (IS_BUILTIN(v)) return true;
|
|
if (IS_PTR(v)) {
|
|
ObjType t = obj_type(v);
|
|
return t == OBJ_PROC || t == OBJ_COMPILED_PROC || t == OBJ_CONTINUATION;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Number extraction — works for int, double, rational, bignum */
|
|
static inline bool is_number(Value v) {
|
|
return IS_INT(v) || IS_DOUBLE(v) || IS_RATIONAL(v) || IS_BIGNUM(v);
|
|
}
|
|
|
|
double as_number_double(Value v); /* coerce any number to double */
|
|
int64_t as_number_int(Value v); /* coerce any number to int (truncate) */
|
|
|
|
/* Arithmetic that preserves exactness */
|
|
Value num_add(Value a, Value b);
|
|
Value num_sub(Value a, Value b);
|
|
Value num_mul(Value a, Value b);
|
|
Value num_div(Value a, Value b);
|
|
Value num_neg(Value a);
|
|
bool num_eq(Value a, Value b);
|
|
bool num_lt(Value a, Value b);
|
|
bool num_gt(Value a, Value b);
|
|
bool num_le(Value a, Value b);
|
|
bool num_ge(Value a, Value b);
|
|
|
|
/* Record type registry */
|
|
typedef struct RecordType {
|
|
char *name;
|
|
char **fields;
|
|
int nfields;
|
|
char *parent;
|
|
struct RecordType *next;
|
|
} RecordType;
|
|
|
|
extern RecordType *g_record_types;
|
|
RecordType *find_record_type(const char *name);
|
|
void register_record_type(const char *name, char **fields, int nfields, const char *parent);
|
|
bool is_subtype(const char *child, const char *ancestor);
|
|
|
|
/* Module registry */
|
|
typedef struct Module {
|
|
char *name;
|
|
Env *env;
|
|
char **exports;
|
|
int nexports;
|
|
struct Module *next;
|
|
} Module;
|
|
|
|
extern Module *g_modules;
|
|
|
|
#endif /* LUMBDA_H */
|