From 8d1e4d647bafb227b645d06ed82e3184f7e90314 Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Thu, 10 Oct 2024 22:53:49 +1100 Subject: [PATCH] Started working on Toy_Scope, incomplete I only worked for a couple hours today. --- source/toy_opcodes.h | 4 +- source/toy_scope.c.txt | 149 ++++++++++++++++++++++++++++++++++++++ source/toy_scope.h.txt | 28 +++++++ source/toy_string.c | 34 ++++++++- source/toy_string.h | 3 +- source/toy_value.c | 37 +--------- source/toy_vm.c | 2 - tests/cases/test_string.c | 8 +- 8 files changed, 220 insertions(+), 45 deletions(-) create mode 100644 source/toy_scope.c.txt create mode 100644 source/toy_scope.h.txt diff --git a/source/toy_opcodes.h b/source/toy_opcodes.h index a875d1c..a24bbe0 100644 --- a/source/toy_opcodes.h +++ b/source/toy_opcodes.h @@ -3,8 +3,6 @@ typedef enum Toy_OpcodeType { //variable instructions TOY_OPCODE_READ, - TOY_OPCODE_LOAD, - TOY_OPCODE_LOAD_LONG, //corner case TOY_OPCODE_DECLARE, TOY_OPCODE_ASSIGN, TOY_OPCODE_ACCESS, @@ -18,7 +16,7 @@ typedef enum Toy_OpcodeType { //comparison instructions TOY_OPCODE_COMPARE_EQUAL, - // TOY_OPCODE_COMPARE_NOT, + // TOY_OPCODE_COMPARE_NOT, //NOTE: optimized into a composite of TOY_OPCODE_COMPARE_EQUAL + TOY_OPCODE_NEGATE TOY_OPCODE_COMPARE_LESS, TOY_OPCODE_COMPARE_LESS_EQUAL, TOY_OPCODE_COMPARE_GREATER, diff --git a/source/toy_scope.c.txt b/source/toy_scope.c.txt new file mode 100644 index 0000000..dc105a7 --- /dev/null +++ b/source/toy_scope.c.txt @@ -0,0 +1,149 @@ +#include "toy_scope.h" +#include "toy_console_colors.h" + +#include +#include +#include + +#include "toy_print.h" + +//utils +static void incrementRefCount(Toy_Scope* scope) { + for (Toy_Scope* iter = scope; iter; iter = iter->next) { + iter->refCount++; + } +} + +static void decrementRefCount(Toy_Scope* scope) { + for (Toy_Scope* iter = scope; iter; iter = iter->next) { + iter->refCount--; + } +} + +static Toy_Value* lookupScope(Toy_Scope* scope, Toy_Value key, unsigned int hash, bool recursive) { + //terminate + if (scope == NULL) { + return NULL; + } + + //copy and modify the code from Toy_lookupTable, so it can behave slightly differently + unsigned int probe = hash % scope->table->capacity; + + while (true) { + //found the entry + if (TOY_VALUES_ARE_EQUAL(scope->table->data[probe].key, key)) { + return &(scope->table->data[probe].value); + } + + //if its an empty slot + if (TOY_VALUE_IS_NULL(scope->table->data[probe].key)) { + return recursive ? lookupScope(scope->next, key, hash, recursive) : NULL; + } + + //adjust and continue + probe = (probe + 1) % scope->table->capacity; + } +} + +//exposed functions +Toy_Scope* Toy_pushScope(Toy_Bucket** bucketHandle, Toy_Scope* scope) { + Toy_Scope* newScope = Toy_partitionBucket(bucketHandle, sizeof(Toy_Scope)); + + newScope->next = scope; + newScope->table = Toy_allocateTable(); + newScope->refCount = 0; + + incrementRefCount(newScope); + + return newScope; +} + +Toy_Scope* Toy_popScope(Toy_Scope* scope) { + decrementRefCount(scope); + + if (scope->refCount == 0) { + Toy_freeTable(scope->table); + scope->table = NULL; + } + + return scope->next; +} + +Toy_Scope* deepCopyScope(Toy_Bucket** bucketHandle, Toy_Scope* scope) { + Toy_Scope* newScope = Toy_pushScope(bucketHandle, scope->next); + + //forcibly copy the contents + for (int i = 0; i < scope->table->capacity; i++) { + if (!TOY_VALUE_IS_NULL(scope->table->data[i].key)) { + Toy_insertTable(&newScope->table, scope->table->data[i].key, scope->table->data[i].value); + } + } + + return newScope; +} + +void Toy_declareScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String key, Toy_Value value) { + if (key.type != TOY_STRING_NAME) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Toy_Scope only allows name strings as keys\n" TOY_CC_RESET); + exit(-1); + } + + Toy_Value* valuePtr = lookupScope(scope, key, Toy_hashValue(key), false); + + if (valuePtr != NULL) { + + char buffer[key.length + 256]; + snprintf(buffer, "Can't redefine a variable: %s", key.as.name.data); + Toy_error(buffer); + return; + } + + Toy_insertTable(&scope->table, key, value); +} + +void Toy_assignScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String key, Toy_Value value) { + if (key.type != TOY_STRING_NAME) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Toy_Scope only allows name strings as keys\n" TOY_CC_RESET); + exit(-1); + } + + Toy_Value* valuePtr = lookupScope(scope, key, Toy_hashValue(key), true); + + if (valuePtr == NULL) { + char buffer[key.length + 256]; + snprintf(buffer, "Undefined variable: %s", key.as.name.data); + Toy_error(buffer); + return; + } + + *valuePtr = value; +} + +Toy_Value Toy_accessScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String key) { + if (key.type != TOY_STRING_NAME) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Toy_Scope only allows name strings as keys\n" TOY_CC_RESET); + exit(-1); + } + + Toy_Value* valuePtr = lookupScope(scope, key, Toy_hashValue(key), true); + + if (valuePtr == NULL) { + char buffer[key.length + 256]; + snprintf(buffer, "Undefined variable: %s", key.as.name.data); + Toy_error(buffer); + return; + } + + return *valuePtr; +} + +bool Toy_isDeclaredScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String key) { + if (key.type != TOY_STRING_NAME) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Toy_Scope only allows name strings as keys\n" TOY_CC_RESET); + exit(-1); + } + + Toy_Value* valuePtr = lookupScope(scope, key, Toy_hashValue(key), true); + + return valuePtr != NULL; +} diff --git a/source/toy_scope.h.txt b/source/toy_scope.h.txt new file mode 100644 index 0000000..31f251b --- /dev/null +++ b/source/toy_scope.h.txt @@ -0,0 +1,28 @@ +#pragma once + +#include "toy_common.h" + +#include "toy_bucket.h" +#include "toy_value.h" +#include "toy_string.h" +#include "toy_table.h" + +//wraps Toy_Table, restricting keys to name strings, and handles scopes as a linked list +typedef struct Toy_Scope { + struct Toy_Scope* next; + Toy_Table* table; + unsigned int refCount; +} Toy_Scope; + +//handle deep scopes +TOY_API Toy_Scope* Toy_pushScope(Toy_Bucket** bucketHandle, Toy_Scope* scope); +TOY_API Toy_Scope* Toy_popScope(Toy_Scope* scope); + +TOY_API Toy_Scope* deepCopyScope(Toy_Bucket** bucketHandle, Toy_Scope* scope); + +//manage the contents +TOY_API void Toy_declareScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String key, Toy_Value value); +TOY_API void Toy_assignScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String key, Toy_Value value); +TOY_API Toy_Value Toy_accessScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String key); + +TOY_API bool Toy_isDeclaredScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String key); diff --git a/source/toy_string.c b/source/toy_string.c index 7473fb1..55635b3 100644 --- a/source/toy_string.c +++ b/source/toy_string.c @@ -34,6 +34,17 @@ static void decrementRefCount(Toy_String* str) { } } +static unsigned int hashCString(const char* string) { + unsigned int hash = 2166136261u; + + for (unsigned int i = 0; string[i]; i++) { + hash *= string[i]; + hash ^= 16777619; + } + + return hash; +} + //exposed functions Toy_String* Toy_createString(Toy_Bucket** bucketHandle, const char* cstring) { int length = strlen(cstring); @@ -59,7 +70,7 @@ Toy_String* Toy_createStringLength(Toy_Bucket** bucketHandle, const char* cstrin return ret; } -TOY_API Toy_String* Toy_createNameString(Toy_Bucket** bucketHandle, const char* cname) { +TOY_API Toy_String* Toy_createNameString(Toy_Bucket** bucketHandle, const char* cname, Toy_ValueType type) { int length = strlen(cname); if (length > TOY_STRING_MAX_LENGTH) { @@ -75,6 +86,7 @@ TOY_API Toy_String* Toy_createNameString(Toy_Bucket** bucketHandle, const char* ret->cachedHash = 0; //don't calc until needed memcpy(ret->as.name.data, cname, length + 1); ret->as.name.data[length] = '\0'; + ret->as.name.type = type; return ret; } @@ -271,3 +283,23 @@ int Toy_compareStrings(Toy_String* left, Toy_String* right) { return deepCompareUtil(left, right, &leftHead, &rightHead); } + +unsigned int Toy_hashString(Toy_String* str) { + if (str->cachedHash != 0) { + return str->cachedHash; + } + else if (str->type == TOY_STRING_NODE) { + //TODO: I wonder if it would be possible to discretely swap the composite node string with a new leaf string here? Would that speed up other parts of the code by not having to walk the tree in future? + char* buffer = Toy_getStringRawBuffer(str); + str->cachedHash = hashCString(buffer); + free(buffer); + } + else if (str->type == TOY_STRING_LEAF) { + str->cachedHash = hashCString(str->as.leaf.data); + } + else if (str->type == TOY_STRING_NAME) { + str->cachedHash = hashCString(str->as.name.data); + } + + return str->cachedHash; +} diff --git a/source/toy_string.h b/source/toy_string.h index ad53ec4..aaba337 100644 --- a/source/toy_string.h +++ b/source/toy_string.h @@ -41,7 +41,7 @@ typedef struct Toy_String { //32 | 64 BITNESS TOY_API Toy_String* Toy_createString(Toy_Bucket** bucketHandle, const char* cstring); TOY_API Toy_String* Toy_createStringLength(Toy_Bucket** bucketHandle, const char* cstring, int length); -TOY_API Toy_String* Toy_createNameString(Toy_Bucket** bucketHandle, const char* cname); //for variable names +TOY_API Toy_String* Toy_createNameString(Toy_Bucket** bucketHandle, const char* cname, Toy_ValueType type); //for variable names TOY_API Toy_String* Toy_copyString(Toy_Bucket** bucketHandle, Toy_String* str); TOY_API Toy_String* Toy_deepCopyString(Toy_Bucket** bucketHandle, Toy_String* str); @@ -57,3 +57,4 @@ TOY_API char* Toy_getStringRawBuffer(Toy_String* str); //allocates the buffer on TOY_API int Toy_compareStrings(Toy_String* left, Toy_String* right); //return value mimics strcmp() +TOY_API unsigned int Toy_hashString(Toy_String* string); diff --git a/source/toy_value.c b/source/toy_value.c index 8b5f7b9..870e9c9 100644 --- a/source/toy_value.c +++ b/source/toy_value.c @@ -70,18 +70,7 @@ bool Toy_private_isEqual(Toy_Value left, Toy_Value right) { return 0; } -//hash utils -static unsigned int hashCString(const char* string) { - unsigned int hash = 2166136261u; - - for (unsigned int i = 0; string[i]; i++) { - hash *= string[i]; - hash ^= 16777619; - } - - return hash; -} - +//hash util static unsigned int hashUInt(unsigned int x) { x = ((x >> 16) ^ x) * 0x45d9f3b; x = ((x >> 16) ^ x) * 0x45d9f3b; @@ -89,7 +78,6 @@ static unsigned int hashUInt(unsigned int x) { return x; } - unsigned int Toy_hashValue(Toy_Value value) { switch(value.type) { case TOY_VALUE_NULL: @@ -104,27 +92,8 @@ unsigned int Toy_hashValue(Toy_Value value) { case TOY_VALUE_FLOAT: return hashUInt( *((int*)(&TOY_VALUE_AS_FLOAT(value))) ); - case TOY_VALUE_STRING: { - Toy_String* str = TOY_VALUE_AS_STRING(value); - - if (str->cachedHash != 0) { - return str->cachedHash; - } - else if (str->type == TOY_STRING_NODE) { - //TODO: I wonder if it would be possible to discretely swap the composite node string with a new leaf string here? Would that speed up other parts of the code by not having to walk the tree in future? - char* buffer = Toy_getStringRawBuffer(str); - str->cachedHash = hashCString(buffer); - free(buffer); - } - else if (str->type == TOY_STRING_LEAF) { - str->cachedHash = hashCString(str->as.leaf.data); - } - else if (str->type == TOY_STRING_NAME) { - str->cachedHash = hashCString(str->as.name.data); - } - - return str->cachedHash; - } + case TOY_VALUE_STRING: + return Toy_hashString(TOY_VALUE_AS_STRING(value)); case TOY_VALUE_ARRAY: case TOY_VALUE_DICTIONARY: diff --git a/source/toy_vm.c b/source/toy_vm.c index 15cbe9d..bd78180 100644 --- a/source/toy_vm.c +++ b/source/toy_vm.c @@ -371,8 +371,6 @@ static void process(Toy_VM* vm) { break; //not yet implemented - case TOY_OPCODE_LOAD: - case TOY_OPCODE_LOAD_LONG: case TOY_OPCODE_DECLARE: case TOY_OPCODE_ASSIGN: case TOY_OPCODE_ACCESS: diff --git a/tests/cases/test_string.c b/tests/cases/test_string.c index 4376be9..1b381a1 100644 --- a/tests/cases/test_string.c +++ b/tests/cases/test_string.c @@ -106,7 +106,7 @@ int test_string_allocation() { Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); const char* cstring = "Hello world"; - Toy_String* str = Toy_createNameString(&bucket, cstring); + Toy_String* str = Toy_createNameString(&bucket, cstring, TOY_VALUE_NULL); //shallow and deep Toy_String* shallow = Toy_copyString(&bucket, str); @@ -652,9 +652,9 @@ int test_string_equality() { { //setup Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); - Toy_String* helloWorldOne = Toy_createNameString(&bucket, "Hello world"); - Toy_String* helloWorldTwo = Toy_createNameString(&bucket, "Hello world"); - Toy_String* helloEveryone = Toy_createNameString(&bucket, "Hello everyone"); + Toy_String* helloWorldOne = Toy_createNameString(&bucket, "Hello world", TOY_VALUE_NULL); + Toy_String* helloWorldTwo = Toy_createNameString(&bucket, "Hello world", TOY_VALUE_NULL); + Toy_String* helloEveryone = Toy_createNameString(&bucket, "Hello everyone", TOY_VALUE_NULL); //TODO: compare types? int result = 0; //for print the errors