/* * Copyright (c) 1999-2003 Smithsonian Astrophysical Observatory */ /* * word.c -- token parser, pattern matcher, macro expander, * and other word-related routines * */ #include #include #include #include /* SAOMOD_CTYPE -- work around Slackware/RedHat incompatibility */ #ifdef linux #ifdef isalnum #undef isalnum #define isalnum(c) (isalpha(c)||isdigit(c)) #endif #endif /* ************************************************************************** * * * PRIVATE ROUTINES AND DATA * * * **************************************************************************/ /* word */ #define MAXDELIM 256 #define MAXDTABLES 1024 #define BUFINC 5000 static char lastd; static char dtable[MAXDELIM]; static char *dtables[MAXDTABLES]; static int ndtable=0; /* tmatch */ #define ALL '*' #define ANY '?' #define RANGE '[' #define ENDRANGE ']' #define RANGEDELIM '-' #define NOTRANGE '~' /* *---------------------------------------------------------------------------- * * Routine: checkrange (from tmatch) * * Purpose: see if character is in specified range * * Returns: 1 if in range, otherwise 0 * *---------------------------------------------------------------------------- */ #ifdef ANSI_FUNC static int checkrange (char *xtemplate, int *ptr, int c) #else static int checkrange(xtemplate, ptr, c) char *xtemplate; int *ptr; int c; #endif { int inrange, notrange; char lorange, hirange; int tptr; tptr = *ptr; /* make sure we have a close bracket */ if( strchr(&xtemplate[tptr], ENDRANGE) == NULL ) return(0); /* check for negation - match if not in range */ if( xtemplate [tptr+1] == NOTRANGE ){ notrange = 1; tptr++; } else notrange = 0; /* start pessimistically */ inrange = 0; /* point past RANGE character */ tptr++; while( xtemplate[tptr] != ENDRANGE ){ /* get lo range */ lorange = xtemplate[tptr]; /* and hi range */ tptr++; if( xtemplate[tptr] != RANGEDELIM ) hirange = lorange; else{ tptr++;hirange = xtemplate[tptr];tptr++; } if( (c>=lorange) && (c<=hirange) ){ inrange = 1; break; } } /* only exclusive OR of inrange and notrange is ok */ if( (inrange ^ notrange) ==0 ) return(0); else{ *ptr = strchr(&xtemplate[tptr],']') - xtemplate + 1; return(1); } } /* *---------------------------------------------------------------------------- * * Routine: addstring (from macro) * * Purpose: add a string to a buffer * * Returns: none * *---------------------------------------------------------------------------- */ #ifdef ANSI_FUNC static void addstring (char **buf, int *blen, int *maxlen, char *str) #else static void addstring(buf, blen, maxlen, str) char **buf; int *blen; int *maxlen; char *str; #endif { int slen; slen = strlen(str) + 1; while( (*blen + slen) >= *maxlen ){ *maxlen += BUFINC; *buf = (char *)xrealloc(*buf, *maxlen); } strcat(*buf, str); *blen += slen; } /* *---------------------------------------------------------------------------- * * Routine: addchar (from macro) * * Purpose: add a single char to a buffer * * Returns: none * *---------------------------------------------------------------------------- */ #ifdef ANSI_FUNC static void addchar (char **buf, int *blen, int *maxlen, int c) #else static void addchar(buf, blen, maxlen, c) char **buf; int *blen; int *maxlen; int c; #endif { char tbuf[2]; tbuf[0] = c; tbuf[1] = '\0'; addstring(buf, blen, maxlen, tbuf); } /* *---------------------------------------------------------------------------- * * Routine: lookupkeywords (from macro) * * Purpose: lookup a name in a list of keywords * * Returns: return the associated value or NULL * *---------------------------------------------------------------------------- */ #ifdef ANSI_FUNC static char * lookupkeywords (char *name, char **keyword, char **value, int nkey) #else static char *lookupkeywords(name, keyword, value, nkey) char *name; char **keyword; char **value; int nkey; #endif { int i; for(i=0; i0) ){ if( lbuf[ip] == '\0' ){ *lptr = ip; return(0); } else ip++; } /* check for an explicit quote */ quotes = '\0'; if( lbuf[ip] == '"' ){ quotes = '"'; lastd = '"'; } if( lbuf[ip] == '\'' ){ quotes = '\''; lastd = '\''; } /* grab next token */ if( quotes != '\0' ){ /* bump past quotes */ ip++; /* grab up to next quotes -- but skip escaped quotes */ for(i=0; lbuf[ip] != '\0'; i++, ip++){ if( (lbuf[ip] == quotes) && (lbuf[ip-1] != '\\') ) break; else tbuf[i] = lbuf[ip]; } } else{ /* grab up to next whitespace */ for(i=0; lbuf[ip] && !isspace((int)lbuf[ip]) && (dtable[(int)lbuf[ip]]==0); i++, ip++) tbuf[i] = lbuf[ip]; /* save this delimiter */ lastd = lbuf[ip]; } /* bump past delimiter (but not null terminator) */ if( lbuf[ip] ) ip++; /* null terminate */ tbuf[i] = '\0'; /* got something */ *lptr = ip; return(1); } /* *---------------------------------------------------------------------------- * * Routine: newdtable * * Purpose: save the current delim table and init a new one * * Returns: 1 if another delim table can be allocated, 0 otherwise * *---------------------------------------------------------------------------- */ #ifdef ANSI_FUNC int newdtable (char *s) #else int newdtable(s) char *s; #endif { int i; char *cur; if( ndtable >= MAXDTABLES ){ fprintf(stderr, "ERROR: no more delimiter tables available\n"); return(0); } /* save another dtable */ ndtable++; /* allocate new space for this table */ dtables[ndtable-1] = (char *)xmalloc(MAXDELIM); cur = dtables[ndtable-1]; /* copy and zero the old table */ for(i=0; i 0 ) dtable[(int)*s] -= 1; } else{ for(i=0; i 0 ) dtable[i] -= 1; } } /* *---------------------------------------------------------------------------- * * Routine: lastdelim * * Purpose: return the last delimiter * * Returns: delim character * *---------------------------------------------------------------------------- */ #ifdef ANSI_FUNC int lastdelim (void) #else int lastdelim() #endif { return((int)lastd); } /* *---------------------------------------------------------------------------- * * Routine: tmatch * * Purpose: match string to a template * * the legal meta characters in a template are just like the * C-shell meta characters, i.e: * ? match any character, but there must be one * * match anything, or nothing * [...] match an inclusive set * * * Returns: non-zero if match, zero otherwise * *---------------------------------------------------------------------------- */ #ifdef ANSI_FUNC int tmatch (char *string, char *xtemplate) #else int tmatch(string, xtemplate) char *string; char *xtemplate; #endif { char *lastmeta=0; char *nonabsorbed=0; int sptr=0; int tptr=0; /* loop through string and template */ while( (xtemplate[tptr] != '\0') || (string[sptr] != '\0') ){ /* if exact match, just bump both pointers */ if( string[sptr] == xtemplate[tptr] ){ sptr++; tptr++; continue; } /* if range character, check ranges */ if( xtemplate[tptr] == RANGE ){ if( checkrange(xtemplate, &tptr, string[sptr]) == 0 ){ /* no match - was there a meta character before */ if( lastmeta == 0 ) return(0); /* if so, back up to it and try again */ xtemplate = lastmeta; tptr=0; /* begin checking at the non-absorbed point */ string = nonabsorbed; sptr=0; continue; } /* got a match, so bump past */ else{ sptr++; continue; } } /* if ANY, any character if fine, but there must be one */ if( xtemplate[tptr] == ANY ){ if( string[sptr] == '\0' ) return(0); else{ sptr++; tptr++; continue; } } /* if ALL, we can match anything */ if( xtemplate[tptr] == ALL ){ /* remember where the * is */ lastmeta = &xtemplate[tptr]; tptr++; /* no more template after this means a win */ if( xtemplate[tptr] == '\0' ) return(1); /* if the next template char is not a meta, we skip up to its match in the string */ if( xtemplate[tptr] == RANGE){ while( checkrange(xtemplate, &tptr, string[sptr]) == 0 ){ /* missing the next template char */ if( string[sptr] == '\0' ) return(0); sptr++; } /* remember the first non-absorbed character */ nonabsorbed = &string[sptr];nonabsorbed++; sptr++; continue; } /* skip past characters, if next template char is not a meta */ else if( xtemplate[tptr] != ANY && xtemplate[tptr] != ALL ){ while(string[sptr] != xtemplate[tptr]){ /* not finding the next template char is bad */ if( string[sptr] == '\0' ) return(0); sptr++; } /* remember the first non-absorbed character */ nonabsorbed = &string[sptr];nonabsorbed++; continue; } else{ /* remember the first non-absorbed character */ nonabsorbed = &string[sptr];nonabsorbed++; continue; } } /* no match, no meta char - see if we once had a meta */ else{ if( lastmeta == 0 ) return(0); /* if so, back up to it and try again */ xtemplate = lastmeta; tptr=0; /* begin checking at the non-absorbed point */ string = nonabsorbed; sptr=0; continue; } } /* matched to the nulls - we win */ return(1); } /* *---------------------------------------------------------------------------- * * Routine: keyword * * Purpose: look for a keyword= string inside another string, * remove and return the in another buffer * * Returns: len if keyword was found, 0 otherwise * * NB: ibuf cannot be static, as it is modified in place * *---------------------------------------------------------------------------- */ #ifdef ANSI_FUNC int keyword (char *ibuf, char *key, char *obuf, int maxlen) #else int keyword(ibuf, key, obuf, maxlen) char *ibuf; char *key; char *obuf; int maxlen; #endif { int len; int qlev; char *s; char *t; char *u; char *v; char *iptr; char quote; /* if we have no input string, we are done */ if( (ibuf == NULL) || (*ibuf == '\0') ){ return(0); } /* start out pessimistically */ *obuf = '\0'; iptr = ibuf; /* maxlen generally is 1 more than we can handle */ maxlen--; /* keep trying */ while( *iptr ){ /* look for key from current position */ if( (s = (char *)strstr(iptr, key)) == NULL ) return(0); /* if we found a key, we need to make sure ... */ /* it must be preceeded by beginning of string, beginning of bracket, or by a "," from previous keyword */ if( (s == ibuf) || (*(s-1) == ',') || (*(s-1) == '[') ){ /* it can be followed by spaces ... */ t = s + strlen(key); while( isspace((int)*t) ) t++; /* but must be followed by an "=" */ if( *t == '=' ){ t++; /* skip spaces again */ while( isspace((int)*t) ) t++; /* this is where the actual value part of the string begins */ u = t; /* this will be where it ends */ v = t; /* gather up everything to the next "," or end of filter */ if( (*t == '"') || (*t == '\'') || (*t == '(') || (*t == '[') ){ switch(*t){ case '"': case '\'': quote = *t; break; case '(': quote = ')'; break; case '[': quote = ']'; break; } /* bump past opening quote char */ t++; u++; v++; while( *t && (*t != quote) ){ t++; v++; } if( *t == quote ){ t++; } } else{ qlev = 0; while( *t && ((qlev != 0) || (*t != ',')) && ((qlev != 0) || (*t != ']')) ){ if( *t == '[' ) qlev++; else if( *t == ']' ) qlev--; t++; v++; } } len = MIN(maxlen, v - u); strncpy(obuf, u, len); obuf[len] = '\0'; /* remove keyword=value string from the original buffer */ /* first remove preceding comma, if necessary */ if( (s > ibuf) && (*(s-1) == ',') ) s--; /* but leave 1 comma in place */ else if( *t == ',' ) t++; /* now overwrite original from where the keyword started */ memmove(s, t, strlen(t)+1); return(len); } } /* start next search just past this one */ iptr = s+1; } return(0); } /* *---------------------------------------------------------------------------- * * Routine: macro * * Purpose: expand a macro using a client's callback * * Returns: expanded macro as an allocated string * *---------------------------------------------------------------------------- */ #ifdef ANSI_FUNC char * macro (char *icmd, char **keyword, char **value, int nkey, MacroCB client_callback, void *client_data) #else char *macro(icmd, keyword, value, nkey, client_callback, client_data) char *icmd; char **keyword; char **value; int nkey; MacroCB client_callback; void *client_data; #endif { int i, j; int maxlen; char brace; char *result; char tbuf[1000]; char tbuf1[1000]; char *s; char *ip; char *mip; /* make a new string using the command as a base, but substituting for "$" values as needed */ result = (char *)xmalloc(BUFINC+1); maxlen = BUFINC; *result = '\0'; for(i=0, ip=icmd; *ip; ip++){ if( *ip != '$' ){ addchar(&result, &i, &maxlen, *ip); } else{ /* save beginning of macro */ mip = ip; /* skip past '$' */ ip++; /* check for brace mode */ if( *ip == '{' ){ brace = '{'; ip++; } else if( *ip == '(' ){ brace = '('; ip++; } else brace = '\0'; /* get variable up to next non-alpha character or close brace */ for(*tbuf='\0', j=0; *ip; ip++ ){ /* if we are in brace mode, look for trailing brace */ if( brace && *ip == (brace == '(' ? ')' : '}') ){ ip++; break; } /* else look for a non-alpha character */ else if( !isalnum((int)*ip) && *ip != '_'){ break; } else{ tbuf[j++] = *ip; tbuf[j] = '\0'; } } /* back up so the outer loop adds this delimiting char to the output */ ip--; /* search for keyword from the list */ if( (nkey > 0) && (s=lookupkeywords(tbuf, keyword, value, nkey)) != NULL ){ addstring(&result, &i, &maxlen, s); } /* execute the client routine to expand macros */ else if( (client_callback != NULL) && ((s=(*client_callback)(tbuf, client_data)) != NULL) ){ addstring(&result, &i, &maxlen, s); } /* look for an environment variable */ else if( (s = (char *)getenv(tbuf)) != NULL ){ addstring(&result, &i, &maxlen, s); } /* if we don't recognize this macro, put it back onto the string */ else{ int len; len = ip - mip + 1; strncpy(tbuf1, mip, len); tbuf1[len] = '\0'; addstring(&result, &i, &maxlen, tbuf1); } } } /* null terminate and save the string */ result[i] = '\0'; result = (char *)xrealloc(result, i+1); return(result); } /* ************************************************************************** * * misc word routines * * **************************************************************************/ /* *---------------------------------------------------------------------------- * * Routine: cluc * * Purpose: convert lower to upper case string in place * * Returns: none * *---------------------------------------------------------------------------- */ #ifdef ANSI_FUNC void cluc (char *s) #else void cluc(s) char *s; #endif { while(*s){ if( islower((int)*s) ) *s = toupper(*s); s++; } } /* *---------------------------------------------------------------------------- * * Routine: culc * * Purpose: convert upper to lower case string in place * * Returns: * *---------------------------------------------------------------------------- */ #ifdef ANSI_FUNC void culc (char *s) #else void culc(s) char *s; #endif { while(*s){ if( isupper((int)*s) ) *s = tolower(*s); s++; } } /* *---------------------------------------------------------------------------- * * Routine: nowhite * * Purpose: removes all beginning and ending white space from string * * Returns: returns the number of characters * *---------------------------------------------------------------------------- */ #ifdef ANSI_FUNC int nowhite ( char *c, /* buffer to be cleaned */ char *cr /* buffer for returned string */ ) #else int nowhite(c,cr) char *c; /* buffer to be cleaned */ char *cr; /* buffer for returned string */ #endif { char *cr0; /* initial value of cr */ int n; /* the number of characters */ /* skip leading white space */ while(*c && isspace((int)*c)) c++; /* copy up to the null */ cr0 = cr; while(*c) *cr++ = *c++; n = cr - cr0; /* the number of characters */ *cr-- = '\0'; /* Null and point to the last character */ /* remove trailing white space */ while( n && isspace((int)*cr) ){ *cr-- = '\0'; n--; } return(n); } /* *---------------------------------------------------------------------------- * * Routine: nocr * * Purpose: remove trailing from a string (in place) * * Returns: none * *---------------------------------------------------------------------------- */ #ifdef ANSI_FUNC void nocr (char *s) #else void nocr(s) char *s; #endif { int len; if( (s==NULL) || (*s=='\0') ) return; len = strlen(s); if( s[len-1] == '\n' ) s[len-1] = '\0'; } /* *---------------------------------------------------------------------------- * * Routine: istrue * * Purpose: check if a string is "true" or "yes" or "on" * * Returns: 1 if true string, 0 otherwise * *---------------------------------------------------------------------------- */ #ifdef ANSI_FUNC int istrue (char *s) #else int istrue(s) char *s; #endif { char *t; int result; if( (s==NULL) || (*s=='\0') ) return(0); t = (char *)xmalloc(strlen(s)+1); nowhite(s, t); culc(t); result = (!strcmp(t, "true") || !strcmp(t, "yes") || !strcmp(t, "on") || !strcmp(t, "1") ); xfree(t); return(result); } /* *---------------------------------------------------------------------------- * * Routine: isfalse * * Purpose: check if a string is "false" or "no" or "off" * * Returns: 1 if false string, 0 otherwise * *---------------------------------------------------------------------------- */ #ifdef ANSI_FUNC int isfalse (char *s) #else int isfalse(s) char *s; #endif { char *t; int result; if( (s==NULL) || (*s=='\0') ) return(0); t = (char *)xmalloc(strlen(s)+1); nowhite(s, t); culc(t); result = (!strcmp(t, "false") || !strcmp(t, "no") || !strcmp(t, "off") || !strcmp(t, "0") ); xfree(t); return(result); } /* *---------------------------------------------------------------------------- * * Routine: strtoul16 * * Purpose: convert a string to an unsigned long hex value * * Returns: converted hex value (end of converted string is in t) * *---------------------------------------------------------------------------- */ #ifdef ANSI_FUNC unsigned long strtoul16 (char *s, char **t) #else unsigned long strtoul16(s, t) char *s; char **t; #endif { unsigned long v=0; int h; while ( *s != ' ' && *s != '\n' && *s != '\r' && *s != '\0' ){ v *= 16; if( (h = hexval(*s)) >= 0 ){ v += h; s++; } else{ break; } } if( t != NULL ) *t = s; return(v); }