Merge branch 'interpreter' into terminal
This commit is contained in:
commit
ba8aee6d78
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Kyler Olsen
|
||||||
|
// YREA SLS
|
||||||
|
// Hash Table Header
|
||||||
|
// November 2025
|
||||||
|
|
||||||
|
#ifndef SLS_HASH_TABLE_H
|
||||||
|
#define SLS_HASH_TABLE_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Kyler Olsen
|
||||||
|
// YREA SLS
|
||||||
|
// Interpreter Header
|
||||||
|
// November 2025
|
||||||
|
|
||||||
|
#ifndef SLS_INTERPRETER_H
|
||||||
|
#define SLS_INTERPRETER_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
@ -39,6 +39,7 @@ typedef struct {
|
||||||
|
|
||||||
TestsReport run_string_tests();
|
TestsReport run_string_tests();
|
||||||
TestsReport run_lexer_tests();
|
TestsReport run_lexer_tests();
|
||||||
|
TestsReport run_hash_table_tests();
|
||||||
TestsReport run_extra_tests();
|
TestsReport run_extra_tests();
|
||||||
|
|
||||||
#endif // SLS_TESTS_H
|
#endif // SLS_TESTS_H
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
// Kyler Olsen
|
||||||
|
// YREA SLS
|
||||||
|
// Hash Table
|
||||||
|
// November 2025
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,383 @@
|
||||||
|
// Kyler Olsen
|
||||||
|
// YREA SLS
|
||||||
|
// Hash Table Tests
|
||||||
|
// November 2025
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
@ -85,6 +85,8 @@ int main(void) {
|
||||||
test_report(run_string_tests(), &counts);
|
test_report(run_string_tests(), &counts);
|
||||||
printf(" ========== Lexer Tests ==========\n");
|
printf(" ========== Lexer Tests ==========\n");
|
||||||
test_report(run_lexer_tests(), &counts);
|
test_report(run_lexer_tests(), &counts);
|
||||||
|
printf(" ========== Hash Table Tests ==========\n");
|
||||||
|
test_report(run_hash_table_tests(), &counts);
|
||||||
printf(" ========== Extra Tests ==========\n");
|
printf(" ========== Extra Tests ==========\n");
|
||||||
test_report(run_extra_tests(), &counts);
|
test_report(run_extra_tests(), &counts);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue