Files
centvrion/centvrion/compiler/runtime/cent_runtime.c
2026-04-22 12:35:00 +02:00

1237 lines
40 KiB
C

#include "cent_runtime.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include <unistd.h>
#include <regex.h>
#include <curl/curl.h>
#include <microhttpd.h>
/* ------------------------------------------------------------------ */
/* Global arena */
/* ------------------------------------------------------------------ */
CentArena *cent_arena;
int cent_magnvm = 0;
/* ------------------------------------------------------------------ */
/* Portable xorshift32 RNG (matches Python _CentRng) */
/* ------------------------------------------------------------------ */
static uint32_t cent_rng_state = 1;
static uint32_t cent_rng_next(void) {
uint32_t x = cent_rng_state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
cent_rng_state = x;
return x;
}
jmp_buf _cent_try_stack[CENT_TRY_STACK_MAX];
int _cent_try_depth = 0;
const char *_cent_error_msg = NULL;
/* ------------------------------------------------------------------ */
/* Arena allocator */
/* ------------------------------------------------------------------ */
CentArena *cent_arena_new(size_t cap) {
CentArena *a = malloc(sizeof(CentArena));
if (!a) { fputs("cent: out of memory\n", stderr); exit(1); }
a->buf = malloc(cap);
if (!a->buf) { fputs("cent: out of memory\n", stderr); exit(1); }
a->used = 0;
a->cap = cap;
a->next = NULL;
return a;
}
static size_t align8(size_t n) { return (n + 7) & ~(size_t)7; }
void *cent_arena_alloc(CentArena *a, size_t n) {
n = align8(n);
CentArena *cur = a;
for (;;) {
if (cur->used + n <= cur->cap) {
void *p = cur->buf + cur->used;
cur->used += n;
return p;
}
if (!cur->next) {
size_t new_cap = cur->cap > n ? cur->cap : n;
cur->next = cent_arena_new(new_cap);
}
cur = cur->next;
}
}
/* ------------------------------------------------------------------ */
/* Error handling */
/* ------------------------------------------------------------------ */
void cent_type_error(const char *msg) {
if (_cent_try_depth > 0) {
_cent_error_msg = msg;
longjmp(_cent_try_stack[_cent_try_depth - 1], 1);
}
fprintf(stderr, "CENTVRION type error: %s\n", msg);
exit(1);
}
void cent_runtime_error(const char *msg) {
if (_cent_try_depth > 0) {
_cent_error_msg = msg;
longjmp(_cent_try_stack[_cent_try_depth - 1], 1);
}
fprintf(stderr, "CENTVRION error: %s\n", msg);
exit(1);
}
/* ------------------------------------------------------------------ */
/* Scope operations */
/* ------------------------------------------------------------------ */
CentValue cent_scope_get(CentScope *s, const char *name) {
for (int i = 0; i < s->len; i++) {
if (strcmp(s->names[i], name) == 0)
return s->vals[i];
}
fprintf(stderr, "CENTVRION error: undefined variable '%s'\n", name);
exit(1);
}
void cent_scope_set(CentScope *s, const char *name, CentValue v) {
for (int i = 0; i < s->len; i++) {
if (strcmp(s->names[i], name) == 0) {
s->vals[i] = v;
return;
}
}
if (s->len >= s->cap) {
int new_cap = s->cap ? s->cap * 2 : 8;
const char **new_names = cent_arena_alloc(cent_arena, new_cap * sizeof(char *));
CentValue *new_vals = cent_arena_alloc(cent_arena, new_cap * sizeof(CentValue));
if (s->len > 0) {
memcpy(new_names, s->names, s->len * sizeof(char *));
memcpy(new_vals, s->vals, s->len * sizeof(CentValue));
}
s->names = new_names;
s->vals = new_vals;
s->cap = new_cap;
}
s->names[s->len] = name;
s->vals[s->len] = v;
s->len++;
}
CentScope cent_scope_copy(CentScope *s) {
CentScope dst;
dst.len = s->len;
dst.cap = s->cap;
if (s->cap > 0) {
dst.names = cent_arena_alloc(cent_arena, s->cap * sizeof(char *));
dst.vals = cent_arena_alloc(cent_arena, s->cap * sizeof(CentValue));
if (s->len > 0) {
memcpy(dst.names, s->names, s->len * sizeof(char *));
memcpy(dst.vals, s->vals, s->len * sizeof(CentValue));
}
} else {
dst.names = NULL;
dst.vals = NULL;
}
return dst;
}
/* ------------------------------------------------------------------ */
/* Roman numeral conversion */
/* ------------------------------------------------------------------ */
/* Descending table used for both int→roman and roman→int */
static const struct { const char *str; long val; } ROMAN_TABLE[] = {
{"M", 1000}, {"CM", 900}, {"D", 500}, {"CD", 400},
{"C", 100}, {"XC", 90}, {"L", 50}, {"XL", 40},
{"X", 10}, {"IX", 9}, {"V", 5}, {"IV", 4},
{"I", 1}
};
#define ROMAN_TABLE_LEN 13
/* Transform a 1-3999 Roman string into its x1000 equivalent.
Each char: 'I' -> "M" (since I*1000 = M), others -> char + "_". */
static void transform_thousands(const char *src, char *dst, size_t dstsz) {
size_t pos = 0;
for (const char *p = src; *p; p++) {
if (*p == 'I') {
if (pos + 1 >= dstsz) cent_runtime_error("Roman numeral buffer overflow");
dst[pos++] = 'M';
} else {
if (pos + 2 >= dstsz) cent_runtime_error("Roman numeral buffer overflow");
dst[pos++] = *p;
dst[pos++] = '_';
}
}
dst[pos] = '\0';
}
void cent_int_to_roman(long n, char *buf, size_t bufsz) {
if (n == 0) {
if (bufsz > 6) { memcpy(buf, "NVLLVS", 6); buf[6] = '\0'; }
return;
}
if (n < 0 || (n > 3999 && !cent_magnvm))
cent_runtime_error("number out of range for Roman numerals");
size_t pos = 0;
if (n > 3999) {
char base[64];
cent_int_to_roman(n / 1000, base, sizeof(base));
char transformed[128];
transform_thousands(base, transformed, sizeof(transformed));
size_t tlen = strlen(transformed);
if (tlen >= bufsz) cent_runtime_error("Roman numeral buffer overflow");
memcpy(buf, transformed, tlen);
pos = tlen;
n = n % 1000;
if (n == 0) { buf[pos] = '\0'; return; }
}
for (int i = 0; i < ROMAN_TABLE_LEN && n > 0; i++) {
while (n >= ROMAN_TABLE[i].val) {
size_t slen = strlen(ROMAN_TABLE[i].str);
if (pos + slen >= bufsz)
cent_runtime_error("Roman numeral buffer overflow");
memcpy(buf + pos, ROMAN_TABLE[i].str, slen);
pos += slen;
n -= ROMAN_TABLE[i].val;
}
}
buf[pos] = '\0';
}
long cent_roman_to_int(const char *s) {
long result = 0;
int pos = 0;
int slen = (int)strlen(s);
if (slen == 0)
cent_runtime_error("empty Roman numeral");
while (pos < slen) {
int matched = 0;
for (int i = 0; i < ROMAN_TABLE_LEN; i++) {
int tlen = (int)strlen(ROMAN_TABLE[i].str);
if (strncmp(s + pos, ROMAN_TABLE[i].str, tlen) == 0) {
result += ROMAN_TABLE[i].val;
pos += tlen;
matched = 1;
break;
}
}
if (!matched) {
fprintf(stderr, "CENTVRION error: invalid Roman numeral: %s\n", s);
exit(1);
}
}
return result;
}
/* ------------------------------------------------------------------ */
/* Display */
/* ------------------------------------------------------------------ */
/* Write value as string into buf[0..bufsz-1]; return chars needed */
/* (not counting null). buf may be NULL (just count mode). */
static int write_val(CentValue v, char *buf, int bufsz) {
char tmp[64];
int n;
switch (v.type) {
case CENT_INT:
cent_int_to_roman(v.ival, tmp, sizeof(tmp));
n = (int)strlen(tmp);
if (buf && n < bufsz) { memcpy(buf, tmp, n); buf[n] = '\0'; }
return n;
case CENT_STR:
n = (int)strlen(v.sval);
if (buf && n < bufsz) { memcpy(buf, v.sval, n); buf[n] = '\0'; }
return n;
case CENT_BOOL:
if (v.bval) {
if (buf && bufsz > 7) { memcpy(buf, "VERITAS", 7); buf[7] = '\0'; }
return 7;
} else {
if (buf && bufsz > 8) { memcpy(buf, "FALSITAS", 8); buf[8] = '\0'; }
return 8;
}
case CENT_NULL:
if (buf && bufsz > 6) { memcpy(buf, "NVLLVS", 6); buf[6] = '\0'; }
return 6;
case CENT_FRAC: {
long num = v.fval.num, den = v.fval.den;
if (den < 0) { num = -num; den = -den; }
if (num < 0)
cent_runtime_error("cannot display negative numbers without SVBNVLLA");
long int_part = num / den;
long rem_num = num % den;
/* Integer part (omit leading zero for pure fractions) */
char int_buf[64] = {0};
int int_len = 0;
if (int_part > 0 || rem_num == 0) {
cent_int_to_roman(int_part, int_buf, sizeof(int_buf));
int_len = (int)strlen(int_buf);
}
/* Duodecimal fractional expansion — mirrors fraction_to_frac() */
char frac_buf[64] = {0};
int frac_pos = 0;
long cur_num = rem_num, cur_den = den;
for (int lvl = 0; lvl < 6 && cur_num != 0; lvl++) {
if (lvl > 0) frac_buf[frac_pos++] = '|';
long level_int = (cur_num * 12) / cur_den;
cur_num = (cur_num * 12) % cur_den;
for (int i = 0; i < (int)(level_int / 6); i++) frac_buf[frac_pos++] = 'S';
for (int i = 0; i < (int)((level_int % 6) / 2); i++) frac_buf[frac_pos++] = ':';
for (int i = 0; i < (int)((level_int % 6) % 2); i++) frac_buf[frac_pos++] = '.';
}
n = int_len + frac_pos;
if (buf && n < bufsz) {
memcpy(buf, int_buf, int_len);
memcpy(buf + int_len, frac_buf, frac_pos);
buf[n] = '\0';
}
return n;
}
case CENT_LIST: {
/* "[elem1 elem2 ...]" */
int total = 2; /* '[' + ']' */
for (int i = 0; i < v.lval.len; i++) {
if (i > 0) total++; /* space separator */
total += write_val(v.lval.items[i], NULL, 0);
}
if (!buf) return total;
int pos = 0;
buf[pos++] = '[';
for (int i = 0; i < v.lval.len; i++) {
if (i > 0) buf[pos++] = ' ';
int written = write_val(v.lval.items[i], buf + pos, bufsz - pos);
pos += written;
}
buf[pos++] = ']';
if (pos < bufsz) buf[pos] = '\0';
return total;
}
case CENT_FUNC:
if (buf && bufsz > 7) { memcpy(buf, "FVNCTIO", 7); buf[7] = '\0'; }
return 7;
case CENT_DICT: {
/* "{key VT val, key VT val}" */
int total = 2; /* '{' + '}' */
for (int i = 0; i < v.dval.len; i++) {
if (i > 0) total += 2; /* ", " */
total += write_val(v.dval.keys[i], NULL, 0);
total += 4; /* " VT " */
total += write_val(v.dval.vals[i], NULL, 0);
}
if (!buf) return total;
int pos = 0;
buf[pos++] = '{';
for (int i = 0; i < v.dval.len; i++) {
if (i > 0) { buf[pos++] = ','; buf[pos++] = ' '; }
pos += write_val(v.dval.keys[i], buf + pos, bufsz - pos);
memcpy(buf + pos, " VT ", 4); pos += 4;
pos += write_val(v.dval.vals[i], buf + pos, bufsz - pos);
}
buf[pos++] = '}';
if (pos < bufsz) buf[pos] = '\0';
return total;
}
default:
cent_runtime_error("cannot display value");
return 0;
}
}
char *cent_make_string(CentValue v) {
int len = write_val(v, NULL, 0);
char *buf = cent_arena_alloc(cent_arena, len + 1);
write_val(v, buf, len + 1);
return buf;
}
/* ------------------------------------------------------------------ */
/* Arithmetic and comparison operators */
/* ------------------------------------------------------------------ */
static long gcd(long a, long b) {
a = a < 0 ? -a : a;
b = b < 0 ? -b : b;
while (b) { long t = b; b = a % b; a = t; }
return a ? a : 1;
}
static CentValue frac_reduce(long num, long den) {
if (den < 0) { num = -num; den = -den; }
long g = gcd(num < 0 ? -num : num, den);
return cent_frac(num / g, den / g);
}
static void to_frac(CentValue v, long *num, long *den) {
if (v.type == CENT_INT) { *num = v.ival; *den = 1; }
else if (v.type == CENT_NULL) { *num = 0; *den = 1; }
else { *num = v.fval.num; *den = v.fval.den; }
}
CentValue cent_add(CentValue a, CentValue b) {
if (a.type == CENT_INT && b.type == CENT_INT)
return cent_int(a.ival + b.ival);
if (a.type == CENT_NULL && b.type == CENT_NULL)
return cent_null();
if ((a.type == CENT_INT || a.type == CENT_FRAC || a.type == CENT_NULL) &&
(b.type == CENT_INT || b.type == CENT_FRAC || b.type == CENT_NULL)) {
long an, ad, bn, bd;
to_frac(a, &an, &ad); to_frac(b, &bn, &bd);
return frac_reduce(an * bd + bn * ad, ad * bd);
}
if (a.type == CENT_STR || b.type == CENT_STR)
cent_type_error("'+' cannot be used with strings; use '&' for concatenation");
else
cent_type_error("'+' requires two integers");
return cent_null();
}
CentValue cent_concat(CentValue a, CentValue b) {
const char *sa = (a.type == CENT_NULL) ? "" : cent_make_string(a);
const char *sb = (b.type == CENT_NULL) ? "" : cent_make_string(b);
int la = (int)strlen(sa), lb = (int)strlen(sb);
char *s = cent_arena_alloc(cent_arena, la + lb + 1);
memcpy(s, sa, la);
memcpy(s + la, sb, lb);
s[la + lb] = '\0';
return cent_str(s);
}
CentValue cent_sub(CentValue a, CentValue b) {
if (a.type == CENT_INT && b.type == CENT_INT)
return cent_int(a.ival - b.ival);
if ((a.type == CENT_INT || a.type == CENT_FRAC || a.type == CENT_NULL) &&
(b.type == CENT_INT || b.type == CENT_FRAC || b.type == CENT_NULL)) {
long an, ad, bn, bd;
to_frac(a, &an, &ad); to_frac(b, &bn, &bd);
return frac_reduce(an * bd - bn * ad, ad * bd);
}
cent_type_error("'-' requires two integers");
return cent_null();
}
CentValue cent_mul(CentValue a, CentValue b) {
if (a.type == CENT_INT && b.type == CENT_INT)
return cent_int(a.ival * b.ival);
if ((a.type == CENT_INT || a.type == CENT_FRAC || a.type == CENT_NULL) &&
(b.type == CENT_INT || b.type == CENT_FRAC || b.type == CENT_NULL)) {
long an, ad, bn, bd;
to_frac(a, &an, &ad); to_frac(b, &bn, &bd);
return frac_reduce(an * bn, ad * bd);
}
cent_type_error("'*' requires two integers");
return cent_null();
}
CentValue cent_div(CentValue a, CentValue b) {
if (a.type != CENT_INT || b.type != CENT_INT)
cent_type_error("'/' requires two integers");
if (b.ival == 0)
cent_runtime_error("division by zero");
return cent_int(a.ival / b.ival);
}
CentValue cent_div_frac(CentValue a, CentValue b) {
long an, ad, bn, bd;
to_frac(a, &an, &ad); to_frac(b, &bn, &bd);
if (bn == 0) cent_runtime_error("division by zero");
return frac_reduce(an * bd, ad * bn);
}
CentValue cent_mod(CentValue a, CentValue b) {
if (a.type != CENT_INT || b.type != CENT_INT)
cent_type_error("'RELIQVVM' requires two integers");
if (b.ival == 0)
cent_runtime_error("modulo by zero");
return cent_int(a.ival % b.ival);
}
CentValue cent_mod_frac(CentValue a, CentValue b) {
long an, ad, bn, bd;
to_frac(a, &an, &ad); to_frac(b, &bn, &bd);
if (bn == 0) cent_runtime_error("modulo by zero");
/* a/b mod c/d over a common denominator ad*bd:
num_a = an*bd, num_b = bn*ad
result = (num_a - floor(num_a/num_b) * num_b) / (ad*bd)
Use floored division so the result matches Python's Fraction.__mod__. */
long num_a = an * bd;
long num_b = bn * ad;
long q = num_a / num_b;
if ((num_a % num_b != 0) && ((num_a < 0) != (num_b < 0))) q -= 1;
long new_num = num_a - q * num_b;
return frac_reduce(new_num, ad * bd);
}
CentValue cent_eq(CentValue a, CentValue b) {
if ((a.type == CENT_INT && a.ival == 0 && b.type == CENT_NULL) ||
(a.type == CENT_NULL && b.type == CENT_INT && b.ival == 0))
return cent_bool(1);
if ((a.type == CENT_INT || a.type == CENT_FRAC) &&
(b.type == CENT_INT || b.type == CENT_FRAC)) {
long an, ad, bn, bd;
to_frac(a, &an, &ad); to_frac(b, &bn, &bd);
return cent_bool(an * bd == bn * ad);
}
if (a.type != b.type) return cent_bool(0);
switch (a.type) {
case CENT_STR: return cent_bool(strcmp(a.sval, b.sval) == 0);
case CENT_BOOL: return cent_bool(a.bval == b.bval);
case CENT_FUNC: return cent_bool(a.fnval.fn == b.fnval.fn);
case CENT_NULL: return cent_bool(1);
default:
cent_type_error("'EST' not supported for this type");
return cent_null();
}
}
CentValue cent_neq(CentValue a, CentValue b) {
CentValue r = cent_eq(a, b);
return cent_bool(!r.bval);
}
CentValue cent_lt(CentValue a, CentValue b) {
if ((a.type == CENT_INT || a.type == CENT_FRAC || a.type == CENT_NULL) &&
(b.type == CENT_INT || b.type == CENT_FRAC || b.type == CENT_NULL)) {
long an, ad, bn, bd;
to_frac(a, &an, &ad); to_frac(b, &bn, &bd);
return cent_bool(an * bd < bn * ad);
}
cent_type_error("'MINVS' requires two integers");
return cent_null();
}
CentValue cent_gt(CentValue a, CentValue b) {
if ((a.type == CENT_INT || a.type == CENT_FRAC || a.type == CENT_NULL) &&
(b.type == CENT_INT || b.type == CENT_FRAC || b.type == CENT_NULL)) {
long an, ad, bn, bd;
to_frac(a, &an, &ad); to_frac(b, &bn, &bd);
return cent_bool(an * bd > bn * ad);
}
cent_type_error("'PLVS' requires two integers");
return cent_null();
}
CentValue cent_and(CentValue a, CentValue b) {
if (a.type != CENT_BOOL || b.type != CENT_BOOL)
cent_type_error("'ET' requires two booleans");
return cent_bool(a.bval && b.bval);
}
CentValue cent_or(CentValue a, CentValue b) {
if (a.type != CENT_BOOL || b.type != CENT_BOOL)
cent_type_error("'AVT' requires two booleans");
return cent_bool(a.bval || b.bval);
}
/* ------------------------------------------------------------------ */
/* Builtin functions */
/* ------------------------------------------------------------------ */
void cent_dic(CentValue v) {
char *s = cent_make_string(v);
fputs(s, stdout);
fputc('\n', stdout);
}
void cent_everre(void) {
fputs("\033[2J\033[H", stdout);
fflush(stdout);
}
CentValue cent_avdi(void) {
char *buf = cent_arena_alloc(cent_arena, 1024);
if (!fgets(buf, 1024, stdin)) {
buf[0] = '\0';
return cent_str(buf);
}
int len = (int)strlen(buf);
if (len > 0 && buf[len - 1] == '\n')
buf[len - 1] = '\0';
return cent_str(buf);
}
CentValue cent_avdi_numerus(void) {
CentValue s = cent_avdi();
return cent_int(cent_roman_to_int(s.sval));
}
CentValue cent_numerus(CentValue s) {
if (s.type != CENT_STR)
cent_type_error("'NVMERVS' expects a string");
return cent_int(cent_roman_to_int(s.sval));
}
CentValue cent_longitudo(CentValue v) {
if (v.type == CENT_LIST) return cent_int(v.lval.len);
if (v.type == CENT_STR) return cent_int((long)strlen(v.sval));
if (v.type == CENT_DICT) return cent_int(v.dval.len);
cent_type_error("'LONGITVDO' requires a list, string, or dict");
return cent_null(); /* unreachable; silences warning */
}
CentValue cent_typvs(CentValue v) {
switch (v.type) {
case CENT_INT: return cent_str("NVMERVS");
case CENT_STR: return cent_str("LITTERA");
case CENT_BOOL: return cent_str("VERAX");
case CENT_LIST: return cent_str("CATALOGVS");
case CENT_FRAC: return cent_str("FRACTIO");
case CENT_DICT: return cent_str("TABVLA");
case CENT_FUNC: return cent_str("FVNCTIO");
case CENT_NULL: return cent_str("NVLLVS");
}
return cent_str("IGNOTA"); /* unreachable */
}
void cent_dormi(CentValue n) {
struct timespec ts;
if (n.type == CENT_NULL) {
ts.tv_sec = 0; ts.tv_nsec = 0;
} else if (n.type == CENT_INT) {
ts.tv_sec = n.ival; ts.tv_nsec = 0;
} else if (n.type == CENT_FRAC) {
long sec = n.fval.num / n.fval.den;
long rem = n.fval.num % n.fval.den;
ts.tv_sec = sec;
ts.tv_nsec = rem * 1000000000L / n.fval.den;
} else {
cent_type_error("'DORMI' requires a number or NVLLVS");
}
nanosleep(&ts, NULL);
}
/* ---- SCRIPTA module ---- */
CentValue cent_lege(CentValue path) {
const char *p = cent_make_string(path);
FILE *f = fopen(p, "r");
if (!f) cent_runtime_error("LEGE: cannot open file");
fseek(f, 0, SEEK_END);
long len = ftell(f);
fseek(f, 0, SEEK_SET);
char *buf = cent_arena_alloc(cent_arena, len + 1);
fread(buf, 1, len, f);
buf[len] = '\0';
fclose(f);
return cent_str(buf);
}
void cent_scribe(CentValue path, CentValue content) {
const char *p = cent_make_string(path);
const char *c = cent_make_string(content);
FILE *f = fopen(p, "w");
if (!f) cent_runtime_error("SCRIBE: cannot open file");
fputs(c, f);
fclose(f);
}
void cent_adivnge(CentValue path, CentValue content) {
const char *p = cent_make_string(path);
const char *c = cent_make_string(content);
FILE *f = fopen(p, "a");
if (!f) cent_runtime_error("ADIVNGE: cannot open file");
fputs(c, f);
fclose(f);
}
CentValue cent_fortuitus_numerus(CentValue lo, CentValue hi) {
if (lo.type != CENT_INT || hi.type != CENT_INT)
cent_type_error("'FORTVITVS_NVMERVS' requires two integers");
long range = hi.ival - lo.ival + 1;
if (range <= 0)
cent_runtime_error("'FORTVITVS_NVMERVS' requires lo <= hi");
return cent_int(lo.ival + cent_rng_next() % range);
}
CentValue cent_fortuita_electionis(CentValue lst) {
if (lst.type != CENT_LIST)
cent_type_error("'FORTVITA_ELECTIO' requires a list");
if (lst.lval.len == 0)
cent_runtime_error("'FORTVITA_ELECTIO' requires a non-empty list");
return lst.lval.items[cent_rng_next() % lst.lval.len];
}
CentValue cent_senatus(CentValue *args, int n) {
/* Single array argument: unpack it */
if (n == 1 && args[0].type == CENT_LIST) {
n = args[0].lval.len;
args = args[0].lval.items;
}
int true_count = 0;
for (int i = 0; i < n; i++) {
if (args[i].type != CENT_BOOL)
cent_type_error("'SENATVS' requires boolean arguments");
if (args[i].bval) true_count++;
}
return cent_bool(true_count * 2 > n);
}
CentValue cent_decimatio(CentValue lst) {
if (lst.type != CENT_LIST)
cent_type_error("'DECIMATIO' requires a list");
int len = lst.lval.len;
/* Copy the list so we can remove in-place */
CentValue result = cent_list_new(len);
for (int i = 0; i < len; i++)
cent_list_push(&result, lst.lval.items[i]);
int to_remove = result.lval.len / 10;
for (int i = 0; i < to_remove; i++) {
int idx = cent_rng_next() % result.lval.len;
/* Shift remaining elements left */
for (int j = idx; j < result.lval.len - 1; j++)
result.lval.items[j] = result.lval.items[j + 1];
result.lval.len--;
}
return result;
}
void cent_semen(CentValue seed) {
if (seed.type != CENT_INT)
cent_type_error("'SEMEN' requires an integer seed");
cent_rng_state = (uint32_t)seed.ival;
if (cent_rng_state == 0) cent_rng_state = 1;
}
static int _ordina_comparator(const void *a, const void *b) {
const CentValue *va = (const CentValue *)a;
const CentValue *vb = (const CentValue *)b;
if ((va->type == CENT_INT || va->type == CENT_FRAC) &&
(vb->type == CENT_INT || vb->type == CENT_FRAC)) {
long an, ad, bn, bd;
to_frac(*va, &an, &ad);
to_frac(*vb, &bn, &bd);
long lhs = an * bd;
long rhs = bn * ad;
return (lhs > rhs) - (lhs < rhs);
}
if (va->type == CENT_STR && vb->type == CENT_STR)
return strcmp(va->sval, vb->sval);
cent_type_error("'ORDINA' requires all elements to be the same type");
return 0;
}
CentValue cent_ordina(CentValue lst) {
if (lst.type != CENT_LIST)
cent_type_error("'ORDINA' requires a list");
int len = lst.lval.len;
CentValue result = cent_list_new(len);
for (int i = 0; i < len; i++)
cent_list_push(&result, lst.lval.items[i]);
if (len > 1)
qsort(result.lval.items, len, sizeof(CentValue), _ordina_comparator);
return result;
}
/* ------------------------------------------------------------------ */
/* Array helpers */
/* ------------------------------------------------------------------ */
CentValue cent_list_new(int cap) {
CentValue *items = cent_arena_alloc(cent_arena, cap * sizeof(CentValue));
return cent_list(items, 0, cap);
}
void cent_list_push(CentValue *lst, CentValue v) {
if (lst->lval.len >= lst->lval.cap) {
int new_cap = lst->lval.cap ? lst->lval.cap * 2 : 8;
CentValue *new_items = cent_arena_alloc(cent_arena, new_cap * sizeof(CentValue));
if (lst->lval.len > 0)
memcpy(new_items, lst->lval.items, lst->lval.len * sizeof(CentValue));
lst->lval.items = new_items;
lst->lval.cap = new_cap;
}
lst->lval.items[lst->lval.len++] = v;
}
CentValue cent_list_index(CentValue lst, CentValue idx) {
if (lst.type == CENT_DICT)
return cent_dict_get(lst, idx);
if (lst.type == CENT_STR) {
long i;
if (idx.type == CENT_INT)
i = idx.ival;
else if (idx.type == CENT_FRAC && idx.fval.den == 1)
i = idx.fval.num;
else
cent_type_error("string index must be an integer");
long slen = (long)strlen(lst.sval);
if (i < 1 || i > slen)
cent_runtime_error("string index out of range");
char *ch = cent_arena_alloc(cent_arena, 2);
ch[0] = lst.sval[i - 1];
ch[1] = '\0';
return cent_str(ch);
}
if (lst.type != CENT_LIST)
cent_type_error("index requires a list or dict");
long i;
if (idx.type == CENT_INT)
i = idx.ival;
else if (idx.type == CENT_FRAC && idx.fval.den == 1)
i = idx.fval.num;
else
cent_type_error("list index must be an integer");
if (i < 1 || i > lst.lval.len)
cent_runtime_error("list index out of range");
return lst.lval.items[i - 1];
}
CentValue cent_list_slice(CentValue lst, CentValue lo, CentValue hi) {
if (lst.type == CENT_STR) {
if (lo.type != CENT_INT || hi.type != CENT_INT)
cent_type_error("slice indices must be integers");
long from = lo.ival;
long to = hi.ival;
long slen = (long)strlen(lst.sval);
if (from < 1 || to > slen || from > to)
cent_runtime_error("string slice out of range");
int len = (int)(to - from + 1);
char *buf = cent_arena_alloc(cent_arena, len + 1);
memcpy(buf, lst.sval + from - 1, len);
buf[len] = '\0';
return cent_str(buf);
}
if (lst.type != CENT_LIST)
cent_type_error("slice requires a list");
if (lo.type != CENT_INT || hi.type != CENT_INT)
cent_type_error("slice indices must be integers");
long from = lo.ival;
long to = hi.ival;
if (from < 1 || to > lst.lval.len || from > to)
cent_runtime_error("slice out of range");
int len = (int)(to - from + 1);
CentValue result = cent_list_new(len);
for (long j = from; j <= to; j++)
cent_list_push(&result, lst.lval.items[j - 1]);
return result;
}
void cent_list_index_set(CentValue *lst, CentValue idx, CentValue v) {
if (lst->type == CENT_DICT) {
cent_dict_set(lst, idx, v);
return;
}
if (lst->type != CENT_LIST)
cent_type_error("index-assign requires a list or dict");
if (idx.type != CENT_INT)
cent_type_error("list index must be an integer");
long i = idx.ival;
if (i < 1 || i > lst->lval.len)
cent_runtime_error("list index out of range");
lst->lval.items[i - 1] = v;
}
/* ------------------------------------------------------------------ */
/* Dict helpers */
/* ------------------------------------------------------------------ */
static int _cent_key_eq(CentValue a, CentValue b) {
if (a.type != b.type) return 0;
if (a.type == CENT_INT) return a.ival == b.ival;
if (a.type == CENT_STR) return strcmp(a.sval, b.sval) == 0;
return 0;
}
CentValue cent_dict_new(int cap) {
if (cap < 4) cap = 4;
CentValue *keys = cent_arena_alloc(cent_arena, cap * sizeof(CentValue));
CentValue *vals = cent_arena_alloc(cent_arena, cap * sizeof(CentValue));
return cent_dict_val(keys, vals, 0, cap);
}
void cent_dict_set(CentValue *dict, CentValue key, CentValue val) {
if (dict->type != CENT_DICT)
cent_type_error("dict-set requires a dict");
for (int i = 0; i < dict->dval.len; i++) {
if (_cent_key_eq(dict->dval.keys[i], key)) {
dict->dval.vals[i] = val;
return;
}
}
if (dict->dval.len >= dict->dval.cap) {
int new_cap = dict->dval.cap * 2;
CentValue *new_keys = cent_arena_alloc(cent_arena, new_cap * sizeof(CentValue));
CentValue *new_vals = cent_arena_alloc(cent_arena, new_cap * sizeof(CentValue));
memcpy(new_keys, dict->dval.keys, dict->dval.len * sizeof(CentValue));
memcpy(new_vals, dict->dval.vals, dict->dval.len * sizeof(CentValue));
dict->dval.keys = new_keys;
dict->dval.vals = new_vals;
dict->dval.cap = new_cap;
}
dict->dval.keys[dict->dval.len] = key;
dict->dval.vals[dict->dval.len] = val;
dict->dval.len++;
}
CentValue cent_dict_get(CentValue dict, CentValue key) {
if (dict.type != CENT_DICT)
cent_type_error("dict-get requires a dict");
for (int i = 0; i < dict.dval.len; i++) {
if (_cent_key_eq(dict.dval.keys[i], key))
return dict.dval.vals[i];
}
cent_runtime_error("Key not found in dict");
return cent_null();
}
CentValue cent_dict_keys(CentValue dict) {
if (dict.type != CENT_DICT)
cent_type_error("CLAVES requires a dict");
CentValue result = cent_list_new(dict.dval.len);
for (int i = 0; i < dict.dval.len; i++)
cent_list_push(&result, dict.dval.keys[i]);
return result;
}
/* ------------------------------------------------------------------ */
/* Regex */
/* ------------------------------------------------------------------ */
CentValue cent_qvaere(CentValue pattern, CentValue text) {
if (pattern.type != CENT_STR || text.type != CENT_STR)
cent_type_error("'QVAERE' requires two strings");
regex_t re;
int rc = regcomp(&re, pattern.sval, REG_EXTENDED);
if (rc != 0) {
char errbuf[256];
regerror(rc, &re, errbuf, sizeof(errbuf));
regfree(&re);
cent_runtime_error(errbuf);
}
CentValue result = cent_list_new(8);
const char *cursor = text.sval;
regmatch_t match;
while (*cursor && regexec(&re, cursor, 1, &match, 0) == 0) {
int len = match.rm_eo - match.rm_so;
char *buf = cent_arena_alloc(cent_arena, len + 1);
memcpy(buf, cursor + match.rm_so, len);
buf[len] = '\0';
cent_list_push(&result, cent_str(buf));
cursor += match.rm_eo;
if (len == 0) cursor++; // avoid infinite loop on zero-length match
}
regfree(&re);
return result;
}
/* Expand replacement string, substituting \1..\9 with captured groups */
static void _expand_replacement(const char *repl, const char *subject,
regmatch_t *matches, int ngroups,
char **out, size_t *opos, size_t *ocap) {
for (const char *r = repl; *r; r++) {
if (*r == '\\' && r[1] >= '1' && r[1] <= '9') {
int g = r[1] - '0';
r++;
if (g < ngroups && matches[g].rm_so != -1) {
size_t glen = matches[g].rm_eo - matches[g].rm_so;
while (*opos + glen + 1 > *ocap) {
*ocap *= 2;
char *newbuf = cent_arena_alloc(cent_arena, *ocap);
memcpy(newbuf, *out, *opos);
*out = newbuf;
}
memcpy(*out + *opos, subject + matches[g].rm_so, glen);
*opos += glen;
}
} else if (*r == '\\' && r[1] == '\\') {
/* escaped backslash → literal \ */
if (*opos + 2 > *ocap) {
*ocap *= 2;
char *newbuf = cent_arena_alloc(cent_arena, *ocap);
memcpy(newbuf, *out, *opos);
*out = newbuf;
}
(*out)[(*opos)++] = '\\';
r++;
} else {
if (*opos + 2 > *ocap) {
*ocap *= 2;
char *newbuf = cent_arena_alloc(cent_arena, *ocap);
memcpy(newbuf, *out, *opos);
*out = newbuf;
}
(*out)[(*opos)++] = *r;
}
}
}
CentValue cent_svbstitve(CentValue pattern, CentValue replacement, CentValue text) {
if (pattern.type != CENT_STR || replacement.type != CENT_STR || text.type != CENT_STR)
cent_type_error("'SVBSTITVE' requires three strings");
regex_t re;
int rc = regcomp(&re, pattern.sval, REG_EXTENDED);
if (rc != 0) {
char errbuf[256];
regerror(rc, &re, errbuf, sizeof(errbuf));
regfree(&re);
cent_runtime_error(errbuf);
}
size_t text_len = strlen(text.sval);
size_t repl_len = strlen(replacement.sval);
size_t cap = text_len + repl_len * 4 + 1;
char *result = cent_arena_alloc(cent_arena, cap);
size_t rpos = 0;
const char *cursor = text.sval;
int ngroups = (int)re.re_nsub + 1;
if (ngroups > 10) ngroups = 10;
regmatch_t matches[10];
while (*cursor && regexec(&re, cursor, ngroups, matches, 0) == 0) {
/* copy text before match */
size_t prefix_len = matches[0].rm_so;
while (rpos + prefix_len + 1 > cap) {
cap *= 2;
char *newbuf = cent_arena_alloc(cent_arena, cap);
memcpy(newbuf, result, rpos);
result = newbuf;
}
memcpy(result + rpos, cursor, prefix_len);
rpos += prefix_len;
/* expand replacement with backreferences */
_expand_replacement(replacement.sval, cursor, matches, ngroups,
&result, &rpos, &cap);
cursor += matches[0].rm_eo;
if (matches[0].rm_eo == 0) cursor++;
}
/* copy remaining text */
size_t tail_len = strlen(cursor);
while (rpos + tail_len + 1 > cap) {
cap *= 2;
char *newbuf = cent_arena_alloc(cent_arena, cap);
memcpy(newbuf, result, rpos);
result = newbuf;
}
memcpy(result + rpos, cursor, tail_len);
rpos += tail_len;
result[rpos] = '\0';
regfree(&re);
return cent_str(result);
}
CentValue cent_scinde(CentValue str, CentValue delim) {
if (str.type != CENT_STR || delim.type != CENT_STR)
cent_type_error("'SCINDE' requires two strings");
const char *s = str.sval;
const char *d = delim.sval;
size_t dlen = strlen(d);
CentValue result = cent_list_new(8);
if (dlen == 0) {
/* empty delimiter: split into individual characters */
for (const char *p = s; *p; p++) {
char *buf = cent_arena_alloc(cent_arena, 2);
buf[0] = *p;
buf[1] = '\0';
cent_list_push(&result, cent_str(buf));
}
return result;
}
const char *cursor = s;
for (;;) {
const char *found = strstr(cursor, d);
if (!found) {
cent_list_push(&result, cent_str(cursor));
break;
}
size_t len = found - cursor;
char *buf = cent_arena_alloc(cent_arena, len + 1);
memcpy(buf, cursor, len);
buf[len] = '\0';
cent_list_push(&result, cent_str(buf));
cursor = found + dlen;
}
return result;
}
/* ------------------------------------------------------------------ */
/* Networking (RETE) */
/* ------------------------------------------------------------------ */
typedef struct {
char *buf;
size_t len;
size_t cap;
} CurlBuf;
static size_t _curl_write_cb(void *data, size_t size, size_t nmemb, void *userp) {
size_t bytes = size * nmemb;
CurlBuf *cb = (CurlBuf *)userp;
while (cb->len + bytes + 1 > cb->cap) {
cb->cap = cb->cap ? cb->cap * 2 : 4096;
cb->buf = realloc(cb->buf, cb->cap);
if (!cb->buf) cent_runtime_error("PETE: out of memory");
}
memcpy(cb->buf + cb->len, data, bytes);
cb->len += bytes;
cb->buf[cb->len] = '\0';
return bytes;
}
CentValue cent_pete(CentValue url) {
if (url.type != CENT_STR)
cent_type_error("'PETE' requires a string URL");
CURL *curl = curl_easy_init();
if (!curl) cent_runtime_error("PETE: failed to init curl");
CurlBuf cb = {0};
curl_easy_setopt(curl, CURLOPT_URL, url.sval);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _curl_write_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &cb);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
CURLcode res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if (res != CURLE_OK) {
free(cb.buf);
cent_runtime_error(curl_easy_strerror(res));
}
char *arena_buf = cent_arena_alloc(cent_arena, cb.len + 1);
memcpy(arena_buf, cb.buf, cb.len + 1);
free(cb.buf);
return cent_str(arena_buf);
}
/* ------------------------------------------------------------------ */
/* Server (RETE) */
/* ------------------------------------------------------------------ */
#define CENT_MAX_ROUTES 64
typedef struct {
const char *path;
CentFuncInfo handler;
CentScope scope;
} CentRoute;
static CentRoute _cent_routes[CENT_MAX_ROUTES];
static int _cent_route_count = 0;
void cent_petitvr(CentValue path, CentValue handler, CentScope scope) {
if (path.type != CENT_STR)
cent_type_error("PETITVR requires a string path");
if (handler.type != CENT_FUNC)
cent_type_error("PETITVR requires a function handler");
if (_cent_route_count >= CENT_MAX_ROUTES)
cent_runtime_error("PETITVR: too many routes");
_cent_routes[_cent_route_count].path = path.sval;
_cent_routes[_cent_route_count].handler = handler.fnval;
_cent_routes[_cent_route_count].scope = cent_scope_copy(&scope);
_cent_route_count++;
}
static enum MHD_Result
_cent_request_handler(void *cls, struct MHD_Connection *conn,
const char *url, const char *method,
const char *version, const char *upload_data,
size_t *upload_data_size, void **con_cls) {
(void)cls; (void)version; (void)upload_data;
(void)upload_data_size; (void)con_cls;
/* Find matching route */
CentFuncInfo *fi = NULL;
CentScope *captured_scope = NULL;
/* Split url at '?' to match path only */
const char *qmark = strchr(url, '?');
size_t path_len = qmark ? (size_t)(qmark - url) : strlen(url);
for (int i = 0; i < _cent_route_count; i++) {
if (strlen(_cent_routes[i].path) == path_len
&& strncmp(_cent_routes[i].path, url, path_len) == 0) {
fi = &_cent_routes[i].handler;
captured_scope = &_cent_routes[i].scope;
break;
}
}
if (!fi) {
const char *msg = "CDIV — not found\n";
struct MHD_Response *resp = MHD_create_response_from_buffer(
strlen(msg), (void *)msg, MHD_RESPMEM_PERSISTENT);
enum MHD_Result ret = MHD_queue_response(conn, MHD_HTTP_NOT_FOUND, resp);
MHD_destroy_response(resp);
return ret;
}
/* Build request dict: via, quaestio, methodus */
char *via_buf = cent_arena_alloc(cent_arena, path_len + 1);
memcpy(via_buf, url, path_len);
via_buf[path_len] = '\0';
const char *qs = qmark ? qmark + 1 : "";
size_t qs_len = strlen(qs);
char *qs_buf = cent_arena_alloc(cent_arena, qs_len + 1);
memcpy(qs_buf, qs, qs_len + 1);
CentValue dict = cent_dict_new(4);
cent_dict_set(&dict, cent_str("via"), cent_str(via_buf));
cent_dict_set(&dict, cent_str("quaestio"), cent_str(qs_buf));
cent_dict_set(&dict, cent_str("methodus"), cent_str(method));
/* Call handler — start from the scope captured at registration time */
CentScope call_scope = cent_scope_copy(captured_scope);
if (fi->param_count == 1) {
cent_scope_set(&call_scope, fi->param_names[0], dict);
}
CentValue result = fi->fn(call_scope);
/* Build response */
const char *body = "";
size_t body_len = 0;
if (result.type == CENT_STR) {
body = result.sval;
body_len = strlen(body);
} else if (result.type != CENT_NULL) {
char *s = cent_make_string(result);
body = s;
body_len = strlen(s);
}
struct MHD_Response *resp = MHD_create_response_from_buffer(
body_len, (void *)body, MHD_RESPMEM_PERSISTENT);
MHD_add_response_header(resp, "Content-Type", "text/plain");
enum MHD_Result ret = MHD_queue_response(conn, MHD_HTTP_OK, resp);
MHD_destroy_response(resp);
return ret;
}
void cent_avscvlta(CentValue port) {
if (port.type != CENT_INT)
cent_type_error("AVSCVLTA requires an integer port");
if (_cent_route_count == 0)
cent_runtime_error("AVSCVLTA: no routes registered");
struct MHD_Daemon *d = MHD_start_daemon(
MHD_USE_INTERNAL_POLLING_THREAD,
(uint16_t)port.ival, NULL, NULL,
_cent_request_handler, NULL,
MHD_OPTION_END);
if (!d) cent_runtime_error("AVSCVLTA: failed to start server");
/* Block forever */
while (1) pause();
}
/* ------------------------------------------------------------------ */
/* Initialisation */
/* ------------------------------------------------------------------ */
void cent_init(void) {
cent_arena = cent_arena_new(1024 * 1024); /* 1 MiB initial arena */
cent_rng_state = (uint32_t)time(NULL);
if (cent_rng_state == 0) cent_rng_state = 1;
}