From b092b8ce50c2125cea41b11cbcf6da62643393c7 Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Tue, 24 Dec 2024 16:08:42 +1100 Subject: [PATCH] Added tables to the scripts, untested This also has a lot of bugfixing. --- scripts/tables.toy | 33 ++++++++ scripts/valgrind.toy | 5 ++ source/toy_array.c | 11 +-- source/toy_ast.h | 5 +- source/toy_parser.c | 34 ++++++-- source/toy_routine.c | 20 ++++- source/toy_scope.c | 2 +- source/toy_table.c | 18 +++- source/toy_table.h | 5 +- source/toy_value.c | 195 +++++++++++++++++++++++++++++++++++++------ source/toy_value.h | 4 + source/toy_vm.c | 94 ++++++++++++++++++--- 12 files changed, 371 insertions(+), 55 deletions(-) create mode 100644 scripts/tables.toy create mode 100644 scripts/valgrind.toy diff --git a/scripts/tables.toy b/scripts/tables.toy new file mode 100644 index 0000000..08ad0e8 --- /dev/null +++ b/scripts/tables.toy @@ -0,0 +1,33 @@ +//1-D table +var a = ["alpha": 1, "beta": 2, "gamma": 3]; +a["beta"] = 6; + +print a; +assert a == ["alpha": 1, "beta": 6, "gamma": 3], "1-D tables failed"; + +//nested +var b = [ + "outer": ["inner": true], + "alpha": 1, + "beta": 2, + "gamma": 3 +]; + +print b; +assert b == ["alpha": 1, "beta": 2, "gamma": 3, "outer": ["inner": true]], "nested tables failed"; + +//test empty tables +var empty = [:]; + +print empty; +assert empty == [:], "empty tables failed"; + +//test trailing commas +var trailing = [ + "alpha":1, + "beta":2, + "gamma":3, +]; + +print trailing; +assert trailing == ["alpha": 1, "beta": 2, "gamma": 3], "trailing tables failed"; \ No newline at end of file diff --git a/scripts/valgrind.toy b/scripts/valgrind.toy new file mode 100644 index 0000000..0a4dea8 --- /dev/null +++ b/scripts/valgrind.toy @@ -0,0 +1,5 @@ +var barr = [ + [1, 2, 3], +// [4, 5, 6], +// [7, 8, 9] +]; \ No newline at end of file diff --git a/source/toy_array.c b/source/toy_array.c index 2640e71..aa778f4 100644 --- a/source/toy_array.c +++ b/source/toy_array.c @@ -5,11 +5,6 @@ #include Toy_Array* Toy_resizeArray(Toy_Array* paramArray, unsigned int capacity) { - if (capacity == 0) { - free(paramArray); - return NULL; - } - //if some values will be removed, free them first if (paramArray != NULL && paramArray->count > capacity) { for (unsigned int i = capacity; i < paramArray->count; i++) { @@ -17,6 +12,12 @@ Toy_Array* Toy_resizeArray(Toy_Array* paramArray, unsigned int capacity) { } } + //if you're freeing everything, just return + if (capacity == 0) { + free(paramArray); + return NULL; + } + unsigned int originalCapacity = paramArray == NULL ? 0 : paramArray->capacity; Toy_Array* array = realloc(paramArray, capacity * sizeof(Toy_Value) + sizeof(Toy_Array)); diff --git a/source/toy_ast.h b/source/toy_ast.h index 0f3ad81..9509fbd 100644 --- a/source/toy_ast.h +++ b/source/toy_ast.h @@ -60,9 +60,10 @@ typedef enum Toy_AstFlag { TOY_AST_FLAG_COMPARE_GREATER_EQUAL = 25, TOY_AST_FLAG_COMPOUND_ARRAY = 30, - // TOY_AST_FLAG_COMPOUND_TABLE = 31, + TOY_AST_FLAG_COMPOUND_TABLE = 31, TOY_AST_FLAG_COLLECTION = 32, - TOY_AST_FLAG_INDEX = 33, + TOY_AST_FLAG_PAIR = 33, + TOY_AST_FLAG_INDEX = 34, TOY_AST_FLAG_AND = 40, TOY_AST_FLAG_OR = 41, diff --git a/source/toy_parser.c b/source/toy_parser.c index ad38200..7106519 100644 --- a/source/toy_parser.c +++ b/source/toy_parser.c @@ -206,7 +206,7 @@ static ParsingTuple parsingRulesetTable[] = { {PREC_OR,NULL,binary},// TOY_TOKEN_OPERATOR_OR, {PREC_NONE,unary,NULL},// TOY_TOKEN_OPERATOR_NEGATE, {PREC_NONE,NULL,NULL},// TOY_TOKEN_OPERATOR_QUESTION, - {PREC_NONE,NULL,NULL},// TOY_TOKEN_OPERATOR_COLON, + {PREC_GROUP,compound,aggregate},// TOY_TOKEN_OPERATOR_COLON, {PREC_NONE,NULL,NULL},// TOY_TOKEN_OPERATOR_SEMICOLON, // ; {PREC_GROUP,NULL,aggregate},// TOY_TOKEN_OPERATOR_COMMA, // , @@ -576,21 +576,39 @@ static Toy_AstFlag compound(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_A return TOY_AST_FLAG_NONE; } + //BUGFIX: special case for empty tables + if (match(parser, TOY_TOKEN_OPERATOR_COLON)) { + consume(parser, TOY_TOKEN_OPERATOR_BRACKET_RIGHT, "Expected ']' at the end of empty table"); + Toy_private_emitAstPass(bucketHandle, rootHandle); + Toy_private_emitAstAggregate(bucketHandle, rootHandle, TOY_AST_FLAG_PAIR, *rootHandle); + Toy_private_emitAstCompound(bucketHandle, rootHandle, TOY_AST_FLAG_COMPOUND_TABLE); + return TOY_AST_FLAG_NONE; + } + parsePrecedence(bucketHandle, parser, rootHandle, PREC_GROUP); + //peek inside to see what you have + Toy_AstFlag flag = TOY_AST_FLAG_NONE; + if ((*rootHandle)->type == TOY_AST_AGGREGATE) { + //1 element in a table will mean the top-level node is a collection + flag = (*rootHandle)->aggregate.flag; + + //more than 1 element in a table will mean you need to check the last element in the collection to find a pair + if (flag == TOY_AST_FLAG_COLLECTION && (*rootHandle)->aggregate.right->type == TOY_AST_AGGREGATE) { + flag = (*rootHandle)->aggregate.right->aggregate.flag; //yes, this is hacky + } + } + //BUGFIX: special case for trailing commas if (parser->previous.type == TOY_TOKEN_OPERATOR_BRACKET_RIGHT && parser->current.type != TOY_TOKEN_OPERATOR_BRACKET_RIGHT) { - Toy_private_emitAstCompound(bucketHandle, rootHandle, TOY_AST_FLAG_COMPOUND_ARRAY); - //NOTE: will probably need tweaking for tables + Toy_private_emitAstCompound(bucketHandle, rootHandle, flag == TOY_AST_FLAG_PAIR ? TOY_AST_FLAG_COMPOUND_TABLE : TOY_AST_FLAG_COMPOUND_ARRAY); return TOY_AST_FLAG_NONE; } consume(parser, TOY_TOKEN_OPERATOR_BRACKET_RIGHT, "Expected ']' at the end of compound expression"); - Toy_private_emitAstCompound(bucketHandle, rootHandle, TOY_AST_FLAG_COMPOUND_ARRAY); + Toy_private_emitAstCompound(bucketHandle, rootHandle, flag == TOY_AST_FLAG_PAIR ? TOY_AST_FLAG_COMPOUND_TABLE : TOY_AST_FLAG_COMPOUND_ARRAY); return TOY_AST_FLAG_NONE; - - //TODO: read in a table } else if (parser->previous.type == TOY_TOKEN_OPERATOR_BRACKET_RIGHT) { //allows for trailing commas @@ -613,6 +631,10 @@ static Toy_AstFlag aggregate(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_ parsePrecedence(bucketHandle, parser, rootHandle, PREC_GROUP); //NOT +1, as compounds are right-recursive return TOY_AST_FLAG_COLLECTION; } + if (parser->previous.type == TOY_TOKEN_OPERATOR_COLON) { + parsePrecedence(bucketHandle, parser, rootHandle, PREC_GROUP); //NOT +1, as compounds are right-recursive + return TOY_AST_FLAG_PAIR; + } else if (parser->previous.type == TOY_TOKEN_OPERATOR_BRACKET_LEFT) { parsePrecedence(bucketHandle, parser, rootHandle, PREC_GROUP); consume(parser, TOY_TOKEN_OPERATOR_BRACKET_RIGHT, "Expected ']' at the end of index expression"); diff --git a/source/toy_routine.c b/source/toy_routine.c index a9eb42a..8ba98c1 100644 --- a/source/toy_routine.c +++ b/source/toy_routine.c @@ -266,7 +266,7 @@ static unsigned int writeInstructionCompound(Toy_Routine** rt, Toy_AstCompound a unsigned int result = writeRoutineCode(rt, ast.child); if (ast.flag == TOY_AST_FLAG_COMPOUND_ARRAY) { - //signal how many values to read in as an array value + //signal how many values to read in as array elements EMIT_BYTE(rt, code, TOY_OPCODE_READ); EMIT_BYTE(rt, code, TOY_VALUE_ARRAY); @@ -279,6 +279,20 @@ static unsigned int writeInstructionCompound(Toy_Routine** rt, Toy_AstCompound a return 1; //leaves only 1 value on the stack } + if (ast.flag == TOY_AST_FLAG_COMPOUND_TABLE) { + //signal how many values to read in as table elements + EMIT_BYTE(rt, code, TOY_OPCODE_READ); + EMIT_BYTE(rt, code, TOY_VALUE_TABLE); + + //4-byte alignment + EMIT_BYTE(rt, code,0); + EMIT_BYTE(rt, code,0); + + //how many elements + EMIT_INT(rt, code, result); + + return 1; //leaves only 1 value on the stack + } else { fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST compound flag found\n" TOY_CC_RESET); exit(-1); @@ -297,6 +311,10 @@ static unsigned int writeInstructionAggregate(Toy_Routine** rt, Toy_AstAggregate //collections are handled above return result; } + else if (ast.flag == TOY_AST_FLAG_PAIR) { + //pairs are handled above + return result; + } else if (ast.flag == TOY_AST_FLAG_INDEX) { //value[index, length] EMIT_BYTE(rt, code, TOY_OPCODE_INDEX); diff --git a/source/toy_scope.c b/source/toy_scope.c index 36e8c33..7f36224 100644 --- a/source/toy_scope.c +++ b/source/toy_scope.c @@ -175,7 +175,7 @@ Toy_Value* Toy_accessScopeAsPointer(Toy_Scope* scope, Toy_String* key) { char buffer[key->info.length + 256]; sprintf(buffer, "Undefined variable: %s\n", key->name.data); Toy_error(buffer); - NULL; + return NULL; } return &(entryPtr->value); diff --git a/source/toy_table.c b/source/toy_table.c index 6f5ed2a..98ee437 100644 --- a/source/toy_table.c +++ b/source/toy_table.c @@ -114,7 +114,7 @@ void Toy_insertTable(Toy_Table** tableHandle, Toy_Value key, Toy_Value value) { probeAndInsert(tableHandle, key, value); } -Toy_Value Toy_lookupTable(Toy_Table** tableHandle, Toy_Value key) { +Toy_TableEntry* Toy_private_lookupTableEntryPtr(Toy_Table** tableHandle, Toy_Value key) { if (TOY_VALUE_IS_NULL(key) || TOY_VALUE_IS_BOOLEAN(key)) { //TODO: disallow functions and opaques Toy_error(TOY_CC_ERROR "ERROR: Bad table key\n" TOY_CC_RESET); } @@ -125,12 +125,12 @@ Toy_Value Toy_lookupTable(Toy_Table** tableHandle, Toy_Value key) { while (true) { //found the entry if (Toy_checkValuesAreEqual((*tableHandle)->data[probe].key, key)) { - return (*tableHandle)->data[probe].value; + return (*tableHandle)->data + probe; } //if its an empty slot if (TOY_VALUE_IS_NULL((*tableHandle)->data[probe].key)) { - return TOY_VALUE_FROM_NULL(); + return NULL; } //adjust and continue @@ -139,6 +139,18 @@ Toy_Value Toy_lookupTable(Toy_Table** tableHandle, Toy_Value key) { } } +Toy_Value Toy_lookupTable(Toy_Table** tableHandle, Toy_Value key) { + //TODO: I hacked this in at a moment's notice, clean it up + Toy_TableEntry* entry = Toy_private_lookupTableEntryPtr(tableHandle, key); + + if (entry == NULL) { + return TOY_VALUE_FROM_NULL(); + } + else { + return entry->value; + } +} + void Toy_removeTable(Toy_Table** tableHandle, Toy_Value key) { if (TOY_VALUE_IS_NULL(key) || TOY_VALUE_IS_BOOLEAN(key)) { //TODO: disallow functions and opaques Toy_error(TOY_CC_ERROR "ERROR: Bad table key\n" TOY_CC_RESET); diff --git a/source/toy_table.h b/source/toy_table.h index d395bfd..78fa103 100644 --- a/source/toy_table.h +++ b/source/toy_table.h @@ -10,7 +10,7 @@ typedef struct Toy_TableEntry { //32 | 64 BITNESS unsigned int psl; //4 | 4 } Toy_TableEntry; //20 | 20 -//key-value table (contains = count + tombstones) +//key-value table typedef struct Toy_Table { //32 | 64 BITNESS unsigned int capacity; //4 | 4 unsigned int count; //4 | 4 @@ -26,7 +26,8 @@ TOY_API Toy_Value Toy_lookupTable(Toy_Table** tableHandle, Toy_Value key); TOY_API void Toy_removeTable(Toy_Table** tableHandle, Toy_Value key); //NOTE: exposed to skip unnecessary allocations within Toy_Scope -TOY_API Toy_Table* Toy_private_adjustTableCapacity(Toy_Table* oldTable, unsigned int newCapacity); +TOY_API Toy_Table* Toy_private_adjustTableCapacity(Toy_Table* oldTable, unsigned int newCapacity); //TODO: make it public +TOY_API Toy_TableEntry* Toy_private_lookupTableEntryPtr(Toy_Table** tableHandle, Toy_Value key); //TODO: make it public? //some useful sizes, could be swapped out as needed #ifndef TOY_TABLE_INITIAL_CAPACITY diff --git a/source/toy_value.c b/source/toy_value.c index 9abe6f4..a6d6602 100644 --- a/source/toy_value.c +++ b/source/toy_value.c @@ -4,6 +4,7 @@ #include "toy_bucket.h" #include "toy_string.h" #include "toy_array.h" +#include "toy_table.h" #include #include @@ -59,7 +60,21 @@ unsigned int Toy_hashValue(Toy_Value value) { return hash; } - case TOY_VALUE_TABLE: + 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_TYPE: @@ -102,7 +117,23 @@ Toy_Value Toy_copyValue(Toy_Value value) { return TOY_VALUE_FROM_ARRAY(result); } - case TOY_VALUE_TABLE: + 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: case TOY_VALUE_OPAQUE: case TOY_VALUE_TYPE: @@ -130,22 +161,18 @@ void Toy_freeValue(Toy_Value value) { break; } - case TOY_VALUE_ARRAY: { - Toy_Array* ptr = value.as.array; - - for (unsigned int i = 0; i < ptr->count; i++) { - Toy_freeValue(ptr->data[i]); - } - - Toy_resizeArray(ptr, 0); + 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_TABLE: case TOY_VALUE_FUNCTION: case TOY_VALUE_OPAQUE: case TOY_VALUE_TYPE: @@ -183,7 +210,7 @@ bool Toy_checkValuesAreEqual(Toy_Value left, Toy_Value right) { return right.type == TOY_VALUE_NULL; case TOY_VALUE_BOOLEAN: - return right.type == TOY_VALUE_NULL && left.as.boolean == right.as.boolean; + return right.type == TOY_VALUE_BOOLEAN && left.as.boolean == right.as.boolean; case TOY_VALUE_INTEGER: if (right.type == TOY_VALUE_INTEGER) { @@ -240,7 +267,37 @@ bool Toy_checkValuesAreEqual(Toy_Value left, Toy_Value right) { return true; } - case TOY_VALUE_TABLE: + 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: case TOY_VALUE_OPAQUE: case TOY_VALUE_TYPE: @@ -278,6 +335,9 @@ bool Toy_checkValuesAreComparable(Toy_Value left, Toy_Value right) { return false; case TOY_VALUE_TABLE: + //nothing is comparable with a table + return false; + case TOY_VALUE_FUNCTION: case TOY_VALUE_OPAQUE: case TOY_VALUE_TYPE: @@ -332,6 +392,8 @@ int Toy_compareValues(Toy_Value left, Toy_Value right) { break; case TOY_VALUE_TABLE: + break; + case TOY_VALUE_FUNCTION: case TOY_VALUE_OPAQUE: case TOY_VALUE_TYPE: @@ -392,35 +454,120 @@ Toy_String* Toy_stringifyValue(Toy_Bucket** bucketHandle, Toy_Value value) { case TOY_VALUE_ARRAY: { //TODO: concat + free is definitely a performance nightmare, could make an append function? Toy_Array* ptr = value.as.array; - Toy_String* string = Toy_createStringLength(bucketHandle, "[", 1); + + //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 + bool needsComma = false; + + Toy_String* string = open; for (unsigned int i = 0; i < ptr->count; i++) { - //append each element - Toy_String* tmp = Toy_concatStrings(bucketHandle, string, Toy_stringifyValue(bucketHandle, ptr->data[i])); //increment ref - Toy_freeString(string); //decrement ref - string = tmp; - - //if we need a comma - if (i + 1 < ptr->count) { + if (needsComma) { Toy_String* tmp = Toy_concatStrings(bucketHandle, string, comma); //increment ref Toy_freeString(string); //decrement ref string = tmp; } + + //append each element + Toy_String* element = Toy_stringifyValue(bucketHandle, ptr->data[i]); + 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, Toy_createStringLength(bucketHandle, "]", 1)); + 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" + + 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 + 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); + Toy_String* c = Toy_concatStrings(bucketHandle, k, colon); //stick the colon between + 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" return string; } - case TOY_VALUE_TABLE: case TOY_VALUE_FUNCTION: case TOY_VALUE_OPAQUE: case TOY_VALUE_TYPE: diff --git a/source/toy_value.h b/source/toy_value.h index 4acdb1b..a2be718 100644 --- a/source/toy_value.h +++ b/source/toy_value.h @@ -7,6 +7,7 @@ struct Toy_Bucket; union Toy_String_t; struct Toy_Array; +struct Toy_Table; typedef enum Toy_ValueType { TOY_VALUE_NULL, @@ -34,6 +35,7 @@ typedef struct Toy_Value { //32 | 64 BITNESS float number; //4 | 4 union Toy_String_t* string; //4 | 8 struct Toy_Array* array; //4 | 8 + struct Toy_Table* table; //4 | 8 //TODO: more types go here //TODO: consider 'stack' as a possible addition @@ -59,6 +61,7 @@ typedef struct Toy_Value { //32 | 64 BITNESS #define TOY_VALUE_AS_FLOAT(value) (Toy_unwrapValue(value).as.number) #define TOY_VALUE_AS_STRING(value) (Toy_unwrapValue(value).as.string) #define TOY_VALUE_AS_ARRAY(value) (Toy_unwrapValue(value).as.array) +#define TOY_VALUE_AS_TABLE(value) (Toy_unwrapValue(value).as.table) //TODO: more #define TOY_VALUE_FROM_NULL() ((Toy_Value){{ .integer = 0 }, TOY_VALUE_NULL}) @@ -67,6 +70,7 @@ typedef struct Toy_Value { //32 | 64 BITNESS #define TOY_VALUE_FROM_FLOAT(value) ((Toy_Value){{ .number = value }, TOY_VALUE_FLOAT}) #define TOY_VALUE_FROM_STRING(value) ((Toy_Value){{ .string = value }, TOY_VALUE_STRING}) #define TOY_VALUE_FROM_ARRAY(value) ((Toy_Value){{ .array = value }, TOY_VALUE_ARRAY}) +#define TOY_VALUE_FROM_TABLE(value) ((Toy_Value){{ .table = value }, TOY_VALUE_TABLE}) //TODO: more #define TOY_REFERENCE_FROM_POINTER(ptr) ((Toy_Value){{ .reference = ptr }, TOY_VALUE_REFERENCE}) diff --git a/source/toy_vm.c b/source/toy_vm.c index 414c695..43113f9 100644 --- a/source/toy_vm.c +++ b/source/toy_vm.c @@ -119,8 +119,39 @@ static void processRead(Toy_VM* vm) { } case TOY_VALUE_TABLE: { - // - // break; + fixAlignment(vm); + + //the number of values to read from the stack + unsigned int count = (unsigned int)READ_INT(vm); + + //capacity covers keys AND values + unsigned int capacity = count / 2; + capacity = capacity > TOY_TABLE_INITIAL_CAPACITY ? capacity : TOY_TABLE_INITIAL_CAPACITY; + + //neat trick to find the next power of two, inclusive (restriction of the table system) TODO: move this into a function + capacity--; + capacity |= capacity >> 1; + capacity |= capacity >> 2; + capacity |= capacity >> 4; + capacity |= capacity >> 8; + capacity |= capacity >> 16; + capacity++; + + //create the table and read in the key-values + Toy_Table* table = Toy_private_adjustTableCapacity(NULL, capacity); + + //read in backwards from the stack + for (unsigned int i = 0; i < count / 2; i++) { + Toy_Value v = Toy_popStack(&vm->stack); + Toy_Value k = Toy_popStack(&vm->stack); + + Toy_insertTable(&table, k, v); + } + + //finished + value = TOY_VALUE_FROM_TABLE(table); + + break; } case TOY_VALUE_FUNCTION: { @@ -214,6 +245,9 @@ static void processAssignCompound(Toy_VM* vm) { if (TOY_VALUE_IS_STRING(target) && TOY_VALUE_AS_STRING(target)->info.type == TOY_STRING_NAME) { Toy_Value* valuePtr = Toy_accessScopeAsPointer(vm->scope, TOY_VALUE_AS_STRING(target)); Toy_freeValue(target); + if (valuePtr == NULL) { + return; + } target = TOY_REFERENCE_FROM_POINTER(valuePtr); } @@ -245,6 +279,16 @@ static void processAssignCompound(Toy_VM* vm) { Toy_freeValue(value); } + else if (TOY_VALUE_IS_TABLE(target)) { + Toy_Table* table = TOY_VALUE_AS_TABLE(target); + + //set the value + Toy_insertTable(&table, key, Toy_copyValue(Toy_unwrapValue(value))); + + //cleanup + Toy_freeValue(value); + } + else { Toy_error("Invalid assignment target"); Toy_freeValue(target); @@ -266,11 +310,15 @@ static void processAccess(Toy_VM* vm) { //find the value Toy_Value* valuePtr = Toy_accessScopeAsPointer(vm->scope, TOY_VALUE_AS_STRING(name)); - //in the event of a certain subset of types, create references instead (these should only exist on the stack) - if (TOY_VALUE_IS_REFERENCE(*valuePtr) || TOY_VALUE_IS_ARRAY(*valuePtr)) { - //TODO: more types to be implemented - Toy_Value ref = TOY_REFERENCE_FROM_POINTER(valuePtr); + if (valuePtr == NULL) { + Toy_freeValue(name); + return; + } + //in the event of a certain subset of types, create references instead (these should only exist on the stack) + if (TOY_VALUE_IS_REFERENCE(*valuePtr) || TOY_VALUE_IS_ARRAY(*valuePtr) || TOY_VALUE_IS_TABLE(*valuePtr)) { + //TODO: more types to be implemented as stack-only references + Toy_Value ref = TOY_REFERENCE_FROM_POINTER(valuePtr); Toy_pushStack(&vm->stack, ref); } @@ -649,7 +697,7 @@ static void processIndex(Toy_VM* vm) { else if (TOY_VALUE_IS_ARRAY(value)) { //type checks if (!TOY_VALUE_IS_INTEGER(index)) { - Toy_error("Failed to index a string"); + Toy_error("Failed to index an array"); Toy_freeValue(value); Toy_freeValue(index); Toy_freeValue(length); @@ -657,7 +705,7 @@ static void processIndex(Toy_VM* vm) { } if (!(TOY_VALUE_IS_NULL(length) || TOY_VALUE_IS_INTEGER(length))) { - Toy_error("Failed to index-length a string"); + Toy_error("Failed to index-length an array"); Toy_freeValue(value); Toy_freeValue(index); Toy_freeValue(length); @@ -679,10 +727,9 @@ static void processIndex(Toy_VM* vm) { } //in the event of a certain subset of types, create references instead (these should only exist on the stack) - if (TOY_VALUE_IS_REFERENCE(array->data[i]) || TOY_VALUE_IS_ARRAY(array->data[i])) { - //TODO: more types to be implemented + if (TOY_VALUE_IS_REFERENCE(array->data[i]) || TOY_VALUE_IS_ARRAY(array->data[i]) || TOY_VALUE_IS_TABLE(array->data[i])) { + //TODO: more types to be implemented as stack-only references Toy_Value ref = TOY_REFERENCE_FROM_POINTER(&(array->data[i])); - Toy_pushStack(&vm->stack, ref); } @@ -691,6 +738,31 @@ static void processIndex(Toy_VM* vm) { } } + else if (TOY_VALUE_IS_TABLE(value)) { + if (TOY_VALUE_IS_NULL(length) != true) { + Toy_error("Can't index-length a table"); + Toy_freeValue(value); + Toy_freeValue(index); + Toy_freeValue(length); + return; + } + + //get the table & element value + Toy_Table* table = TOY_VALUE_AS_TABLE(value); + Toy_TableEntry* entry = Toy_private_lookupTableEntryPtr(&table, index); + + //in the event of a certain subset of types, create references instead (these should only exist on the stack) + if (TOY_VALUE_IS_REFERENCE(entry->value) || TOY_VALUE_IS_ARRAY(entry->value) || TOY_VALUE_IS_TABLE(entry->value)) { + //TODO: more types to be implemented as stack-only references + Toy_Value ref = TOY_REFERENCE_FROM_POINTER(&(entry->value)); + Toy_pushStack(&vm->stack, ref); + } + + else { + Toy_pushStack(&vm->stack, Toy_copyValue(entry->value)); + } + } + else { fprintf(stderr, TOY_CC_ERROR "ERROR: Unknown value type '%s' found in processIndex, exiting\n" TOY_CC_RESET, Toy_private_getValueTypeAsCString(value.type)); exit(-1);