Merge branch 'interpreter' into terminal

This commit is contained in:
Kyler Olsen 2025-11-28 20:35:02 -07:00
commit ba8aee6d78
7 changed files with 677 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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

155
SLS_C/src/hash_table.c Normal file
View File

@ -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;
}

35
SLS_C/src/interpreter.c Normal file
View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);