DVX_GUI/src/apps/kpunch/dvxbasic/runtime/values.c

704 lines
16 KiB
C

// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// 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 <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ============================================================
// 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;
// Function prototypes (alphabetical)
void basArrayFree(BasArrayT *arr);
int32_t basArrayIndex(BasArrayT *arr, int32_t *indices, int32_t ndims);
BasArrayT *basArrayNew(int32_t dims, int32_t *lbounds, int32_t *ubounds, uint8_t elementType);
BasArrayT *basArrayRef(BasArrayT *arr);
void basArrayUnref(BasArrayT *arr);
BasStringT *basStringAlloc(int32_t cap);
int32_t basStringCompare(const BasStringT *a, const BasStringT *b);
int32_t basStringCompareCI(const BasStringT *a, const BasStringT *b);
BasStringT *basStringConcat(const BasStringT *a, const BasStringT *b);
BasStringT *basStringNew(const char *text, int32_t len);
BasStringT *basStringRef(BasStringT *s);
BasStringT *basStringSub(const BasStringT *s, int32_t start, int32_t len);
void basStringSystemInit(void);
void basStringSystemShutdown(void);
void basStringUnref(BasStringT *s);
void basUdtFree(BasUdtT *udt);
BasUdtT *basUdtNew(int32_t typeId, int32_t fieldCount);
BasUdtT *basUdtRef(BasUdtT *udt);
void basUdtUnref(BasUdtT *udt);
BasValueT basValBool(bool v);
int32_t basValCompare(BasValueT a, BasValueT b);
int32_t basValCompareCI(BasValueT a, BasValueT b);
BasValueT basValCopy(BasValueT v);
BasValueT basValDouble(double v);
BasStringT *basValFormatString(BasValueT v);
BasValueT basValInteger(int16_t v);
bool basValIsTruthy(BasValueT v);
BasValueT basValLong(int32_t v);
BasValueT basValObject(void *obj);
uint8_t basValPromoteType(uint8_t a, uint8_t b);
void basValRelease(BasValueT *v);
BasValueT basValSingle(float v);
BasValueT basValString(BasStringT *s);
BasValueT basValStringFromC(const char *text);
BasValueT basValToBool(BasValueT v);
BasValueT basValToDouble(BasValueT v);
BasValueT basValToInteger(BasValueT v);
BasValueT basValToLong(BasValueT v);
double basValToNumber(BasValueT v);
BasValueT basValToSingle(BasValueT v);
BasValueT basValToString(BasValueT v);
// ============================================================
// 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);
}
}
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;
}
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 *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;
}
BasStringT *basStringNew(const char *text, int32_t len) {
if (!text || len <= 0) {
return basStringRef(basEmptyString);
}
BasStringT *s = basStringAlloc(len + 1);
if (s->cap >= 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);
}
}
// ============================================================
// 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;
}
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;
}
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;
}
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);
}
}
BasValueT basValInteger(int16_t v) {
BasValueT val;
val.type = BAS_TYPE_INTEGER;
val.intVal = v;
return val;
}
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;
}
}
BasValueT basValLong(int32_t v) {
BasValueT val;
val.type = BAS_TYPE_LONG;
val.longVal = v;
return val;
}
BasValueT basValObject(void *obj) {
BasValueT val;
val.type = BAS_TYPE_OBJECT;
val.objVal = obj;
return val;
}
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;
}
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;
}
}
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;
}
// ============================================================
// 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;
}