diff --git a/SLS_C/include/sls/hash_table.h b/SLS_C/include/sls/hash_table.h new file mode 100644 index 0000000..3118055 --- /dev/null +++ b/SLS_C/include/sls/hash_table.h @@ -0,0 +1,37 @@ +// Kyler Olsen +// YREA SLS +// Hash Table Header +// November 2025 + +#ifndef SLS_HASH_TABLE_H +#define SLS_HASH_TABLE_H + +#include + +#include "sls/string.h" + +typedef struct Bucket { + uint32_t hash_a; + uint32_t hash_b; + void *item; + struct Bucket *next; +} Bucket; + +typedef struct { + size_t buckets_count; + Bucket *buckets[]; +} HashTable; + +// Initializes a HashTable with atleast buckets_count number of buckets. Returns NULL on Memory Allocation Error. +HashTable *init_hash_table(size_t min_buckets_count); +// Frees memory owned by the HashTable. +void del_hash_table(HashTable *ht); + +// Inserts an item into the HashTable based on its key. Returns FALSE on Memory Allocation Error. +Boolean hash_table_put(HashTable *ht, SlsStr key, void *item); +// Gets the item associated with the given key. Returns default item if not found. +void *hash_table_get(const HashTable *ht, SlsStr key, void *default_item); +// Deletes the item associated with the given key. Returns FALSE if item is not found. +Boolean hash_table_del(HashTable *ht, SlsStr key); + +#endif // SLS_HASH_TABLE_H diff --git a/SLS_C/include/sls/interpreter.h b/SLS_C/include/sls/interpreter.h new file mode 100644 index 0000000..782f860 --- /dev/null +++ b/SLS_C/include/sls/interpreter.h @@ -0,0 +1,64 @@ +// Kyler Olsen +// YREA SLS +// Interpreter Header +// November 2025 + +#ifndef SLS_INTERPRETER_H +#define SLS_INTERPRETER_H + +#include + +#include "sls/lexer.h" +#include "sls/hash_table.h" + +typedef enum { + STACK_IDENTIFIER, + STACK_I64, + STACK_I32, + STACK_I16, + STACK_I8, + STACK_U64, + STACK_U32, + STACK_U16, + STACK_U8, + STACK_FLOAT, + STACK_DOUBLE, + STACK_CHARACTER, + STACK_BOOLEAN, + STACK_TOKEN_STRING, +} StackType; + +extern const char *STACK_TYPES_NAMES[]; +extern const size_t STACK_TYPE_COUNT; + +typedef struct StackItem { + StackType type; + union { + Identifier identifier; + int64_t i64; + int32_t i32; + int16_t i16; + int8_t i8; + uint64_t u64; + uint32_t u32; + uint16_t u16; + uint8_t u8; + float f32; + double f64; + char character; + Boolean boolean; + TokenString token_string; + }; + struct StackItem *next; +} StackItem; + +typedef struct { + StackItem *stack; + HashTable functions; +} InterpreterState; + +InterpreterState *interpreter_create(); +void execute(InterpreterState *interpreter, LexerTokenResult *token); +void interpreter_delete(InterpreterState *interpreter); + +#endif // SLS_INTERPRETER_H diff --git a/SLS_C/include/tests/tests.h b/SLS_C/include/tests/tests.h index 33b9a7d..2d89011 100644 --- a/SLS_C/include/tests/tests.h +++ b/SLS_C/include/tests/tests.h @@ -39,6 +39,7 @@ typedef struct { TestsReport run_string_tests(); TestsReport run_lexer_tests(); +TestsReport run_hash_table_tests(); TestsReport run_extra_tests(); #endif // SLS_TESTS_H diff --git a/SLS_C/src/hash_table.c b/SLS_C/src/hash_table.c new file mode 100644 index 0000000..ab306c5 --- /dev/null +++ b/SLS_C/src/hash_table.c @@ -0,0 +1,155 @@ +// Kyler Olsen +// YREA SLS +// Hash Table +// November 2025 + +#include + +#include "sls/hash_table.h" +#include "sls/string.h" + +static size_t next_power_of_two(size_t n) { + if (n < 2) return 2; + n--; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + #if SIZE_MAX > UINT32_MAX + n |= n >> 32; + #endif + n++; + return n; +} + +HashTable *init_hash_table(size_t min_buckets_count) { + size_t buckets_count = next_power_of_two(min_buckets_count); + size_t size = sizeof(HashTable) + buckets_count * sizeof(Bucket); + HashTable *ht = (HashTable *)malloc(size); + if (!ht) return NULL; + ht->buckets_count = buckets_count; + for (size_t i = 0; i < buckets_count; i++) + ht->buckets[i] = NULL; + return ht; +} + +void del_hash_table(HashTable *ht) { + for (size_t i = 0; i < ht->buckets_count; i++) { + Bucket *head = ht->buckets[i]; + while (head) { + Bucket *next = head->next; + // TODO: Does item need to be freed? + free(head); + head = next; + } + } + free(ht); +} + +static inline uint32_t sls_hash_a(SlsStr key) { + // FNV-1a + uint32_t hash = 2166136261u; + const uint8_t *p = (const uint8_t *)key.str; + for (size_t i = 0; i < key.len; i++) { + hash ^= p[i]; + hash *= 16777619u; + } + return hash; +} + +static inline uint32_t sls_hash_b(SlsStr key) { + // Murmur2A + const uint32_t m = 0x5bd1e995; + uint32_t hash = 0; + + while (key.len >= 4) { + uint32_t k = (uint32_t)key.str[0] + | (uint32_t)key.str[1] << 8 + | (uint32_t)key.str[2] << 16 + | (uint32_t)key.str[3] << 24; + k *= m; + k ^= k >> 24; + k *= m; + + hash *= m; + hash ^= k; + + key.str += 4; + key.len -= 4; + } + + if (key.len >= 3) + hash ^= key.str[2] << 16; + if (key.len >= 2) + hash ^= key.str[1] << 8; + if (key.len >= 1) + hash ^= key.str[0]; + hash *= m; + + hash ^= hash >> 13; + hash *= m; + hash ^= hash >> 15; + + return hash; +} + +Boolean hash_table_put(HashTable *ht, SlsStr key, void *item) { + uint32_t hash_a = sls_hash_a(key); + uint32_t hash_b = sls_hash_b(key); + size_t bucket_index = hash_a & (ht->buckets_count - 1); + + for (Bucket *node = ht->buckets[bucket_index]; node; node = node->next) { + if (node->hash_a == hash_a && node->hash_b == hash_b) { + // TODO: Does item need to be freed before being replaced? + node->item = item; + return TRUE; + } + } + + Bucket *bucket = (Bucket *)malloc(sizeof(Bucket)); + if (!bucket) + return FALSE; + bucket->hash_a = hash_a; + bucket->hash_b = hash_b; + bucket->item = item; + bucket->next = ht->buckets[bucket_index]; + ht->buckets[bucket_index] = bucket; + + return TRUE; +} + +void *hash_table_get(const HashTable *ht, SlsStr key, void *default_item) { + uint32_t hash_a = sls_hash_a(key); + uint32_t hash_b = sls_hash_b(key); + size_t bucket_index = hash_a & (ht->buckets_count - 1); + + for (Bucket *node = ht->buckets[bucket_index]; node; node = node->next) + if (node->hash_a == hash_a && node->hash_b == hash_b) + return node->item; + + return default_item; +} + +Boolean hash_table_del(HashTable *ht, SlsStr key) { + uint32_t hash_a = sls_hash_a(key); + uint32_t hash_b = sls_hash_b(key); + size_t bucket_index = hash_a & (ht->buckets_count - 1); + + Bucket *prev = NULL; + + for (Bucket *node = ht->buckets[bucket_index]; node; node = node->next) { + if (node->hash_a == hash_a && node->hash_b == hash_b) { + if (prev == NULL) + ht->buckets[bucket_index] = node->next; + else + prev->next = node->next; + // TODO: Does item need to be freed? + free(node); + return TRUE; + } + prev = node; + } + + return FALSE; +} diff --git a/SLS_C/src/interpreter.c b/SLS_C/src/interpreter.c new file mode 100644 index 0000000..5d66282 --- /dev/null +++ b/SLS_C/src/interpreter.c @@ -0,0 +1,35 @@ +// Kyler Olsen +// YREA SLS +// Interpreter +// November 2025 + +#include "sls/string.h" +#include "sls/interpreter.h" +#include "sls/lexer.h" + +const char *STACK_TYPES_NAMES[] = { + "Identifier", + "64-bit Integer", + "32-bit Integer", + "16-bit Integer", + "8-bit Integer", + "64-bit U Integer", + "32-bit U Integer", + "16-bit U Integer", + "8-bit U Integer", + "Float", + "Double", + "Character", + "Boolean", + "Token String", +}; + +const size_t STACK_TYPE_COUNT = sizeof(STACK_TYPES_NAMES) / sizeof(*STACK_TYPES_NAMES); + +static Boolean hash_table_put_funcs(HashTable *ht, SlsStr key, TokenString *item) { + return hash_table_put(ht, key, (void *)item); +} + +static TokenString* hash_table_get_funcs(const HashTable *ht, SlsStr key, TokenString *default_item) { + return (TokenString*)hash_table_get(ht, key, (void *)default_item); +} diff --git a/SLS_C/tests/hash_table_tests.c b/SLS_C/tests/hash_table_tests.c new file mode 100644 index 0000000..0ea1935 --- /dev/null +++ b/SLS_C/tests/hash_table_tests.c @@ -0,0 +1,383 @@ +// Kyler Olsen +// YREA SLS +// Hash Table Tests +// November 2025 + +#include +#include +#include +#include + +#include "sls/string.h" +#include "sls/hash_table.h" +#include "tests/tests.h" + +static const size_t NUM_HASH_TABLE_TESTS = 12; + +static TestResult pass_hash_table_test(SlsStr test_name) { + TestResult result = {.name = test_name, .status = TEST_NOT_IMPLEMENTED}; + result.status = TEST_PASS; + return result; +} + +static TestResult fail_hash_table_test(SlsStr test_name, SlsStr message) { + TestResult result = { .name = test_name, .status = TEST_NOT_IMPLEMENTED }; + result.status = TEST_ERROR; + result.message = sls_str_cpy(message); + + if (result.message.str == NULL) { + result.error = (SlsError){ SLS_STR("Out Of Memory Error."), 1 }; + result.status = TEST_ERROR; + } + return result; +} + +// Test init_hash_table and del_hash_table +static TestResult test_init_and_delete() { + HashTable *ht = init_hash_table(10); + if (!ht) return fail_hash_table_test(SLS_STR("test_init_and_delete"), SLS_STR("Initialization failed")); + + if (ht->buckets_count < 10) { + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_init_and_delete"), SLS_STR("Bucket count less than minimum")); + } + + del_hash_table(ht); + return pass_hash_table_test(SLS_STR("test_init_and_delete")); +} + +// Test basic put and get operations +static TestResult test_put_and_get() { + HashTable *ht = init_hash_table(10); + if (!ht) return fail_hash_table_test(SLS_STR("test_put_and_get"), SLS_STR("Initialization failed")); + + int value1 = 42; + int value2 = 84; + SlsStr key1 = SLS_STR("key1"); + SlsStr key2 = SLS_STR("key2"); + + if (!hash_table_put(ht, key1, &value1)) { + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_put_and_get"), SLS_STR("Put operation failed")); + } + + if (!hash_table_put(ht, key2, &value2)) { + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_put_and_get"), SLS_STR("Second put operation failed")); + } + + int *retrieved1 = (int*)hash_table_get(ht, key1, NULL); + int *retrieved2 = (int*)hash_table_get(ht, key2, NULL); + + if (!retrieved1 || *retrieved1 != 42) { + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_put_and_get"), SLS_STR("Get operation failed for key1")); + } + + if (!retrieved2 || *retrieved2 != 84) { + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_put_and_get"), SLS_STR("Get operation failed for key2")); + } + + del_hash_table(ht); + return pass_hash_table_test(SLS_STR("test_put_and_get")); +} + +// Test get with non-existent key returns default +static TestResult test_get_nonexistent_key() { + HashTable *ht = init_hash_table(10); + if (!ht) return fail_hash_table_test(SLS_STR("test_get_nonexistent_key"), SLS_STR("Initialization failed")); + + int default_value = -1; + SlsStr key = SLS_STR("nonexistent"); + + int *result = (int*)hash_table_get(ht, key, &default_value); + if (result != &default_value) { + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_get_nonexistent_key"), SLS_STR("Did not return default value")); + } + + del_hash_table(ht); + return pass_hash_table_test(SLS_STR("test_get_nonexistent_key")); +} + +// Test updating existing key +static TestResult test_update_existing_key() { + HashTable *ht = init_hash_table(10); + if (!ht) return fail_hash_table_test(SLS_STR("test_update_existing_key"), SLS_STR("Initialization failed")); + + int value1 = 100; + int value2 = 200; + SlsStr key = SLS_STR("update_key"); + + hash_table_put(ht, key, &value1); + hash_table_put(ht, key, &value2); + + int *result = (int*)hash_table_get(ht, key, NULL); + if (!result || *result != 200) { + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_update_existing_key"), SLS_STR("Update failed")); + } + + del_hash_table(ht); + return pass_hash_table_test(SLS_STR("test_update_existing_key")); +} + +// Test delete operation +static TestResult test_delete_key() { + HashTable *ht = init_hash_table(10); + if (!ht) return fail_hash_table_test(SLS_STR("test_delete_key"), SLS_STR("Initialization failed")); + + int value = 42; + SlsStr key = SLS_STR("delete_key"); + + hash_table_put(ht, key, &value); + + if (!hash_table_del(ht, key)) { + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_delete_key"), SLS_STR("Delete returned FALSE")); + } + + int default_val = -1; + int *result = (int*)hash_table_get(ht, key, &default_val); + if (result != &default_val) { + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_delete_key"), SLS_STR("Key still exists after delete")); + } + + del_hash_table(ht); + return pass_hash_table_test(SLS_STR("test_delete_key")); +} + +// Test delete non-existent key returns FALSE +static TestResult test_delete_nonexistent_key() { + HashTable *ht = init_hash_table(10); + if (!ht) return fail_hash_table_test(SLS_STR("test_delete_nonexistent_key"), SLS_STR("Initialization failed")); + + SlsStr key = SLS_STR("nonexistent"); + + if (hash_table_del(ht, key)) { + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_delete_nonexistent_key"), SLS_STR("Delete returned TRUE for nonexistent key")); + } + + del_hash_table(ht); + return pass_hash_table_test(SLS_STR("test_delete_nonexistent_key")); +} + +// Test collision handling (multiple keys in same bucket) +static TestResult test_collision_handling() { + HashTable *ht = init_hash_table(1); // Force collisions with single bucket + if (!ht) return fail_hash_table_test(SLS_STR("test_collision_handling"), SLS_STR("Initialization failed")); + + int val1 = 1, val2 = 2, val3 = 3; + SlsStr key1 = SLS_STR("key1"); + SlsStr key2 = SLS_STR("key2"); + SlsStr key3 = SLS_STR("key3"); + + hash_table_put(ht, key1, &val1); + hash_table_put(ht, key2, &val2); + hash_table_put(ht, key3, &val3); + + int *r1 = (int*)hash_table_get(ht, key1, NULL); + int *r2 = (int*)hash_table_get(ht, key2, NULL); + int *r3 = (int*)hash_table_get(ht, key3, NULL); + + if (!r1 || *r1 != 1 || !r2 || *r2 != 2 || !r3 || *r3 != 3) { + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_collision_handling"), SLS_STR("Collision handling failed")); + } + + del_hash_table(ht); + return pass_hash_table_test(SLS_STR("test_collision_handling")); +} + +// Test empty string key +static TestResult test_empty_key() { + HashTable *ht = init_hash_table(10); + if (!ht) return fail_hash_table_test(SLS_STR("test_empty_key"), SLS_STR("Initialization failed")); + + int value = 99; + SlsStr key = SLS_STR(""); + + if (!hash_table_put(ht, key, &value)) { + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_empty_key"), SLS_STR("Put with empty key failed")); + } + + int *result = (int*)hash_table_get(ht, key, NULL); + if (!result || *result != 99) { + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_empty_key"), SLS_STR("Get with empty key failed")); + } + + del_hash_table(ht); + return pass_hash_table_test(SLS_STR("test_empty_key")); +} + +// Test long key +static TestResult test_long_key() { + HashTable *ht = init_hash_table(10); + if (!ht) return fail_hash_table_test(SLS_STR("test_long_key"), SLS_STR("Initialization failed")); + + char long_key_str[256]; + for (size_t i = 0; i < 255; i++) long_key_str[i] = 'A'; + long_key_str[255] = '\0'; + + SlsStr key = sls_str_malloc(long_key_str, 255); + int value = 777; + + if (!hash_table_put(ht, key, &value)) { + sls_str_free(&key); + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_long_key"), SLS_STR("Put with long key failed")); + } + + int *result = (int*)hash_table_get(ht, key, NULL); + if (!result || *result != 777) { + sls_str_free(&key); + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_long_key"), SLS_STR("Get with long key failed")); + } + + sls_str_free(&key); + del_hash_table(ht); + return pass_hash_table_test(SLS_STR("test_long_key")); +} + +// Test multiple operations sequence +static TestResult test_multiple_operations() { + HashTable *ht = init_hash_table(10); + if (!ht) return fail_hash_table_test(SLS_STR("test_multiple_operations"), SLS_STR("Initialization failed")); + + int vals[5] = {10, 20, 30, 40, 50}; + SlsStr keys[5] = {SLS_STR("k1"), SLS_STR("k2"), SLS_STR("k3"), SLS_STR("k4"), SLS_STR("k5")}; + + // Insert all + for (int i = 0; i < 5; i++) { + if (!hash_table_put(ht, keys[i], &vals[i])) { + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_multiple_operations"), SLS_STR("Insert failed")); + } + } + + // Delete some + hash_table_del(ht, keys[1]); + hash_table_del(ht, keys[3]); + + // Verify remaining + int *r0 = (int*)hash_table_get(ht, keys[0], NULL); + int *r2 = (int*)hash_table_get(ht, keys[2], NULL); + int *r4 = (int*)hash_table_get(ht, keys[4], NULL); + + if (!r0 || *r0 != 10 || !r2 || *r2 != 30 || !r4 || *r4 != 50) { + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_multiple_operations"), SLS_STR("Get after delete failed")); + } + + // Verify deleted + int def = -1; + int *r1 = (int*)hash_table_get(ht, keys[1], &def); + int *r3 = (int*)hash_table_get(ht, keys[3], &def); + + if (r1 != &def || r3 != &def) { + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_multiple_operations"), SLS_STR("Deleted keys still present")); + } + + del_hash_table(ht); + return pass_hash_table_test(SLS_STR("test_multiple_operations")); +} + +// Test storing NULL pointers +static TestResult test_null_value() { + HashTable *ht = init_hash_table(10); + if (!ht) return fail_hash_table_test(SLS_STR("test_null_value"), SLS_STR("Initialization failed")); + + SlsStr key = SLS_STR("null_key"); + + if (!hash_table_put(ht, key, NULL)) { + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_null_value"), SLS_STR("Put with NULL value failed")); + } + + int default_val = -1; + void *result = hash_table_get(ht, key, &default_val); + + // Should retrieve NULL, not default + if (result == &default_val) { + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_null_value"), SLS_STR("NULL value not stored correctly")); + } + + del_hash_table(ht); + return pass_hash_table_test(SLS_STR("test_null_value")); +} + +// Test large number of entries +static TestResult test_large_capacity() { + HashTable *ht = init_hash_table(10); + if (!ht) return fail_hash_table_test(SLS_STR("test_large_capacity"), SLS_STR("Initialization failed")); + + const size_t COUNT = 100; + int *values = malloc(sizeof(int) * COUNT); + SlsStr *keys = malloc(sizeof(SlsStr) * COUNT); + + for (size_t i = 0; i < COUNT; i++) { + values[i] = (int)i; + char key_buf[32]; + snprintf(key_buf, 32, "key_%zu", i); + keys[i] = sls_str_malloc(key_buf, strlen(key_buf)); + + if (!hash_table_put(ht, keys[i], &values[i])) { + for (size_t j = 0; j <= i; j++) sls_str_free(&keys[j]); + free(keys); + free(values); + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_large_capacity"), SLS_STR("Put failed in bulk insert")); + } + } + + // Verify all entries + for (size_t i = 0; i < COUNT; i++) { + int *result = (int*)hash_table_get(ht, keys[i], NULL); + if (!result || *result != (int)i) { + for (size_t j = 0; j < COUNT; j++) sls_str_free(&keys[j]); + free(keys); + free(values); + del_hash_table(ht); + return fail_hash_table_test(SLS_STR("test_large_capacity"), SLS_STR("Get failed in bulk verify")); + } + } + + for (size_t i = 0; i < COUNT; i++) sls_str_free(&keys[i]); + free(keys); + free(values); + del_hash_table(ht); + return pass_hash_table_test(SLS_STR("test_large_capacity")); +} + +// Run all hash table tests +TestsReport run_hash_table_tests() { + TestsReport report = { + .section = SLS_STR("hash_table_tests"), + .count = NUM_HASH_TABLE_TESTS, + .tests = malloc(sizeof(TestResult) * NUM_HASH_TABLE_TESTS) + }; + + size_t i = 0; + report.tests[i++] = test_init_and_delete(); + report.tests[i++] = test_put_and_get(); + report.tests[i++] = test_get_nonexistent_key(); + report.tests[i++] = test_update_existing_key(); + report.tests[i++] = test_delete_key(); + report.tests[i++] = test_delete_nonexistent_key(); + report.tests[i++] = test_collision_handling(); + report.tests[i++] = test_empty_key(); + report.tests[i++] = test_long_key(); + report.tests[i++] = test_multiple_operations(); + report.tests[i++] = test_null_value(); + report.tests[i++] = test_large_capacity(); + + return report; +} diff --git a/SLS_C/tests/tests.c b/SLS_C/tests/tests.c index 218f349..08d5f12 100644 --- a/SLS_C/tests/tests.c +++ b/SLS_C/tests/tests.c @@ -85,6 +85,8 @@ int main(void) { test_report(run_string_tests(), &counts); printf(" ========== Lexer Tests ==========\n"); test_report(run_lexer_tests(), &counts); + printf(" ========== Hash Table Tests ==========\n"); + test_report(run_hash_table_tests(), &counts); printf(" ========== Extra Tests ==========\n"); test_report(run_extra_tests(), &counts);