needed for hat/coat - bobm utility library
Bob Mcqueer
bobm at rtech.UUCP
Sun Mar 27 04:41:19 AEST 1988
I'm not packaging this with the hat/coat archives because it's
really a separate utility library which can be used with other
things - archive it into a different directory.
-------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
# Makefile
# README
# diagnostic.c
# hash.c
# prime.c
# strstore.c
# strtok.c
# This archive created: Fri Mar 25 12:34:48 1988
export PATH; PATH=/bin:/usr/bin:$PATH
echo shar: "extracting 'Makefile'" '(550 characters)'
if test -f 'Makefile'
then
echo shar: "will not over-write existing file 'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
#
# where to put the library
#
LIBARC = $(HOME)/lib/bobm.a
# if your compiler doesn't like the initialization of a pointer with
# an array address in strstore.c, define NO_PTR_INIT
#
CFLAGS = -O
#
# if strtok() is available in your c runtime library, remove it from
# the list - this will be all SYSV, and some BSD systems like ULTRIX
#
LIBOBJS = hash.o diagnostic.o prime.o strstore.o strtok.o
#
# for SYSV, make RANLIB an effective no-op, like "ls" or "echo"
#
RANLIB = ranlib
lib : $(LIBOBJS)
ar rvu $(LIBARC) $(LIBOBJS)
$(RANLIB) $(LIBARC)
SHAR_EOF
fi
echo shar: "extracting 'README'" '(637 characters)'
if test -f 'README'
then
echo shar: "will not over-write existing file 'README'"
else
cat << \SHAR_EOF > 'README'
This is a small set of utilities for generic use, at least in bobm
generated programs:
hash.c, prime.c - manage hash tables.
strstore.c - store and free copies of strings.
diagnostic.c - produce diagnostic & fatal error messages.
read comments in the individual sources for directions on using them.
strtok.c is also contained herein. If you are SYSV, or any other system
which has that in the runtime library, remove it from the makefile.
Before you build, fill in the makefile to put the results where you want them.
hash.c and str_store.c manage dynamic memory. #define's in those files
control allocation block sizes, etc.
SHAR_EOF
fi
echo shar: "extracting 'diagnostic.c'" '(1916 characters)'
if test -f 'diagnostic.c'
then
echo shar: "will not over-write existing file 'diagnostic.c'"
else
cat << \SHAR_EOF > 'diagnostic.c'
#include <stdio.h>
/*
** generic error message routines. Diag_xxx, may be externally set by caller.
**
** possible portability problem - use of several "long" arguments to pass
** stack through to underlying printf() family routine.
*/
/*
**
** Copyright (c) 1987, Robert L. McQueer
** All Rights Reserved
**
** Permission granted for use, modification and redistribution of this
** software provided that no use is made for commercial gain without the
** written consent of the author, that all copyright notices remain intact,
** and that all changes are clearly documented. No warranty of any kind
** concerning any use which may be made of this software is offered or implied.
**
*/
char *Diag_file = ""; /* filename for use in diagnostic message */
int Diag_line = 1; /* diagnostic line number */
FILE *Diag_fp = stderr; /* output stream for messages */
char *Diag_cmd = "?"; /* command name for fatal() / usage() */
static int (*Fatcall)() = NULL;
/*
** print nonfatal diagnostic with line number from an input file. Format
** compatible with "context"
*/
diagnostic(s,a,b,c,d,e,f)
char *s;
long a,b,c,d,e,f;
{
fprintf(Diag_fp,"%s line %d: ",Diag_file,Diag_line);
fprintf(Diag_fp,s,a,b,c,d,e,f);
fprintf(Diag_fp,"\n");
}
/*
** print fatal error message and exit. May call user set cleanup routine first.
** argument list passed to fatal() will also be passed to cleanup routine.
*/
fatal (s,a,b,c,d,e,f)
char *s;
long a,b,c,d,e,f;
{
fprintf (Diag_fp,"%s: ",Diag_cmd);
fprintf (Diag_fp,s,a,b,c,d,e,f);
fprintf (Diag_fp,"\n");
if (Fatcall != NULL)
(*Fatcall) (s,a,b,c,d,e,f);
exit (1);
}
/*
** set cleanup routine for fatal() calls
*/
fat_set (fn)
int (*fn) ();
{
Fatcall = fn;
}
/*
** print usage message and exit.
*/
usage (s,a,b,c,d,e,f)
char *s;
long a,b,c,d,e,f;
{
fprintf (Diag_fp,"usage: %s ",Diag_cmd);
fprintf (Diag_fp,s,a,b,c,d,e,f);
fprintf (Diag_fp,"\n");
exit (1);
}
SHAR_EOF
fi
echo shar: "extracting 'hash.c'" '(9146 characters)'
if test -f 'hash.c'
then
echo shar: "will not over-write existing file 'hash.c'"
else
cat << \SHAR_EOF > 'hash.c'
#include <stdio.h>
/*
**
** Copyright (c) 1987, Robert L. McQueer
** All Rights Reserved
**
** Permission granted for use, modification and redistribution of this
** software provided that no use is made for commercial gain without the
** written consent of the author, that all copyright notices remain intact,
** and that all changes are clearly documented. No warranty of any kind
** concerning any use which may be made of this software is offered or implied.
**
*/
/*
** generic hash table routines.
**
** htab_init - initialize a new hash table
** htab_free - destroy a hash table, freeing associated memory
** htab_clear - remove all hash table entries
** htab_find - find entry
** htab_del - delete entry
** htab_enter - enter item into table
** htab_list - scan hash table entries.
**
** Multiple hash tables may be used. Caller may provide key comparison
** and hash routines, or use defaults.
*/
/*
** allocation: nodes are allocated starting with a block of ALLOCINIT,
** doubling the size for the next allocation as long as the size is strictly
** less than ALLOCLIMIT. If you make ALLOCLIMIT a value encountered by
** successive doubling of ALLOCINIT, that will be the final size, otherwise the
** next doubling larger.
**
** The idea of this stunt is that we don't know whether the caller is going
** to make a lot of entries, or just a few. So we start out allocating
** just a few nodes at a crack, and as the caller makes more and more
** entries, allocate bigger bunches. For memory-restrictive environments
** like MS-DOS, one could set ALLOCLIMIT low & simply pay the penalty for
** lots of malloc calls.
*/
#define ALLOCINIT 25
#define ALLOCLIMIT 800
typedef struct _htab
{
struct _htab *next;
char *key;
char *data;
} HASHTAB;
typedef struct _faddr
{
struct _faddr *next;
} FADDR;
/*
** fpool, tpool - tpool is the pool of available nodes. Every time
** a new block is allocated, one FADDR is allocated along with it.
** The address allocated is coerced into the FADDR and placed on fpool
** to facilitate freeing.
*/
typedef struct
{
HASHTAB **tab; /* hash table */
HASHTAB *tpool; /* available nodes */
HASHTAB *srch; /* current search pointer for htab_list */
FADDR *fpool; /* alloc pointers for htab_free */
int (*afail)(); /* allocation error handler */
int (*compare)(); /* comparison routine */
int (*hashfunc)(); /* hash function */
int size; /* size of table (length of tab item) */
int ablock; /* current allocation block size */
int srchidx; /* current table index for htab_list */
} CONTEXT;
extern char *malloc();
/*
** free hash table. tab is pointer returned by htab_init.
*/
htab_free(tab)
char *tab;
{
register FADDR *ptr, *next;
int i;
register CONTEXT *cb;
cb = (CONTEXT *) tab;
for (ptr = cb->fpool; ptr != NULL; ptr = next)
{
next = ptr->next;
free ((char *) ptr);
}
free (tab);
}
/*
** remove all hash table entries. Does not free memory, simply restores
** empty table, as if one had called htab_delete on all the keys. tab is
** pointer returned by htab_init.
*/
htab_clear(tab)
char *tab;
{
register CONTEXT *cb;
register HASHTAB **tptr;
register HASHTAB *nptr;
int i;
cb = (CONTEXT *) tab;
tptr = cb->tab;
for (i=0; i < cb->size; ++i,++tptr)
{
nptr = *tptr;
if (nptr == NULL)
continue;
while (nptr->next != NULL)
nptr = nptr->next;
nptr->next = cb->tpool;
cb->tpool = *tptr;
*tptr = NULL;
}
}
/*
** initialize a hash table. Returns a pointer to be used with other
** calls, NULL for failure.
**
** The hash table will be maintained as a linked list for each node,
** so any number of entries can be made to it, whatever the value for
** size (>100% density is perfectly OK).
**
** size - size of table. If hfunc is NULL, will be incremented
** up to a prime size to suit the type of hash function
** used by default.
**
** allocfail - routine to call in case of memory allocation failure.
** If NULL, allocation failure will make a call to fatal().
**
** comp - routine used to compare keys. returns 0 if equal, non-zero
** otherwise. If NULL, strcmp() will be used. Your own will
** have to be provided if your keys are something other than
** strings.
**
** hfunc - hash function. called as (*hfunc)(key, size). size argument
** may be ignored if function was written for a specific size.
** Must return an integer between 0 and size-1. If NULL, the
** default hash function is the typical "string-divided-modulo
** -table-size" algorithm.
*/
char *
htab_init(size,allocfail,comp,hfunc)
int size;
int (*allocfail)();
int (*comp)();
int (*hfunc)();
{
int def_afail();
int strcmp();
int hash();
int i;
CONTEXT *cb;
if (allocfail == NULL)
allocfail = def_afail;
if (comp == NULL)
comp = strcmp;
if (hfunc == NULL)
{
size = next_prime(size);
hfunc = hash;
}
i = sizeof(CONTEXT) + size * sizeof(HASHTAB *);
if ((cb = (CONTEXT *) malloc(i)) == NULL)
{
(*allocfail)();
return (NULL);
}
cb->afail = allocfail;
cb->compare = comp;
cb->hashfunc = hfunc;
cb->size = size;
cb->tab = (HASHTAB **)(cb+1);
for (i=0; i < cb->size; ++i)
(cb->tab)[i] = NULL;
cb->tpool = NULL;
cb->ablock = ALLOCINIT;
/* safety, in case somebody calls htab_list wrong */
cb->srch = NULL;
cb->srchidx = size;
return ((char *) cb);
}
/*
** find an entry in hash table. The pointer returned is NULL for
** failure, or the data pointer associated with the key in htab_enter.
**
** tab - table pointer returned by htab_init.
** s - search key.
*/
char *
htab_find(tab,s)
char *tab;
char *s;
{
register HASHTAB *ptr;
register CONTEXT *cb;
cb = (CONTEXT *) tab;
for (ptr = (cb->tab)[(*(cb->hashfunc))(s,cb->size)];
ptr != NULL; ptr = ptr->next)
{
if ((*(cb->compare))(s,ptr->key) == 0)
return (ptr->data);
}
return (NULL);
}
/*
** delete a hash table entry. Returns 0 for success, <0 for no entry.
**
** tab - table pointer returned by htab_init.
** s - search key.
*/
htab_del(tab,s)
char *tab;
char *s;
{
register HASHTAB *ptr;
register CONTEXT *cb;
register HASHTAB *pred;
int idx;
cb = (CONTEXT *) tab;
pred = NULL;
for (ptr = (cb->tab)[idx = (*(cb->hashfunc))(s,cb->size)];
ptr != NULL; ptr = ptr->next)
{
if ((*(cb->compare))(s,ptr->key) == 0)
break;
pred = ptr;
}
if (ptr == NULL)
return (-1);
if (pred == NULL)
(cb->tab)[idx] = ptr->next;
else
pred->next = ptr->next;
ptr->next = cb->tpool;
cb->tpool = ptr;
return (0);
}
/*
** enter new item into hash table:
**
** tab - table pointer from htab_init.
** s - key.
** data - data to associate with key. In most cases, will probably
** actually be a pointer to some sort of structure known
** to the caller.
**
** both s and data should point to storage valid for the entire life of
** the table. htab_enter can not allocate copies of either of these
** things since it does not know their structure (if you provided
** comparison & hash routines, the key may not actually be a string).
** htab_enter WILL allocate actual table nodes. Returns 0 for success,
** -1 for failure. Failure return is possible only if allocation
** failure occurs, and was not set up as fatal in htab_init().
*/
htab_enter(tab,s,data)
char *tab;
char *s;
char *data;
{
register HASHTAB *ptr;
register CONTEXT *cb;
HASHTAB *get_node();
int i;
cb = (CONTEXT *) tab;
if ((ptr = get_node(cb)) == NULL)
return (-1);
ptr->next = (cb->tab)[i = (*(cb->hashfunc))(s,cb->size)];
(cb->tab)[i] = ptr;
ptr->key = s;
ptr->data = data;
return (0);
}
/*
** Routine to scan all hash table entries through successive calls.
** Returns 1 if an entry found, 0 for no more entries. Will not
** be returned in any particular order comprehensible to the
** calling program (hash table order).
**
** tab - table pointer from htab_init
** first - 1 to start scan, 0 on succesive calls.
** data, key - returned data and key.
*/
htab_list(tab,first,data,key)
char *tab;
int first;
char **data;
char **key;
{
register CONTEXT *cb;
cb = (CONTEXT *) tab;
if (first)
{
cb->srch = NULL;
cb->srchidx = -1;
}
while (cb->srch == NULL)
{
++(cb->srchidx);
if (cb->srchidx >= cb->size)
return (0);
cb->srch = (cb->tab)[cb->srchidx];
}
*data = (cb->srch)->data;
*key = (cb->srch)->key;
cb->srch = (cb->srch)->next;
return(1);
}
static HASHTAB *
get_node(cb)
CONTEXT *cb;
{
char *addr;
HASHTAB *ptr;
int i;
if (cb->tpool == NULL)
{
addr = malloc((cb->ablock)*sizeof(HASHTAB)+sizeof(FADDR));
if (addr == NULL)
{
(*(cb->afail))();
return (NULL);
}
((FADDR *) addr)->next = cb->fpool;
cb->fpool = (FADDR *) addr;
addr += sizeof(FADDR);
cb->tpool = (HASHTAB *) addr;
for (i=1; i < cb->ablock; ++i)
(cb->tpool)[i-1].next = cb->tpool + i;
(cb->tpool)[i-1].next = NULL;
if (cb->ablock < ALLOCLIMIT)
cb->ablock *= 2;
}
ptr = cb->tpool;
cb->tpool = (cb->tpool)->next;
return (ptr);
}
static int
hash(s,size)
register char *s;
int size;
{
register long rem;
for (rem = *s; *s != '\0'; ++s)
rem = (rem * 128 + *s) % size;
return(rem);
}
static int
def_afail()
{
fatal("memory allocation failure in hash table routines");
}
SHAR_EOF
fi
echo shar: "extracting 'prime.c'" '(1566 characters)'
if test -f 'prime.c'
then
echo shar: "will not over-write existing file 'prime.c'"
else
cat << \SHAR_EOF > 'prime.c'
/*
**
** Copyright (c) 1987, Robert L. McQueer
** All Rights Reserved
**
** Permission granted for use, modification and redistribution of this
** software provided that no use is made for commercial gain without the
** written consent of the author, that all copyright notices remain intact,
** and that all changes are clearly documented. No warranty of any kind
** concerning any use which may be made of this software is offered or implied.
**
*/
/* return smallest prime >= i */
int
next_prime(i)
int i;
{
if (i <= 2)
return (2);
if ((i%2) == 0)
++i;
while (! is_prime(i))
i += 2;
return (i);
}
/*
** simply check all factors <= the square root of the number, with
** a minor wrinkle:
**
** we split our checks into two separate chains which cover all
** numbers with no factors of 2 or 3, avoiding many of the non-
** prime factors. factor1 winds up being all integers = 5 mod 6,
** factor2 all integers >= 7 which = 1 mod 6. Anything = 0,2,3 or
** 4 mod 6 divides by 2 or 3.
**
** this gives a rather small number of redundant factor checks for
** reasonable sized arguments (say < 10000). Only for extremely large
** numbers would the extra overhead justify a "smarter" algorithm.
**
** only valid for i >= 2.
*/
int
is_prime(i)
int i;
{
int factor1,factor2;
if (i == 2 || i == 3)
return(1);
if ((i%3) == 0 || (i%2) == 0)
return(0);
factor1 = 5;
factor2 = 7;
while ((factor1 * factor1) <= i)
{
if ((i % factor1) == 0)
return (0);
if ((i % factor2) == 0)
return (0);
factor1 += 6;
factor2 += 6;
}
return (1);
}
SHAR_EOF
fi
echo shar: "extracting 'strstore.c'" '(7542 characters)'
if test -f 'strstore.c'
then
echo shar: "will not over-write existing file 'strstore.c'"
else
cat << \SHAR_EOF > 'strstore.c'
#include <stdio.h>
/*
**
** Copyright (c) 1987, Robert L. McQueer
** All Rights Reserved
**
** Permission granted for use, modification and redistribution of this
** software provided that no use is made for commercial gain without the
** written consent of the author, that all copyright notices remain intact,
** and that all changes are clearly documented. No warranty of any kind
** concerning any use which may be made of this software is offered or implied.
**
*/
/*
** string storage routines.
**
** str_store - return an allocated copy of a string
** str_free - free all the strings
** str_cnew - make a new context block for separate group of strings
** str_ccur - return the current context block
** str_cset - set the context block
** str_cfree - free a context block
** str_afail - set allocation failure routine
**
** Callers who simply need to make a single group of "permanent" strings
** for the life of their process need only call str_store, without worrying
** about context pointers. This will probably be suitable for a lot of
** applications. The other routines may be used to create separate groups
** of strings which may be released individually. The burden on callers
** to keep track of current context in these cases is traded off against
** the simplicity for the other case.
**
** The intent of these routines is to "micro-allocate" strings into a
** large block of storage, saving malloc() headers. If used exclusively
** to store long strings, it might be inefficient.
*/
char *malloc();
/* actual malloc'ed block will be CH_BLOCK + sizeof(CHAIN) */
#define CH_BLOCK (4096 - sizeof(CHAIN))
#define MAGICNUM 0x525
typedef struct _chain
{
struct _chain *next;
int avail;
char *store;
} CHAIN;
typedef struct
{
int magic;
CHAIN *flist;
} CONTEXT;
static CONTEXT Cb_def[1] =
{
{ MAGICNUM, NULL }
};
/*
** NO_PTR_INIT may be defined if the compiler barfs on attempts
** to initialize a pointer with an array name. If defined, extra
** checks will be made at routine entry to do the initialization
** first time through
*/
#ifdef NO_PTR_INIT
static CONTEXT *Cb = NULL;
#else
static CONTEXT *Cb = Cb_def;
#endif
static def_afail()
{
fatal ("memory allocation failure in string storage");
}
static int (*Afail)() = def_afail;
/*
** str_store: return an allocated copy of a string.
**
** s - the string to make a copy of. If NULL, an empty string
** will be returned.
**
** NOTE: these strings may not be individually freed. This routine
** is intended to save memory used for alloc headers by returning
** pointers into a large blocks of allocated memory.
**
** Will return NULL for allocation failure if a non-fatal failure
** routine has been defined (see str_afail). The default failure
** routine calls "fatal" with an error message. If the failure
** routine does not return, a NULL return from this routine is
** impossible.
*/
char *
str_store(s)
char *s;
{
int len, av, idx;
char *rptr;
CHAIN *fp;
#ifdef NO_PTR_INIT
if (Cb == NULL)
Cb = Cb_def;
#endif
if (s == NULL)
s = "";
len = strlen(s) + 1;
/* should return inside loop */
for (idx = 0; idx < 2; ++idx)
{
for (fp = Cb->flist; fp != NULL; fp = fp->next)
{
if (fp->avail >= len)
{
strcpy ((rptr = fp->store),s);
fp->store += len;
fp->avail -= len;
return (rptr);
}
}
/* alloc new block, let it find it on next iteration */
if (len > CH_BLOCK)
av = len;
else
av = CH_BLOCK;
if ((rptr = malloc(av + sizeof(CHAIN))) == NULL)
{
(*Afail)();
return (NULL);
}
fp = (CHAIN *) rptr;
fp->next = Cb->flist;
Cb->flist = (CHAIN *) fp;
fp->store = rptr + sizeof(CHAIN);
fp->avail = av;
}
/* we're screwed up */
fatal("str_store: BAD craziness");
return(NULL);
}
/*
** str_free:
**
** Free all the strings allocated with str_store. All of those
** pointers will no longer be valid.
**
** If str_cnew / str_cset have been used, this call frees the strings
** in the current context block.
**
** str_store calls may still be made after this - you're simply
** starting over.
*/
str_free()
{
CHAIN *ptr;
#ifdef NO_PTR_INIT
if (Cb == NULL)
Cb = Cb_def;
#endif
for ( ; Cb->flist != NULL; Cb->flist = ptr)
{
ptr = (Cb->flist)->next;
free ((char *) Cb->flist);
}
}
/*
** str_cnew:
**
** Make a new context block for str_store()
**
** A pointer returned from this routine or str_ccur() is the ONLY
** valid argument for str_cset.
**
** In effect what you are doing is declaring a new "pool" for all
** str_store() calls, probably so you can use str_free() to release
** groups of strings selectively.
**
** NOTE: you MUST call str_cset() to actually use this new pool.
**
** You MUST save this pointer to be able to add more strings to
** or free the pool. Any number of str_cnew calls may be made,
** allowing the caller to have as many "pools" of strings as
** desired. It is up to the caller to keep track of the context
** pointers, and which context block is currently in use.
**
** NULL will be returned for failure to allocate a new context block.
** This return is only possible if a non-fatal allocation failure
** routine has been defined.
*/
char *
str_cnew()
{
CONTEXT *ctx;
/*
** this is an inefficient use of malloc, but presumably callers
** aren't going to define large numbers of context blocks
*/
if ((ctx = (CONTEXT *) malloc(sizeof(CONTEXT))) == NULL)
{
(*Afail)();
return (NULL);
}
ctx->magic = MAGICNUM;
ctx->flist = NULL;
return ((char *) ctx);
}
/*
** str_ccur:
**
** return pointer to context in current use, presumably so
** you can use str_cset to switch back to it later.
*/
char *
str_ccur()
{
#ifdef NO_PTR_INIT
if (Cb == NULL)
Cb = Cb_def;
#endif
return ((char *) Cb);
}
/*
** str_cset:
**
** Set str_store() to a new context block. The ONLY
** legitimate argument for this routine is an address returned
** from a previous str_cnew() or str_ccur().
**
** All old strings are still valid. Only str_free returns any
** storage.
**
** You may recover the default context prior to any str_cset calls
** by setting NULL
*/
str_cset(ptr)
char *ptr;
{
if (ptr == NULL)
Cb = Cb_def;
else
Cb = (CONTEXT *) ptr;
if (Cb->magic != MAGICNUM)
fatal("bad context pointer in str_cset");
}
/*
** the ONLY legal argument to this routine is pointer returned from
** str_cnew. This routine may be used to indicate that no more strings
** are to be allocated on that context block, and the pointer will no
** longer be a legal argument to str_cset. Note that the actual
** strings are still allocated, giving you a way to close a pool
** while retaining the strings. If you want to free BOTH the actual
** string storage and the pool, you must use str_free first, then
** switch context, so that this block is not the current context.
**
** -1 returned for errors - attempts to free the current block or
** the default block.
**
** Although the current implementation makes ptr a legal address for
** free(), callers should come through this routine instead, to
** allow that to change.
*/
str_cfree(ptr)
char *ptr;
{
if (ptr == (char *) Cb_def || ptr == (char *) Cb)
return (-1);
if (((CONTEXT *) ptr)->magic != MAGICNUM)
fatal("bad context pointer in str_cfree");
free(ptr);
return(0);
}
/*
** str_afail:
**
** define the routine to be called in the event of an allocation
** failure. By default, fatal() will be called with an error message.
** You may reset the default by using NULL.
*/
str_afail(func)
int (*func)();
{
if (func == NULL)
Afail = def_afail;
Afail = func;
}
SHAR_EOF
fi
echo shar: "extracting 'strtok.c'" '(894 characters)'
if test -f 'strtok.c'
then
echo shar: "will not over-write existing file 'strtok.c'"
else
cat << \SHAR_EOF > 'strtok.c'
#include <stdio.h>
/*
** strtok() is a routine present in SYSV and some BSD runtime libraries.
** Use this if it isn't in yours.
*/
static char *Save=NULL;
char *index ();
static char *first_ch (), *last_ch();
char *
strtok(str,delim)
char *str, *delim;
{
register char *tokstart, *tokend;
if (str != NULL)
Save = str;
if (Save == NULL)
return (NULL);
tokstart = first_ch (Save, delim);
tokend = last_ch (tokstart, delim);
Save = first_ch (tokend, delim);
*tokend = '\0';
if (*tokstart == '\0')
return (NULL);
return (tokstart);
}
static char *
first_ch (str,delim)
char *str;
register char *delim;
{
register char *f;
for (f = str; *f != '\0' && index(delim,*f) != NULL; ++f)
;
return (f);
}
static char *
last_ch (str,delim)
char *str;
register char *delim;
{
register char *f;
for (f = str; *f != '\0' && index(delim,*f) == NULL; ++f)
;
return (f);
}
SHAR_EOF
fi
exit 0
# End of shell archive
More information about the Alt.sources
mailing list