Files
Toy/source/toy_value.c
Kayne Ruse 639250f028 WIP return keyword, read more
Functions are having issues with being copied around, especially
between buckets, leading to the scopes getting looped. The program gets
stuck in 'incrementRefCount()'.

It's past my time limit, so I'll keep working on it tomorrow with a
fresh mind.

All function stuff is still untested.

See #163
2025-02-17 19:10:24 +11:00

724 lines
18 KiB
C

#include "toy_value.h"
#include "toy_console_colors.h"
#include "toy_bucket.h"
#include "toy_string.h"
#include "toy_array.h"
#include "toy_table.h"
#include "toy_function.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//utils
static unsigned int hashUInt(unsigned int x) {
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = ((x >> 16) ^ x) * 0x45d9f3b;
x = (x >> 16) ^ x;
return x;
}
#define MAYBE_UNWRAP(value) if (TOY_VALUE_IS_REFERENCE(value)) { value = Toy_unwrapValue(value); }
//exposed functions
Toy_Value Toy_unwrapValue(Toy_Value value) {
//turns out C doesn't have actual references
if (value.type == TOY_VALUE_REFERENCE) {
return Toy_unwrapValue(*(value.as.reference));
}
else {
return value;
}
}
unsigned int Toy_hashValue(Toy_Value value) {
MAYBE_UNWRAP(value);
switch(value.type) {
case TOY_VALUE_NULL:
return 0;
case TOY_VALUE_BOOLEAN:
return value.as.boolean ? 1 : 0;
case TOY_VALUE_INTEGER:
return hashUInt((unsigned int)value.as.integer);
case TOY_VALUE_FLOAT:
return hashUInt( *((unsigned int*)(&value.as.number)) );
case TOY_VALUE_STRING:
return Toy_hashString(value.as.string);
case TOY_VALUE_ARRAY: {
//since array internals can change, recalc the hash each time it's needed
Toy_Array* ptr = value.as.array;
unsigned int hash = 0;
for (unsigned int i = 0; i < ptr->count; i++) {
hash ^= Toy_hashValue(ptr->data[i]);
}
return hash;
}
case TOY_VALUE_TABLE: {
//since table internals can change, recalc the hash each time it's needed
Toy_Table* ptr = value.as.table;
unsigned int hash = 0;
for (unsigned int i = 0; i < ptr->capacity; i++) {
if (TOY_VALUE_IS_NULL(ptr->data[i].key) != true) {
hash ^= Toy_hashValue(ptr->data[i].key);
hash ^= Toy_hashValue(ptr->data[i].value);
}
}
return hash;
}
case TOY_VALUE_FUNCTION:
case TOY_VALUE_OPAQUE:
case TOY_VALUE_ANY:
case TOY_VALUE_REFERENCE:
case TOY_VALUE_UNKNOWN:
fprintf(stderr, TOY_CC_ERROR "ERROR: Can't hash an unknown value type (%d), exiting\n" TOY_CC_RESET, (int)value.type);
exit(-1);
}
return 0;
}
Toy_Value Toy_copyValue(Toy_Value value) {
MAYBE_UNWRAP(value);
switch(value.type) {
case TOY_VALUE_NULL:
case TOY_VALUE_BOOLEAN:
case TOY_VALUE_INTEGER:
case TOY_VALUE_FLOAT:
return value;
case TOY_VALUE_STRING: {
return TOY_VALUE_FROM_STRING(Toy_copyString(value.as.string));
}
case TOY_VALUE_ARRAY: {
//arrays probably won't get copied much
Toy_Array* ptr = value.as.array;
Toy_Array* result = Toy_resizeArray(NULL, ptr->capacity);
for (unsigned int i = 0; i < ptr->count; i++) {
result->data[i] = Toy_copyValue(ptr->data[i]);
}
result->capacity = ptr->capacity;
result->count = ptr->count;
return TOY_VALUE_FROM_ARRAY(result);
}
case TOY_VALUE_TABLE: {
//tables probably won't get copied much
Toy_Table* ptr = value.as.table;
Toy_Table* result = Toy_private_adjustTableCapacity(NULL, ptr->capacity);
for (unsigned int i = 0; i < ptr->capacity; i++) {
if (TOY_VALUE_IS_NULL(ptr->data[i].key) != true) {
result->data[i].key = Toy_copyValue(ptr->data[i].key);
result->data[i].value = Toy_copyValue(ptr->data[i].value);
}
}
result->capacity = ptr->capacity;
result->count = ptr->count;
return TOY_VALUE_FROM_TABLE(result);
}
case TOY_VALUE_FUNCTION:
// return value; //URGENT: concerning
case TOY_VALUE_OPAQUE:
case TOY_VALUE_ANY:
case TOY_VALUE_REFERENCE:
case TOY_VALUE_UNKNOWN:
fprintf(stderr, TOY_CC_ERROR "ERROR: Can't copy an unknown value type, exiting\n" TOY_CC_RESET);
exit(-1);
}
//dummy return
return TOY_VALUE_FROM_NULL();
}
Toy_Value Toy_deepCopyValue(struct Toy_Bucket** bucketHandle, Toy_Value value) {
//this should be the same as Toy_copyValue(), but it forces a deep copy for the strings
MAYBE_UNWRAP(value);
switch(value.type) {
case TOY_VALUE_NULL:
case TOY_VALUE_BOOLEAN:
case TOY_VALUE_INTEGER:
case TOY_VALUE_FLOAT:
return value;
case TOY_VALUE_STRING: {
return TOY_VALUE_FROM_STRING(Toy_deepCopyString(bucketHandle, value.as.string));
}
case TOY_VALUE_ARRAY: {
//arrays probably won't get copied much
Toy_Array* ptr = value.as.array;
Toy_Array* result = Toy_resizeArray(NULL, ptr->capacity);
for (unsigned int i = 0; i < ptr->count; i++) {
result->data[i] = Toy_deepCopyValue(bucketHandle, ptr->data[i]);
}
result->capacity = ptr->capacity;
result->count = ptr->count;
return TOY_VALUE_FROM_ARRAY(result);
}
case TOY_VALUE_TABLE: {
//tables probably won't get copied much
Toy_Table* ptr = value.as.table;
Toy_Table* result = Toy_private_adjustTableCapacity(NULL, ptr->capacity);
for (unsigned int i = 0; i < ptr->capacity; i++) {
if (TOY_VALUE_IS_NULL(ptr->data[i].key) != true) {
result->data[i].key = Toy_deepCopyValue(bucketHandle, ptr->data[i].key);
result->data[i].value = Toy_deepCopyValue(bucketHandle, ptr->data[i].value);
}
}
result->capacity = ptr->capacity;
result->count = ptr->count;
return TOY_VALUE_FROM_TABLE(result);
}
case TOY_VALUE_FUNCTION: {
Toy_Function* fn = Toy_createModuleFunction(bucketHandle, TOY_VALUE_AS_FUNCTION(value)->module.module); //URGENT: concerning
return TOY_VALUE_FROM_FUNCTION(fn);
}
case TOY_VALUE_OPAQUE:
case TOY_VALUE_ANY:
case TOY_VALUE_REFERENCE:
case TOY_VALUE_UNKNOWN:
fprintf(stderr, TOY_CC_ERROR "ERROR: Can't deep-copy an unknown value type, exiting\n" TOY_CC_RESET);
exit(-1);
}
//dummy return
return TOY_VALUE_FROM_NULL();
}
void Toy_freeValue(Toy_Value value) {
switch(value.type) {
case TOY_VALUE_NULL:
case TOY_VALUE_BOOLEAN:
case TOY_VALUE_INTEGER:
case TOY_VALUE_FLOAT:
break;
case TOY_VALUE_STRING: {
Toy_freeString(value.as.string);
break;
}
case TOY_VALUE_ARRAY:
Toy_resizeArray(value.as.array, 0);
break;
case TOY_VALUE_TABLE:
Toy_freeTable(value.as.table);
break;
case TOY_VALUE_REFERENCE:
//don't free references
return;
case TOY_VALUE_FUNCTION:
//not sure this needs to be freed
return;
case TOY_VALUE_OPAQUE:
case TOY_VALUE_ANY:
case TOY_VALUE_UNKNOWN:
fprintf(stderr, TOY_CC_ERROR "ERROR: Can't free an unknown value type, exiting\n" TOY_CC_RESET);
exit(-1);
}
}
bool Toy_checkValueIsTruthy(Toy_Value value) {
MAYBE_UNWRAP(value);
//null is an error
if (value.type == TOY_VALUE_NULL) {
Toy_error("'null' is neither true nor false");
return false;
}
//only 'false' is falsy
if (value.type == TOY_VALUE_BOOLEAN) {
return value.as.boolean;
}
if (value.type == TOY_VALUE_INTEGER) {
return value.as.integer != 0;
}
if (value.type == TOY_VALUE_FLOAT) {
return value.as.number != 0.0f;
}
//anything else is truthy
return true;
}
bool Toy_checkValuesAreEqual(Toy_Value left, Toy_Value right) {
MAYBE_UNWRAP(left);
MAYBE_UNWRAP(right);
switch(left.type) {
case TOY_VALUE_NULL:
return right.type == TOY_VALUE_NULL;
case TOY_VALUE_BOOLEAN:
return right.type == TOY_VALUE_BOOLEAN && left.as.boolean == right.as.boolean;
case TOY_VALUE_INTEGER:
if (right.type == TOY_VALUE_INTEGER) {
return left.as.integer == right.as.integer;
}
else if (right.type == TOY_VALUE_FLOAT) {
return left.as.integer == right.as.number;
}
else {
break;
}
case TOY_VALUE_FLOAT:
if (right.type == TOY_VALUE_INTEGER) {
return left.as.number == right.as.integer;
}
else if (right.type == TOY_VALUE_FLOAT) {
return left.as.number == right.as.number;
}
else {
break;
}
case TOY_VALUE_STRING:
if (right.type == TOY_VALUE_STRING) {
return Toy_compareStrings(left.as.string, right.as.string) == 0;
}
else {
break;
}
case TOY_VALUE_ARRAY: {
if (right.type == TOY_VALUE_ARRAY) {
Toy_Array* leftArray = left.as.array;
Toy_Array* rightArray = right.as.array;
//different lengths is an easy way to check
if (leftArray->count != rightArray->count) {
return false;
}
for (unsigned int i = 0; i < leftArray->count; i++) {
//any mismatch is an easy difference
if (Toy_checkValuesAreEqual(leftArray->data[i], rightArray->data[i]) != true) {
return false;
}
}
}
else {
break;
}
//finally
return true;
}
case TOY_VALUE_TABLE: {
if (right.type == TOY_VALUE_TABLE) {
Toy_Table* leftTable = left.as.table;
Toy_Table* rightTable = right.as.table;
//different counts
if (leftTable->count != rightTable->count) {
return false;
}
for (unsigned int i = 0; i < leftTable->capacity; i++) {
Toy_TableEntry* entry = leftTable->data + i;
if (TOY_VALUE_IS_NULL(entry->key) != true) {
//any mismatch is an easy difference
Toy_Value rightValue = Toy_lookupTable(&rightTable, entry->key);
if (TOY_VALUE_IS_NULL(rightValue) || Toy_checkValuesAreEqual(entry->value, rightValue) != true) {
return false;
}
}
}
}
else {
break;
}
//finally
return true;
}
case TOY_VALUE_FUNCTION:
return false; //URGENT: test this
case TOY_VALUE_OPAQUE:
case TOY_VALUE_ANY:
case TOY_VALUE_REFERENCE:
case TOY_VALUE_UNKNOWN:
fprintf(stderr, TOY_CC_ERROR "ERROR: Unknown types in value equality, exiting\n" TOY_CC_RESET);
exit(-1);
}
return false;
}
bool Toy_checkValuesAreComparable(Toy_Value left, Toy_Value right) {
MAYBE_UNWRAP(left);
MAYBE_UNWRAP(right);
//NOTE: "equal" and "comparable" are different - equal means they're identical, comparable is only possible for certain types
switch(left.type) {
case TOY_VALUE_NULL:
return false;
case TOY_VALUE_BOOLEAN:
return right.type == TOY_VALUE_BOOLEAN;
case TOY_VALUE_INTEGER:
case TOY_VALUE_FLOAT:
return right.type == TOY_VALUE_INTEGER || right.type == TOY_VALUE_FLOAT;
case TOY_VALUE_STRING:
return right.type == TOY_VALUE_STRING;
case TOY_VALUE_ARRAY:
//nothing is comparable with an array
return false;
case TOY_VALUE_TABLE:
//nothing is comparable with a table
return false;
case TOY_VALUE_FUNCTION:
//nothing is comparable with a function
return false;
case TOY_VALUE_OPAQUE:
case TOY_VALUE_ANY:
case TOY_VALUE_REFERENCE:
case TOY_VALUE_UNKNOWN:
fprintf(stderr, TOY_CC_ERROR "Unknown types in value comparison check, exiting\n" TOY_CC_RESET);
exit(-1);
}
return false;
}
int Toy_compareValues(Toy_Value left, Toy_Value right) {
MAYBE_UNWRAP(left);
MAYBE_UNWRAP(right);
//comparison means there's a difference in value, with some kind of quantity - so null, bool, etc. aren't comparable
switch(left.type) {
case TOY_VALUE_NULL:
case TOY_VALUE_BOOLEAN:
break;
case TOY_VALUE_INTEGER:
if (right.type == TOY_VALUE_INTEGER) {
return left.as.integer - right.as.integer;
}
else if (right.type == TOY_VALUE_FLOAT) {
return left.as.integer - right.as.number;
}
else {
break;
}
case TOY_VALUE_FLOAT:
if (right.type == TOY_VALUE_INTEGER) {
return left.as.number - right.as.integer;
}
else if (right.type == TOY_VALUE_FLOAT) {
return left.as.number - right.as.number;
}
else {
break;
}
case TOY_VALUE_STRING:
if (right.type == TOY_VALUE_STRING) {
return Toy_compareStrings(left.as.string, right.as.string);
}
case TOY_VALUE_ARRAY:
break;
case TOY_VALUE_TABLE:
break;
case TOY_VALUE_FUNCTION:
break;
case TOY_VALUE_OPAQUE:
case TOY_VALUE_ANY:
case TOY_VALUE_REFERENCE:
case TOY_VALUE_UNKNOWN:
break;
}
fprintf(stderr, TOY_CC_ERROR "Unknown types in value comparison, exiting\n" TOY_CC_RESET);
exit(-1);
return ~0;
}
Toy_String* Toy_stringifyValue(Toy_Bucket** bucketHandle, Toy_Value value) {
MAYBE_UNWRAP(value);
//TODO: could have "constant" strings that can be referenced, instead of null, true, false, etc. - new string type of 'permanent'
switch(value.type) {
case TOY_VALUE_NULL:
return Toy_createString(bucketHandle, "<null>");
case TOY_VALUE_BOOLEAN:
return Toy_createString(bucketHandle, value.as.boolean ? "<true>" : "<false>");
case TOY_VALUE_INTEGER: {
char buffer[16];
sprintf(buffer, "%d", value.as.integer);
return Toy_createString(bucketHandle, buffer);
}
case TOY_VALUE_FLOAT: {
//using printf
char buffer[16];
sprintf(buffer, "%f", value.as.number);
//BUGFIX: printf format specificer '%f' will set the precision to 6 decimal places, which means there's trailing zeroes
unsigned int length = strlen(buffer);
//find the decimal, if it exists
unsigned int decimal = 0;
while (decimal != length && buffer[decimal] != '.' && buffer[decimal] != ',') decimal++; //'.' and ',' supports more locales
//locales are hard, sorry!
if (decimal != length && buffer[decimal] == ',') buffer[decimal] = '.';
//wipe the trailing zeros
while(decimal != length && buffer[length-1] == '0') buffer[--length] = '\0';
return Toy_createStringLength(bucketHandle, buffer, length);
}
case TOY_VALUE_STRING:
return Toy_copyString(value.as.string);
case TOY_VALUE_ARRAY: {
//TODO: concat + free is definitely a performance nightmare, could make an append function?
Toy_Array* ptr = value.as.array;
//if array is empty, skip below
if (ptr->count == 0) {
Toy_String* empty = Toy_createString(bucketHandle, "[]");
return empty;
}
Toy_String* open = Toy_createStringLength(bucketHandle, "[", 1);
Toy_String* close = Toy_createStringLength(bucketHandle, "]", 1);
Toy_String* comma = Toy_createStringLength(bucketHandle, ",", 1); //reusable
Toy_String* quote = Toy_createStringLength(bucketHandle, "\"", 1); //reusable
bool needsComma = false;
Toy_String* string = open;
for (unsigned int i = 0; i < ptr->count; i++) {
if (needsComma) {
Toy_String* tmp = Toy_concatStrings(bucketHandle, string, comma); //increment ref
Toy_freeString(string); //decrement ref
string = tmp;
}
//get the element
Toy_String* element = Toy_stringifyValue(bucketHandle, ptr->data[i]);
//put quotemarks around internal string elements
if (TOY_VALUE_IS_STRING(ptr->data[i])) {
Toy_String* tmpA = Toy_concatStrings(bucketHandle, quote, element);
Toy_String* tmpB = Toy_concatStrings(bucketHandle, tmpA, quote);
Toy_freeString(element);
Toy_freeString(tmpA);
element = tmpB;
}
//append each element
Toy_String* final = Toy_concatStrings(bucketHandle, string, element);
Toy_freeString(element);
Toy_freeString(string);
string = final;
needsComma = true;
}
//closing bracket
Toy_String* tmp = Toy_concatStrings(bucketHandle, string, close);
Toy_freeString(string);
string = tmp;
//clean up
Toy_freeString(open);
Toy_freeString(close);
Toy_freeString(comma); //TODO: reusable global, or string type "permanent"
Toy_freeString(quote); //TODO: reusable global, or string type "permanent"
return string;
}
case TOY_VALUE_TABLE: {
//TODO: concat + free is definitely a performance nightmare, could make an append function?
Toy_Table* ptr = value.as.table;
//if table is empty, skip below
if (ptr->count == 0) {
Toy_String* empty = Toy_createString(bucketHandle, "[:]");
return empty;
}
Toy_String* open = Toy_createStringLength(bucketHandle, "[", 1);
Toy_String* close = Toy_createStringLength(bucketHandle, "]", 1);
Toy_String* colon = Toy_createStringLength(bucketHandle, ":", 1); //reusable
Toy_String* comma = Toy_createStringLength(bucketHandle, ",", 1); //reusable
Toy_String* quote = Toy_createStringLength(bucketHandle, "\"", 1); //reusable
bool needsComma = false;
Toy_String* string = open;
for (unsigned int i = 0; i < ptr->capacity; i++) {
if (TOY_VALUE_IS_NULL(ptr->data[i].key)) {
continue;
}
if (needsComma) {
Toy_String* tmp = Toy_concatStrings(bucketHandle, string, comma); //increment ref
Toy_freeString(string); //decrement ref
string = tmp;
}
//make the element pair
Toy_String* k = Toy_stringifyValue(bucketHandle, ptr->data[i].key);
Toy_String* v = Toy_stringifyValue(bucketHandle, ptr->data[i].value);
//put quotemarks around internal string elements (key)
if (TOY_VALUE_IS_STRING(ptr->data[i].key)) {
Toy_String* tmpA = Toy_concatStrings(bucketHandle, quote, k);
Toy_String* tmpB = Toy_concatStrings(bucketHandle, tmpA, quote);
Toy_freeString(k);
Toy_freeString(tmpA);
k = tmpB;
}
//put quotemarks around internal string elements (value)
if (TOY_VALUE_IS_STRING(ptr->data[i].value)) {
Toy_String* tmpA = Toy_concatStrings(bucketHandle, quote, v);
Toy_String* tmpB = Toy_concatStrings(bucketHandle, tmpA, quote);
Toy_freeString(v);
Toy_freeString(tmpA);
v = tmpB;
}
//stick the colon between, make the pair
Toy_String* c = Toy_concatStrings(bucketHandle, k, colon);
Toy_String* pair = Toy_concatStrings(bucketHandle, c, v);
//append the element pair
Toy_String* final = Toy_concatStrings(bucketHandle, string, pair);
//do a bunch of freeing so the internal refCounts stay balanced
Toy_freeString(k);
Toy_freeString(v);
Toy_freeString(c);
Toy_freeString(pair);
Toy_freeString(string);
//finally
string = final;
//TODO: would a simple buffer be faster here?
//if there's more elements
needsComma = true;
}
//closing bracket
Toy_String* tmp = Toy_concatStrings(bucketHandle, string, close);
Toy_freeString(string);
string = tmp;
//clean up
Toy_freeString(open);
Toy_freeString(close);
Toy_freeString(colon); //TODO: reusable global, or string type "permanent"
Toy_freeString(comma); //TODO: reusable global, or string type "permanent"
Toy_freeString(quote); //TODO: reusable global, or string type "permanent"
return string;
}
case TOY_VALUE_FUNCTION:
//dummy
return Toy_createString(bucketHandle, "<fn>");
case TOY_VALUE_OPAQUE:
case TOY_VALUE_ANY:
case TOY_VALUE_REFERENCE:
case TOY_VALUE_UNKNOWN:
fprintf(stderr, TOY_CC_ERROR "Unknown types in value stringify, exiting\n" TOY_CC_RESET);
exit(-1);
}
return NULL;
}
const char* Toy_private_getValueTypeAsCString(Toy_ValueType type) {
switch (type) {
case TOY_VALUE_NULL: return "null";
case TOY_VALUE_BOOLEAN: return "bool";
case TOY_VALUE_INTEGER: return "int";
case TOY_VALUE_FLOAT: return "float";
case TOY_VALUE_STRING: return "string";
case TOY_VALUE_ARRAY: return "array";
case TOY_VALUE_TABLE: return "table";
case TOY_VALUE_FUNCTION: return "function";
case TOY_VALUE_OPAQUE: return "opaque";
case TOY_VALUE_ANY: return "any";
case TOY_VALUE_REFERENCE: return "reference";
case TOY_VALUE_UNKNOWN: return "unknown";
}
return NULL;
}