lumbda/c/printer.c
russell@unturf.com 2f342c3be2
c-tier bignum — arbitrary-precision integers unblock secp256k1 widths
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.
2026-06-06 20:23:37 -04:00

274 lines
6.9 KiB
C

/*
* printer.c — show/display/write for Scheme values
*/
#include "lumbda.h"
/* Dynamic string builder */
typedef struct {
char *buf;
size_t len;
size_t cap;
} StringBuilder;
static void sb_init(StringBuilder *sb) {
sb->cap = 128;
sb->buf = (char *)ul_malloc(sb->cap);
sb->buf[0] = '\0';
sb->len = 0;
}
static void sb_append(StringBuilder *sb, const char *s, size_t n) {
while (sb->len + n + 1 > sb->cap) {
sb->cap *= 2;
sb->buf = (char *)ul_realloc(sb->buf, sb->cap);
}
memcpy(sb->buf + sb->len, s, n);
sb->len += n;
sb->buf[sb->len] = '\0';
}
static void sb_appendz(StringBuilder *sb, const char *s) {
sb_append(sb, s, strlen(s));
}
static void sb_appendc(StringBuilder *sb, char c) {
sb_append(sb, &c, 1);
}
static void show_value(Value v, bool display, StringBuilder *sb);
static void show_pair(Value v, bool display, StringBuilder *sb) {
sb_appendc(sb, '(');
Value n = v;
bool first = true;
while (IS_PAIR(n)) {
if (!first) sb_appendc(sb, ' ');
first = false;
show_value(CAR(n), display, sb);
n = CDR(n);
}
if (!IS_NIL(n)) {
sb_appendz(sb, " . ");
show_value(n, display, sb);
}
sb_appendc(sb, ')');
}
static void show_escaped_string(const char *s, size_t len, StringBuilder *sb) {
sb_appendc(sb, '"');
for (size_t i = 0; i < len; i++) {
switch (s[i]) {
case '\\': sb_appendz(sb, "\\\\"); break;
case '"': sb_appendz(sb, "\\\""); break;
case '\n': sb_appendz(sb, "\\n"); break;
case '\t': sb_appendz(sb, "\\t"); break;
case '\r': sb_appendz(sb, "\\r"); break;
default: sb_appendc(sb, s[i]); break;
}
}
sb_appendc(sb, '"');
}
static void show_value(Value v, bool display, StringBuilder *sb) {
if (IS_NIL(v)) { sb_appendz(sb, "()"); return; }
if (IS_VOID(v)) { return; } /* void prints nothing */
if (IS_TRUE(v)) { sb_appendz(sb, "#t"); return; }
if (IS_FALSE(v)){ sb_appendz(sb, "#f"); return; }
if (IS_EOF(v)) { sb_appendz(sb, "#<eof>"); return; }
if (IS_CHAR(v)) {
int c = AS_CHAR(v);
if (display) {
sb_appendc(sb, (char)c);
} else {
sb_appendz(sb, "#\\");
switch (c) {
case ' ': sb_appendz(sb, "space"); break;
case '\n': sb_appendz(sb, "newline"); break;
case '\t': sb_appendz(sb, "tab"); break;
case '\r': sb_appendz(sb, "return"); break;
case '\0': sb_appendz(sb, "null"); break;
case '\x1b': sb_appendz(sb, "escape"); break;
default: sb_appendc(sb, (char)c); break;
}
}
return;
}
if (IS_INT(v)) {
char buf[32];
snprintf(buf, sizeof(buf), "%lld", (long long)as_int(v));
sb_appendz(sb, buf);
return;
}
if (IS_DOUBLE(v)) {
double d = as_double(v);
if (isinf(d)) {
sb_appendz(sb, d > 0 ? "+inf.0" : "-inf.0");
return;
}
if (isnan(d)) {
sb_appendz(sb, "+nan.0");
return;
}
char buf[64];
snprintf(buf, sizeof(buf), "%.17g", d);
/* Ensure a decimal point for Scheme compat */
if (!strchr(buf, '.') && !strchr(buf, 'e') && !strchr(buf, 'n') && !strchr(buf, 'i')) {
size_t n = strlen(buf);
buf[n] = '.'; buf[n+1] = '0'; buf[n+2] = '\0';
}
sb_appendz(sb, buf);
return;
}
if (IS_SYM(v)) {
sb_appendz(sb, sym_name(v));
return;
}
if (IS_RATIONAL(v)) {
Rational *r = AS_RATIONAL(v);
char buf[64];
snprintf(buf, sizeof(buf), "%lld/%lld", (long long)r->num, (long long)r->den);
sb_appendz(sb, buf);
return;
}
if (IS_BIGNUM(v)) {
char *s = bignum_to_str(AS_BIGNUM(v));
sb_appendz(sb, s);
ul_free(s);
return;
}
if (IS_BUILTIN(v)) {
sb_appendz(sb, "#<builtin>");
return;
}
if (!IS_PTR(v)) {
char buf[32];
snprintf(buf, sizeof(buf), "#<unknown:%llx>", (unsigned long long)v);
sb_appendz(sb, buf);
return;
}
/* Heap objects */
ObjType type = obj_type(v);
switch (type) {
case OBJ_PAIR:
show_pair(v, display, sb);
break;
case OBJ_STRING:
case OBJ_MUTABLE_STRING: {
ULString *s = AS_STRING(v);
if (display) {
sb_append(sb, s->data, s->len);
} else {
show_escaped_string(s->data, s->len, sb);
}
break;
}
case OBJ_VECTOR: {
ULVector *vec = AS_VECTOR(v);
sb_appendz(sb, "#(");
for (size_t i = 0; i < vec->len; i++) {
if (i > 0) sb_appendc(sb, ' ');
show_value(vec->data[i], display, sb);
}
sb_appendc(sb, ')');
break;
}
case OBJ_HASHTABLE:
sb_appendz(sb, "#<hash-table>");
break;
case OBJ_PROC: {
Proc *p = AS_PROC(v);
char buf[128];
snprintf(buf, sizeof(buf), "#<procedure %s>", p->name ? p->name : "λ");
sb_appendz(sb, buf);
break;
}
case OBJ_COMPILED_PROC: {
CompiledProc *cp = AS_COMPILED_PROC(v);
char buf[128];
snprintf(buf, sizeof(buf), "#<compiled %s>", cp->name ? cp->name : "λ");
sb_appendz(sb, buf);
break;
}
case OBJ_MACRO:
sb_appendz(sb, "#<macro>");
break;
case OBJ_CONTINUATION:
sb_appendz(sb, "#<continuation>");
break;
case OBJ_PORT: {
ULPort *p = AS_PORT(v);
char buf[64];
snprintf(buf, sizeof(buf), "#<%s-%s-port>",
p->kind == PORT_STRING ? "string" : "file",
p->dir == PORT_INPUT ? "input" : "output");
sb_appendz(sb, buf);
break;
}
case OBJ_ERROR: {
ErrorObject *e = AS_ERROR(v);
char buf[256];
snprintf(buf, sizeof(buf), "#<error-object \"%s\">", e->message);
sb_appendz(sb, buf);
break;
}
case OBJ_ENV:
sb_appendz(sb, "#<environment>");
break;
case OBJ_CODE:
sb_appendz(sb, "#<code>");
break;
case OBJ_SYNTAX_TRANSFORMER:
sb_appendz(sb, "#<syntax-transformer>");
break;
case OBJ_RATIONAL: {
Rational *r = AS_RATIONAL(v);
char buf[64];
snprintf(buf, sizeof(buf), "%lld/%lld", (long long)r->num, (long long)r->den);
sb_appendz(sb, buf);
break;
}
case OBJ_BIGNUM: {
char *s = bignum_to_str(AS_BIGNUM(v));
sb_appendz(sb, s);
ul_free(s);
break;
}
}
}
char *show(Value v, bool display) {
StringBuilder sb;
sb_init(&sb);
show_value(v, display, &sb);
return sb.buf;
}
void print_value(Value v, bool display, FILE *out) {
char *s = show(v, display);
fputs(s, out);
ul_free(s);
}