// values.c -- DVX BASIC value system implementation // // Tagged union values with reference-counted strings. The string // heap uses simple refcounting: assignment increments, scope exit // decrements, zero frees. No garbage collector needed. #include "values.h" #include "../compiler/opcodes.h" #include #include #include #include #include // ============================================================ // String system // ============================================================ // Singleton empty string -- never freed, always available. // Extra byte for the null terminator via the struct hack. static struct { BasStringT hdr; char nul; } sEmptyStringStorage = { { .refCount = 999999, .len = 0, .cap = 1 }, '\0' }; BasStringT *basEmptyString = &sEmptyStringStorage.hdr; BasStringT *basStringAlloc(int32_t cap) { if (cap < 1) { cap = 1; } BasStringT *s = (BasStringT *)malloc(sizeof(BasStringT) + cap); if (!s) { return basStringRef(basEmptyString); } s->refCount = 1; s->len = 0; s->cap = cap; s->data[0] = '\0'; return s; } BasStringT *basStringConcat(const BasStringT *a, const BasStringT *b) { int32_t newLen = a->len + b->len; BasStringT *s = basStringAlloc(newLen + 1); memcpy(s->data, a->data, a->len); memcpy(s->data + a->len, b->data, b->len); s->data[newLen] = '\0'; s->len = newLen; return s; } int32_t basStringCompare(const BasStringT *a, const BasStringT *b) { int32_t minLen = a->len < b->len ? a->len : b->len; int32_t cmp = memcmp(a->data, b->data, minLen); if (cmp != 0) { return cmp; } if (a->len < b->len) { return -1; } if (a->len > b->len) { return 1; } return 0; } int32_t basStringCompareCI(const BasStringT *a, const BasStringT *b) { int32_t minLen = a->len < b->len ? a->len : b->len; for (int32_t i = 0; i < minLen; i++) { int32_t ca = toupper((unsigned char)a->data[i]); int32_t cb = toupper((unsigned char)b->data[i]); if (ca != cb) { return ca - cb; } } if (a->len < b->len) { return -1; } if (a->len > b->len) { return 1; } return 0; } BasStringT *basStringNew(const char *text, int32_t len) { if (!text || len <= 0) { return basStringRef(basEmptyString); } BasStringT *s = basStringAlloc(len + 1); memcpy(s->data, text, len); s->data[len] = '\0'; s->len = len; return s; } BasStringT *basStringRef(BasStringT *s) { if (s) { s->refCount++; } return s; } BasStringT *basStringSub(const BasStringT *s, int32_t start, int32_t len) { if (start < 0) { start = 0; } if (start >= s->len) { return basStringRef(basEmptyString); } if (len < 0 || start + len > s->len) { len = s->len - start; } return basStringNew(s->data + start, len); } void basStringSystemInit(void) { sEmptyStringStorage.nul = '\0'; } void basStringSystemShutdown(void) { // Nothing to do -- empty string is static } void basStringUnref(BasStringT *s) { if (!s || s == basEmptyString) { return; } s->refCount--; if (s->refCount <= 0) { free(s); } } // ============================================================ // Array system // ============================================================ void basArrayFree(BasArrayT *arr) { if (!arr) { return; } if (arr->elements) { for (int32_t i = 0; i < arr->totalElements; i++) { basValRelease(&arr->elements[i]); } free(arr->elements); } free(arr); } int32_t basArrayIndex(BasArrayT *arr, int32_t *indices, int32_t ndims) { if (!arr || ndims != arr->dims) { return -1; } int32_t flatIdx = 0; int32_t multiplier = 1; // Row-major order: last dimension varies fastest for (int32_t d = ndims - 1; d >= 0; d--) { int32_t idx = indices[d] - arr->lbound[d]; int32_t dimSize = arr->ubound[d] - arr->lbound[d] + 1; if (idx < 0 || idx >= dimSize) { return -1; } flatIdx += idx * multiplier; multiplier *= dimSize; } return flatIdx; } BasArrayT *basArrayNew(int32_t dims, int32_t *lbounds, int32_t *ubounds, uint8_t elementType) { if (dims < 1 || dims > BAS_ARRAY_MAX_DIMS) { return NULL; } BasArrayT *arr = (BasArrayT *)calloc(1, sizeof(BasArrayT)); if (!arr) { return NULL; } arr->refCount = 1; arr->elementType = elementType; arr->dims = dims; int32_t total = 1; for (int32_t d = 0; d < dims; d++) { arr->lbound[d] = lbounds[d]; arr->ubound[d] = ubounds[d]; int32_t dimSize = ubounds[d] - lbounds[d] + 1; if (dimSize < 1) { free(arr); return NULL; } total *= dimSize; } arr->totalElements = total; arr->elements = (BasValueT *)calloc(total, sizeof(BasValueT)); if (!arr->elements) { free(arr); return NULL; } // Initialize all elements to the default for the element type for (int32_t i = 0; i < total; i++) { arr->elements[i].type = elementType; } return arr; } BasArrayT *basArrayRef(BasArrayT *arr) { if (arr) { arr->refCount++; } return arr; } void basArrayUnref(BasArrayT *arr) { if (!arr) { return; } arr->refCount--; if (arr->refCount <= 0) { basArrayFree(arr); } } // ============================================================ // UDT system // ============================================================ void basUdtFree(BasUdtT *udt) { if (!udt) { return; } if (udt->fields) { for (int32_t i = 0; i < udt->fieldCount; i++) { basValRelease(&udt->fields[i]); } free(udt->fields); } free(udt); } BasUdtT *basUdtNew(int32_t typeId, int32_t fieldCount) { BasUdtT *udt = (BasUdtT *)calloc(1, sizeof(BasUdtT)); if (!udt) { return NULL; } udt->refCount = 1; udt->typeId = typeId; udt->fieldCount = fieldCount; udt->fields = (BasValueT *)calloc(fieldCount, sizeof(BasValueT)); if (!udt->fields) { free(udt); return NULL; } return udt; } BasUdtT *basUdtRef(BasUdtT *udt) { if (udt) { udt->refCount++; } return udt; } void basUdtUnref(BasUdtT *udt) { if (!udt) { return; } udt->refCount--; if (udt->refCount <= 0) { basUdtFree(udt); } } // ============================================================ // Value constructors // ============================================================ BasValueT basValBool(bool v) { BasValueT val; val.type = BAS_TYPE_BOOLEAN; val.boolVal = v ? -1 : 0; return val; } BasValueT basValObject(void *obj) { BasValueT val; val.type = BAS_TYPE_OBJECT; val.objVal = obj; return val; } BasValueT basValCopy(BasValueT v) { if (v.type == BAS_TYPE_STRING && v.strVal) { basStringRef(v.strVal); } else if (v.type == BAS_TYPE_ARRAY && v.arrVal) { basArrayRef(v.arrVal); } else if (v.type == BAS_TYPE_UDT && v.udtVal) { basUdtRef(v.udtVal); } return v; } BasValueT basValDouble(double v) { BasValueT val; val.type = BAS_TYPE_DOUBLE; val.dblVal = v; return val; } BasValueT basValInteger(int16_t v) { BasValueT val; val.type = BAS_TYPE_INTEGER; val.intVal = v; return val; } BasValueT basValLong(int32_t v) { BasValueT val; val.type = BAS_TYPE_LONG; val.longVal = v; return val; } BasValueT basValSingle(float v) { BasValueT val; val.type = BAS_TYPE_SINGLE; val.sngVal = v; return val; } BasValueT basValString(BasStringT *s) { BasValueT val; val.type = BAS_TYPE_STRING; val.strVal = s ? basStringRef(s) : basStringRef(basEmptyString); return val; } BasValueT basValStringFromC(const char *text) { BasValueT val; val.type = BAS_TYPE_STRING; val.strVal = basStringNew(text, text ? (int32_t)strlen(text) : 0); return val; } void basValRelease(BasValueT *v) { if (v->type == BAS_TYPE_STRING) { basStringUnref(v->strVal); v->strVal = NULL; } else if (v->type == BAS_TYPE_ARRAY) { basArrayUnref(v->arrVal); v->arrVal = NULL; } else if (v->type == BAS_TYPE_UDT) { basUdtUnref(v->udtVal); v->udtVal = NULL; } } // ============================================================ // Type conversion // ============================================================ BasValueT basValToBool(BasValueT v) { return basValBool(basValIsTruthy(v)); } BasValueT basValToDouble(BasValueT v) { return basValDouble(basValToNumber(v)); } BasValueT basValToInteger(BasValueT v) { double n = basValToNumber(v); // Banker's rounding (round half to even) int32_t rounded = (int32_t)(n + (n > 0 ? 0.5 : -0.5)); return basValInteger((int16_t)rounded); } BasValueT basValToLong(BasValueT v) { double n = basValToNumber(v); int32_t rounded = (int32_t)(n + (n > 0 ? 0.5 : -0.5)); return basValLong(rounded); } double basValToNumber(BasValueT v) { switch (v.type) { case BAS_TYPE_INTEGER: return (double)v.intVal; case BAS_TYPE_LONG: return (double)v.longVal; case BAS_TYPE_SINGLE: return (double)v.sngVal; case BAS_TYPE_DOUBLE: return v.dblVal; case BAS_TYPE_BOOLEAN: return (double)v.boolVal; case BAS_TYPE_STRING: if (v.strVal && v.strVal->len > 0) { return atof(v.strVal->data); } return 0.0; default: return 0.0; } } BasValueT basValToSingle(BasValueT v) { return basValSingle((float)basValToNumber(v)); } BasValueT basValToString(BasValueT v) { if (v.type == BAS_TYPE_STRING) { return basValCopy(v); } BasStringT *s = basValFormatString(v); BasValueT result; result.type = BAS_TYPE_STRING; result.strVal = s; return result; } BasStringT *basValFormatString(BasValueT v) { char buf[64]; switch (v.type) { case BAS_TYPE_INTEGER: snprintf(buf, sizeof(buf), "%d", (int)v.intVal); return basStringNew(buf, (int32_t)strlen(buf)); case BAS_TYPE_LONG: snprintf(buf, sizeof(buf), "%ld", (long)v.longVal); return basStringNew(buf, (int32_t)strlen(buf)); case BAS_TYPE_SINGLE: { snprintf(buf, sizeof(buf), "%g", (double)v.sngVal); return basStringNew(buf, (int32_t)strlen(buf)); } case BAS_TYPE_DOUBLE: snprintf(buf, sizeof(buf), "%g", v.dblVal); return basStringNew(buf, (int32_t)strlen(buf)); case BAS_TYPE_BOOLEAN: return basStringNew(v.boolVal ? "True" : "False", v.boolVal ? 4 : 5); case BAS_TYPE_STRING: return v.strVal ? basStringRef(v.strVal) : basStringRef(basEmptyString); default: return basStringRef(basEmptyString); } } bool basValIsTruthy(BasValueT v) { switch (v.type) { case BAS_TYPE_INTEGER: return v.intVal != 0; case BAS_TYPE_LONG: return v.longVal != 0; case BAS_TYPE_SINGLE: return v.sngVal != 0.0f; case BAS_TYPE_DOUBLE: return v.dblVal != 0.0; case BAS_TYPE_BOOLEAN: return v.boolVal != 0; case BAS_TYPE_STRING: return v.strVal && v.strVal->len > 0; default: return false; } } int32_t basValCompare(BasValueT a, BasValueT b) { // String comparison if (a.type == BAS_TYPE_STRING && b.type == BAS_TYPE_STRING) { return basStringCompare(a.strVal ? a.strVal : basEmptyString, b.strVal ? b.strVal : basEmptyString); } // Numeric comparison double na = basValToNumber(a); double nb = basValToNumber(b); if (na < nb) { return -1; } if (na > nb) { return 1; } return 0; } int32_t basValCompareCI(BasValueT a, BasValueT b) { // String comparison (case-insensitive) if (a.type == BAS_TYPE_STRING && b.type == BAS_TYPE_STRING) { return basStringCompareCI(a.strVal ? a.strVal : basEmptyString, b.strVal ? b.strVal : basEmptyString); } // Numeric comparison (same as basValCompare) double na = basValToNumber(a); double nb = basValToNumber(b); if (na < nb) { return -1; } if (na > nb) { return 1; } return 0; } uint8_t basValPromoteType(uint8_t a, uint8_t b) { // String stays string (concat, not arithmetic) if (a == BAS_TYPE_STRING || b == BAS_TYPE_STRING) { return BAS_TYPE_STRING; } // Double wins over everything if (a == BAS_TYPE_DOUBLE || b == BAS_TYPE_DOUBLE) { return BAS_TYPE_DOUBLE; } // Single wins over integer/long if (a == BAS_TYPE_SINGLE || b == BAS_TYPE_SINGLE) { return BAS_TYPE_SINGLE; } // Long wins over integer if (a == BAS_TYPE_LONG || b == BAS_TYPE_LONG) { return BAS_TYPE_LONG; } return BAS_TYPE_INTEGER; }