/*1* alist.c -- resizeable arrays.2* Written by Jeremy Nelson3* Copyright 1997 EPIC Software Labs4*5* This file presumes a good deal of chicanery. Specifically, it assumes6* that your compiler will allocate disparate structures congruently as7* long as the members match as to their type and location. This is8* critically important for how this code works, and all hell will break9* loose if your compiler doesnt do this. Every compiler i know of does10* it, which is why im assuming it, even though im not allowed to assume it.11*12* This file is hideous. Ill kill each and every one of you who made13* me do this. ;-)14*/1516#define _cs_alist_hash_17#define _ci_alist_hash_18#include "irc.h"19static char cvsrevision[] = "$Id: alist.c 107 2011-02-02 12:11:03Z keaston $";20CVS_REVISION(alist_c)21#include "alist.h"22#include "ircaux.h"23#include "output.h"24#define MAIN_SOURCE25#include "modval.h"2627u_32int_t bin_ints = 0;28u_32int_t lin_ints = 0;29u_32int_t bin_chars = 0;30u_32int_t lin_chars = 0;31u_32int_t alist_searches = 0;32u_32int_t char_searches = 0;333435#define ARRAY_ITEM(array, loc) ((Array_item *) ((array) -> list [ (loc) ]))36#define LARRAY_ITEM(array, loc) (((array) -> list [ (loc) ]))3738/* Function decls */39static void check_array_size (Array *list);40void move_array_items (Array *list, int start, int end, int dir);4142/*43* Returns an entry that has been displaced, if any.44*/45Array_item *BX_add_to_array (Array *array, Array_item *item)46{47int count;48int location = 0;49Array_item *ret = NULL;50u_32int_t mask; /* Dummy var */5152if (array->hash == HASH_INSENSITIVE)53item->hash = ci_alist_hash(item->name, &mask);54else55item->hash = cs_alist_hash(item->name, &mask);5657check_array_size(array);58if (array->max)59{60find_array_item(array, item->name, &count, &location);61if (count < 0)62{63ret = ARRAY_ITEM(array, location);64array->max--;65}66else67move_array_items(array, location, array->max, 1);68}6970array->list[location] = item;71array->max++;72return ret;73}7475/*76* Returns the entry that has been removed, if any.77*/78Array_item *BX_remove_from_array (Array *array, char *name)79{80int count, location = 0;8182if (array->max)83{84find_array_item(array, name, &count, &location);85if (count >= 0)86return NULL;8788return array_pop(array, location);89}90return NULL; /* Cant delete whats not there */91}9293/* Remove the 'which'th item from the given array */94Array_item *BX_array_pop (Array *array, int which)95{96Array_item *ret = NULL;9798if (which < 0 || which >= array->max)99return NULL;100101ret = ARRAY_ITEM(array, which);102move_array_items(array, which + 1, array->max, -1);103array->max--;104return ret;105}106107/*108* Returns the entry that has been removed, if any.109*/110Array_item *BX_remove_all_from_array (Array *array, char *name)111{112int count, location = 0;113Array_item *ret = NULL;114115if (array->max)116{117find_array_item(array, name, &count, &location);118if (count == 0)119return NULL;120ret = ARRAY_ITEM(array, location);121move_array_items(array, location + 1, array->max, -1);122array->max--;123return ret;124}125return NULL; /* Cant delete whats not there */126}127128Array_item *BX_array_lookup (Array *array, char *name, int wild, int delete)129{130int count, location;131132if (delete)133return remove_from_array(array, name);134else135return find_array_item(array, name, &count, &location);136}137138static void check_array_size (Array *array)139{140if (array->total_max == 0)141array->total_max = 6; /* Good size to start with */142else if (array->max == array->total_max-1)143array->total_max *= 2;144else if (array->max * 3 < array->total_max)145array->total_max /= 2;146else147return;148149/*yell("Resizing...");*/150RESIZE(array->list, Array_item *, array->total_max);151}152153/*154* Move ``start'' through ``end'' array elements ``dir'' places up155* in the array. If ``dir'' is negative, move them down in the array.156* Fill in the vacated spots with NULLs.157*/158void move_array_items (Array *array, int start, int end, int dir)159{160int i;161162if (dir > 0)163{164for (i = end; i >= start; i--)165LARRAY_ITEM(array, i + dir) = ARRAY_ITEM(array, i);166for (i = dir; i > 0; i--)167LARRAY_ITEM(array, start + i - 1) = NULL;168}169else if (dir < 0)170{171for (i = start; i <= end; i++)172LARRAY_ITEM(array, i + dir) = ARRAY_ITEM(array, i);173for (i = end - dir + 1; i <= end; i++)174LARRAY_ITEM(array, i) = NULL;175}176}177178179/*180* This is just a generalization of the old function ``find_command''181*182* You give it an alist_item and a name, and it gives you the number of183* items that are completed by that name, and where you could find/put that184* item in the list. It returns the alist_item most appropriate.185*186* If ``cnt'' is less than -1, then there was one exact match and one or187* more ambiguous matches in addition. The exact match's location188* is put into ``loc'' and its entry is returned. The ambigous matches189* are (of course) immediately subsequent to it.190*191* If ``cnt'' is -1, then there was one exact match. Its location is192* put into ``loc'' and its entry is returned.193*194* If ``cnt'' is zero, then there were no matches for ``name'', but ``loc''195* is set to the location in the array in which you could place the196* specified name in the sorted list.197*198* If ``cnt'' is one, then there was one command that non-ambiguously199* completes ``name''200*201* If ``cnt'' is greater than one, then there was exactly ``cnt'' number202* of entries that completed ``name'', but they are all ambiguous.203* The entry that is lowest alphabetically is returned, and its204* location is put into ``loc''.205*/206Array_item *BX_find_array_item (Array *set, char *name, int *cnt, int *loc)207{208size_t len = strlen(name);209int c = 0,210pos = 0,211min,212max;213u_32int_t mask, hash;214215if (set->hash == HASH_INSENSITIVE)216hash = ci_alist_hash(name, &mask);217else218hash = cs_alist_hash(name, &mask);219220*cnt = 0;221if (!set->list || !set->max)222{223*loc = 0;224return NULL;225}226227alist_searches++;228max = set->max - 1;229min = 0;230231while (max >= min)232{233bin_ints++;234pos = (max - min) / 2 + min;235c = (hash & mask) - (ARRAY_ITEM(set, pos)->hash & mask);236if (c == 0)237break;238else if (c < 0)239max = pos - 1;240else241min = pos + 1;242}243244/*245* If we can't find a symbol that qualifies, then we can just drop246* out here. This is good because a "pass" (lookup for a symbol that247* does not exist) requires only cheap integer comparisons.248*/249if (c != 0)250{251if (c > 0)252*loc = pos + 1;253else254*loc = pos;255return NULL;256}257258/*259* Now we've found some symbol that has the same first four letters.260* Expand the min/max range to include all of the symbols that have261* the same first four letters...262*/263min = max = pos;264while ((min > 0) && (hash & mask) == (ARRAY_ITEM(set, min)->hash & mask))265min--, lin_ints++;266while ((max < set->max - 1) && (hash &mask) == (ARRAY_ITEM(set, max)->hash & mask))267max++, lin_ints++;268269char_searches++;270271/*272* Then do a full blown binary search on the smaller range273*/274while (max >= min)275{276bin_chars++;277pos = (max - min) / 2 + min;278c = set->func(name, ARRAY_ITEM(set, pos)->name, len);279if (c == 0)280break;281else if (c < 0)282max = pos - 1;283else284min = pos + 1;285}286287288if (c != 0)289{290if (c > 0)291*loc = pos + 1;292else293*loc = pos;294return NULL;295}296297/*298* If we've gotten this far, then we've found at least299* one appropriate entry. So we set *cnt to one300*/301*cnt = 1;302303/*304* So we know that 'pos' is a match. So we start 'min'305* at one less than 'pos' and walk downard until we find306* an entry that is NOT matched.307*/308min = pos - 1;309while (min >= 0 && !set->func(name, ARRAY_ITEM(set, min)->name, len))310(*cnt)++, min--, lin_chars++;311min++;312313/*314* Repeat the same ordeal, except this time we walk upwards315* from 'pos' until we dont find a match.316*/317max = pos + 1;318while (max < set->max && !set->func(name, ARRAY_ITEM(set, max)->name, len))319(*cnt)++, max++, lin_chars++;320321/*322* Alphabetically, the string that would be identical to323* 'name' would be the first item in a string of items that324* all began with 'name'. That is to say, if there is an325* exact match, its sitting under item 'min'. So we check326* for that and whack the count appropriately.327*/328if (strlen(ARRAY_ITEM(set, min)->name) == len)329*cnt *= -1;330331/*332* Then we tell the caller where the lowest match is,333* in case they want to insert here.334*/335if (loc)336*loc = min;337338/*339* Then we return the first item that matches.340*/341return ARRAY_ITEM(set, min);342}343344#define FIXED_ITEM(list, pos, size) (*(Array_item *)((char *)list + ( pos * size )))345346/*347* This is useful for finding items in a fixed array (eg, those lists that348* are a simple fixed length arrays of 1st level structs.)349*350* This code is identical to find_array_item except ``list'' is a 1st351* level array instead of a 2nd level array.352*/353void * BX_find_fixed_array_item (void *list, size_t size, int howmany, char *name, int *cnt, int *loc)354{355int len = strlen(name),356min = 0,357max = howmany,358old_pos = -1,359pos,360c;361362*cnt = 0;363364while (1)365{366pos = (max + min) / 2;367if (pos == old_pos)368{369*loc = pos;370return NULL;371}372old_pos = pos;373374c = strncmp(name, FIXED_ITEM(list, pos, size).name, len);375if (c == 0)376break;377else if (c > 0)378min = pos;379else380max = pos;381}382383/*384* If we've gotten this far, then we've found at least385* one appropriate entry. So we set *cnt to one386*/387*cnt = 1;388389/*390* So we know that 'pos' is a match. So we start 'min'391* at one less than 'pos' and walk downard until we find392* an entry that is NOT matched.393*/394min = pos - 1;395while (min >= 0 && !strncmp(name, FIXED_ITEM(list, min, size).name, len))396(*cnt)++, min--;397min++;398399/*400* Repeat the same ordeal, except this time we walk upwards401* from 'pos' until we dont find a match.402*/403max = pos + 1;404while ((max < howmany) && !strncmp(name, FIXED_ITEM(list, max, size).name, len))405(*cnt)++, max++;406407/*408* Alphabetically, the string that would be identical to409* 'name' would be the first item in a string of items that410* all began with 'name'. That is to say, if there is an411* exact match, its sitting under item 'min'. So we check412* for that and whack the count appropriately.413*/414if (strlen(FIXED_ITEM(list, min, size).name) == len)415*cnt *= -1;416417/*418* Then we tell the caller where the lowest match is,419* in case they want to insert here.420*/421if (loc)422*loc = min;423424/*425* Then we return the first item that matches.426*/427return (void *)&FIXED_ITEM(list, min, size);428}429430431432433