/* bi_inputline.c CSQC string hashes builtins Copyright (C) 2002 Robin Redeker This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ static const char rcsid[] = "$Id$"; #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_STRINGS_H # include #endif #include "QF/console.h" #include "QF/draw.h" #include "QF/progs.h" #include "QF/zone.h" #define MAX_SH_VALUES 16 /* DESIGN NOTE (by elmex): This file contains code for QuakeC hashes. The hases are designed to have around 10 sub-values for one key. Hashes are stored as hash-ids in the QC-environment. The key->value pairs in the hashes are stored in the order they are set. So its easy to make array-hashes. The function which gets the Lenght of a hash is specialy made for array purposes. TODO: Check the FIXME's below in the code. (please taniwha ;) (about memory leaks) */ // a hash element typedef struct { char * key; char * values[MAX_SH_VALUES]; } str_hash_elem; // a structure of a hash typedef struct { str_hash_elem **elements; int cnt_elements; } str_hash; // a list structure of hashes typedef struct { str_hash **hashes; int cnt_hashes; } strh_resources_t; /* bi_StringHash_Create Creates a hash structure and gives back the hash-id to the QC-environment */ static void bi_StringHash_Create (progs_t *pr) { strh_resources_t *res = PR_Resources_Find (pr, "StringHash"); int i; int hash_id=-1; // check if there is a empty hash in the array for(i = 0; i < res->cnt_hashes; i++) { if(res->hashes[i]->cnt_elements == 0) { // we found a empty already allocated hash hash_id = i; break; } } if(hash_id == -1) { /* allocate a new hash struct, if there is no old one */ if(res->hashes == NULL) { // allocate a new list of hashes res->hashes = (str_hash **) malloc( sizeof(str_hash*) * (res->cnt_hashes + 1)); } else { // reallocate the list of hashes res->hashes = (str_hash **) realloc(res->hashes, sizeof(str_hash*) * (res->cnt_hashes + 1)); } hash_id = res->cnt_hashes; res->hashes[hash_id] = (str_hash*) malloc(sizeof(str_hash)); // dont forge to clean the hash memset(res->hashes[hash_id],0,sizeof(str_hash)); res->cnt_hashes++; // increase cnt of allocated hashes } R_INT (pr) = hash_id; } /* bi_StringHash_Destroy Destroys a hash */ static void bi_StringHash_Destroy (progs_t *pr) { strh_resources_t* res = PR_Resources_Find (pr, "StringHash"); int hash_id = P_INT (pr, 0); str_hash *sh = NULL; int i,d; if(hash_id >= res->cnt_hashes || hash_id < 0) { R_INT (pr) = 0; return; } sh = res->hashes[hash_id]; /* we dont really destroy the allocated hash, but we free the elements of the hash and mark it for reuseing */ for(i = 0; i < sh->cnt_elements; i++) { if(sh->elements[i] != NULL) { /* they should never be NULL, buy, who knows? */ PR_Error(pr, "NULL hash-element found -> not supposed!"); } else { for(d=0;delements[i]->key); // free values if(sh->elements[i]->values[d] != NULL) { free(sh->elements[i]->values[d]); } } free(sh->elements[i]); // free str_hash_elem structs } /* FIXME: HELP: taniwha??? do i have to FREE the strings ?? */ } free(sh->elements); // free the list pointer sh->elements = NULL; sh->cnt_elements = 0; R_INT (pr) = 1; } /* bi_StringHash_Set Sets a the key-value (with a special id) in a hash to a value. If a non existing key is given a element if generated for it. FIXME: look if this functions does mem-leak */ static void bi_StringHash_Set (progs_t *pr) { strh_resources_t* res = PR_Resources_Find (pr, "StringHash"); int hash_id = P_INT (pr, 0); const char *key = P_STRING (pr, 1); const char *val = P_STRING (pr, 2); int val_id = P_INT (pr, 3); str_hash *sh = NULL; int i,found_fl=0; // validate the hash ID if(res->hashes == NULL || (hash_id >= res->cnt_hashes || hash_id < 0) || (val_id < 0 || val_id >= MAX_SH_VALUES)) { R_INT (pr) = 0; return; } sh = res->hashes[hash_id]; // first search for existing key for(i = 0; i < sh->cnt_elements; i++) { if(strcmp(sh->elements[i]->key, key) == 0) { // found already a element with that key if(sh->elements[i]->values[val_id] == NULL) { // empty val // strdup() because strings can dissappear sh->elements[i]->values[val_id] = strdup(val); } else { // when using strdup(), we have to free the stuff properly free(sh->elements[i]->values[val_id]); sh->elements[i]->values[val_id] = strdup(val); } found_fl = 1; } } if(!found_fl) { // add a new element if(sh->elements == NULL) { // alloc new elements list pointer sh->elements = (str_hash_elem**) malloc(sizeof(str_hash_elem*)); sh->cnt_elements = 0; // 0 because usage as index here } else { sh->elements = (str_hash_elem**) realloc(sh->elements, sizeof(str_hash_elem*) * (sh->cnt_elements+1)); } sh->elements[sh->cnt_elements] = malloc(sizeof(str_hash_elem)); memset(sh->elements[sh->cnt_elements],0,sizeof(str_hash_elem)); sh->elements[sh->cnt_elements]->key = strdup(key); sh->elements[sh->cnt_elements]->values[val_id] = strdup(val); sh->cnt_elements++; } R_INT (pr) = 1; return; } /* bi_StringHash_SetIdx Sets a the key-value (with a special id) in a hash to a value. This function works by index of the element. A element in hash is NOT generated automatically. FIXME: look if this functions does mem-leak */ static void bi_StringHash_SetIdx (progs_t *pr) { strh_resources_t* res = PR_Resources_Find (pr, "StringHash"); int hash_id = P_INT (pr, 0); int idx = P_INT (pr, 1); const char *val = P_STRING (pr, 2); int val_id = P_INT (pr, 3); str_hash *sh = NULL; // validate the hash ID if(res->hashes == NULL || (hash_id >= res->cnt_hashes || hash_id < 0) || (val_id < 0 || val_id >= MAX_SH_VALUES)) { R_INT (pr) = 0; return; } sh = res->hashes[hash_id]; if(idx < 0 || idx >= sh->cnt_elements || sh->elements[idx] == NULL) { if(sh->elements[idx] == NULL) PR_Error(pr, "NULL hash-element found -> not supposed!"); R_INT (pr) = 0; return; } if(sh->elements[idx]->values[val_id] != NULL) { free(sh->elements[idx]->values[val_id]); } sh->elements[idx]->values[val_id] = strdup(val); R_INT (pr) = 1; return; } /* bi_StringHash_Get Gets the value of a key and its id in a hash */ static void bi_StringHash_Get (progs_t *pr) { strh_resources_t* res = PR_Resources_Find (pr, "StringHash"); int hash_id = P_INT (pr, 0); const char *key = P_STRING (pr, 1); int val_id = P_INT (pr, 2); str_hash *sh = NULL; int i,found_fl=0; const char *retstr = NULL; // validate the hash ID if(res->hashes == NULL || hash_id >= res->cnt_hashes || hash_id < 0 || val_id >= MAX_SH_VALUES) { retstr = ""; RETURN_STRING(pr, retstr); return; } sh = res->hashes[hash_id]; // first search for existing key for(i = 0; i < sh->cnt_elements; i++) { if(strcmp(sh->elements[i]->key, key) == 0) { if(sh->elements[i]->values[val_id] != NULL) { retstr = sh->elements[i]->values[val_id]; } else { retstr = ""; } found_fl = 1; } } if(!found_fl) { retstr = ""; } RETURN_STRING(pr, retstr); } /* bi_StringHash_Length Gets the count of the elements in a hash */ static void bi_StringHash_Length (progs_t *pr) { strh_resources_t* res = PR_Resources_Find (pr, "StringHash"); int hash_id = P_INT (pr, 0); str_hash *sh = NULL; // validate the hash ID if(res->hashes == NULL || hash_id >= res->cnt_hashes || hash_id < 0) { R_INT (pr) = 0; return; } sh = res->hashes[hash_id]; R_INT (pr) = sh->cnt_elements; } /* bi_StringHash_GetIdx Gets a hash elment by its index special: if the val_id is -1 the key of the element will be returned */ static void bi_StringHash_GetIdx (progs_t *pr) { strh_resources_t* res = PR_Resources_Find (pr, "StringHash"); int hash_id = P_INT (pr, 0); int idx = P_INT (pr, 1); int val_id = P_INT (pr, 2); str_hash *sh = NULL; const char *retstr = NULL; // validate the hash ID if(res->hashes == NULL || hash_id >= res->cnt_hashes || hash_id < 0) { retstr = NULL; } sh = res->hashes[hash_id]; if(idx < 0 || idx >= sh->cnt_elements || (val_id < -1 || val_id >= MAX_SH_VALUES)) { retstr = NULL; } else { if(val_id == -1) { retstr = sh->elements[idx]->key; } else { retstr = sh->elements[idx]->values[val_id]; } } if(retstr == NULL) { retstr = ""; } RETURN_STRING(pr, retstr); } /* bi_strh_clear Free the dynamic allocated memory XXX: taniwha: i dont know what to free exactly, could you validate this code? */ static void bi_strh_clear (progs_t *pr, void *data) { strh_resources_t *res = (strh_resources_t *)data; int i,d,n; for (i = 0; i < res->cnt_hashes; i++) { if (res->hashes[i]) { for(d = 0; d < res->hashes[i]->cnt_elements; d++) { free(res->hashes[i]->elements[d]->key); // Free the key for (n = 0; n < MAX_SH_VALUES; n++) // Free all values if (res->hashes[i]->elements[d]->values[n]) free(res->hashes[i]->elements[d]->values[n]); free(res->hashes[i]->elements[d]); // Free the element itself } free(res->hashes[i]->elements); free(res->hashes[i]); res->hashes[i] = 0; } } free (res->hashes); res->hashes = 0; res->cnt_hashes = 0; } /* StringHash_Progs_Init Inits the Progs-system with StringHash resources and functions. */ void StringHash_Progs_Init (progs_t *pr) { strh_resources_t *res = malloc (sizeof (strh_resources_t)); res->cnt_hashes = 0; res->hashes = NULL; PR_Resources_Register (pr, "StringHash", res, bi_strh_clear); PR_AddBuiltin (pr, "StringHash_Create", bi_StringHash_Create, -1); PR_AddBuiltin (pr, "StringHash_Destroy", bi_StringHash_Destroy, -1); PR_AddBuiltin (pr, "StringHash_Set", bi_StringHash_Set, -1); PR_AddBuiltin (pr, "StringHash_Get", bi_StringHash_Get, -1); PR_AddBuiltin (pr, "StringHash_Length", bi_StringHash_Length, -1); PR_AddBuiltin (pr, "StringHash_SetIdx", bi_StringHash_SetIdx, -1); PR_AddBuiltin (pr, "StringHash_GetIdx", bi_StringHash_GetIdx, -1); } /* XXX NOTE by elmex: A file, decripted like this is what i want to see everywhere in qf-cvs =) No excuse for undocumented code and design without a reason for it. We/I want to know why something was designed how it is. */