diff options
author | Flavian Kaufmann <flavian@flaviankaufmann.ch> | 2025-05-25 15:54:50 +0200 |
---|---|---|
committer | Flavian Kaufmann <flavian@flaviankaufmann.ch> | 2025-05-25 15:54:50 +0200 |
commit | 330e46236b421ffb8fe263caf91196f4cd1114c5 (patch) | |
tree | 09b3df77fca2d23dacef64f6962e9a1afd5b4d29 | |
parent | de59dbd1773dff06051b7b604977bcb2803ada4f (diff) | |
download | imp-330e46236b421ffb8fe263caf91196f4cd1114c5.tar.gz imp-330e46236b421ffb8fe263caf91196f4cd1114c5.zip |
[cleanup] codebase cleanup
-rw-r--r-- | 3rdparty/stb_ds/LICENSE | 37 | ||||
-rw-r--r-- | 3rdparty/stb_ds/stb_ds.h | 1895 | ||||
-rw-r--r-- | Makefile | 12 | ||||
-rw-r--r-- | include/ast.h | 5 | ||||
-rw-r--r-- | include/driver.h | 13 | ||||
-rw-r--r-- | include/hashmap.h | 63 | ||||
-rw-r--r-- | include/interpreter.h | 38 | ||||
-rw-r--r-- | include/interpreter_context.h | 143 | ||||
-rw-r--r-- | include/repl.h | 8 | ||||
-rw-r--r-- | src/ast.c | 56 | ||||
-rw-r--r-- | src/driver.c | 260 | ||||
-rw-r--r-- | src/hashmap.c | 160 | ||||
-rw-r--r-- | src/interpreter.c | 421 | ||||
-rw-r--r-- | src/interpreter_context.c | 123 | ||||
-rw-r--r-- | src/main.c | 45 | ||||
-rw-r--r-- | src/repl.c | 28 | ||||
-rw-r--r-- | test/test.c | 131 |
17 files changed, 2761 insertions, 677 deletions
diff --git a/3rdparty/stb_ds/LICENSE b/3rdparty/stb_ds/LICENSE new file mode 100644 index 0000000..a77ae91 --- /dev/null +++ b/3rdparty/stb_ds/LICENSE @@ -0,0 +1,37 @@ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/3rdparty/stb_ds/stb_ds.h b/3rdparty/stb_ds/stb_ds.h new file mode 100644 index 0000000..e84c82d --- /dev/null +++ b/3rdparty/stb_ds/stb_ds.h @@ -0,0 +1,1895 @@ +/* stb_ds.h - v0.67 - public domain data structures - Sean Barrett 2019 + + This is a single-header-file library that provides easy-to-use + dynamic arrays and hash tables for C (also works in C++). + + For a gentle introduction: + http://nothings.org/stb_ds + + To use this library, do this in *one* C or C++ file: + #define STB_DS_IMPLEMENTATION + #include "stb_ds.h" + +TABLE OF CONTENTS + + Table of Contents + Compile-time options + License + Documentation + Notes + Notes - Dynamic arrays + Notes - Hash maps + Credits + +COMPILE-TIME OPTIONS + + #define STBDS_NO_SHORT_NAMES + + This flag needs to be set globally. + + By default stb_ds exposes shorter function names that are not qualified + with the "stbds_" prefix. If these names conflict with the names in your + code, define this flag. + + #define STBDS_SIPHASH_2_4 + + This flag only needs to be set in the file containing #define STB_DS_IMPLEMENTATION. + + By default stb_ds.h hashes using a weaker variant of SipHash and a custom hash for + 4- and 8-byte keys. On 64-bit platforms, you can define the above flag to force + stb_ds.h to use specification-compliant SipHash-2-4 for all keys. Doing so makes + hash table insertion about 20% slower on 4- and 8-byte keys, 5% slower on + 64-byte keys, and 10% slower on 256-byte keys on my test computer. + + #define STBDS_REALLOC(context,ptr,size) better_realloc + #define STBDS_FREE(context,ptr) better_free + + These defines only need to be set in the file containing #define STB_DS_IMPLEMENTATION. + + By default stb_ds uses stdlib realloc() and free() for memory management. You can + substitute your own functions instead by defining these symbols. You must either + define both, or neither. Note that at the moment, 'context' will always be NULL. + @TODO add an array/hash initialization function that takes a memory context pointer. + + #define STBDS_UNIT_TESTS + + Defines a function stbds_unit_tests() that checks the functioning of the data structures. + + Note that on older versions of gcc (e.g. 5.x.x) you may need to build with '-std=c++0x' + (or equivalentally '-std=c++11') when using anonymous structures as seen on the web + page or in STBDS_UNIT_TESTS. + +LICENSE + + Placed in the public domain and also MIT licensed. + See end of file for detailed license information. + +DOCUMENTATION + + Dynamic Arrays + + Non-function interface: + + Declare an empty dynamic array of type T + T* foo = NULL; + + Access the i'th item of a dynamic array 'foo' of type T, T* foo: + foo[i] + + Functions (actually macros) + + arrfree: + void arrfree(T*); + Frees the array. + + arrlen: + ptrdiff_t arrlen(T*); + Returns the number of elements in the array. + + arrlenu: + size_t arrlenu(T*); + Returns the number of elements in the array as an unsigned type. + + arrpop: + T arrpop(T* a) + Removes the final element of the array and returns it. + + arrput: + T arrput(T* a, T b); + Appends the item b to the end of array a. Returns b. + + arrins: + T arrins(T* a, int p, T b); + Inserts the item b into the middle of array a, into a[p], + moving the rest of the array over. Returns b. + + arrinsn: + void arrinsn(T* a, int p, int n); + Inserts n uninitialized items into array a starting at a[p], + moving the rest of the array over. + + arraddnptr: + T* arraddnptr(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns a pointer to the first uninitialized item added. + + arraddnindex: + size_t arraddnindex(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns the index of the first uninitialized item added. + + arrdel: + void arrdel(T* a, int p); + Deletes the element at a[p], moving the rest of the array over. + + arrdeln: + void arrdeln(T* a, int p, int n); + Deletes n elements starting at a[p], moving the rest of the array over. + + arrdelswap: + void arrdelswap(T* a, int p); + Deletes the element at a[p], replacing it with the element from + the end of the array. O(1) performance. + + arrsetlen: + void arrsetlen(T* a, int n); + Changes the length of the array to n. Allocates uninitialized + slots at the end if necessary. + + arrsetcap: + size_t arrsetcap(T* a, int n); + Sets the length of allocated storage to at least n. It will not + change the length of the array. + + arrcap: + size_t arrcap(T* a); + Returns the number of total elements the array can contain without + needing to be reallocated. + + Hash maps & String hash maps + + Given T is a structure type: struct { TK key; TV value; }. Note that some + functions do not require TV value and can have other fields. For string + hash maps, TK must be 'char *'. + + Special interface: + + stbds_rand_seed: + void stbds_rand_seed(size_t seed); + For security against adversarially chosen data, you should seed the + library with a strong random number. Or at least seed it with time(). + + stbds_hash_string: + size_t stbds_hash_string(char *str, size_t seed); + Returns a hash value for a string. + + stbds_hash_bytes: + size_t stbds_hash_bytes(void *p, size_t len, size_t seed); + These functions hash an arbitrary number of bytes. The function + uses a custom hash for 4- and 8-byte data, and a weakened version + of SipHash for everything else. On 64-bit platforms you can get + specification-compliant SipHash-2-4 on all data by defining + STBDS_SIPHASH_2_4, at a significant cost in speed. + + Non-function interface: + + Declare an empty hash map of type T + T* foo = NULL; + + Access the i'th entry in a hash table T* foo: + foo[i] + + Function interface (actually macros): + + hmfree + shfree + void hmfree(T*); + void shfree(T*); + Frees the hashmap and sets the pointer to NULL. + + hmlen + shlen + ptrdiff_t hmlen(T*) + ptrdiff_t shlen(T*) + Returns the number of elements in the hashmap. + + hmlenu + shlenu + size_t hmlenu(T*) + size_t shlenu(T*) + Returns the number of elements in the hashmap. + + hmgeti + shgeti + hmgeti_ts + ptrdiff_t hmgeti(T*, TK key) + ptrdiff_t shgeti(T*, char* key) + ptrdiff_t hmgeti_ts(T*, TK key, ptrdiff_t tempvar) + Returns the index in the hashmap which has the key 'key', or -1 + if the key is not present. + + hmget + hmget_ts + shget + TV hmget(T*, TK key) + TV shget(T*, char* key) + TV hmget_ts(T*, TK key, ptrdiff_t tempvar) + Returns the value corresponding to 'key' in the hashmap. + The structure must have a 'value' field + + hmgets + shgets + T hmgets(T*, TK key) + T shgets(T*, char* key) + Returns the structure corresponding to 'key' in the hashmap. + + hmgetp + shgetp + hmgetp_ts + hmgetp_null + shgetp_null + T* hmgetp(T*, TK key) + T* shgetp(T*, char* key) + T* hmgetp_ts(T*, TK key, ptrdiff_t tempvar) + T* hmgetp_null(T*, TK key) + T* shgetp_null(T*, char *key) + Returns a pointer to the structure corresponding to 'key' in + the hashmap. Functions ending in "_null" return NULL if the key + is not present in the hashmap; the others return a pointer to a + structure holding the default value (but not the searched-for key). + + hmdefault + shdefault + TV hmdefault(T*, TV value) + TV shdefault(T*, TV value) + Sets the default value for the hashmap, the value which will be + returned by hmget/shget if the key is not present. + + hmdefaults + shdefaults + TV hmdefaults(T*, T item) + TV shdefaults(T*, T item) + Sets the default struct for the hashmap, the contents which will be + returned by hmgets/shgets if the key is not present. + + hmput + shput + TV hmput(T*, TK key, TV value) + TV shput(T*, char* key, TV value) + Inserts a <key,value> pair into the hashmap. If the key is already + present in the hashmap, updates its value. + + hmputs + shputs + T hmputs(T*, T item) + T shputs(T*, T item) + Inserts a struct with T.key into the hashmap. If the struct is already + present in the hashmap, updates it. + + hmdel + shdel + int hmdel(T*, TK key) + int shdel(T*, char* key) + If 'key' is in the hashmap, deletes its entry and returns 1. + Otherwise returns 0. + + Function interface (actually macros) for strings only: + + sh_new_strdup + void sh_new_strdup(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate and free + each string key using realloc/free + + sh_new_arena + void sh_new_arena(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate each string + key to a string arena. Every string key ever used by this + hash table remains in the arena until the arena is freed. + Additionally, any key which is deleted and reinserted will + be allocated multiple times in the string arena. + +NOTES + + * These data structures are realloc'd when they grow, and the macro + "functions" write to the provided pointer. This means: (a) the pointer + must be an lvalue, and (b) the pointer to the data structure is not + stable, and you must maintain it the same as you would a realloc'd + pointer. For example, if you pass a pointer to a dynamic array to a + function which updates it, the function must return back the new + pointer to the caller. This is the price of trying to do this in C. + + * The following are the only functions that are thread-safe on a single data + structure, i.e. can be run in multiple threads simultaneously on the same + data structure + hmlen shlen + hmlenu shlenu + hmget_ts shget_ts + hmgeti_ts shgeti_ts + hmgets_ts shgets_ts + + * You iterate over the contents of a dynamic array and a hashmap in exactly + the same way, using arrlen/hmlen/shlen: + + for (i=0; i < arrlen(foo); ++i) + ... foo[i] ... + + * All operations except arrins/arrdel are O(1) amortized, but individual + operations can be slow, so these data structures may not be suitable + for real time use. Dynamic arrays double in capacity as needed, so + elements are copied an average of once. Hash tables double/halve + their size as needed, with appropriate hysteresis to maintain O(1) + performance. + +NOTES - DYNAMIC ARRAY + + * If you know how long a dynamic array is going to be in advance, you can avoid + extra memory allocations by using arrsetlen to allocate it to that length in + advance and use foo[n] while filling it out, or arrsetcap to allocate the memory + for that length and use arrput/arrpush as normal. + + * Unlike some other versions of the dynamic array, this version should + be safe to use with strict-aliasing optimizations. + +NOTES - HASH MAP + + * For compilers other than GCC and clang (e.g. Visual Studio), for hmput/hmget/hmdel + and variants, the key must be an lvalue (so the macro can take the address of it). + Extensions are used that eliminate this requirement if you're using C99 and later + in GCC or clang, or if you're using C++ in GCC. But note that this can make your + code less portable. + + * To test for presence of a key in a hashmap, just do 'hmgeti(foo,key) >= 0'. + + * The iteration order of your data in the hashmap is determined solely by the + order of insertions and deletions. In particular, if you never delete, new + keys are always added at the end of the array. This will be consistent + across all platforms and versions of the library. However, you should not + attempt to serialize the internal hash table, as the hash is not consistent + between different platforms, and may change with future versions of the library. + + * Use sh_new_arena() for string hashmaps that you never delete from. Initialize + with NULL if you're managing the memory for your strings, or your strings are + never freed (at least until the hashmap is freed). Otherwise, use sh_new_strdup(). + @TODO: make an arena variant that garbage collects the strings with a trivial + copy collector into a new arena whenever the table shrinks / rebuilds. Since + current arena recommendation is to only use arena if it never deletes, then + this can just replace current arena implementation. + + * If adversarial input is a serious concern and you're on a 64-bit platform, + enable STBDS_SIPHASH_2_4 (see the 'Compile-time options' section), and pass + a strong random number to stbds_rand_seed. + + * The default value for the hash table is stored in foo[-1], so if you + use code like 'hmget(T,k)->value = 5' you can accidentally overwrite + the value stored by hmdefault if 'k' is not present. + +CREDITS + + Sean Barrett -- library, idea for dynamic array API/implementation + Per Vognsen -- idea for hash table API/implementation + Rafael Sachetto -- arrpop() + github:HeroicKatora -- arraddn() reworking + + Bugfixes: + Andy Durdin + Shane Liesegang + Vinh Truong + Andreas Molzer + github:hashitaku + github:srdjanstipic + Macoy Madson + Andreas Vennstrom + Tobias Mansfield-Williams +*/ + +#ifdef STBDS_UNIT_TESTS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#ifndef INCLUDE_STB_DS_H +#define INCLUDE_STB_DS_H + +#include <stddef.h> +#include <string.h> + +#ifndef STBDS_NO_SHORT_NAMES +#define arrlen stbds_arrlen +#define arrlenu stbds_arrlenu +#define arrput stbds_arrput +#define arrpush stbds_arrput +#define arrpop stbds_arrpop +#define arrfree stbds_arrfree +#define arraddn stbds_arraddn // deprecated, use one of the following instead: +#define arraddnptr stbds_arraddnptr +#define arraddnindex stbds_arraddnindex +#define arrsetlen stbds_arrsetlen +#define arrlast stbds_arrlast +#define arrins stbds_arrins +#define arrinsn stbds_arrinsn +#define arrdel stbds_arrdel +#define arrdeln stbds_arrdeln +#define arrdelswap stbds_arrdelswap +#define arrcap stbds_arrcap +#define arrsetcap stbds_arrsetcap + +#define hmput stbds_hmput +#define hmputs stbds_hmputs +#define hmget stbds_hmget +#define hmget_ts stbds_hmget_ts +#define hmgets stbds_hmgets +#define hmgetp stbds_hmgetp +#define hmgetp_ts stbds_hmgetp_ts +#define hmgetp_null stbds_hmgetp_null +#define hmgeti stbds_hmgeti +#define hmgeti_ts stbds_hmgeti_ts +#define hmdel stbds_hmdel +#define hmlen stbds_hmlen +#define hmlenu stbds_hmlenu +#define hmfree stbds_hmfree +#define hmdefault stbds_hmdefault +#define hmdefaults stbds_hmdefaults + +#define shput stbds_shput +#define shputi stbds_shputi +#define shputs stbds_shputs +#define shget stbds_shget +#define shgeti stbds_shgeti +#define shgets stbds_shgets +#define shgetp stbds_shgetp +#define shgetp_null stbds_shgetp_null +#define shdel stbds_shdel +#define shlen stbds_shlen +#define shlenu stbds_shlenu +#define shfree stbds_shfree +#define shdefault stbds_shdefault +#define shdefaults stbds_shdefaults +#define sh_new_arena stbds_sh_new_arena +#define sh_new_strdup stbds_sh_new_strdup + +#define stralloc stbds_stralloc +#define strreset stbds_strreset +#endif + +#if defined(STBDS_REALLOC) && !defined(STBDS_FREE) || !defined(STBDS_REALLOC) && defined(STBDS_FREE) +#error "You must define both STBDS_REALLOC and STBDS_FREE, or neither." +#endif +#if !defined(STBDS_REALLOC) && !defined(STBDS_FREE) +#include <stdlib.h> +#define STBDS_REALLOC(c,p,s) realloc(p,s) +#define STBDS_FREE(c,p) free(p) +#endif + +#ifdef _MSC_VER +#define STBDS_NOTUSED(v) (void)(v) +#else +#define STBDS_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// for security against attackers, seed the library with a random number, at least time() but stronger is better +extern void stbds_rand_seed(size_t seed); + +// these are the hash functions used internally if you want to test them or use them for other purposes +extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed); +extern size_t stbds_hash_string(char *str, size_t seed); + +// this is a simple string arena allocator, initialize with e.g. 'stbds_string_arena my_arena={0}'. +typedef struct stbds_string_arena stbds_string_arena; +extern char * stbds_stralloc(stbds_string_arena *a, char *str); +extern void stbds_strreset(stbds_string_arena *a); + +// have to #define STBDS_UNIT_TESTS to call this +extern void stbds_unit_tests(void); + +/////////////// +// +// Everything below here is implementation details +// + +extern void * stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap); +extern void stbds_arrfreef(void *a); +extern void stbds_hmfree_func(void *p, size_t elemsize); +extern void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); +extern void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode); +extern void * stbds_hmput_default(void *a, size_t elemsize); +extern void * stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); +extern void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode); +extern void * stbds_shmode_func(size_t elemsize, int mode); + +#ifdef __cplusplus +} +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define STBDS_HAS_TYPEOF +#ifdef __cplusplus +//#define STBDS_HAS_LITERAL_ARRAY // this is currently broken for clang +#endif +#endif + +#if !defined(__cplusplus) +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define STBDS_HAS_LITERAL_ARRAY +#endif +#endif + +// this macro takes the address of the argument, but on gcc/clang can accept rvalues +#if defined(STBDS_HAS_LITERAL_ARRAY) && defined(STBDS_HAS_TYPEOF) + #if __clang__ + #define STBDS_ADDRESSOF(typevar, value) ((__typeof__(typevar)[1]){value}) // literal array decays to pointer to value + #else + #define STBDS_ADDRESSOF(typevar, value) ((typeof(typevar)[1]){value}) // literal array decays to pointer to value + #endif +#else +#define STBDS_ADDRESSOF(typevar, value) &(value) +#endif + +#define STBDS_OFFSETOF(var,field) ((char *) &(var)->field - (char *) (var)) + +#define stbds_header(t) ((stbds_array_header *) (t) - 1) +#define stbds_temp(t) stbds_header(t)->temp +#define stbds_temp_key(t) (*(char **) stbds_header(t)->hash_table) + +#define stbds_arrsetcap(a,n) (stbds_arrgrow(a,0,n)) +#define stbds_arrsetlen(a,n) ((stbds_arrcap(a) < (size_t) (n) ? stbds_arrsetcap((a),(size_t)(n)),0 : 0), (a) ? stbds_header(a)->length = (size_t) (n) : 0) +#define stbds_arrcap(a) ((a) ? stbds_header(a)->capacity : 0) +#define stbds_arrlen(a) ((a) ? (ptrdiff_t) stbds_header(a)->length : 0) +#define stbds_arrlenu(a) ((a) ? stbds_header(a)->length : 0) +#define stbds_arrput(a,v) (stbds_arrmaybegrow(a,1), (a)[stbds_header(a)->length++] = (v)) +#define stbds_arrpush stbds_arrput // synonym +#define stbds_arrpop(a) (stbds_header(a)->length--, (a)[stbds_header(a)->length]) +#define stbds_arraddn(a,n) ((void)(stbds_arraddnindex(a, n))) // deprecated, use one of the following instead: +#define stbds_arraddnptr(a,n) (stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), &(a)[stbds_header(a)->length-(n)]) : (a)) +#define stbds_arraddnindex(a,n)(stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), stbds_header(a)->length-(n)) : stbds_arrlen(a)) +#define stbds_arraddnoff stbds_arraddnindex +#define stbds_arrlast(a) ((a)[stbds_header(a)->length-1]) +#define stbds_arrfree(a) ((void) ((a) ? STBDS_FREE(NULL,stbds_header(a)) : (void)0), (a)=NULL) +#define stbds_arrdel(a,i) stbds_arrdeln(a,i,1) +#define stbds_arrdeln(a,i,n) (memmove(&(a)[i], &(a)[(i)+(n)], sizeof *(a) * (stbds_header(a)->length-(n)-(i))), stbds_header(a)->length -= (n)) +#define stbds_arrdelswap(a,i) ((a)[i] = stbds_arrlast(a), stbds_header(a)->length -= 1) +#define stbds_arrinsn(a,i,n) (stbds_arraddn((a),(n)), memmove(&(a)[(i)+(n)], &(a)[i], sizeof *(a) * (stbds_header(a)->length-(n)-(i)))) +#define stbds_arrins(a,i,v) (stbds_arrinsn((a),(i),1), (a)[i]=(v)) + +#define stbds_arrmaybegrow(a,n) ((!(a) || stbds_header(a)->length + (n) > stbds_header(a)->capacity) \ + ? (stbds_arrgrow(a,n,0),0) : 0) + +#define stbds_arrgrow(a,b,c) ((a) = stbds_arrgrowf_wrapper((a), sizeof *(a), (b), (c))) + +#define stbds_hmput(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, 0), \ + (t)[stbds_temp((t)-1)].key = (k), \ + (t)[stbds_temp((t)-1)].value = (v)) + +#define stbds_hmputs(t, s) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), &(s).key, sizeof (s).key, STBDS_HM_BINARY), \ + (t)[stbds_temp((t)-1)] = (s)) + +#define stbds_hmgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_HM_BINARY), \ + stbds_temp((t)-1)) + +#define stbds_hmgeti_ts(t,k,temp) \ + ((t) = stbds_hmget_key_ts_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, &(temp), STBDS_HM_BINARY), \ + (temp)) + +#define stbds_hmgetp(t, k) \ + ((void) stbds_hmgeti(t,k), &(t)[stbds_temp((t)-1)]) + +#define stbds_hmgetp_ts(t, k, temp) \ + ((void) stbds_hmgeti_ts(t,k,temp), &(t)[temp]) + +#define stbds_hmdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_BINARY)),(t)?stbds_temp((t)-1):0) + +#define stbds_hmdefault(t, v) \ + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1].value = (v)) + +#define stbds_hmdefaults(t, s) \ + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1] = (s)) + +#define stbds_hmfree(p) \ + ((void) ((p) != NULL ? stbds_hmfree_func((p)-1,sizeof*(p)),0 : 0),(p)=NULL) + +#define stbds_hmgets(t, k) (*stbds_hmgetp(t,k)) +#define stbds_hmget(t, k) (stbds_hmgetp(t,k)->value) +#define stbds_hmget_ts(t, k, temp) (stbds_hmgetp_ts(t,k,temp)->value) +#define stbds_hmlen(t) ((t) ? (ptrdiff_t) stbds_header((t)-1)->length-1 : 0) +#define stbds_hmlenu(t) ((t) ? stbds_header((t)-1)->length-1 : 0) +#define stbds_hmgetp_null(t,k) (stbds_hmgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) + +#define stbds_shput(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v)) + +#define stbds_shputi(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v), stbds_temp((t)-1)) + +#define stbds_shputs(t, s) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (s).key, sizeof (s).key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)] = (s), \ + (t)[stbds_temp((t)-1)].key = stbds_temp_key((t)-1)) // above line overwrites whole structure, so must rewrite key here if it was allocated internally + +#define stbds_pshput(t, p) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (p)->key, sizeof (p)->key, STBDS_HM_PTR_TO_STRING), \ + (t)[stbds_temp((t)-1)] = (p)) + +#define stbds_shgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + stbds_temp((t)-1)) + +#define stbds_pshgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_HM_PTR_TO_STRING), \ + stbds_temp((t)-1)) + +#define stbds_shgetp(t, k) \ + ((void) stbds_shgeti(t,k), &(t)[stbds_temp((t)-1)]) + +#define stbds_pshget(t, k) \ + ((void) stbds_pshgeti(t,k), (t)[stbds_temp((t)-1)]) + +#define stbds_shdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_STRING)),(t)?stbds_temp((t)-1):0) +#define stbds_pshdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_OFFSETOF(*(t),key), STBDS_HM_PTR_TO_STRING)),(t)?stbds_temp((t)-1):0) + +#define stbds_sh_new_arena(t) \ + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_ARENA)) +#define stbds_sh_new_strdup(t) \ + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_STRDUP)) + +#define stbds_shdefault(t, v) stbds_hmdefault(t,v) +#define stbds_shdefaults(t, s) stbds_hmdefaults(t,s) + +#define stbds_shfree stbds_hmfree +#define stbds_shlenu stbds_hmlenu + +#define stbds_shgets(t, k) (*stbds_shgetp(t,k)) +#define stbds_shget(t, k) (stbds_shgetp(t,k)->value) +#define stbds_shgetp_null(t,k) (stbds_shgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) +#define stbds_shlen stbds_hmlen + +typedef struct +{ + size_t length; + size_t capacity; + void * hash_table; + ptrdiff_t temp; +} stbds_array_header; + +typedef struct stbds_string_block +{ + struct stbds_string_block *next; + char storage[8]; +} stbds_string_block; + +struct stbds_string_arena +{ + stbds_string_block *storage; + size_t remaining; + unsigned char block; + unsigned char mode; // this isn't used by the string arena itself +}; + +#define STBDS_HM_BINARY 0 +#define STBDS_HM_STRING 1 + +enum +{ + STBDS_SH_NONE, + STBDS_SH_DEFAULT, + STBDS_SH_STRDUP, + STBDS_SH_ARENA +}; + +#ifdef __cplusplus +// in C we use implicit assignment from these void*-returning functions to T*. +// in C++ these templates make the same code work +template<class T> static T * stbds_arrgrowf_wrapper(T *a, size_t elemsize, size_t addlen, size_t min_cap) { + return (T*)stbds_arrgrowf((void *)a, elemsize, addlen, min_cap); +} +template<class T> static T * stbds_hmget_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { + return (T*)stbds_hmget_key((void*)a, elemsize, key, keysize, mode); +} +template<class T> static T * stbds_hmget_key_ts_wrapper(T *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) { + return (T*)stbds_hmget_key_ts((void*)a, elemsize, key, keysize, temp, mode); +} +template<class T> static T * stbds_hmput_default_wrapper(T *a, size_t elemsize) { + return (T*)stbds_hmput_default((void *)a, elemsize); +} +template<class T> static T * stbds_hmput_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { + return (T*)stbds_hmput_key((void*)a, elemsize, key, keysize, mode); +} +template<class T> static T * stbds_hmdel_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode){ + return (T*)stbds_hmdel_key((void*)a, elemsize, key, keysize, keyoffset, mode); +} +template<class T> static T * stbds_shmode_func_wrapper(T *, size_t elemsize, int mode) { + return (T*)stbds_shmode_func(elemsize, mode); +} +#else +#define stbds_arrgrowf_wrapper stbds_arrgrowf +#define stbds_hmget_key_wrapper stbds_hmget_key +#define stbds_hmget_key_ts_wrapper stbds_hmget_key_ts +#define stbds_hmput_default_wrapper stbds_hmput_default +#define stbds_hmput_key_wrapper stbds_hmput_key +#define stbds_hmdel_key_wrapper stbds_hmdel_key +#define stbds_shmode_func_wrapper(t,e,m) stbds_shmode_func(e,m) +#endif + +#endif // INCLUDE_STB_DS_H + + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// + +#ifdef STB_DS_IMPLEMENTATION +#include <assert.h> +#include <string.h> + +#ifndef STBDS_ASSERT +#define STBDS_ASSERT_WAS_UNDEFINED +#define STBDS_ASSERT(x) ((void) 0) +#endif + +#ifdef STBDS_STATISTICS +#define STBDS_STATS(x) x +size_t stbds_array_grow; +size_t stbds_hash_grow; +size_t stbds_hash_shrink; +size_t stbds_hash_rebuild; +size_t stbds_hash_probes; +size_t stbds_hash_alloc; +size_t stbds_rehash_probes; +size_t stbds_rehash_items; +#else +#define STBDS_STATS(x) +#endif + +// +// stbds_arr implementation +// + +//int *prev_allocs[65536]; +//int num_prev; + +void *stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap) +{ + stbds_array_header temp={0}; // force debugging + void *b; + size_t min_len = stbds_arrlen(a) + addlen; + (void) sizeof(temp); + + // compute the minimum capacity needed + if (min_len > min_cap) + min_cap = min_len; + + if (min_cap <= stbds_arrcap(a)) + return a; + + // increase needed capacity to guarantee O(1) amortized + if (min_cap < 2 * stbds_arrcap(a)) + min_cap = 2 * stbds_arrcap(a); + else if (min_cap < 4) + min_cap = 4; + + //if (num_prev < 65536) if (a) prev_allocs[num_prev++] = (int *) ((char *) a+1); + //if (num_prev == 2201) + // num_prev = num_prev; + b = STBDS_REALLOC(NULL, (a) ? stbds_header(a) : 0, elemsize * min_cap + sizeof(stbds_array_header)); + //if (num_prev < 65536) prev_allocs[num_prev++] = (int *) (char *) b; + b = (char *) b + sizeof(stbds_array_header); + if (a == NULL) { + stbds_header(b)->length = 0; + stbds_header(b)->hash_table = 0; + stbds_header(b)->temp = 0; + } else { + STBDS_STATS(++stbds_array_grow); + } + stbds_header(b)->capacity = min_cap; + + return b; +} + +void stbds_arrfreef(void *a) +{ + STBDS_FREE(NULL, stbds_header(a)); +} + +// +// stbds_hm hash table implementation +// + +#ifdef STBDS_INTERNAL_SMALL_BUCKET +#define STBDS_BUCKET_LENGTH 4 +#else +#define STBDS_BUCKET_LENGTH 8 +#endif + +#define STBDS_BUCKET_SHIFT (STBDS_BUCKET_LENGTH == 8 ? 3 : 2) +#define STBDS_BUCKET_MASK (STBDS_BUCKET_LENGTH-1) +#define STBDS_CACHE_LINE_SIZE 64 + +#define STBDS_ALIGN_FWD(n,a) (((n) + (a) - 1) & ~((a)-1)) + +typedef struct +{ + size_t hash [STBDS_BUCKET_LENGTH]; + ptrdiff_t index[STBDS_BUCKET_LENGTH]; +} stbds_hash_bucket; // in 32-bit, this is one 64-byte cache line; in 64-bit, each array is one 64-byte cache line + +typedef struct +{ + char * temp_key; // this MUST be the first field of the hash table + size_t slot_count; + size_t used_count; + size_t used_count_threshold; + size_t used_count_shrink_threshold; + size_t tombstone_count; + size_t tombstone_count_threshold; + size_t seed; + size_t slot_count_log2; + stbds_string_arena string; + stbds_hash_bucket *storage; // not a separate allocation, just 64-byte aligned storage after this struct +} stbds_hash_index; + +#define STBDS_INDEX_EMPTY -1 +#define STBDS_INDEX_DELETED -2 +#define STBDS_INDEX_IN_USE(x) ((x) >= 0) + +#define STBDS_HASH_EMPTY 0 +#define STBDS_HASH_DELETED 1 + +static size_t stbds_hash_seed=0x31415926; + +void stbds_rand_seed(size_t seed) +{ + stbds_hash_seed = seed; +} + +#define stbds_load_32_or_64(var, temp, v32, v64_hi, v64_lo) \ + temp = v64_lo ^ v32, temp <<= 16, temp <<= 16, temp >>= 16, temp >>= 16, /* discard if 32-bit */ \ + var = v64_hi, var <<= 16, var <<= 16, /* discard if 32-bit */ \ + var ^= temp ^ v32 + +#define STBDS_SIZE_T_BITS ((sizeof (size_t)) * 8) + +static size_t stbds_probe_position(size_t hash, size_t slot_count, size_t slot_log2) +{ + size_t pos; + STBDS_NOTUSED(slot_log2); + pos = hash & (slot_count-1); + #ifdef STBDS_INTERNAL_BUCKET_START + pos &= ~STBDS_BUCKET_MASK; + #endif + return pos; +} + +static size_t stbds_log2(size_t slot_count) +{ + size_t n=0; + while (slot_count > 1) { + slot_count >>= 1; + ++n; + } + return n; +} + +static stbds_hash_index *stbds_make_hash_index(size_t slot_count, stbds_hash_index *ot) +{ + stbds_hash_index *t; + t = (stbds_hash_index *) STBDS_REALLOC(NULL,0,(slot_count >> STBDS_BUCKET_SHIFT) * sizeof(stbds_hash_bucket) + sizeof(stbds_hash_index) + STBDS_CACHE_LINE_SIZE-1); + t->storage = (stbds_hash_bucket *) STBDS_ALIGN_FWD((size_t) (t+1), STBDS_CACHE_LINE_SIZE); + t->slot_count = slot_count; + t->slot_count_log2 = stbds_log2(slot_count); + t->tombstone_count = 0; + t->used_count = 0; + + #if 0 // A1 + t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink + #elif 1 // A2 + //t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow + //t->tombstone_count_threshold = slot_count* 3/16; // if tombstones are 3/16th of table, rebuild + //t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink + + // compute without overflowing + t->used_count_threshold = slot_count - (slot_count>>2); + t->tombstone_count_threshold = (slot_count>>3) + (slot_count>>4); + t->used_count_shrink_threshold = slot_count >> 2; + + #elif 0 // B1 + t->used_count_threshold = slot_count*13/16; // if 13/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 5/16; // if table is only 5/16th full, shrink + #else // C1 + t->used_count_threshold = slot_count*14/16; // if 14/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 6/16; // if table is only 6/16th full, shrink + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // A1 A2 B1 C1 + // 0.10ms : 0.10ms : 0.10ms : 0.11ms : 2,000 inserts creating 2K table + // 0.96ms : 0.95ms : 0.97ms : 1.04ms : 20,000 inserts creating 20K table + // 14.48ms : 14.46ms : 10.63ms : 11.00ms : 200,000 inserts creating 200K table + // 195.74ms : 196.35ms : 203.69ms : 214.92ms : 2,000,000 inserts creating 2M table + // 2193.88ms : 2209.22ms : 2285.54ms : 2437.17ms : 20,000,000 inserts creating 20M table + // 65.27ms : 53.77ms : 65.33ms : 65.47ms : 500,000 inserts & deletes in 2K table + // 72.78ms : 62.45ms : 71.95ms : 72.85ms : 500,000 inserts & deletes in 20K table + // 89.47ms : 77.72ms : 96.49ms : 96.75ms : 500,000 inserts & deletes in 200K table + // 97.58ms : 98.14ms : 97.18ms : 97.53ms : 500,000 inserts & deletes in 2M table + // 118.61ms : 119.62ms : 120.16ms : 118.86ms : 500,000 inserts & deletes in 20M table + // 192.11ms : 194.39ms : 196.38ms : 195.73ms : 500,000 inserts & deletes in 200M table + + if (slot_count <= STBDS_BUCKET_LENGTH) + t->used_count_shrink_threshold = 0; + // to avoid infinite loop, we need to guarantee that at least one slot is empty and will terminate probes + STBDS_ASSERT(t->used_count_threshold + t->tombstone_count_threshold < t->slot_count); + STBDS_STATS(++stbds_hash_alloc); + if (ot) { + t->string = ot->string; + // reuse old seed so we can reuse old hashes so below "copy out old data" doesn't do any hashing + t->seed = ot->seed; + } else { + size_t a,b,temp; + memset(&t->string, 0, sizeof(t->string)); + t->seed = stbds_hash_seed; + // LCG + // in 32-bit, a = 2147001325 b = 715136305 + // in 64-bit, a = 2862933555777941757 b = 3037000493 + stbds_load_32_or_64(a,temp, 2147001325, 0x27bb2ee6, 0x87b0b0fd); + stbds_load_32_or_64(b,temp, 715136305, 0, 0xb504f32d); + stbds_hash_seed = stbds_hash_seed * a + b; + } + + { + size_t i,j; + for (i=0; i < slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *b = &t->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->hash[j] = STBDS_HASH_EMPTY; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->index[j] = STBDS_INDEX_EMPTY; + } + } + + // copy out the old data, if any + if (ot) { + size_t i,j; + t->used_count = ot->used_count; + for (i=0; i < ot->slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *ob = &ot->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) { + if (STBDS_INDEX_IN_USE(ob->index[j])) { + size_t hash = ob->hash[j]; + size_t pos = stbds_probe_position(hash, t->slot_count, t->slot_count_log2); + size_t step = STBDS_BUCKET_LENGTH; + STBDS_STATS(++stbds_rehash_items); + for (;;) { + size_t limit,z; + stbds_hash_bucket *bucket; + bucket = &t->storage[pos >> STBDS_BUCKET_SHIFT]; + STBDS_STATS(++stbds_rehash_probes); + + for (z=pos & STBDS_BUCKET_MASK; z < STBDS_BUCKET_LENGTH; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } + + limit = pos & STBDS_BUCKET_MASK; + for (z = 0; z < limit; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } + + pos += step; // quadratic probing + step += STBDS_BUCKET_LENGTH; + pos &= (t->slot_count-1); + } + } + done: + ; + } + } + } + + return t; +} + +#define STBDS_ROTATE_LEFT(val, n) (((val) << (n)) | ((val) >> (STBDS_SIZE_T_BITS - (n)))) +#define STBDS_ROTATE_RIGHT(val, n) (((val) >> (n)) | ((val) << (STBDS_SIZE_T_BITS - (n)))) + +size_t stbds_hash_string(char *str, size_t seed) +{ + size_t hash = seed; + while (*str) + hash = STBDS_ROTATE_LEFT(hash, 9) + (unsigned char) *str++; + + // Thomas Wang 64-to-32 bit mix function, hopefully also works in 32 bits + hash ^= seed; + hash = (~hash) + (hash << 18); + hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,31); + hash = hash * 21; + hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,11); + hash += (hash << 6); + hash ^= STBDS_ROTATE_RIGHT(hash,22); + return hash+seed; +} + +#ifdef STBDS_SIPHASH_2_4 +#define STBDS_SIPHASH_C_ROUNDS 2 +#define STBDS_SIPHASH_D_ROUNDS 4 +typedef int STBDS_SIPHASH_2_4_can_only_be_used_in_64_bit_builds[sizeof(size_t) == 8 ? 1 : -1]; +#endif + +#ifndef STBDS_SIPHASH_C_ROUNDS +#define STBDS_SIPHASH_C_ROUNDS 1 +#endif +#ifndef STBDS_SIPHASH_D_ROUNDS +#define STBDS_SIPHASH_D_ROUNDS 1 +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4127) // conditional expression is constant, for do..while(0) and sizeof()== +#endif + +static size_t stbds_siphash_bytes(void *p, size_t len, size_t seed) +{ + unsigned char *d = (unsigned char *) p; + size_t i,j; + size_t v0,v1,v2,v3, data; + + // hash that works on 32- or 64-bit registers without knowing which we have + // (computes different results on 32-bit and 64-bit platform) + // derived from siphash, but on 32-bit platforms very different as it uses 4 32-bit state not 4 64-bit + v0 = ((((size_t) 0x736f6d65 << 16) << 16) + 0x70736575) ^ seed; + v1 = ((((size_t) 0x646f7261 << 16) << 16) + 0x6e646f6d) ^ ~seed; + v2 = ((((size_t) 0x6c796765 << 16) << 16) + 0x6e657261) ^ seed; + v3 = ((((size_t) 0x74656462 << 16) << 16) + 0x79746573) ^ ~seed; + + #ifdef STBDS_TEST_SIPHASH_2_4 + // hardcoded with key material in the siphash test vectors + v0 ^= 0x0706050403020100ull ^ seed; + v1 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; + v2 ^= 0x0706050403020100ull ^ seed; + v3 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; + #endif + + #define STBDS_SIPROUND() \ + do { \ + v0 += v1; v1 = STBDS_ROTATE_LEFT(v1, 13); v1 ^= v0; v0 = STBDS_ROTATE_LEFT(v0,STBDS_SIZE_T_BITS/2); \ + v2 += v3; v3 = STBDS_ROTATE_LEFT(v3, 16); v3 ^= v2; \ + v2 += v1; v1 = STBDS_ROTATE_LEFT(v1, 17); v1 ^= v2; v2 = STBDS_ROTATE_LEFT(v2,STBDS_SIZE_T_BITS/2); \ + v0 += v3; v3 = STBDS_ROTATE_LEFT(v3, 21); v3 ^= v0; \ + } while (0) + + for (i=0; i+sizeof(size_t) <= len; i += sizeof(size_t), d += sizeof(size_t)) { + data = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + data |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // discarded if size_t == 4 + + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; + } + data = len << (STBDS_SIZE_T_BITS-8); + switch (len - i) { + case 7: data |= ((size_t) d[6] << 24) << 24; // fall through + case 6: data |= ((size_t) d[5] << 20) << 20; // fall through + case 5: data |= ((size_t) d[4] << 16) << 16; // fall through + case 4: data |= (d[3] << 24); // fall through + case 3: data |= (d[2] << 16); // fall through + case 2: data |= (d[1] << 8); // fall through + case 1: data |= d[0]; // fall through + case 0: break; + } + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; + v2 ^= 0xff; + for (j=0; j < STBDS_SIPHASH_D_ROUNDS; ++j) + STBDS_SIPROUND(); + +#ifdef STBDS_SIPHASH_2_4 + return v0^v1^v2^v3; +#else + return v1^v2^v3; // slightly stronger since v0^v3 in above cancels out final round operation? I tweeted at the authors of SipHash about this but they didn't reply +#endif +} + +size_t stbds_hash_bytes(void *p, size_t len, size_t seed) +{ +#ifdef STBDS_SIPHASH_2_4 + return stbds_siphash_bytes(p,len,seed); +#else + unsigned char *d = (unsigned char *) p; + + if (len == 4) { + unsigned int hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + #if 0 + // HASH32-A Bob Jenkin's hash function w/o large constants + hash ^= seed; + hash -= (hash<<6); + hash ^= (hash>>17); + hash -= (hash<<9); + hash ^= seed; + hash ^= (hash<<4); + hash -= (hash<<3); + hash ^= (hash<<10); + hash ^= (hash>>15); + #elif 1 + // HASH32-BB Bob Jenkin's presumably-accidental version of Thomas Wang hash with rotates turned into shifts. + // Note that converting these back to rotates makes it run a lot slower, presumably due to collisions, so I'm + // not really sure what's going on. + hash ^= seed; + hash = (hash ^ 61) ^ (hash >> 16); + hash = hash + (hash << 3); + hash = hash ^ (hash >> 4); + hash = hash * 0x27d4eb2d; + hash ^= seed; + hash = hash ^ (hash >> 15); + #else // HASH32-C - Murmur3 + hash ^= seed; + hash *= 0xcc9e2d51; + hash = (hash << 17) | (hash >> 15); + hash *= 0x1b873593; + hash ^= seed; + hash = (hash << 19) | (hash >> 13); + hash = hash*5 + 0xe6546b64; + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= seed; + hash ^= hash >> 13; + hash *= 0xc2b2ae35; + hash ^= hash >> 16; + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // HASH32-A // HASH32-BB // HASH32-C + // 0.10ms // 0.10ms // 0.10ms : 2,000 inserts creating 2K table + // 0.96ms // 0.95ms // 0.99ms : 20,000 inserts creating 20K table + // 14.69ms // 14.43ms // 14.97ms : 200,000 inserts creating 200K table + // 199.99ms // 195.36ms // 202.05ms : 2,000,000 inserts creating 2M table + // 2234.84ms // 2187.74ms // 2240.38ms : 20,000,000 inserts creating 20M table + // 55.68ms // 53.72ms // 57.31ms : 500,000 inserts & deletes in 2K table + // 63.43ms // 61.99ms // 65.73ms : 500,000 inserts & deletes in 20K table + // 80.04ms // 77.96ms // 81.83ms : 500,000 inserts & deletes in 200K table + // 100.42ms // 97.40ms // 102.39ms : 500,000 inserts & deletes in 2M table + // 119.71ms // 120.59ms // 121.63ms : 500,000 inserts & deletes in 20M table + // 185.28ms // 195.15ms // 187.74ms : 500,000 inserts & deletes in 200M table + // 15.58ms // 14.79ms // 15.52ms : 200,000 inserts creating 200K table with varying key spacing + + return (((size_t) hash << 16 << 16) | hash) ^ seed; + } else if (len == 8 && sizeof(size_t) == 8) { + size_t hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + hash |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // avoid warning if size_t == 4 + hash ^= seed; + hash = (~hash) + (hash << 21); + hash ^= STBDS_ROTATE_RIGHT(hash,24); + hash *= 265; + hash ^= STBDS_ROTATE_RIGHT(hash,14); + hash ^= seed; + hash *= 21; + hash ^= STBDS_ROTATE_RIGHT(hash,28); + hash += (hash << 31); + hash = (~hash) + (hash << 18); + return hash; + } else { + return stbds_siphash_bytes(p,len,seed); + } +#endif +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + +static int stbds_is_key_equal(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode, size_t i) +{ + if (mode >= STBDS_HM_STRING) + return 0==strcmp((char *) key, * (char **) ((char *) a + elemsize*i + keyoffset)); + else + return 0==memcmp(key, (char *) a + elemsize*i + keyoffset, keysize); +} + +#define STBDS_HASH_TO_ARR(x,elemsize) ((char*) (x) - (elemsize)) +#define STBDS_ARR_TO_HASH(x,elemsize) ((char*) (x) + (elemsize)) + +#define stbds_hash_table(a) ((stbds_hash_index *) stbds_header(a)->hash_table) + +void stbds_hmfree_func(void *a, size_t elemsize) +{ + if (a == NULL) return; + if (stbds_hash_table(a) != NULL) { + if (stbds_hash_table(a)->string.mode == STBDS_SH_STRDUP) { + size_t i; + // skip 0th element, which is default + for (i=1; i < stbds_header(a)->length; ++i) + STBDS_FREE(NULL, *(char**) ((char *) a + elemsize*i)); + } + stbds_strreset(&stbds_hash_table(a)->string); + } + STBDS_FREE(NULL, stbds_header(a)->hash_table); + STBDS_FREE(NULL, stbds_header(a)); +} + +static ptrdiff_t stbds_hm_find_slot(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) +{ + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + stbds_hash_index *table = stbds_hash_table(raw_a); + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t limit,i; + size_t pos; + stbds_hash_bucket *bucket; + + if (hash < 2) hash += 2; // stored hash values are forbidden from being 0, so we can detect empty slots + + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + + for (;;) { + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + + // start searching from pos to end of bucket, this should help performance on small hash tables that fit in cache + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } + + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } + + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } + /* NOTREACHED */ +} + +void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) +{ + size_t keyoffset = 0; + if (a == NULL) { + // make it non-empty so we can return a temp + a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + *temp = STBDS_INDEX_EMPTY; + // adjust a to point after the default element + return STBDS_ARR_TO_HASH(a,elemsize); + } else { + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + // adjust a to point to the default element + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + if (table == 0) { + *temp = -1; + } else { + ptrdiff_t slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) { + *temp = STBDS_INDEX_EMPTY; + } else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + *temp = b->index[slot & STBDS_BUCKET_MASK]; + } + } + return a; + } +} + +void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) +{ + ptrdiff_t temp; + void *p = stbds_hmget_key_ts(a, elemsize, key, keysize, &temp, mode); + stbds_temp(STBDS_HASH_TO_ARR(p,elemsize)) = temp; + return p; +} + +void * stbds_hmput_default(void *a, size_t elemsize) +{ + // three cases: + // a is NULL <- allocate + // a has a hash table but no entries, because of shmode <- grow + // a has entries <- do nothing + if (a == NULL || stbds_header(STBDS_HASH_TO_ARR(a,elemsize))->length == 0) { + a = stbds_arrgrowf(a ? STBDS_HASH_TO_ARR(a,elemsize) : NULL, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + a=STBDS_ARR_TO_HASH(a,elemsize); + } + return a; +} + +static char *stbds_strdup(char *str); + +void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) +{ + size_t keyoffset=0; + void *raw_a; + stbds_hash_index *table; + + if (a == NULL) { + a = stbds_arrgrowf(0, elemsize, 0, 1); + memset(a, 0, elemsize); + stbds_header(a)->length += 1; + // adjust a to point AFTER the default element + a = STBDS_ARR_TO_HASH(a,elemsize); + } + + // adjust a to point to the default element + raw_a = a; + a = STBDS_HASH_TO_ARR(a,elemsize); + + table = (stbds_hash_index *) stbds_header(a)->hash_table; + + if (table == NULL || table->used_count >= table->used_count_threshold) { + stbds_hash_index *nt; + size_t slot_count; + + slot_count = (table == NULL) ? STBDS_BUCKET_LENGTH : table->slot_count*2; + nt = stbds_make_hash_index(slot_count, table); + if (table) + STBDS_FREE(NULL, table); + else + nt->string.mode = mode >= STBDS_HM_STRING ? STBDS_SH_DEFAULT : 0; + stbds_header(a)->hash_table = table = nt; + STBDS_STATS(++stbds_hash_grow); + } + + // we iterate hash table explicitly because we want to track if we saw a tombstone + { + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t pos; + ptrdiff_t tombstone = -1; + stbds_hash_bucket *bucket; + + // stored hash values are forbidden from being 0, so we can detect empty slots to early out quickly + if (hash < 2) hash += 2; + + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + + for (;;) { + size_t limit, i; + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + + // start searching from pos to end of bucket + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + if (mode >= STBDS_HM_STRING) + stbds_temp_key(a) = * (char **) ((char *) raw_a + elemsize*bucket->index[i] + keyoffset); + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } + + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } + + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } + found_empty_slot: + if (tombstone >= 0) { + pos = tombstone; + --table->tombstone_count; + } + ++table->used_count; + + { + ptrdiff_t i = (ptrdiff_t) stbds_arrlen(a); + // we want to do stbds_arraddn(1), but we can't use the macros since we don't have something of the right type + if ((size_t) i+1 > stbds_arrcap(a)) + *(void **) &a = stbds_arrgrowf(a, elemsize, 1, 0); + raw_a = STBDS_ARR_TO_HASH(a,elemsize); + + STBDS_ASSERT((size_t) i+1 <= stbds_arrcap(a)); + stbds_header(a)->length = i+1; + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + bucket->hash[pos & STBDS_BUCKET_MASK] = hash; + bucket->index[pos & STBDS_BUCKET_MASK] = i-1; + stbds_temp(a) = i-1; + + switch (table->string.mode) { + case STBDS_SH_STRDUP: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_strdup((char*) key); break; + case STBDS_SH_ARENA: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_stralloc(&table->string, (char*)key); break; + case STBDS_SH_DEFAULT: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = (char *) key; break; + default: memcpy((char *) a + elemsize*i, key, keysize); break; + } + } + return STBDS_ARR_TO_HASH(a,elemsize); + } +} + +void * stbds_shmode_func(size_t elemsize, int mode) +{ + void *a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_hash_index *h; + memset(a, 0, elemsize); + stbds_header(a)->length = 1; + stbds_header(a)->hash_table = h = (stbds_hash_index *) stbds_make_hash_index(STBDS_BUCKET_LENGTH, NULL); + h->string.mode = (unsigned char) mode; + return STBDS_ARR_TO_HASH(a,elemsize); +} + +void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) +{ + if (a == NULL) { + return 0; + } else { + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + stbds_temp(raw_a) = 0; + if (table == 0) { + return a; + } else { + ptrdiff_t slot; + slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) + return a; + else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + int i = slot & STBDS_BUCKET_MASK; + ptrdiff_t old_index = b->index[i]; + ptrdiff_t final_index = (ptrdiff_t) stbds_arrlen(raw_a)-1-1; // minus one for the raw_a vs a, and minus one for 'last' + STBDS_ASSERT(slot < (ptrdiff_t) table->slot_count); + --table->used_count; + ++table->tombstone_count; + stbds_temp(raw_a) = 1; + STBDS_ASSERT(table->used_count >= 0); + //STBDS_ASSERT(table->tombstone_count < table->slot_count/4); + b->hash[i] = STBDS_HASH_DELETED; + b->index[i] = STBDS_INDEX_DELETED; + + if (mode == STBDS_HM_STRING && table->string.mode == STBDS_SH_STRDUP) + STBDS_FREE(NULL, *(char**) ((char *) a+elemsize*old_index)); + + // if indices are the same, memcpy is a no-op, but back-pointer-fixup will fail, so skip + if (old_index != final_index) { + // swap delete + memmove((char*) a + elemsize*old_index, (char*) a + elemsize*final_index, elemsize); + + // now find the slot for the last element + if (mode == STBDS_HM_STRING) + slot = stbds_hm_find_slot(a, elemsize, *(char**) ((char *) a+elemsize*old_index + keyoffset), keysize, keyoffset, mode); + else + slot = stbds_hm_find_slot(a, elemsize, (char* ) a+elemsize*old_index + keyoffset, keysize, keyoffset, mode); + STBDS_ASSERT(slot >= 0); + b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + i = slot & STBDS_BUCKET_MASK; + STBDS_ASSERT(b->index[i] == final_index); + b->index[i] = old_index; + } + stbds_header(raw_a)->length -= 1; + + if (table->used_count < table->used_count_shrink_threshold && table->slot_count > STBDS_BUCKET_LENGTH) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count>>1, table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_shrink); + } else if (table->tombstone_count > table->tombstone_count_threshold) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count , table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_rebuild); + } + + return a; + } + } + } + /* NOTREACHED */ +} + +static char *stbds_strdup(char *str) +{ + // to keep replaceable allocator simple, we don't want to use strdup. + // rolling our own also avoids problem of strdup vs _strdup + size_t len = strlen(str)+1; + char *p = (char*) STBDS_REALLOC(NULL, 0, len); + memmove(p, str, len); + return p; +} + +#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MIN +#define STBDS_STRING_ARENA_BLOCKSIZE_MIN 512u +#endif +#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MAX +#define STBDS_STRING_ARENA_BLOCKSIZE_MAX (1u<<20) +#endif + +char *stbds_stralloc(stbds_string_arena *a, char *str) +{ + char *p; + size_t len = strlen(str)+1; + if (len > a->remaining) { + // compute the next blocksize + size_t blocksize = a->block; + + // size is 512, 512, 1024, 1024, 2048, 2048, 4096, 4096, etc., so that + // there are log(SIZE) allocations to free when we destroy the table + blocksize = (size_t) (STBDS_STRING_ARENA_BLOCKSIZE_MIN) << (blocksize>>1); + + // if size is under 1M, advance to next blocktype + if (blocksize < (size_t)(STBDS_STRING_ARENA_BLOCKSIZE_MAX)) + ++a->block; + + if (len > blocksize) { + // if string is larger than blocksize, then just allocate the full size. + // note that we still advance string_block so block size will continue + // increasing, so e.g. if somebody only calls this with 1000-long strings, + // eventually the arena will start doubling and handling those as well + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + len); + memmove(sb->storage, str, len); + if (a->storage) { + // insert it after the first element, so that we don't waste the space there + sb->next = a->storage->next; + a->storage->next = sb; + } else { + sb->next = 0; + a->storage = sb; + a->remaining = 0; // this is redundant, but good for clarity + } + return sb->storage; + } else { + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + blocksize); + sb->next = a->storage; + a->storage = sb; + a->remaining = blocksize; + } + } + + STBDS_ASSERT(len <= a->remaining); + p = a->storage->storage + a->remaining - len; + a->remaining -= len; + memmove(p, str, len); + return p; +} + +void stbds_strreset(stbds_string_arena *a) +{ + stbds_string_block *x,*y; + x = a->storage; + while (x) { + y = x->next; + STBDS_FREE(NULL, x); + x = y; + } + memset(a, 0, sizeof(*a)); +} + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// UNIT TESTS +// + +#ifdef STBDS_UNIT_TESTS +#include <stdio.h> +#ifdef STBDS_ASSERT_WAS_UNDEFINED +#undef STBDS_ASSERT +#endif +#ifndef STBDS_ASSERT +#define STBDS_ASSERT assert +#include <assert.h> +#endif + +typedef struct { int key,b,c,d; } stbds_struct; +typedef struct { int key[2],b,c,d; } stbds_struct2; + +static char buffer[256]; +char *strkey(int n) +{ +#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) + sprintf_s(buffer, sizeof(buffer), "test_%d", n); +#else + sprintf(buffer, "test_%d", n); +#endif + return buffer; +} + +void stbds_unit_tests(void) +{ +#if defined(_MSC_VER) && _MSC_VER <= 1200 && defined(__cplusplus) + // VC6 C++ doesn't like the template<> trick on unnamed structures, so do nothing! + STBDS_ASSERT(0); +#else + const int testsize = 100000; + const int testsize2 = testsize/20; + int *arr=NULL; + struct { int key; int value; } *intmap = NULL; + struct { char *key; int value; } *strmap = NULL, s; + struct { stbds_struct key; int value; } *map = NULL; + stbds_struct *map2 = NULL; + stbds_struct2 *map3 = NULL; + stbds_string_arena sa = { 0 }; + int key3[2] = { 1,2 }; + ptrdiff_t temp; + + int i,j; + + STBDS_ASSERT(arrlen(arr)==0); + for (i=0; i < 20000; i += 50) { + for (j=0; j < i; ++j) + arrpush(arr,j); + arrfree(arr); + } + + for (i=0; i < 4; ++i) { + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdel(arr,i); + arrfree(arr); + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdelswap(arr,i); + arrfree(arr); + } + + for (i=0; i < 5; ++i) { + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + stbds_arrins(arr,i,5); + STBDS_ASSERT(arr[i] == 5); + if (i < 4) + STBDS_ASSERT(arr[4] == 4); + arrfree(arr); + } + + i = 1; + STBDS_ASSERT(hmgeti(intmap,i) == -1); + hmdefault(intmap, -2); + STBDS_ASSERT(hmgeti(intmap, i) == -1); + STBDS_ASSERT(hmget (intmap, i) == -2); + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*5); + for (i=0; i < testsize; i+=1) { + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(intmap, i, temp) == -2 ); + else STBDS_ASSERT(hmget_ts(intmap, i, temp) == i*5); + } + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); + for (i=2; i < testsize; i+=4) + hmdel(intmap, i); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); + for (i=0; i < testsize; i+=1) + hmdel(intmap, i); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(hmget(intmap, i) == -2 ); + hmfree(intmap); + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*3); + hmfree(intmap); + + #if defined(__clang__) || defined(__GNUC__) + #ifndef __cplusplus + intmap = NULL; + hmput(intmap, 15, 7); + hmput(intmap, 11, 3); + hmput(intmap, 9, 5); + STBDS_ASSERT(hmget(intmap, 9) == 5); + STBDS_ASSERT(hmget(intmap, 11) == 3); + STBDS_ASSERT(hmget(intmap, 15) == 7); + #endif + #endif + + for (i=0; i < testsize; ++i) + stralloc(&sa, strkey(i)); + strreset(&sa); + + { + s.key = "a", s.value = 1; + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key == s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + { + s.key = "a", s.value = 1; + sh_new_strdup(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + { + s.key = "a", s.value = 1; + sh_new_arena(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + for (j=0; j < 2; ++j) { + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + if (j == 0) + sh_new_strdup(strmap); + else + sh_new_arena(strmap); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + shdefault(strmap, -2); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + for (i=0; i < testsize; i+=2) + shput(strmap, strkey(i), i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=2; i < testsize; i+=4) + shdel(strmap, strkey(i)); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=0; i < testsize; i+=1) + shdel(strmap, strkey(i)); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + shfree(strmap); + } + + { + struct { char *key; char value; } *hash = NULL; + char name[4] = "jen"; + shput(hash, "bob" , 'h'); + shput(hash, "sally" , 'e'); + shput(hash, "fred" , 'l'); + shput(hash, "jen" , 'x'); + shput(hash, "doug" , 'o'); + + shput(hash, name , 'l'); + shfree(hash); + } + + for (i=0; i < testsize; i += 2) { + stbds_struct s = { i,i*2,i*3,i*4 }; + hmput(map, s, i*5); + } + + for (i=0; i < testsize; i += 1) { + stbds_struct s = { i,i*2,i*3 ,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmget(map, s) == 0); + else STBDS_ASSERT(hmget(map, s) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(map, s, temp) == 0); + else STBDS_ASSERT(hmget_ts(map, s, temp) == i*5); + //STBDS_ASSERT(hmget(map, t.key) == 0); + } + + for (i=0; i < testsize; i += 2) { + stbds_struct s = { i,i*2,i*3,i*4 }; + hmputs(map2, s); + } + hmfree(map); + + for (i=0; i < testsize; i += 1) { + stbds_struct s = { i,i*2,i*3,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmgets(map2, s.key).d == 0); + else STBDS_ASSERT(hmgets(map2, s.key).d == i*4); + //STBDS_ASSERT(hmgetp(map2, t.key) == 0); + } + hmfree(map2); + + for (i=0; i < testsize; i += 2) { + stbds_struct2 s = { { i,i*2 }, i*3,i*4, i*5 }; + hmputs(map3, s); + } + for (i=0; i < testsize; i += 1) { + stbds_struct2 s = { { i,i*2}, i*3, i*4, i*5 }; + stbds_struct2 t = { { i,i*2}, i*3+1, i*4, i*5 }; + if (i & 1) STBDS_ASSERT(hmgets(map3, s.key).d == 0); + else STBDS_ASSERT(hmgets(map3, s.key).d == i*5); + //STBDS_ASSERT(hmgetp(map3, t.key) == 0); + } +#endif +} +#endif + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2019 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ @@ -6,6 +6,7 @@ FLEX ?= flex SRC_DIR := src INC_DIR := include +TEST_DIR := test BUILD_DIR := build PARSER_Y := $(SRC_DIR)/parser.y @@ -23,11 +24,12 @@ LEXER_O := $(BUILD_DIR)/lex.yy.o OBJS := $(C_OBJS) $(PARSER_O) $(LEXER_O) TARGET := $(BUILD_DIR)/imp +TEST_TARGET := $(BUILD_DIR)/test -CFLAGS += -I$(INC_DIR) -MMD -MP +CFLAGS += -I$(INC_DIR) -I. -MMD -MP DEPS := $(OBJS:.o=.d) -.PHONY: all clean example repl +.PHONY: all clean example repl test all: $(TARGET) @@ -52,12 +54,18 @@ $(LEXER_C): $(LEXER_L) $(PARSER_H) | $(BUILD_DIR) $(LEXER_O): $(LEXER_C) $(CC) $(CFLAGS) -c $< -o $@ +$(TEST_TARGET): $(wildcard $(TEST_DIR)/*.c) $(filter-out $(BUILD_DIR)/main.o, $(OBJS)) | $(BUILD_DIR) + $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) + example: $(TARGET) ./$(TARGET) -i examples/example.imp repl: $(TARGET) ./$(TARGET) +test: $(BUILD_DIR)/test + ./$(BUILD_DIR)/test + clean: @rm -rf $(BUILD_DIR) diff --git a/include/ast.h b/include/ast.h index 068372e..a687a9b 100644 --- a/include/ast.h +++ b/include/ast.h @@ -8,7 +8,6 @@ * Provides data structures, enums, and functions for creating, managing, cloning, and freeing AST nodes. * * @author Flavian Kaufmann - * @date 2025-05-23 */ @@ -142,7 +141,7 @@ IMP_ASTNode *imp_ast_clone(const IMP_ASTNode *node); * * @param node Node to free. */ -void imp_ast_free(IMP_ASTNode *node); +void imp_ast_destroy(IMP_ASTNode *node); /** * Creates a new linked list of AST nodes. @@ -160,6 +159,6 @@ IMP_ASTNodeList *imp_ast_list(IMP_ASTNode *node, IMP_ASTNodeList *list); * * @param list List to free. */ -void imp_ast_list_free(IMP_ASTNodeList *list); +void imp_ast_list_destroy(IMP_ASTNodeList *list); #endif /* IMP_AST_H */
\ No newline at end of file diff --git a/include/driver.h b/include/driver.h new file mode 100644 index 0000000..40cfc58 --- /dev/null +++ b/include/driver.h @@ -0,0 +1,13 @@ +#ifndef IMP_DRIVER_H +#define IMP_DRIVER_H + +#include "interpreter_context.h" + +int imp_driver_interpret_file (IMP_InterpreterContext *context, const char *path); +int imp_driver_interpret_str (IMP_InterpreterContext *context, const char *str); +int imp_driver_print_ast_file (const char *path); + +void imp_driver_print_var_table(IMP_InterpreterContext *context); +void imp_driver_print_proc_table(IMP_InterpreterContext *context); + +#endif /* IMP_DRIVER_H */
\ No newline at end of file diff --git a/include/hashmap.h b/include/hashmap.h deleted file mode 100644 index 7cc20b1..0000000 --- a/include/hashmap.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef HASHMAP_H -#define HASHMAP_H - -/* Hashmap Handle */ -typedef struct HashMap *hashmap_t; - -/* Hashmap Key Iterator Handle */ -typedef struct HashMapKeysIter *hashmap_keys_iter_t; - - -/* - Creates hashmap that maps char* to void*. - Keys are copied, and will be freed when the hashmap is freed. - Elements are not copied, and must be freed by the caller. - Caller is responsible for freeing the hashmap using hashmap_free. -*/ -hashmap_t hashmap_create(void); - -/* - Frees the hashmap and all keys. - Elements are not freed, and must be freed by the caller. -*/ -void hashmap_free(hashmap_t map); - -/* - Looks up element with given key. - Returns pointer to element, or NULL if not found. -*/ -void **hashmap_get(hashmap_t map, const char *key); - -/* - Inserts element with given key. - If key already exists, the old element is replaced (caller is responsible for freeing beforehand). -*/ -void hashmap_insert(hashmap_t map, const char *key, void *element); - -/* - Deletes element with given key. - Caller is responsible for freeing the element. - Returns 0 on success, -1 when not found. -*/ -int hashmap_delete(hashmap_t map, const char *key); - - -/* - Creates iterator for keys. - Caller is responsible for freeing the iterator using hashmap_keys_iter_free. - Iterator is invalid when the hashmap is modified and must be freed. -*/ -hashmap_keys_iter_t hashmap_keys_iter_create(hashmap_t map); - -/* - Returns next key in the iterator. - Returns NULL when there are no more keys. - Keys are not copied, and must not be freed. -*/ -const char *hashmap_keys_iter_next(hashmap_keys_iter_t iter); - -/* Frees the iterator. */ -void hashmap_keys_iter_free(hashmap_keys_iter_t iter); - - -#endif
\ No newline at end of file diff --git a/include/interpreter.h b/include/interpreter.h index 5eff23f..d5a1e7a 100644 --- a/include/interpreter.h +++ b/include/interpreter.h @@ -1,26 +1,30 @@ -#ifndef INTERPRETER_H -#define INTERPRETER_H +#ifndef IMP_INTERPRETER_H +#define IMP_INTERPRETER_H -#include "ast.h" - +/** + * @file interpreter.h + * @brief Interpreter for evaluating IMP language programs. + * + * @author Flavian Kaufmann + */ -typedef struct Context *context_t; +#include "ast.h" +#include "interpreter_context.h" -context_t context_create(void); -void context_free(context_t context); - -int context_get_var(context_t context, const char *name); -void context_set_var(context_t context, const char *name, int value); -void context_print_var_table(context_t context); -void context_print_proc_table(context_t context); -int interp_ast(context_t context, IMP_ASTNode *node); -int interp_file (context_t context, const char *path); -int interp_str (context_t context, const char *str); +/** + * Evaluates an AST node within a given context. + * + * @param context The interpreter context. + * @param node AST node to evaluate. + * @return Status code or result of evaluation. (0 for success, non-zero for error). + * + * @note The interpreter does neither take ownership of the context nor the AST node. + */ +int imp_interpreter_interpret_ast(IMP_InterpreterContext *context, const IMP_ASTNode *node); -int print_ast_file (const char *path); -#endif
\ No newline at end of file +#endif /* IMP_INTERPRETER_H */
\ No newline at end of file diff --git a/include/interpreter_context.h b/include/interpreter_context.h new file mode 100644 index 0000000..acc63c1 --- /dev/null +++ b/include/interpreter_context.h @@ -0,0 +1,143 @@ +#ifndef IMP_INTERPRETER_CONTEXT_H +#define IMP_INTERPRETER_CONTEXT_H + +/** + * @file interpreter_context.h + * @brief Defines the context structure for the IMP interpreter. + * + * Provides the interface for managing the interpreter state, specifically + * the variable and procedure environments. + * + * Author: Flavian Kaufmann + */ + +#include "ast.h" + +/** + * @brief Opaque type representing the interpreter context. + */ +typedef struct IMP_InterpreterContext IMP_InterpreterContext; + +/** + * @brief Opaque type for iterating over variable entries in the context. + */ +typedef struct IMP_InterpreterContextVarIter IMP_InterpreterContextVarIter; + +/** + * @brief Opaque type for iterating over procedure entries in the context. + */ +typedef struct IMP_InterpreterContextProcIter IMP_InterpreterContextProcIter; + +/** + * @brief A single variable entry in the interpreter's environment. + */ +typedef struct IMP_InterpreterContextVarTableEntry { + const char *key; /**< The variable name (identifier). */ + int value; /**< The value associated with the variable. */ +} IMP_InterpreterContextVarTableEntry; + +/** + * @brief A single procedure entry in the interpreter's environment. + */ +typedef struct IMP_InterpreterContextProcTableEntry { + const char *key; /**< The procedure name (identifier). */ + const IMP_ASTNode *value; /**< The AST node representing the procedure declaration. */ +} IMP_InterpreterContextProcTableEntry; + +/** + * @brief Creates and initializes a new interpreter context. + * + * @return A pointer to the newly created context. + */ +IMP_InterpreterContext *imp_interpreter_context_create(void); + +/** + * @brief Frees all memory associated with the interpreter context. + * + * @param context The context to destroy. + */ +void imp_interpreter_context_destroy(IMP_InterpreterContext *context); + +/** + * @brief Retrieves the value of a variable from the context. + * + * @param context The interpreter context. + * @param name The name of the variable. (Is copied internally.) + * @return The value of the variable. Returns 0 if the variable is not found. + */ +int imp_interpreter_context_var_get(IMP_InterpreterContext *context, const char *name); + +/** + * @brief Sets or updates the value of a variable in the context. + * + * @param context The interpreter context. + * @param name The name of the variable. (Is copied internally.) + * @param value The value to assign. (If value is 0, the variable is removed from the context.) + */ +void imp_interpreter_context_var_set(IMP_InterpreterContext *context, const char *name, int value); + +/** + * @brief Retrieves the AST node for a procedure from the context. + * + * @param context The interpreter context. + * @param name The name of the procedure. (Is copied internally.) + * @return A pointer to the procedure's AST node, or NULL if not found. + */ +const IMP_ASTNode *imp_interpreter_context_proc_get(IMP_InterpreterContext *context, const char *name); + +/** + * @brief Adds or updates a procedure in the context. + * + * @param context The interpreter context. + * @param name The name of the procedure. (Is copied internally.) + * @param proc The AST node representing the procedure body. (Is cloned internally.) + */ +void imp_interpreter_context_proc_set(IMP_InterpreterContext *context, const char *name, const IMP_ASTNode *proc); + +/** + * @brief Creates an iterator over the variables in the context. (Is invalid if the variable table is modified.) + * + * @param context The interpreter context. + * @return A pointer to the variable iterator. + */ +IMP_InterpreterContextVarIter *imp_interpreter_context_var_iter_create(IMP_InterpreterContext *context); + +/** + * @brief Destroys a variable iterator. + * + * @param iter The iterator to destroy. + */ +void imp_interpreter_context_var_iter_destroy(IMP_InterpreterContextVarIter *iter); + +/** + * @brief Retrieves the next variable entry from the iterator. + * + * @param iter The variable iterator. + * @return A pointer to the next variable entry, or NULL if end is reached. + */ +IMP_InterpreterContextVarTableEntry *imp_interpreter_context_var_iter_next(IMP_InterpreterContextVarIter *iter); + +/** + * @brief Creates an iterator over the procedures in the context. (Is invalid if the procedure table is modified.) + * + * @param context The interpreter context. + * @return A pointer to the procedure iterator. + */ +IMP_InterpreterContextProcIter *imp_interpreter_context_proc_iter_create(IMP_InterpreterContext *context); + +/** + * @brief Destroys a procedure iterator. + * + * @param iter The iterator to destroy. + */ +void imp_interpreter_context_proc_iter_destroy(IMP_InterpreterContextProcIter *iter); + +/** + * @brief Retrieves the next procedure entry from the iterator. + * + * @param iter The procedure iterator. + * @return A pointer to the next procedure entry, or NULL if end is reached. + */ +IMP_InterpreterContextProcTableEntry *imp_interpreter_context_proc_iter_next(IMP_InterpreterContextProcIter *iter); + +#endif /* IMP_INTERPRETER_CONTEXT_H */
\ No newline at end of file diff --git a/include/repl.h b/include/repl.h index 4b833ad..5e6e168 100644 --- a/include/repl.h +++ b/include/repl.h @@ -1,8 +1,8 @@ -#ifndef REPL_H -#define REPL_H +#ifndef IMP_REPL_H +#define IMP_REPL_H -void repl(void); +void imp_repl(void); -#endif
\ No newline at end of file +#endif /* IMP_REPL_H */
\ No newline at end of file @@ -170,27 +170,27 @@ IMP_ASTNode *imp_ast_clone(const IMP_ASTNode *node) { } } -void imp_ast_free(IMP_ASTNode *node) { +void imp_ast_destroy(IMP_ASTNode *node) { if (!node) return; switch (node->type) { case IMP_AST_NT_SKIP: break; case IMP_AST_NT_ASSIGN: - imp_ast_free(node->data.assign.var); - imp_ast_free(node->data.assign.aexpr); + imp_ast_destroy(node->data.assign.var); + imp_ast_destroy(node->data.assign.aexpr); break; case IMP_AST_NT_SEQ: - imp_ast_free(node->data.seq.fst_stmt); - imp_ast_free(node->data.seq.snd_stmt); + imp_ast_destroy(node->data.seq.fst_stmt); + imp_ast_destroy(node->data.seq.snd_stmt); break; case IMP_AST_NT_IF: - imp_ast_free(node->data.if_stmt.cond_bexpr); - imp_ast_free(node->data.if_stmt.then_stmt); - imp_ast_free(node->data.if_stmt.else_stmt); + imp_ast_destroy(node->data.if_stmt.cond_bexpr); + imp_ast_destroy(node->data.if_stmt.then_stmt); + imp_ast_destroy(node->data.if_stmt.else_stmt); break; case IMP_AST_NT_WHILE: - imp_ast_free(node->data.while_stmt.cond_bexpr); - imp_ast_free(node->data.while_stmt.body_stmt); + imp_ast_destroy(node->data.while_stmt.cond_bexpr); + imp_ast_destroy(node->data.while_stmt.body_stmt); break; case IMP_AST_NT_INT: break; @@ -198,35 +198,35 @@ void imp_ast_free(IMP_ASTNode *node) { free(node->data.variable.name); break; case IMP_AST_NT_AOP: - imp_ast_free(node->data.arith_op.l_aexpr); - imp_ast_free(node->data.arith_op.r_aexpr); + imp_ast_destroy(node->data.arith_op.l_aexpr); + imp_ast_destroy(node->data.arith_op.r_aexpr); break; case IMP_AST_NT_BOP: - imp_ast_free(node->data.bool_op.l_bexpr); - imp_ast_free(node->data.bool_op.r_bexpr); + imp_ast_destroy(node->data.bool_op.l_bexpr); + imp_ast_destroy(node->data.bool_op.r_bexpr); break; case IMP_AST_NT_ROP: - imp_ast_free(node->data.rel_op.l_aexpr); - imp_ast_free(node->data.rel_op.r_aexpr); + imp_ast_destroy(node->data.rel_op.l_aexpr); + imp_ast_destroy(node->data.rel_op.r_aexpr); break; case IMP_AST_NT_NOT: - imp_ast_free(node->data.bool_not.bexpr); + imp_ast_destroy(node->data.bool_not.bexpr); break; case IMP_AST_NT_LET: - imp_ast_free(node->data.let_stmt.var); - imp_ast_free(node->data.let_stmt.aexpr); - imp_ast_free(node->data.let_stmt.body_stmt); + imp_ast_destroy(node->data.let_stmt.var); + imp_ast_destroy(node->data.let_stmt.aexpr); + imp_ast_destroy(node->data.let_stmt.body_stmt); break; case IMP_AST_NT_PROCDECL: free(node->data.proc_decl.name); - imp_ast_list_free(node->data.proc_decl.val_args); - imp_ast_list_free(node->data.proc_decl.var_args); - imp_ast_free(node->data.proc_decl.body_stmt); + imp_ast_list_destroy(node->data.proc_decl.val_args); + imp_ast_list_destroy(node->data.proc_decl.var_args); + imp_ast_destroy(node->data.proc_decl.body_stmt); break; case IMP_AST_NT_PROCCALL: free(node->data.proc_call.name); - imp_ast_list_free(node->data.proc_call.val_args); - imp_ast_list_free(node->data.proc_call.var_args); + imp_ast_list_destroy(node->data.proc_call.val_args); + imp_ast_list_destroy(node->data.proc_call.var_args); break; default: assert(0 && "Unknown AST node type"); } @@ -241,9 +241,9 @@ IMP_ASTNodeList *imp_ast_list(IMP_ASTNode *node, IMP_ASTNodeList *list) { return new_list; } -void imp_ast_list_free(IMP_ASTNodeList *list) { +void imp_ast_list_destroy(IMP_ASTNodeList *list) { if (!list) return; - imp_ast_free(list->node); - imp_ast_list_free(list->next); + imp_ast_destroy(list->node); + imp_ast_list_destroy(list->next); free(list); }
\ No newline at end of file diff --git a/src/driver.c b/src/driver.c index 441ee40..5f6037d 100644 --- a/src/driver.c +++ b/src/driver.c @@ -1,44 +1,238 @@ -#include <stdio.h> +#include "driver.h" + #include <stdlib.h> -#include <string.h> -#include <unistd.h> +#include <stdio.h> +#include <assert.h> +#include "ast.h" #include "interpreter.h" -#include "repl.h" -static int interpret_file(const char *path) { - context_t context = context_create(); - if (interp_file(context, path)) { - fprintf(stderr, "Error interpreting file: %s\n", path); - context_free(context); +typedef void *YY_BUFFER_STATE; +extern FILE *yyin; +extern IMP_ASTNode *ast_root; +extern int yyparse(void); +extern void yyrestart (FILE*); +extern YY_BUFFER_STATE yy_scan_string(const char*); +extern void yy_delete_buffer(YY_BUFFER_STATE); + +int imp_driver_interpret_file (IMP_InterpreterContext *context, const char *path) { + yyin = fopen(path, "r"); + if (!yyin) return -1; + yyrestart(yyin); + if (yyparse()) { + imp_ast_destroy(ast_root); + fclose(yyin); + return -1; + } + if (imp_interpreter_interpret_ast(context, ast_root)) { + imp_ast_destroy(ast_root); + fclose(yyin); return -1; } - context_print_var_table(context); - context_free(context); + imp_ast_destroy(ast_root); + fclose(yyin); return 0; } -int main(int argc, char **argv) { - int opt; - while ((opt = getopt(argc, argv, "i:a:h")) != -1) { - switch (opt) { - case 'i': - return interpret_file(optarg) ? EXIT_FAILURE : EXIT_SUCCESS; - case 'a': - return print_ast_file(optarg) ? EXIT_FAILURE : EXIT_SUCCESS; - case 'h': - default: - fprintf(stderr, - "Usage: %s [ARGS]\n" - " (no args) start REPL\n" - " -i <program.imp> interpret program\n" - " -a <program.imp> print ast\n" - " -h print this message\n", - argv[0]); - return (opt == 'h') ? EXIT_SUCCESS : EXIT_FAILURE; - } - } - repl(); - return EXIT_SUCCESS; +int imp_driver_interpret_str (IMP_InterpreterContext *context, const char *str) { + YY_BUFFER_STATE buf = yy_scan_string(str); + if (yyparse()) { + imp_ast_destroy(ast_root); + yy_delete_buffer(buf); + return -1; + } + if (imp_interpreter_interpret_ast(context, ast_root)) { + imp_ast_destroy(ast_root); + yy_delete_buffer(buf); + return -1; + } + yy_delete_buffer(buf); + return 0; +} + +static void ast_print (IMP_ASTNode *node, int depth) { + int indent = depth * 2; + switch (node->type) { + case IMP_AST_NT_SKIP: { + printf("%*sSKIP\n", indent, ""); + break; + } + case IMP_AST_NT_ASSIGN: { + printf("%*sASSIGN %s=", indent, "", + node->data.assign.var->data.variable.name); + ast_print(node->data.assign.aexpr, 0); + printf("\n"); + break; + } + case IMP_AST_NT_SEQ: { + ast_print(node->data.seq.fst_stmt, depth); + printf("%*sSEQ\n", indent, ""); + ast_print(node->data.seq.snd_stmt, depth); + break; + } + case IMP_AST_NT_IF: { + printf("%*sIF (", indent, ""); + ast_print(node->data.if_stmt.cond_bexpr, 0); + printf(")\n"); + ast_print(node->data.if_stmt.then_stmt, depth + 1); + printf("%*sELSE\n", indent, ""); + ast_print(node->data.if_stmt.else_stmt, depth + 1); + break; + } + case IMP_AST_NT_WHILE: { + printf("%*sWHILE (", indent, ""); + ast_print(node->data.while_stmt.cond_bexpr, 0); + printf(")\n"); + ast_print(node->data.while_stmt.body_stmt, depth + 1); + break; + } + case IMP_AST_NT_INT: { + printf("%d", node->data.integer.val); + break; + } + case IMP_AST_NT_VAR: { + printf("%s", node->data.variable.name); + break; + } + case IMP_AST_NT_AOP: { + printf("("); + ast_print(node->data.arith_op.l_aexpr, 0); + switch (node->data.arith_op.aopr) { + case IMP_AST_AOP_ADD: printf(" + "); break; + case IMP_AST_AOP_SUB: printf(" - "); break; + case IMP_AST_AOP_MUL: printf(" * "); break; + default: assert(0); + } + ast_print(node->data.arith_op.r_aexpr, 0); + printf(")"); + break; + } + case IMP_AST_NT_BOP: { + printf("("); + ast_print(node->data.bool_op.l_bexpr, 0); + switch (node->data.bool_op.bopr) { + case IMP_AST_BOP_AND: printf(" && "); break; + case IMP_AST_BOP_OR: printf(" || "); break; + default: assert(0); + } + ast_print(node->data.bool_op.r_bexpr, 0); + printf(")"); + break; + } + case IMP_AST_NT_NOT: { + printf("!"); + ast_print(node->data.bool_not.bexpr, 0); + break; + } + case IMP_AST_NT_ROP: { + printf("("); + ast_print(node->data.rel_op.l_aexpr, 0); + switch (node->data.rel_op.ropr) { + case IMP_AST_ROP_EQ: printf(" == "); break; + case IMP_AST_ROP_NE: printf(" != "); break; + case IMP_AST_ROP_LT: printf(" < "); break; + case IMP_AST_ROP_LE: printf(" <= "); break; + case IMP_AST_ROP_GT: printf(" > "); break; + case IMP_AST_ROP_GE: printf(" >= "); break; + default: assert(0); + } + ast_print(node->data.rel_op.r_aexpr, 0); + printf(")"); + break; + } + case IMP_AST_NT_LET: { + printf("%*sLET %s = ", indent, "", node->data.let_stmt.var->data.variable.name); + ast_print(node->data.let_stmt.aexpr, 0); + printf("\n"); + ast_print(node->data.let_stmt.body_stmt, depth + 1); + break; + } + case IMP_AST_NT_PROCDECL: { + printf("%*sPROC %s(", indent, "", node->data.proc_decl.name); + IMP_ASTNodeList *args = node->data.proc_decl.val_args; + while (args) { + printf("%s", args->node->data.variable.name); + args = args->next; + if (args) printf(", "); + } + printf("; "); + IMP_ASTNodeList *vargs = node->data.proc_decl.var_args; + while (vargs) { + printf("%s", vargs->node->data.variable.name); + vargs = vargs->next; + if (vargs) printf(", "); + } + printf(")\n"); + ast_print(node->data.proc_decl.body_stmt, depth + 1); + break; + } + case IMP_AST_NT_PROCCALL: { + printf("%*sCALL %s(", indent, "", node->data.proc_call.name); + IMP_ASTNodeList *args = node->data.proc_call.val_args; + while (args) { + printf("%s", args->node->data.variable.name); + args = args->next; + if (args) printf(", "); + } + printf("; "); + IMP_ASTNodeList *vargs = node->data.proc_call.var_args; + while (vargs) { + printf("%s", vargs->node->data.variable.name); + vargs = vargs->next; + if (vargs) printf(", "); + } + printf(")\n"); + break; + } + default: assert(0); + } +} + +int imp_driver_print_ast_file (const char *path) { + yyin = fopen(path, "r"); + if (!yyin) return -1; + yyrestart(yyin); + if (yyparse()) { + imp_ast_destroy(ast_root); + fclose(yyin); + return -1; + } + ast_print(ast_root, 0); + imp_ast_destroy(ast_root); + fclose(yyin); + return 0; +} + +void imp_driver_print_var_table(IMP_InterpreterContext *context) { + IMP_InterpreterContextVarIter *iter = imp_interpreter_context_var_iter_create(context); + const IMP_InterpreterContextVarTableEntry *var_entry; + while ((var_entry = imp_interpreter_context_var_iter_next(iter))) { + printf("%s = %d\n", var_entry->key, var_entry->value); + } + imp_interpreter_context_var_iter_destroy(iter); +} + +void imp_driver_print_proc_table(IMP_InterpreterContext *context) { + IMP_InterpreterContextProcIter *iter = imp_interpreter_context_proc_iter_create(context); + const IMP_InterpreterContextProcTableEntry *proc_entry; + while ((proc_entry = imp_interpreter_context_proc_iter_next(iter))) { + const IMP_ASTNode *procdecl = proc_entry->value; + printf("%s(", proc_entry->key); + IMP_ASTNodeList *args = procdecl->data.proc_decl.val_args; + while (args) { + printf("%s", args->node->data.variable.name); + args = args->next; + if (args) printf(", "); + } + printf("; "); + IMP_ASTNodeList *vargs = procdecl->data.proc_decl.var_args; + while (vargs) { + printf("%s", vargs->node->data.variable.name); + vargs = vargs->next; + if (vargs) printf(", "); + } + printf(")\n"); + } + imp_interpreter_context_proc_iter_destroy(iter); }
\ No newline at end of file diff --git a/src/hashmap.c b/src/hashmap.c deleted file mode 100644 index 89d0106..0000000 --- a/src/hashmap.c +++ /dev/null @@ -1,160 +0,0 @@ -#include "hashmap.h" - -#include <stdlib.h> -#include <string.h> -#include <assert.h> - - -#define INITIAL_SIZE (16) -#define LOAD_FACTOR_THRESHOLD (0.75) - - -typedef struct Pair { - char *key; - void *element; - struct Pair *next; -} Pair; - -typedef struct HashMap { - Pair **table; - size_t size; - size_t count; -} HashMap; - -typedef struct HashMapKeysIter { - HashMap *map; - size_t bucket_index; - Pair *current; -} HashMapKeysIter; - - -static unsigned int hash(const char *key, size_t size) { - unsigned int hash_value = 0; - while (*key) hash_value = (hash_value * 31) + *key++; - return hash_value % size; -} - -static void resize(HashMap *map) { - size_t new_size = map->size * 2; - Pair **new_table = (Pair**)calloc(new_size, sizeof(Pair*)); - assert(new_table); - for (size_t i = 0; i < map->size; ++i) { - Pair *pair = map->table[i]; - while (pair) { - unsigned int index = hash(pair->key, new_size); - Pair *next = pair->next; - pair->next = new_table[index]; - new_table[index] = pair; - pair = next; - } - } - - free(map->table); - map->table = new_table; - map->size = new_size; -} - -HashMap *hashmap_create(void) { - HashMap *map = (HashMap*)malloc(sizeof(HashMap)); - assert(map); - map->size = INITIAL_SIZE; - map->count = 0; - map->table = (Pair**)calloc(map->size, sizeof(Pair*)); - assert(map->table); - return map; -} - -void hashmap_insert(HashMap *map, const char *key, void *element) { - void **old_value = hashmap_get(map, key); - if (old_value) { - *old_value = element; - return; - } - if ((float)map->count / map->size > LOAD_FACTOR_THRESHOLD) resize(map); - unsigned int index = hash(key, map->size); - Pair *new_pair = (Pair*)malloc(sizeof(Pair)); - assert(new_pair); - new_pair->key = strdup(key); - assert(new_pair->key); - new_pair->element = element; - new_pair->next = map->table[index]; - map->table[index] = new_pair; - map->count++; -} - -void **hashmap_get(HashMap *map, const char *key) { - unsigned int index = hash(key, map->size); - Pair *pair = map->table[index]; - while (pair) { - if (strcmp(pair->key, key) == 0) return &pair->element; - pair = pair->next; - } - return NULL; -} - -int hashmap_delete(HashMap *map, const char *key) { - unsigned int index = hash(key, map->size); - Pair *pair = map->table[index]; - Pair *prev = NULL; - while (pair) { - if (strcmp(pair->key, key) == 0) { - if (prev) prev->next = pair->next; - else map->table[index] = pair->next; - free(pair->key); - free(pair); - map->count--; - return 0; - } - prev = pair; - pair = pair->next; - } - return -1; -} - -void hashmap_free(HashMap *map) { - for (size_t i = 0; i < map->size; ++i) { - Pair *pair = map->table[i]; - while (pair) { - Pair *temp = pair; - pair = pair->next; - free(temp->key); - free(temp); - } - } - free(map->table); - free(map); -} - -HashMapKeysIter *hashmap_keys_iter_create(HashMap *map) { - HashMapKeysIter *iter = (HashMapKeysIter*)malloc(sizeof(HashMapKeysIter)); - assert(iter); - iter->map = map; - iter->bucket_index = 0; - iter->current = NULL; - while (iter->bucket_index < map->size && map->table[iter->bucket_index] == NULL) { - iter->bucket_index++; - } - if (iter->bucket_index < map->size) { - iter->current = map->table[iter->bucket_index]; - } - return iter; -} - -const char *hashmap_keys_iter_next(HashMapKeysIter *iter) { - if (!iter->current) return NULL; - const char *key = iter->current->key; - if (iter->current->next) { - iter->current = iter->current->next; - } else { - iter->bucket_index++; - while (iter->bucket_index < iter->map->size && iter->map->table[iter->bucket_index] == NULL) { - iter->bucket_index++; - } - iter->current = (iter->bucket_index < iter->map->size) ? iter->map->table[iter->bucket_index] : NULL; - } - return key; -} - -void hashmap_keys_iter_free(HashMapKeysIter *iter) { - free(iter); -}
\ No newline at end of file diff --git a/src/interpreter.c b/src/interpreter.c index c0678b7..b04a293 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -4,248 +4,18 @@ #include <stdio.h> #include <assert.h> -#include "hashmap.h" - -typedef void *YY_BUFFER_STATE; -extern FILE *yyin; -extern IMP_ASTNode *ast_root; -extern int yyparse(void); -extern void yyrestart (FILE*); -extern YY_BUFFER_STATE yy_scan_string(const char*); -extern void yy_delete_buffer(YY_BUFFER_STATE); - -typedef struct Context{ - hashmap_t var_table; - hashmap_t proc_table; -} Context; - - -static IMP_ASTNode *context_get_proc(Context *context, const char *name) { - IMP_ASTNode **procdecl = (IMP_ASTNode**)hashmap_get(context->proc_table, name); - if (procdecl) return *procdecl; - return NULL; -} - -static void context_set_proc(Context *context, const char *name, IMP_ASTNode *procdecl) { - hashmap_insert(context->proc_table, name, (void*)procdecl); -} - -Context *context_create(void) { - Context *context = malloc(sizeof(Context)); - assert(context); - context->var_table = hashmap_create(); - context->proc_table = hashmap_create(); - return context; -} - -void context_free(Context *context) { - hashmap_free(context->var_table); - hashmap_keys_iter_t iter = hashmap_keys_iter_create(context->proc_table); - const char *key; - while ((key = hashmap_keys_iter_next(iter)) != NULL) { - IMP_ASTNode *procdecl = context_get_proc(context, key); - assert(procdecl); - imp_ast_free(procdecl); - } - hashmap_keys_iter_free(iter); - hashmap_free(context->proc_table); - free(context); -} - -int context_get_var(Context *context, const char *name) { - int *val = (int*)hashmap_get(context->var_table, name); - if (val) return *val; - return 0; -} - -void context_set_var(Context *context, const char *name, int val) { - hashmap_insert(context->var_table, name, (void*)val); -} - -void context_print_var_table(Context *context) { - hashmap_keys_iter_t iter = hashmap_keys_iter_create(context->var_table); - const char *key; - while ((key = hashmap_keys_iter_next(iter)) != NULL) { - int val = context_get_var(context, key); - printf("%s = %d\n", key, val); - } - hashmap_keys_iter_free(iter); -} - -void context_print_proc_table(Context *context) { - hashmap_keys_iter_t iter = hashmap_keys_iter_create(context->proc_table); - const char *key; - while ((key = hashmap_keys_iter_next(iter)) != NULL) { - IMP_ASTNode *procdecl = context_get_proc(context, key); - printf("%s(", key); - IMP_ASTNodeList *args = procdecl->data.proc_decl.val_args; - while (args) { - printf("%s", args->node->data.variable.name); - args = args->next; - if (args) printf(", "); - } - printf("; "); - IMP_ASTNodeList *vargs = procdecl->data.proc_decl.var_args; - while (vargs) { - printf("%s", vargs->node->data.variable.name); - vargs = vargs->next; - if (vargs) printf(", "); - } - printf(")\n"); - } - hashmap_keys_iter_free(iter); -} - -void ast_print(IMP_ASTNode *node, int depth) { - int indent = depth * 2; - switch (node->type) { - case IMP_AST_NT_SKIP: { - printf("%*sSKIP\n", indent, ""); - break; - } - case IMP_AST_NT_ASSIGN: { - printf("%*sASSIGN %s=", indent, "", - node->data.assign.var->data.variable.name); - ast_print(node->data.assign.aexpr, 0); - printf("\n"); - break; - } - case IMP_AST_NT_SEQ: { - ast_print(node->data.seq.fst_stmt, depth); - printf("%*sSEQ\n", indent, ""); - ast_print(node->data.seq.snd_stmt, depth); - break; - } - case IMP_AST_NT_IF: { - printf("%*sIF (", indent, ""); - ast_print(node->data.if_stmt.cond_bexpr, 0); - printf(")\n"); - ast_print(node->data.if_stmt.then_stmt, depth + 1); - printf("%*sELSE\n", indent, ""); - ast_print(node->data.if_stmt.else_stmt, depth + 1); - break; - } - case IMP_AST_NT_WHILE: { - printf("%*sWHILE (", indent, ""); - ast_print(node->data.while_stmt.cond_bexpr, 0); - printf(")\n"); - ast_print(node->data.while_stmt.body_stmt, depth + 1); - break; - } - case IMP_AST_NT_INT: { - printf("%d", node->data.integer.val); - break; - } - case IMP_AST_NT_VAR: { - printf("%s", node->data.variable.name); - break; - } - case IMP_AST_NT_AOP: { - printf("("); - ast_print(node->data.arith_op.l_aexpr, 0); - switch (node->data.arith_op.aopr) { - case IMP_AST_AOP_ADD: printf(" + "); break; - case IMP_AST_AOP_SUB: printf(" - "); break; - case IMP_AST_AOP_MUL: printf(" * "); break; - default: assert(0); - } - ast_print(node->data.arith_op.r_aexpr, 0); - printf(")"); - break; - } - case IMP_AST_NT_BOP: { - printf("("); - ast_print(node->data.bool_op.l_bexpr, 0); - switch (node->data.bool_op.bopr) { - case IMP_AST_BOP_AND: printf(" && "); break; - case IMP_AST_BOP_OR: printf(" || "); break; - default: assert(0); - } - ast_print(node->data.bool_op.r_bexpr, 0); - printf(")"); - break; - } - case IMP_AST_NT_NOT: { - printf("!"); - ast_print(node->data.bool_not.bexpr, 0); - break; - } - case IMP_AST_NT_ROP: { - printf("("); - ast_print(node->data.rel_op.l_aexpr, 0); - switch (node->data.rel_op.ropr) { - case IMP_AST_ROP_EQ: printf(" == "); break; - case IMP_AST_ROP_NE: printf(" != "); break; - case IMP_AST_ROP_LT: printf(" < "); break; - case IMP_AST_ROP_LE: printf(" <= "); break; - case IMP_AST_ROP_GT: printf(" > "); break; - case IMP_AST_ROP_GE: printf(" >= "); break; - default: assert(0); - } - ast_print(node->data.rel_op.r_aexpr, 0); - printf(")"); - break; - } - case IMP_AST_NT_LET: { - printf("%*sLET %s = ", indent, "", node->data.let_stmt.var->data.variable.name); - ast_print(node->data.let_stmt.aexpr, 0); - printf("\n"); - ast_print(node->data.let_stmt.body_stmt, depth + 1); - break; - } - case IMP_AST_NT_PROCDECL: { - printf("%*sPROC %s(", indent, "", node->data.proc_decl.name); - IMP_ASTNodeList *args = node->data.proc_decl.val_args; - while (args) { - printf("%s", args->node->data.variable.name); - args = args->next; - if (args) printf(", "); - } - printf("; "); - IMP_ASTNodeList *vargs = node->data.proc_decl.var_args; - while (vargs) { - printf("%s", vargs->node->data.variable.name); - vargs = vargs->next; - if (vargs) printf(", "); - } - printf(")\n"); - ast_print(node->data.proc_decl.body_stmt, depth + 1); - break; - } - case IMP_AST_NT_PROCCALL: { - printf("%*sCALL %s(", indent, "", node->data.proc_call.name); - IMP_ASTNodeList *args = node->data.proc_call.val_args; - while (args) { - printf("%s", args->node->data.variable.name); - args = args->next; - if (args) printf(", "); - } - printf("; "); - IMP_ASTNodeList *vargs = node->data.proc_call.var_args; - while (vargs) { - printf("%s", vargs->node->data.variable.name); - vargs = vargs->next; - if (vargs) printf(", "); - } - printf(")\n"); - break; - } - default: assert(0); - } -} - -static int eval_aexpr(Context *context, IMP_ASTNode *node) { +static int eval_aexpr(IMP_InterpreterContext *context, const IMP_ASTNode *node) { switch (node->type) { case IMP_AST_NT_INT: return node->data.integer.val; - case IMP_AST_NT_VAR: return context_get_var(context, node->data.variable.name); + case IMP_AST_NT_VAR: return imp_interpreter_context_var_get(context, node->data.variable.name); case IMP_AST_NT_AOP: { - int aexp1_val = eval_aexpr(context, node->data.arith_op.l_aexpr); - int aexp2_val = eval_aexpr(context, node->data.arith_op.r_aexpr); + int l_val = eval_aexpr(context, node->data.arith_op.l_aexpr); + int r_val = eval_aexpr(context, node->data.arith_op.r_aexpr); switch (node->data.arith_op.aopr) { - case IMP_AST_AOP_ADD: return aexp1_val + aexp2_val; - case IMP_AST_AOP_SUB: return aexp1_val - aexp2_val; - case IMP_AST_AOP_MUL: return aexp1_val * aexp2_val; + case IMP_AST_AOP_ADD: return l_val + r_val; + case IMP_AST_AOP_SUB: return l_val - r_val; + case IMP_AST_AOP_MUL: return l_val * r_val; default: assert(0); } } @@ -253,28 +23,28 @@ static int eval_aexpr(Context *context, IMP_ASTNode *node) { } } -static int eval_bexpr(Context *context, IMP_ASTNode *node) { +static int eval_bexpr(IMP_InterpreterContext *context, const IMP_ASTNode *node) { switch (node->type) { case IMP_AST_NT_BOP: { - int bexp1_val = eval_bexpr(context, node->data.bool_op.l_bexpr); - int bexp2_val = eval_bexpr(context, node->data.bool_op.r_bexpr); + int l_val = eval_bexpr(context, node->data.bool_op.l_bexpr); + int r_val = eval_bexpr(context, node->data.bool_op.r_bexpr); switch (node->data.bool_op.bopr) { - case IMP_AST_BOP_AND: return bexp1_val && bexp2_val; - case IMP_AST_BOP_OR: return bexp1_val || bexp2_val; + case IMP_AST_BOP_AND: return l_val && r_val; + case IMP_AST_BOP_OR: return l_val || r_val; default: assert(0); } } case IMP_AST_NT_NOT: return !eval_bexpr(context, node->data.bool_not.bexpr); case IMP_AST_NT_ROP: { - int aexp1_val = eval_aexpr(context, node->data.rel_op.l_aexpr); - int aexp2_val = eval_aexpr(context, node->data.rel_op.r_aexpr); + int l_val = eval_aexpr(context, node->data.rel_op.l_aexpr); + int r_val = eval_aexpr(context, node->data.rel_op.r_aexpr); switch (node->data.rel_op.ropr) { - case IMP_AST_ROP_EQ: return aexp1_val == aexp2_val; - case IMP_AST_ROP_NE: return aexp1_val != aexp2_val; - case IMP_AST_ROP_LT: return aexp1_val < aexp2_val; - case IMP_AST_ROP_LE: return aexp1_val <= aexp2_val; - case IMP_AST_ROP_GT: return aexp1_val > aexp2_val; - case IMP_AST_ROP_GE: return aexp1_val >= aexp2_val; + case IMP_AST_ROP_EQ: return l_val == r_val; + case IMP_AST_ROP_NE: return l_val != r_val; + case IMP_AST_ROP_LT: return l_val < r_val; + case IMP_AST_ROP_LE: return l_val <= r_val; + case IMP_AST_ROP_GT: return l_val > r_val; + case IMP_AST_ROP_GE: return l_val >= r_val; default: assert(0); } } @@ -282,155 +52,98 @@ static int eval_bexpr(Context *context, IMP_ASTNode *node) { } } -static int interp_proccall(Context *context, IMP_ASTNode *node) { +static int interpret_proccall(IMP_InterpreterContext *context, const IMP_ASTNode *node) { const char *name = node->data.proc_call.name; - IMP_ASTNode *procdecl = context_get_proc(context, name); + const IMP_ASTNode *procdecl = imp_interpreter_context_proc_get(context, name); if (!procdecl) { fprintf(stderr, "Error: procedure %s not defined\n", name); return -1; } - IMP_ASTNodeList *caller_args = node->data.proc_call.val_args; - IMP_ASTNodeList *callee_args = procdecl->data.proc_decl.val_args; - Context *proc_context = context_create(); - hashmap_keys_iter_t iter = hashmap_keys_iter_create(context->proc_table); - const char *key; - while ((key = hashmap_keys_iter_next(iter)) != NULL) { - IMP_ASTNode *proc = context_get_proc(context, key); - context_set_proc(proc_context, key, imp_ast_clone(proc)); - } - hashmap_keys_iter_free(iter); - while (caller_args && callee_args) { - const char *caller_arg_name = caller_args->node->data.variable.name; - const char *callee_arg_name = callee_args->node->data.variable.name; - context_set_var(proc_context, callee_arg_name, context_get_var(context, caller_arg_name)); - caller_args = caller_args->next; - callee_args = callee_args->next; - } - if (caller_args || callee_args) { + IMP_InterpreterContext *proc_context = imp_interpreter_context_create(); + IMP_InterpreterContextProcIter *proc_iter = imp_interpreter_context_proc_iter_create(context); + const IMP_InterpreterContextProcTableEntry *proc_entry; + while ((proc_entry = imp_interpreter_context_proc_iter_next(proc_iter))) { + imp_interpreter_context_proc_set(proc_context, proc_entry->key, proc_entry->value); + } + imp_interpreter_context_proc_iter_destroy(proc_iter); + IMP_ASTNodeList *caller_val_args = node->data.proc_call.val_args; + IMP_ASTNodeList *callee_val_args = procdecl->data.proc_decl.val_args; + while (caller_val_args && callee_val_args) { + const char *caller_arg_name = caller_val_args->node->data.variable.name; + const char *callee_arg_name = callee_val_args->node->data.variable.name; + imp_interpreter_context_var_set(proc_context, callee_arg_name, imp_interpreter_context_var_get(context, caller_arg_name)); + caller_val_args = caller_val_args->next; + callee_val_args = callee_val_args->next; + } + if (caller_val_args || callee_val_args) { fprintf(stderr, "Error: procedure %s called with wrong number of value arguments\n", name); - context_free(proc_context); + imp_interpreter_context_destroy(proc_context); return -1; } - if (interp_ast(proc_context, procdecl->data.proc_decl.body_stmt)) { - context_free(proc_context); + if (imp_interpreter_interpret_ast(proc_context, procdecl->data.proc_decl.body_stmt)) { + imp_interpreter_context_destroy(proc_context); return -1; } - IMP_ASTNodeList *caller_vargs = node->data.proc_call.var_args; - IMP_ASTNodeList *callee_vargs = procdecl->data.proc_decl.var_args; - while (caller_vargs && callee_vargs) { - const char *caller_varg_name = caller_vargs->node->data.variable.name; - const char *callee_varg_name = callee_vargs->node->data.variable.name; - context_set_var(context, caller_varg_name, context_get_var(proc_context, callee_varg_name)); - caller_vargs = caller_vargs->next; - callee_vargs = callee_vargs->next; + IMP_ASTNodeList *caller_var_args = node->data.proc_call.var_args; + IMP_ASTNodeList *callee_var_args = procdecl->data.proc_decl.var_args; + while (caller_var_args && callee_var_args) { + const char *caller_varg_name = caller_var_args->node->data.variable.name; + const char *callee_varg_name = callee_var_args->node->data.variable.name; + imp_interpreter_context_var_set(context, caller_varg_name, imp_interpreter_context_var_get(proc_context, callee_varg_name)); + caller_var_args = caller_var_args->next; + callee_var_args = callee_var_args->next; } - if (caller_vargs || callee_vargs) { + if (caller_var_args || callee_var_args) { fprintf(stderr, "Error: procedure %s called with wrong number of variable arguments\n", name); - context_free(proc_context); + imp_interpreter_context_destroy(proc_context); return -1; } - context_free(proc_context); + imp_interpreter_context_destroy(proc_context); return 0; } -int interp_ast(Context *context, IMP_ASTNode *node) { +int imp_interpreter_interpret_ast(IMP_InterpreterContext *context, const IMP_ASTNode *node) { switch (node->type) { case IMP_AST_NT_SKIP: return 0; case IMP_AST_NT_ASSIGN: { const char *name = node->data.assign.var->data.variable.name; int val = eval_aexpr(context, node->data.assign.aexpr); - context_set_var(context, name, val); + imp_interpreter_context_var_set(context, name, val); return 0; } case IMP_AST_NT_SEQ: - if (interp_ast(context, node->data.seq.fst_stmt)) return -1; - if (interp_ast(context, node->data.seq.snd_stmt)) return -1; + if (imp_interpreter_interpret_ast(context, node->data.seq.fst_stmt)) return -1; + if (imp_interpreter_interpret_ast(context, node->data.seq.snd_stmt)) return -1; return 0; case IMP_AST_NT_IF: - if (eval_bexpr(context, node->data.if_stmt.cond_bexpr)) return interp_ast(context, node->data.if_stmt.then_stmt); - else return interp_ast(context, node->data.if_stmt.else_stmt); + if (eval_bexpr(context, node->data.if_stmt.cond_bexpr)) return imp_interpreter_interpret_ast(context, node->data.if_stmt.then_stmt); + else return imp_interpreter_interpret_ast(context, node->data.if_stmt.else_stmt); case IMP_AST_NT_WHILE: while (eval_bexpr(context, node->data.while_stmt.cond_bexpr)) { - if (interp_ast(context, node->data.while_stmt.body_stmt)) return -1; + if (imp_interpreter_interpret_ast(context, node->data.while_stmt.body_stmt)) return -1; } return 0; case IMP_AST_NT_LET: { const char *name = node->data.let_stmt.var->data.variable.name; - int old_val = context_get_var(context, name); + int old_val = imp_interpreter_context_var_get(context, name); int new_val = eval_aexpr(context, node->data.let_stmt.aexpr); - context_set_var(context, name, new_val); - int ret = interp_ast(context, node->data.let_stmt.body_stmt); - context_set_var(context, name, old_val); + imp_interpreter_context_var_set(context, name, new_val); + int ret = imp_interpreter_interpret_ast(context, node->data.let_stmt.body_stmt); + imp_interpreter_context_var_set(context, name, old_val); return ret; } case IMP_AST_NT_PROCDECL: { const char *name = node->data.proc_decl.name; - IMP_ASTNode *procdecl_old = context_get_proc(context, name); - if (procdecl_old) { + if (imp_interpreter_context_proc_get(context, name)) { fprintf(stderr, "Error: procedure %s already defined\n", name); return -1; } - IMP_ASTNode *procdecl = imp_ast_clone(node); - context_set_proc(context, name, procdecl); + imp_interpreter_context_proc_set(context, name, node); return 0; } case IMP_AST_NT_PROCCALL: { - return interp_proccall(context, node); + return interpret_proccall(context, node); } default: assert(0); } -} - -int interp_file (Context *context, const char *path) { - yyin = fopen(path, "r"); - if (!yyin) { - return -1; - } - yyrestart(yyin); - if (yyparse()) { - imp_ast_free(ast_root); - fclose(yyin); - return -1; - } - if (interp_ast(context, ast_root)) { - imp_ast_free(ast_root); - fclose(yyin); - return -1; - } - imp_ast_free(ast_root); - fclose(yyin); - return 0; -} - -int interp_str (Context *context, const char *str) { - YY_BUFFER_STATE buf = yy_scan_string(str); - if (yyparse()) { - imp_ast_free(ast_root); - yy_delete_buffer(buf); - return -1; - } - if (interp_ast(context, ast_root)) { - imp_ast_free(ast_root); - yy_delete_buffer(buf); - return -1; - } - yy_delete_buffer(buf); - return 0; -} - -int print_ast_file (const char *path) { - yyin = fopen(path, "r"); - if (!yyin) { - return -1; - } - yyrestart(yyin); - if (yyparse()) { - imp_ast_free(ast_root); - fclose(yyin); - return -1; - } - ast_print(ast_root, 0); - imp_ast_free(ast_root); - fclose(yyin); - return 0; }
\ No newline at end of file diff --git a/src/interpreter_context.c b/src/interpreter_context.c new file mode 100644 index 0000000..488f872 --- /dev/null +++ b/src/interpreter_context.c @@ -0,0 +1,123 @@ +#include "interpreter_context.h" + +#define STB_DS_IMPLEMENTATION +#include "3rdparty/stb_ds/stb_ds.h" + + +struct IMP_InterpreterContext { + IMP_InterpreterContextVarTableEntry *var_table; + IMP_InterpreterContextProcTableEntry *proc_table; +}; + +struct IMP_InterpreterContextVarIter { + IMP_InterpreterContextVarTableEntry *var_table; + ptrdiff_t index; + ptrdiff_t len; +}; + +struct IMP_InterpreterContextProcIter { + IMP_InterpreterContextProcTableEntry *proc_table; + ptrdiff_t index; + ptrdiff_t len; +}; + +IMP_InterpreterContext *imp_interpreter_context_create(void) { + IMP_InterpreterContext *context = malloc(sizeof(IMP_InterpreterContext)); + assert(context && "Memory allocation failed"); + context->var_table = NULL; + context->proc_table = NULL; + return context; +} + +void imp_interpreter_context_destroy(IMP_InterpreterContext *context) { + ptrdiff_t len = shlen(context->var_table); + for (ptrdiff_t i = 0; i < len; ++i) free((char*)context->var_table[i].key); + shfree(context->var_table); +} + +int imp_interpreter_context_var_get(IMP_InterpreterContext *context, const char *name) { + ptrdiff_t index = shgeti(context->var_table, name); + if (index < 0) return 0; + return context->var_table[index].value; +} + +void imp_interpreter_context_var_set(IMP_InterpreterContext *context, const char *name, int value) { + ptrdiff_t index = shgeti(context->var_table, name); + if (index < 0) { + if (value == 0) return; + const char* key = strdup(name); + assert(key && "Memory allocation failed"); + shput(context->var_table, key, value); + } else { + if (value == 0) { + const char *key = context->var_table[index].key; + shdel(context->var_table, name); + free((char*)key); + return; + } + context->var_table[index].value = value; + } +} + +const IMP_ASTNode *imp_interpreter_context_proc_get(IMP_InterpreterContext *context, const char *name) { + ptrdiff_t index = shgeti(context->proc_table, name); + if (index < 0) return NULL; + return context->proc_table[index].value; +} + +void imp_interpreter_context_proc_set(IMP_InterpreterContext *context, const char *name, const IMP_ASTNode *proc) { + ptrdiff_t index = shgeti(context->proc_table, name); + proc = imp_ast_clone(proc); + if (proc) assert(proc->type == IMP_AST_NT_PROCDECL); + if (index < 0) { + if (proc == NULL) return; + const char* key = strdup(name); + assert(key && "Memory allocation failed"); + shput(context->proc_table, key, proc); + } else { + imp_ast_destroy((IMP_ASTNode*)context->proc_table[index].value); + if (proc == NULL) { + const char *key = context->proc_table[index].key; + shdel(context->proc_table, name); + free((char*)key); + return; + } + context->proc_table[index].value = proc; + } +} + +IMP_InterpreterContextVarIter *imp_interpreter_context_var_iter_create(IMP_InterpreterContext *context) { + IMP_InterpreterContextVarIter *iter = malloc(sizeof(IMP_InterpreterContextVarIter)); + assert(iter && "Memory allocation failed"); + iter->var_table = context->var_table; + iter->index = 0; + iter->len = shlen(context->var_table); + return iter; +} + +void imp_interpreter_context_var_iter_destroy(IMP_InterpreterContextVarIter *iter) { + free(iter); +} + +IMP_InterpreterContextVarTableEntry *imp_interpreter_context_var_iter_next(IMP_InterpreterContextVarIter *iter) { + if (iter->index >= iter->len) return NULL; + return &iter->var_table[iter->index++]; +} + +IMP_InterpreterContextProcIter *imp_interpreter_context_proc_iter_create(IMP_InterpreterContext *context) { + IMP_InterpreterContextProcIter *iter = malloc(sizeof(IMP_InterpreterContextProcIter)); + assert(iter && "Memory allocation failed"); + iter->proc_table = context->proc_table; + iter->index = 0; + iter->len = shlen(context->proc_table); + return iter; +} + +void imp_interpreter_context_proc_iter_destroy(IMP_InterpreterContextProcIter *iter) { + free(iter); +} + +IMP_InterpreterContextProcTableEntry *imp_interpreter_context_proc_iter_next(IMP_InterpreterContextProcIter *iter) { + if (iter->index >= iter->len) return NULL; + return &iter->proc_table[iter->index++]; +}
\ No newline at end of file diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..5ef854d --- /dev/null +++ b/src/main.c @@ -0,0 +1,45 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "interpreter_context.h" +#include "driver.h" +#include "repl.h" + + +static int interpret_file(const char *path) { + IMP_InterpreterContext *context = imp_interpreter_context_create(); + if (imp_driver_interpret_file(context, path)) { + fprintf(stderr, "Error interpreting file: %s\n", path); + imp_interpreter_context_destroy(context); + return -1; + } + imp_driver_print_var_table(context); + imp_interpreter_context_destroy(context); + return 0; +} + +int main(int argc, char **argv) { + int opt; + while ((opt = getopt(argc, argv, "i:a:h")) != -1) { + switch (opt) { + case 'i': + return interpret_file(optarg) ? EXIT_FAILURE : EXIT_SUCCESS; + case 'a': + return imp_driver_print_ast_file(optarg) ? EXIT_FAILURE : EXIT_SUCCESS; + case 'h': + default: + fprintf(stderr, + "Usage: %s [ARGS]\n" + " (no args) start REPL\n" + " -i <program.imp> interpret program\n" + " -a <program.imp> print ast\n" + " -h print this message\n", + argv[0]); + return (opt == 'h') ? EXIT_SUCCESS : EXIT_FAILURE; + } + } + imp_repl(); + return EXIT_SUCCESS; +}
\ No newline at end of file @@ -8,7 +8,8 @@ #include <readline/readline.h> #include <readline/history.h> -#include "interpreter.h" +#include "interpreter_context.h" +#include "driver.h" static void print_help(void) { @@ -31,7 +32,7 @@ static int is_valid_identifier(const char *var) { return 1; } -static void repl_exec_command(context_t context, char *command) { +static void repl_exec_command(IMP_InterpreterContext *context, char *command) { char *cmd = strtok(command, " \t"); if (strcmp(cmd, "%quit") == 0) { exit(EXIT_SUCCESS); @@ -40,7 +41,8 @@ static void repl_exec_command(context_t context, char *command) { } else if (strcmp(cmd, "%run") == 0) { char *file = strtok(NULL, " \t"); if (file) { - if (!interp_file(context, file)) context_print_var_table(context); + if (!imp_driver_interpret_file(context, file)) imp_driver_print_var_table(context); + else fprintf(stderr, "Error interpreting file: %s\n", file); } else { fprintf(stderr, "Usage: %%run <path/to/file.imp>\n"); } @@ -49,7 +51,7 @@ static void repl_exec_command(context_t context, char *command) { char *val = strtok(NULL, " \t"); if (var && val) { if (is_valid_identifier(var)) { - context_set_var(context, var, atoi(val)); + imp_interpreter_context_var_set(context, var, atoi(val)); } else { fprintf(stderr, "Invalid variable name: %s\n", var); } @@ -58,28 +60,28 @@ static void repl_exec_command(context_t context, char *command) { char *var = strtok(NULL, " \t"); if (var) { if (is_valid_identifier(var)) { - printf("%s = %d\n", var, context_get_var(context, var)); + printf("%s = %d\n", var, imp_interpreter_context_var_get(context, var)); } else { fprintf(stderr, "Invalid variable name: %s\n", var); } - } else context_print_var_table(context); + } else imp_driver_print_var_table(context); } else if (strcmp(cmd, "%procedures") == 0) { - context_print_proc_table(context); + imp_driver_print_proc_table(context); } else { fprintf(stderr, "Unknown command: %s\n", cmd); } } -static void repl_exec_statement(context_t context, const char *statement) { - if (interp_str(context, statement)) { +static void repl_exec_statement(IMP_InterpreterContext *context, const char *statement) { + if (imp_driver_interpret_str(context, statement)) { fprintf(stderr, "Error interpreting statement: %s\n", statement); return; } - context_print_var_table(context); + imp_driver_print_var_table(context); } -void repl(void) { - context_t context = context_create(); +void imp_repl(void) { + IMP_InterpreterContext *context = imp_interpreter_context_create(); char *line; print_help(); @@ -91,5 +93,5 @@ void repl(void) { } printf("\n"); - context_free(context); + imp_interpreter_context_destroy(context); }
\ No newline at end of file diff --git a/test/test.c b/test/test.c new file mode 100644 index 0000000..a2f8a34 --- /dev/null +++ b/test/test.c @@ -0,0 +1,131 @@ +#include <stdio.h> +#include <string.h> +#include <assert.h> + +#include "ast.h" +#include "interpreter_context.h" +#include "interpreter.h" + +static void test_interpreter_context(void) { + IMP_InterpreterContext *context = imp_interpreter_context_create(); + assert(context); + + int value; + + value = imp_interpreter_context_var_get(context, "a"); + assert(value == 0); + + imp_interpreter_context_var_set(context, "a", 1); + value = imp_interpreter_context_var_get(context, "a"); + assert(value == 1); + + imp_interpreter_context_var_set(context, "b", 2); + value = imp_interpreter_context_var_get(context, "b"); + assert(value == 2); + + imp_interpreter_context_var_set(context, "c", 3); + value = imp_interpreter_context_var_get(context, "c"); + assert(value == 3); + + imp_interpreter_context_var_set(context, "a", 4); + value = imp_interpreter_context_var_get(context, "a"); + assert(value == 4); + + imp_interpreter_context_var_set(context, "b", 0); + value = imp_interpreter_context_var_get(context, "b"); + assert(value == 0); + + IMP_InterpreterContextVarIter *var_iter = imp_interpreter_context_var_iter_create(context); + IMP_InterpreterContextVarTableEntry *var_entry; + while ((var_entry = imp_interpreter_context_var_iter_next(var_iter))) { + if (strcmp(var_entry->key, "a") == 0) assert(var_entry->value == 4); + else if (strcmp(var_entry->key, "c") == 0) assert(var_entry->value == 3); + else assert(0); + } + imp_interpreter_context_var_iter_destroy(var_iter); + + IMP_ASTNode *proc_a = imp_ast_procdecl("a", NULL, NULL, imp_ast_skip()); + IMP_ASTNode *proc_b = imp_ast_procdecl("b", NULL, NULL, imp_ast_skip()); + const IMP_ASTNode *proc; + + proc = imp_interpreter_context_proc_get(context, "a"); + assert(proc == NULL); + + imp_interpreter_context_proc_set(context, "a", proc_a); + proc = imp_interpreter_context_proc_get(context, "a"); + assert(proc != NULL); + + imp_interpreter_context_proc_set(context, "b", proc_b); + proc = imp_interpreter_context_proc_get(context, "b"); + assert(proc != NULL); + + imp_interpreter_context_proc_set(context, "b", NULL); + proc = imp_interpreter_context_proc_get(context, "b"); + assert(proc == NULL); + + IMP_InterpreterContextProcIter *proc_iter = imp_interpreter_context_proc_iter_create(context); + IMP_InterpreterContextProcTableEntry *proc_entry; + while ((proc_entry = imp_interpreter_context_proc_iter_next(proc_iter))) { + if (strcmp(proc_entry->key, "a") == 0) assert(proc_entry->value != NULL); + else assert(0); + } + imp_interpreter_context_proc_iter_destroy(proc_iter); + + imp_ast_destroy(proc_a); + imp_ast_destroy(proc_b); + + imp_interpreter_context_destroy(context); +} + +static void test_interpreter(void) { + IMP_ASTNode *factorial_procdecl = imp_ast_procdecl( + "factorial", + imp_ast_list(imp_ast_var("n"), NULL), + imp_ast_list(imp_ast_var("r"), NULL), + imp_ast_if( + imp_ast_rop(IMP_AST_ROP_LE, imp_ast_var("n"), imp_ast_int(0)), + imp_ast_assign(imp_ast_var("r"), imp_ast_int(1)), + imp_ast_seq( + imp_ast_assign(imp_ast_var("m"), imp_ast_aop(IMP_AST_AOP_SUB, imp_ast_var("n"), imp_ast_int(1))), + imp_ast_seq( + imp_ast_proccall( + "factorial", + imp_ast_list(imp_ast_var("m"), NULL), + imp_ast_list(imp_ast_var("r"), NULL) + ), + imp_ast_assign(imp_ast_var("r"), imp_ast_aop(IMP_AST_AOP_MUL, imp_ast_var("r"), imp_ast_var("n"))) + ) + ) + ) + ); + + IMP_ASTNode *main = imp_ast_seq( + factorial_procdecl, + imp_ast_seq( + imp_ast_assign(imp_ast_var("n"), imp_ast_int(5)), + imp_ast_proccall( + "factorial", + imp_ast_list(imp_ast_var("n"), NULL), + imp_ast_list(imp_ast_var("r"), NULL) + ) + ) + ); + + IMP_InterpreterContext *context = imp_interpreter_context_create(); + int result = imp_interpreter_interpret_ast(context, main); + assert(result == 0); + int factorial_result = imp_interpreter_context_var_get(context, "r"); + assert(factorial_result == 120); + const IMP_ASTNode *proc = imp_interpreter_context_proc_get(context, "factorial"); + assert(proc != NULL); + + imp_ast_destroy(main); + imp_interpreter_context_destroy(context); +} + +int main(void) { + printf("Starting tests...\n"); + test_interpreter_context(); + test_interpreter(); + printf("All tests passed\n"); +}
\ No newline at end of file |