/*
* alist.c -- resizeable arrays.
* Written by Jeremy Nelson
* Copyright 1997 EPIC Software Labs
*
* This file presumes a good deal of chicanery. Specifically, it assumes
* that your compiler will allocate disparate structures congruently as
* long as the members match as to their type and location. This is
* critically important for how this code works, and all hell will break
* loose if your compiler doesnt do this. Every compiler i know of does
* it, which is why im assuming it, even though im not allowed to assume it.
*
* This file is hideous. Ill kill each and every one of you who made
* me do this. ;-)
*/
#define _cs_alist_hash_
#define _ci_alist_hash_
#include "irc.h"
static char cvsrevision[] = "$Id: alist.c,v 1.1.1.1 2003/04/11 01:09:07 dan Exp $";
CVS_REVISION(alist_c)
#include "alist.h"
#include "ircaux.h"
#include "output.h"
#define MAIN_SOURCE
#include "modval.h"
u_32int_t bin_ints = 0;
u_32int_t lin_ints = 0;
u_32int_t bin_chars = 0;
u_32int_t lin_chars = 0;
u_32int_t alist_searches = 0;
u_32int_t char_searches = 0;
#define ARRAY_ITEM(array, loc) ((Array_item *) ((array) -> list [ (loc) ]))
#define LARRAY_ITEM(array, loc) (((array) -> list [ (loc) ]))
/* Function decls */
static void check_array_size (Array *list);
void move_array_items (Array *list, int start, int end, int dir);
/*
* Returns an entry that has been displaced, if any.
*/
Array_item *BX_add_to_array (Array *array, Array_item *item)
{
int count;
int location = 0;
Array_item *ret = NULL;
u_32int_t mask; /* Dummy var */
if (array->hash == HASH_INSENSITIVE)
item->hash = ci_alist_hash(item->name, &mask);
else
item->hash = cs_alist_hash(item->name, &mask);
check_array_size(array);
if (array->max)
{
find_array_item(array, item->name, &count, &location);
if (count < 0)
{
ret = ARRAY_ITEM(array, location);
array->max--;
}
else
move_array_items(array, location, array->max, 1);
}
array->list[location] = item;
array->max++;
return ret;
}
/*
* Returns the entry that has been removed, if any.
*/
Array_item *BX_remove_from_array (Array *array, char *name)
{
int count, location = 0;
if (array->max)
{
find_array_item(array, name, &count, &location);
if (count >= 0)
return NULL;
return array_pop(array, location);
}
return NULL; /* Cant delete whats not there */
}
/* Remove the 'which'th item from the given array */
Array_item *BX_array_pop (Array *array, int which)
{
Array_item *ret = NULL;
if (which < 0 || which >= array->max)
return NULL;
ret = ARRAY_ITEM(array, which);
move_array_items(array, which + 1, array->max, -1);
array->max--;
return ret;
}
/*
* Returns the entry that has been removed, if any.
*/
Array_item *BX_remove_all_from_array (Array *array, char *name)
{
int count, location = 0;
Array_item *ret = NULL;
if (array->max)
{
find_array_item(array, name, &count, &location);
if (count == 0)
return NULL;
ret = ARRAY_ITEM(array, location);
move_array_items(array, location + 1, array->max, -1);
array->max--;
return ret;
}
return NULL; /* Cant delete whats not there */
}
Array_item *BX_array_lookup (Array *array, char *name, int wild, int delete)
{
int count, location;
if (delete)
return remove_from_array(array, name);
else
return find_array_item(array, name, &count, &location);
}
static void check_array_size (Array *array)
{
if (array->total_max == 0)
array->total_max = 6; /* Good size to start with */
else if (array->max == array->total_max-1)
array->total_max *= 2;
else if (array->max * 3 < array->total_max)
array->total_max /= 2;
else
return;
/*yell("Resizing...");*/
RESIZE(array->list, Array_item *, array->total_max);
}
/*
* Move ``start'' through ``end'' array elements ``dir'' places up
* in the array. If ``dir'' is negative, move them down in the array.
* Fill in the vacated spots with NULLs.
*/
void move_array_items (Array *array, int start, int end, int dir)
{
int i;
if (dir > 0)
{
for (i = end; i >= start; i--)
LARRAY_ITEM(array, i + dir) = ARRAY_ITEM(array, i);
for (i = dir; i > 0; i--)
LARRAY_ITEM(array, start + i - 1) = NULL;
}
else if (dir < 0)
{
for (i = start; i <= end; i++)
LARRAY_ITEM(array, i + dir) = ARRAY_ITEM(array, i);
for (i = end - dir + 1; i <= end; i++)
LARRAY_ITEM(array, i) = NULL;
}
}
/*
* This is just a generalization of the old function ``find_command''
*
* You give it an alist_item and a name, and it gives you the number of
* items that are completed by that name, and where you could find/put that
* item in the list. It returns the alist_item most appropriate.
*
* If ``cnt'' is less than -1, then there was one exact match and one or
* more ambiguous matches in addition. The exact match's location
* is put into ``loc'' and its entry is returned. The ambigous matches
* are (of course) immediately subsequent to it.
*
* If ``cnt'' is -1, then there was one exact match. Its location is
* put into ``loc'' and its entry is returned.
*
* If ``cnt'' is zero, then there were no matches for ``name'', but ``loc''
* is set to the location in the array in which you could place the
* specified name in the sorted list.
*
* If ``cnt'' is one, then there was one command that non-ambiguously
* completes ``name''
*
* If ``cnt'' is greater than one, then there was exactly ``cnt'' number
* of entries that completed ``name'', but they are all ambiguous.
* The entry that is lowest alphabetically is returned, and its
* location is put into ``loc''.
*/
Array_item *BX_find_array_item (Array *set, char *name, int *cnt, int *loc)
{
size_t len = strlen(name);
int c = 0,
pos = 0,
min,
max;
u_32int_t mask, hash;
if (set->hash == HASH_INSENSITIVE)
hash = ci_alist_hash(name, &mask);
else
hash = cs_alist_hash(name, &mask);
*cnt = 0;
if (!set->list || !set->max)
{
*loc = 0;
return NULL;
}
alist_searches++;
max = set->max - 1;
min = 0;
while (max >= min)
{
bin_ints++;
pos = (max - min) / 2 + min;
c = (hash & mask) - (ARRAY_ITEM(set, pos)->hash & mask);
if (c == 0)
break;
else if (c < 0)
max = pos - 1;
else
min = pos + 1;
}
/*
* If we can't find a symbol that qualifies, then we can just drop
* out here. This is good because a "pass" (lookup for a symbol that
* does not exist) requires only cheap integer comparisons.
*/
if (c != 0)
{
if (c > 0)
*loc = pos + 1;
else
*loc = pos;
return NULL;
}
/*
* Now we've found some symbol that has the same first four letters.
* Expand the min/max range to include all of the symbols that have
* the same first four letters...
*/
min = max = pos;
while ((min > 0) && (hash & mask) == (ARRAY_ITEM(set, min)->hash & mask))
min--, lin_ints++;
while ((max < set->max - 1) && (hash &mask) == (ARRAY_ITEM(set, max)->hash & mask))
max++, lin_ints++;
char_searches++;
/*
* Then do a full blown binary search on the smaller range
*/
while (max >= min)
{
bin_chars++;
pos = (max - min) / 2 + min;
c = set->func(name, ARRAY_ITEM(set, pos)->name, len);
if (c == 0)
break;
else if (c < 0)
max = pos - 1;
else
min = pos + 1;
}
if (c != 0)
{
if (c > 0)
*loc = pos + 1;
else
*loc = pos;
return NULL;
}
/*
* If we've gotten this far, then we've found at least
* one appropriate entry. So we set *cnt to one
*/
*cnt = 1;
/*
* So we know that 'pos' is a match. So we start 'min'
* at one less than 'pos' and walk downard until we find
* an entry that is NOT matched.
*/
min = pos - 1;
while (min >= 0 && !set->func(name, ARRAY_ITEM(set, min)->name, len))
(*cnt)++, min--, lin_chars++;
min++;
/*
* Repeat the same ordeal, except this time we walk upwards
* from 'pos' until we dont find a match.
*/
max = pos + 1;
while (max < set->max && !set->func(name, ARRAY_ITEM(set, max)->name, len))
(*cnt)++, max++, lin_chars++;
/*
* Alphabetically, the string that would be identical to
* 'name' would be the first item in a string of items that
* all began with 'name'. That is to say, if there is an
* exact match, its sitting under item 'min'. So we check
* for that and whack the count appropriately.
*/
if (strlen(ARRAY_ITEM(set, min)->name) == len)
*cnt *= -1;
/*
* Then we tell the caller where the lowest match is,
* in case they want to insert here.
*/
if (loc)
*loc = min;
/*
* Then we return the first item that matches.
*/
return ARRAY_ITEM(set, min);
}
#define FIXED_ITEM(list, pos, size) (*(Array_item *) (list + ( pos * size )))
/*
* This is useful for finding items in a fixed array (eg, those lists that
* are a simple fixed length arrays of 1st level structs.)
*
* This code is identical to find_array_item except ``list'' is a 1st
* level array instead of a 2nd level array.
*/
void * BX_find_fixed_array_item (void *list, size_t size, int howmany, char *name, int *cnt, int *loc)
{
int len = strlen(name),
min = 0,
max = howmany,
old_pos = -1,
pos,
c;
*cnt = 0;
while (1)
{
pos = (max + min) / 2;
if (pos == old_pos)
{
*loc = pos;
return NULL;
}
old_pos = pos;
c = strncmp(name, FIXED_ITEM(list, pos, size).name, len);
if (c == 0)
break;
else if (c > 0)
min = pos;
else
max = pos;
}
/*
* If we've gotten this far, then we've found at least
* one appropriate entry. So we set *cnt to one
*/
*cnt = 1;
/*
* So we know that 'pos' is a match. So we start 'min'
* at one less than 'pos' and walk downard until we find
* an entry that is NOT matched.
*/
min = pos - 1;
while (min >= 0 && !strncmp(name, FIXED_ITEM(list, min, size).name, len))
(*cnt)++, min--;
min++;
/*
* Repeat the same ordeal, except this time we walk upwards
* from 'pos' until we dont find a match.
*/
max = pos + 1;
while ((max < howmany) && !strncmp(name, FIXED_ITEM(list, max, size).name, len))
(*cnt)++, max++;
/*
* Alphabetically, the string that would be identical to
* 'name' would be the first item in a string of items that
* all began with 'name'. That is to say, if there is an
* exact match, its sitting under item 'min'. So we check
* for that and whack the count appropriately.
*/
if (strlen(FIXED_ITEM(list, min, size).name) == len)
*cnt *= -1;
/*
* Then we tell the caller where the lowest match is,
* in case they want to insert here.
*/
if (loc)
*loc = min;
/*
* Then we return the first item that matches.
*/
return (void *)&FIXED_ITEM(list, min, size);
}
syntax highlighted by Code2HTML, v. 0.9.1