4.4BSD/usr/src/contrib/rcs-V5.6/src/partime.c

Compare this file to the similar file:
Show the results in this format:

/*
 * PARTIME		parse date/time string into a TM structure
 *
 * Returns:
 *	0 if parsing failed
 *	else time values in specified TM structure and zone (unspecified values
 *		set to TMNULL)
 * Notes:
 *	This code is quasi-public; it may be used freely in like software.
 *	It is not to be sold, nor used in licensed software without
 *	permission of the author.
 *	For everyone's benefit, please report bugs and improvements!
 * 	Copyright 1980 by Ken Harrenstien, SRI International.
 *	(ARPANET: KLH @ SRI)
 */

/* Hacknotes:
 *	If parsing changed so that no backup needed, could perhaps modify
 *		to use a FILE input stream.  Need terminator, though.
 *	Perhaps should return 0 on success, else a non-zero error val?
 */

/* $Log: partime.c,v $
 * Revision 5.6  1991/08/19  03:13:55  eggert
 * Update timezones.
 *
 * Revision 5.5  1991/04/21  11:58:18  eggert
 * Don't put , just before } in initializer.
 *
 * Revision 5.4  1990/10/04  06:30:15  eggert
 * Remove date vs time heuristics that fail between 2000 and 2400.
 * Check for overflow when lexing an integer.
 * Parse 'Jan 10 LT' as 'Jan 10, LT', not 'Jan, 10 LT'.
 *
 * Revision 5.3  1990/09/24  18:56:31  eggert
 * Update timezones.
 *
 * Revision 5.2  1990/09/04  08:02:16  eggert
 * Don't parse two-digit years, because it won't work after 1999/12/31.
 * Don't permit 'Aug Aug'.
 *
 * Revision 5.1  1990/08/29  07:13:49  eggert
 * Be able to parse our own date format.  Don't assume year<10000.
 *
 * Revision 5.0  1990/08/22  08:12:40  eggert
 * Switch to GMT and fix the bugs exposed thereby.  Update timezones.
 * Ansify and Posixate.  Fix peekahead and int-size bugs.
 *
 * Revision 1.4  89/05/01  14:48:46  narten
 * fixed #ifdef DEBUG construct
 * 
 * Revision 1.3  88/08/28  14:53:40  eggert
 * Remove unportable "#endif XXX"s.
 * 
 * Revision 1.2  87/03/27  14:21:53  jenkins
 * Port to suns
 * 
 * Revision 1.1  82/05/06  11:38:26  wft
 * Initial revision
 * 
 */

#include "rcsbase.h"

libId(partId, "$Id: partime.c,v 5.6 1991/08/19 03:13:55 eggert Exp $")

#define given(v) (0 <= (v))
#define TMNULL (-1) /* Items not given are given this value */
#define TZ_OFFSET (24*60) /* TMNULL  <  zone_offset - TZ_OFFSET */

struct tmwent {
	char const *went;
	short wval;
	char wflgs;
	char wtype;
};
	/* wflgs */
#define TWTIME 02	/* Word is a time value (absence implies date) */
#define TWDST  04	/* Word is a DST-type timezone */
	/* wtype */
#define TM_MON	1	/* month name */
#define TM_WDAY	2	/* weekday name */
#define TM_ZON	3	/* time zone name */
#define TM_LT	4	/* local time */
#define TM_DST	5	/* daylight savings time */
#define TM_12	6	/* AM, PM, NOON, or MIDNIGHT */
	/* wval (for wtype==TM_12) */
#define T12_AM 1
#define T12_PM 2
#define T12_NOON 12
#define T12_MIDNIGHT 0

static struct tmwent const tmwords [] = {
	{"january",      0, 0, TM_MON},
	{"february",     1, 0, TM_MON},
	{"march",        2, 0, TM_MON},
	{"april",        3, 0, TM_MON},
	{"may",          4, 0, TM_MON},
	{"june",         5, 0, TM_MON},
	{"july",         6, 0, TM_MON},
	{"august",       7, 0, TM_MON},
	{"september",    8, 0, TM_MON},
	{"october",      9, 0, TM_MON},
	{"november",     10, 0, TM_MON},
	{"december",     11, 0, TM_MON},

	{"sunday",       0, 0, TM_WDAY},
	{"monday",       1, 0, TM_WDAY},
	{"tuesday",      2, 0, TM_WDAY},
	{"wednesday",    3, 0, TM_WDAY},
	{"thursday",     4, 0, TM_WDAY},
	{"friday",       5, 0, TM_WDAY},
	{"saturday",     6, 0, TM_WDAY},

	{"gmt",          0*60, TWTIME, TM_ZON},   /* Greenwich */
	{"utc",          0*60, TWTIME, TM_ZON},
	{"ut",           0*60, TWTIME, TM_ZON},
	{"cut",		 0*60, TWTIME, TM_ZON},

	{"nzst",        -12*60, TWTIME, TM_ZON},  /* New Zealand */
	{"jst",         -9*60, TWTIME, TM_ZON},   /* Japan */
	{"kst",         -9*60, TWTIME, TM_ZON},   /* Korea */
	{"ist",         -5*60-30, TWTIME, TM_ZON},/* India */
	{"eet",         -2*60, TWTIME, TM_ZON},   /* Eastern Europe */
	{"cet",         -1*60, TWTIME, TM_ZON},   /* Central Europe */
	{"met",         -1*60, TWTIME, TM_ZON},   /* Middle Europe */
	{"wet",          0*60, TWTIME, TM_ZON},   /* Western Europe */
	{"nst",          3*60+30, TWTIME, TM_ZON},/* Newfoundland */
	{"ast",          4*60, TWTIME, TM_ZON},   /* Atlantic */
	{"est",          5*60, TWTIME, TM_ZON},   /* Eastern */
	{"cst",          6*60, TWTIME, TM_ZON},   /* Central */
	{"mst",          7*60, TWTIME, TM_ZON},   /* Mountain */
	{"pst",          8*60, TWTIME, TM_ZON},   /* Pacific */
	{"akst",         9*60, TWTIME, TM_ZON},   /* Alaska */
	{"hast",         10*60, TWTIME, TM_ZON},  /* Hawaii-Aleutian */
	{"hst",          10*60, TWTIME, TM_ZON},  /* Hawaii */
	{"sst",          11*60, TWTIME, TM_ZON},  /* Samoa */

	{"nzdt",        -12*60, TWTIME+TWDST, TM_ZON},    /* New Zealand */
	{"kdt",         -9*60, TWTIME+TWDST, TM_ZON},     /* Korea */
	{"bst",          0*60, TWTIME+TWDST, TM_ZON},     /* Britain */
	{"ndt",		 3*60+30, TWTIME+TWDST, TM_ZON},  /* Newfoundland */
	{"adt",          4*60, TWTIME+TWDST, TM_ZON},     /* Atlantic */
	{"edt",          5*60, TWTIME+TWDST, TM_ZON},     /* Eastern */
	{"cdt",          6*60, TWTIME+TWDST, TM_ZON},     /* Central */
	{"mdt",          7*60, TWTIME+TWDST, TM_ZON},     /* Mountain */
	{"pdt",          8*60, TWTIME+TWDST, TM_ZON},     /* Pacific */
	{"akdt",         9*60, TWTIME+TWDST, TM_ZON},     /* Alaska */
	{"hadt",         10*60, TWTIME+TWDST, TM_ZON},    /* Hawaii-Aleutian */

#if 0
	/*
	 * The following names are duplicates or are not well attested.
	 * A standard is needed.
	 */
	{"east",        -10*60, TWTIME, TM_ZON},  /* Eastern Australia */
	{"cast",        -9*60-30, TWTIME, TM_ZON},/* Central Australia */
	{"cst",         -8*60, TWTIME, TM_ZON},   /* China */
	{"hkt",         -8*60, TWTIME, TM_ZON},   /* Hong Kong */
	{"sst",         -8*60, TWTIME, TM_ZON},   /* Singapore */
	{"wast",        -8*60, TWTIME, TM_ZON},   /* Western Australia */
	{"?",		-6*60-30, TWTIME, TM_ZON},/* Burma */
	{"?",           -4*60-30, TWTIME, TM_ZON},/* Afghanistan */
	{"it",          -3*60-30, TWTIME, TM_ZON},/* Iran */
	{"ist",         -2*60, TWTIME, TM_ZON},   /* Israel */
	{"mez",		-1*60, TWTIME, TM_ZON},   /* Mittel-Europaeische Zeit */
	{"ast",          1*60, TWTIME, TM_ZON},   /* Azores */
	{"fst",          2*60, TWTIME, TM_ZON},   /* Fernando de Noronha */
	{"bst",          3*60, TWTIME, TM_ZON},   /* Brazil */
	{"wst",          4*60, TWTIME, TM_ZON},   /* Western Brazil */
	{"ast",          5*60, TWTIME, TM_ZON},   /* Acre Brazil */
	{"?",            9*60+30, TWTIME, TM_ZON},/* Marquesas */
	{"?",		 12*60, TWTIME, TM_ZON},  /* Kwajalein */

	{"eadt",        -10*60, TWTIME+TWDST, TM_ZON},    /* Eastern Australia */
	{"cadt",        -9*60-30, TWTIME+TWDST, TM_ZON},  /* Central Australia */
	{"cdt",         -8*60, TWTIME+TWDST, TM_ZON},     /* China */
	{"wadt",        -8*60, TWTIME+TWDST, TM_ZON},     /* Western Australia */
	{"idt",         -2*60, TWTIME+TWDST, TM_ZON},     /* Israel */
	{"eest",        -2*60, TWTIME+TWDST, TM_ZON},     /* Eastern Europe */
	{"cest",        -1*60, TWTIME+TWDST, TM_ZON},     /* Central Europe */
	{"mest",        -1*60, TWTIME+TWDST, TM_ZON},     /* Middle Europe */
	{"mesz",	-1*60, TWTIME+TWDST, TM_ZON},	  /* Mittel-Europaeische Sommerzeit */
	{"west",         0*60, TWTIME+TWDST, TM_ZON},     /* Western Europe */
	{"adt",          1*60, TWTIME+TWDST, TM_ZON},	  /* Azores */
	{"fdt",          2*60, TWTIME+TWDST, TM_ZON},     /* Fernando de Noronha */
	{"edt",          3*60, TWTIME+TWDST, TM_ZON},     /* Eastern Brazil */
	{"wdt",          4*60, TWTIME+TWDST, TM_ZON},     /* Western Brazil */
	{"adt",          5*60, TWTIME+TWDST, TM_ZON},     /* Acre Brazil */
#endif

	{"lt",           0, TWTIME, TM_LT},       /* local time */
	{"dst",          1*60, TWTIME, TM_DST},      /* daylight savings time */
	{"ddst",         2*60, TWTIME, TM_DST},      /* double dst */

	{"am",           T12_AM,	TWTIME, TM_12},
	{"pm",           T12_PM,	TWTIME, TM_12},
	{"noon",         T12_NOON,	TWTIME, TM_12},
	{"midnight",     T12_MIDNIGHT,	TWTIME, TM_12},

	{0, 0, 0, 0}	/* Zero entry to terminate searches */
};

struct token {
	char const *tcp;/* pointer to string */
	int tcnt;	/* # chars */
	char tbrk;	/* "break" char */
	char tbrkl;	/* last break char */
	char tflg;	/* 0 = alpha, 1 = numeric */
	union {         /* Resulting value; */
		int tnum;/* either a #, or */
		struct tmwent const *ttmw;/* a ptr to a tmwent.  */
	} tval;
};

static struct tmwent const*ptmatchstr P((char const*,int,struct tmwent const*));
static int pt12hack P((struct tm *,int));
static int ptitoken P((struct token *));
static int ptstash P((int *,int));
static int pttoken P((struct token *));

	static int
goodzone(t, offset, am)
	register struct token const *t;
	int offset;
	int *am;
{
	register int m;
	if (
		t->tflg  &&
		t->tcnt == 4+offset  &&
		(m = t->tval.tnum) <= 2400  &&
		isdigit(t->tcp[offset]) &&
		(m%=100) < 60
	) {
		m += t->tval.tnum/100 * 60;
		if (t->tcp[offset-1]=='+')
			m = -m;
		*am = m;
		return 1;
	}
	return 0;
}

    int
partime(astr, atm, zone)
char const *astr;
register struct tm *atm;
int *zone;
{
    register int i;
    struct token btoken, atoken;
    int zone_offset; /* minutes west of GMT, plus TZ_OFFSET */
    register char const *cp;
    register char ch;
    int ord, midnoon;
    int *atmfield, dst, m;
    int got1 = 0;

    atm->tm_sec = TMNULL;
    atm->tm_min = TMNULL;
    atm->tm_hour = TMNULL;
    atm->tm_mday = TMNULL;
    atm->tm_mon = TMNULL;
    atm->tm_year = TMNULL;
    atm->tm_wday = TMNULL;
    atm->tm_yday = TMNULL;
    midnoon = TMNULL;		/* and our own temp stuff */
    zone_offset = TMNULL;
    dst = TMNULL;
    btoken.tcnt = btoken.tbrk = 0;
    btoken.tcp = astr;

    for (;; got1=1) {
	if (!ptitoken(&btoken))				/* Get a token */
	  {     if(btoken.tval.tnum) return(0);         /* Read error? */
		if (given(midnoon))			/* EOF, wrap up */
			if (!pt12hack(atm, midnoon))
				return 0;
		if (!given(atm->tm_min))
			atm->tm_min = 0;
		*zone  =
				(given(zone_offset) ? zone_offset-TZ_OFFSET : 0)
			-	(given(dst) ? dst : 0);
		return got1;
	  }
	if(btoken.tflg == 0)		/* Alpha? */
	  {     i = btoken.tval.ttmw->wval;
		switch (btoken.tval.ttmw->wtype) {
		  default:
			return 0;
		  case TM_MON:
			atmfield = &atm->tm_mon;
			break;
		  case TM_WDAY:
			atmfield = &atm->tm_wday;
			break;
		  case TM_DST:
			atmfield = &dst;
			break;
		  case TM_LT:
			if (ptstash(&dst, 0))
				return 0;
			i = 48*60; /* local time magic number -- see maketime() */
			/* fall into */
		  case TM_ZON:
			i += TZ_OFFSET;
			if (btoken.tval.ttmw->wflgs & TWDST)
				if (ptstash(&dst, 60))
					return 0;
			/* Peek ahead for offset immediately afterwards. */
			if (
			    (btoken.tbrk=='-' || btoken.tbrk=='+') &&
			    (atoken=btoken, ++atoken.tcnt, ptitoken(&atoken)) &&
			    goodzone(&atoken, 0, &m)
			) {
				i += m;
				btoken = atoken;
			}
			atmfield = &zone_offset;
			break;
		  case TM_12:
			atmfield = &midnoon;
		}
		if (ptstash(atmfield, i))
			return(0);		/* ERR: val already set */
		continue;
	  }

	/* Token is number.  Lots of hairy heuristics. */
	if (!isdigit(*btoken.tcp)) {
		if (!goodzone(&btoken, 1, &m))
			return 0;
		zone_offset = TZ_OFFSET + m;
		continue;
	}

	i = btoken.tval.tnum;   /* Value now known to be valid; get it. */
	if (btoken.tcnt == 3)	/*  3 digits = HMM   */
	  {
hhmm4:		if (ptstash(&atm->tm_min, i%100))
			return(0);		/* ERR: min conflict */
		i /= 100;
hh2:            if (ptstash(&atm->tm_hour, i))
			return(0);		/* ERR: hour conflict */
		continue;
	  }

	if (4 < btoken.tcnt)
		goto year4; /* far in the future */
	if(btoken.tcnt == 4)	/* 4 digits = YEAR or HHMM */
	  {	if (given(atm->tm_year)) goto hhmm4;	/* Already got yr? */
		if (given(atm->tm_hour)) goto year4;	/* Already got hr? */
		if(btoken.tbrk == ':')			/* HHMM:SS ? */
			if ( ptstash(&atm->tm_hour, i/100)
			  || ptstash(&atm->tm_min, i%100))
				return(0);		/* ERR: hr/min clash */
			else goto coltm2;		/* Go handle SS */
		if(btoken.tbrk != ',' && btoken.tbrk != '/'
		  && (atoken=btoken, ptitoken(&atoken))	/* Peek */
		  && ( atoken.tflg
		     ? !isdigit(*atoken.tcp)
		     : atoken.tval.ttmw->wflgs & TWTIME)) /* HHMM-ZON */
			goto hhmm4;
		goto year4;			/* Give up, assume year. */
	  }

	/* From this point on, assume tcnt == 1 or 2 */
	/* 2 digits = MM, DD, or HH (MM and SS caught at coltime) */
	if(btoken.tbrk == ':')		/* HH:MM[:SS] */
		goto coltime;		/*  must be part of time. */
	if (31 < i)
		return 0;

	/* Check for numerical-format date */
	for (cp = "/-."; ch = *cp++;)
	  {	ord = (ch == '.' ? 0 : 1);	/* n/m = D/M or M/D */
		if(btoken.tbrk == ch)			/* "NN-" */
		  {	if(btoken.tbrkl != ch)
			  {
				atoken = btoken;
				atoken.tcnt++;
				if (ptitoken(&atoken)
				  && atoken.tflg == 0
				  && atoken.tval.ttmw->wtype == TM_MON)
					goto dd2;
				if(ord)goto mm2; else goto dd2; /* "NN-" */
			  }				/* "-NN-" */
			if (!given(atm->tm_mday)
			  && given(atm->tm_year))	/* If "YYYY-NN-" */
				goto mm2;		/* then always MM */
			if(ord)goto dd2; else goto mm2;
		  }
		if(btoken.tbrkl == ch			/* "-NN" */
		  && given(ord ? atm->tm_mon : atm->tm_mday))
			if (!given(ord ? atm->tm_mday : atm->tm_mon)) /* MM/DD */
				if(ord)goto dd2; else goto mm2;
	  }

	/* Now reduced to choice between HH and DD */
	if (given(atm->tm_hour)) goto dd2;	/* Have hour? Assume day. */
	if (given(atm->tm_mday)) goto hh2;	/* Have day? Assume hour. */
	if (given(atm->tm_mon)) goto dd2;	/* Have month? Assume day. */
	if(i > 24) goto dd2;			/* Impossible HH means DD */
	atoken = btoken;
	if (!ptitoken(&atoken))			/* Read ahead! */
		if(atoken.tval.tnum) return(0); /* ERR: bad token */
		else goto dd2;			/* EOF, assume day. */
	if ( atoken.tflg
	   ? !isdigit(*atoken.tcp)
	   : atoken.tval.ttmw->wflgs & TWTIME)
		/* If next token is a time spec, assume hour */
		goto hh2;		/* e.g. "3 PM", "11-EDT"  */

dd2:	if (ptstash(&atm->tm_mday, i))	/* Store day (1 based) */
		return(0);
	continue;

mm2:	if (ptstash(&atm->tm_mon, i-1))	/* Store month (make zero based) */
		return(0);
	continue;

year4:	if ((i-=1900) < 0  ||  ptstash(&atm->tm_year, i)) /* Store year-1900 */
		return(0);		/* ERR: year conflict */
	continue;

	/* Hack HH:MM[[:]SS] */
coltime:
	if (ptstash(&atm->tm_hour, i)) return 0;
	if (!ptitoken(&btoken))
		return(!btoken.tval.tnum);
	if(!btoken.tflg) return(0);	/* ERR: HH:<alpha> */
	if(btoken.tcnt == 4)		/* MMSS */
		if (ptstash(&atm->tm_min, btoken.tval.tnum/100)
		  || ptstash(&atm->tm_sec, btoken.tval.tnum%100))
			return(0);
		else continue;
	if(btoken.tcnt != 2
	  || ptstash(&atm->tm_min, btoken.tval.tnum))
		return(0);		/* ERR: MM bad */
	if (btoken.tbrk != ':') continue;	/* Seconds follow? */
coltm2:	if (!ptitoken(&btoken))
		return(!btoken.tval.tnum);
	if(!btoken.tflg || btoken.tcnt != 2	/* Verify SS */
	  || ptstash(&atm->tm_sec, btoken.tval.tnum))
		return(0);		/* ERR: SS bad */
    }
}

/* Store date/time value, return 0 if successful.
 * Fail if entry is already set.
 */
	static int
ptstash(adr,val)
int *adr;
int val;
{	register int *a;
	if (given(*(a=adr)))
		return 1;
	*a = val;
	return(0);
}

/* This subroutine is invoked for AM, PM, NOON and MIDNIGHT when wrapping up
 * just prior to returning from partime.
 */
	static int
pt12hack(tm, aval)
register struct tm *tm;
register int aval;
{	register int h = tm->tm_hour;
	switch (aval) {
	  case T12_AM:
	  case T12_PM:
		if (h > 12)
			return 0;
		if (h == 12)
			tm->tm_hour = 0;
		if (aval == T12_PM)
			tm->tm_hour += 12;
		break;
	  default:
		if (0 < tm->tm_min  ||  0 < tm->tm_sec)
			return 0;
		if (!given(h) || h==12)
			tm->tm_hour = aval;
		else if (aval==T12_MIDNIGHT  &&  (h==0 || h==24))
			return 0;
	}
	return 1;
}

/* Get a token and identify it to some degree.
 * Returns 0 on failure; token.tval will be 0 for normal EOF, otherwise
 * hit error of some sort
 */

	static int
ptitoken(tkp)
register struct token *tkp;
{
	register char const *cp;
	register int i, j, k;

	if (!pttoken(tkp))
#ifdef DEBUG
	    {
		VOID printf("EOF\n");
		return(0);
	    }
#else
		return(0);
#endif	
	cp = tkp->tcp;

#ifdef DEBUG
	VOID printf("Token: \"%.*s\" ", tkp->tcnt, cp);
#endif

	if (tkp->tflg) {
		i = tkp->tcnt;
		if (*cp == '+' || *cp == '-') {
			cp++;
			i--;
		}
		while (0 <= --i) {
			j = tkp->tval.tnum*10;
			k = j + (*cp++ - '0');
			if (j/10 != tkp->tval.tnum  ||  k < j) {
				/* arithmetic overflow */
				tkp->tval.tnum = 1;
				return 0;
			}
			tkp->tval.tnum = k;
		}
	} else if (!(tkp->tval.ttmw  =  ptmatchstr(cp, tkp->tcnt, tmwords)))
	  {
#ifdef DEBUG
		VOID printf("Not found!\n");
#endif
		tkp->tval.tnum = 1;
		return 0;
	  }

#ifdef DEBUG
	if(tkp->tflg)
		VOID printf("Val: %d.\n",tkp->tval.tnum);
	else VOID printf("Found: \"%s\", val: %d, type %d\n",
		tkp->tval.ttmw->went,tkp->tval.ttmw->wval,tkp->tval.ttmw->wtype);
#endif

	return(1);
}

/* Read token from input string into token structure */
	static int
pttoken(tkp)
register struct token *tkp;
{
	register char const *cp;
	register int c;
	char const *astr;

	tkp->tcp = astr = cp = tkp->tcp + tkp->tcnt;
	tkp->tbrkl = tkp->tbrk;		/* Set "last break" */
	tkp->tcnt = tkp->tbrk = tkp->tflg = 0;
	tkp->tval.tnum = 0;

	while(c = *cp++)
	  {	switch(c)
		  {	case ' ': case '\t':	/* Flush all whitespace */
			case '\r': case '\n':
			case '\v': case '\f':
				if (!tkp->tcnt) {	/* If no token yet */
					tkp->tcp = cp;	/* ignore the brk */
					continue;	/* and go on. */
				}
				/* fall into */
			case '(': case ')':	/* Perhaps any non-alphanum */
			case '-': case ',':	/* shd qualify as break? */
			case '+':
			case '/': case ':': case '.':	/* Break chars */
				if(tkp->tcnt == 0)	/* If no token yet */
				  {	tkp->tcp = cp;	/* ignore the brk */
					tkp->tbrkl = c;
				  	continue;	/* and go on. */
				  }
				tkp->tbrk = c;
				return(tkp->tcnt);
		  }
		if (!tkp->tcnt++) {		/* If first char of token, */
			if (isdigit(c)) {
				tkp->tflg = 1;
				if (astr<cp-2 && (cp[-2]=='-'||cp[-2]=='+')) {
					/* timezone is break+sign+digit */
					tkp->tcp--;
					tkp->tcnt++;
				}
			}
		} else if ((isdigit(c)!=0) != tkp->tflg) { /* else check type */
			tkp->tbrk = c;
			return --tkp->tcnt;	/* Wrong type, back up */
		}
	  }
	return(tkp->tcnt);		/* When hit EOF */
}


	static struct tmwent const *
ptmatchstr(astr,cnt,astruc)
	char const *astr;
	int cnt;
	struct tmwent const *astruc;
{
	register char const *cp, *mp;
	register int c;
	struct tmwent const *lastptr;
	int i;

	lastptr = 0;
	for(;mp = astruc->went; astruc += 1)
	  {	cp = astr;
		for(i = cnt; i > 0; i--)
		  {
			switch (*cp++ - (c = *mp++))
			  {	case 0: continue;	/* Exact match */
				case 'A'-'a':
				    if (ctab[c] == Letter)
					continue;
			  }
			break;
		  }
		if(i==0)
			if (!*mp) return astruc;	/* Exact match */
			else if(lastptr) return(0);	/* Ambiguous */
			else lastptr = astruc;		/* 1st ambig */
	  }
	return lastptr;
}