704 lines
16 KiB
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;
|
|
}
|