*.c and *.h files. git-svn-id: svn://svn.code.sf.net/p/bitchx/code/branches/ircii-pana-1.1@3 13b04d17-f746-0410-82c6-800466cd88b0
432 lines
10 KiB
C
432 lines
10 KiB
C
/*
|
|
* 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$";
|
|
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);
|
|
}
|
|
|
|
|