xref: /freebsd/contrib/one-true-awk/lib.c (revision 884a2a699669ec61e2366e3e358342dbc94be24a)
1 /****************************************************************
2 Copyright (C) Lucent Technologies 1997
3 All Rights Reserved
4 
5 Permission to use, copy, modify, and distribute this software and
6 its documentation for any purpose and without fee is hereby
7 granted, provided that the above copyright notice appear in all
8 copies and that both that the copyright notice and this
9 permission notice and warranty disclaimer appear in supporting
10 documentation, and that the name Lucent Technologies or any of
11 its entities not be used in advertising or publicity pertaining
12 to distribution of the software without specific, written prior
13 permission.
14 
15 LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
16 INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
17 IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
18 SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
20 IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
21 ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
22 THIS SOFTWARE.
23 ****************************************************************/
24 
25 #define DEBUG
26 #include <stdio.h>
27 #include <string.h>
28 #include <ctype.h>
29 #include <errno.h>
30 #include <stdlib.h>
31 #include <stdarg.h>
32 #include "awk.h"
33 #include "ytab.h"
34 
35 FILE	*infile	= NULL;
36 char	*file	= "";
37 char	*record;
38 int	recsize	= RECSIZE;
39 char	*fields;
40 int	fieldssize = RECSIZE;
41 
42 Cell	**fldtab;	/* pointers to Cells */
43 char	inputFS[100] = " ";
44 
45 #define	MAXFLD	2
46 int	nfields	= MAXFLD;	/* last allocated slot for $i */
47 
48 int	donefld;	/* 1 = implies rec broken into fields */
49 int	donerec;	/* 1 = record is valid (no flds have changed) */
50 
51 int	lastfld	= 0;	/* last used field */
52 int	argno	= 1;	/* current input argument number */
53 extern	Awkfloat *ARGC;
54 
55 static Cell dollar0 = { OCELL, CFLD, NULL, "", 0.0, REC|STR|DONTFREE };
56 static Cell dollar1 = { OCELL, CFLD, NULL, "", 0.0, FLD|STR|DONTFREE };
57 
58 void recinit(unsigned int n)
59 {
60 	if ( (record = (char *) malloc(n)) == NULL
61 	  || (fields = (char *) malloc(n+1)) == NULL
62 	  || (fldtab = (Cell **) malloc((nfields+1) * sizeof(Cell *))) == NULL
63 	  || (fldtab[0] = (Cell *) malloc(sizeof(Cell))) == NULL )
64 		FATAL("out of space for $0 and fields");
65 	*fldtab[0] = dollar0;
66 	fldtab[0]->sval = record;
67 	fldtab[0]->nval = tostring("0");
68 	makefields(1, nfields);
69 }
70 
71 void makefields(int n1, int n2)		/* create $n1..$n2 inclusive */
72 {
73 	char temp[50];
74 	int i;
75 
76 	for (i = n1; i <= n2; i++) {
77 		fldtab[i] = (Cell *) malloc(sizeof (struct Cell));
78 		if (fldtab[i] == NULL)
79 			FATAL("out of space in makefields %d", i);
80 		*fldtab[i] = dollar1;
81 		sprintf(temp, "%d", i);
82 		fldtab[i]->nval = tostring(temp);
83 	}
84 }
85 
86 void initgetrec(void)
87 {
88 	int i;
89 	char *p;
90 
91 	for (i = 1; i < *ARGC; i++) {
92 		if (!isclvar(p = getargv(i))) {	/* find 1st real filename */
93 			setsval(lookup("FILENAME", symtab), getargv(i));
94 			return;
95 		}
96 		setclvar(p);	/* a commandline assignment before filename */
97 		argno++;
98 	}
99 	infile = stdin;		/* no filenames, so use stdin */
100 }
101 
102 static int firsttime = 1;
103 
104 int getrec(char **pbuf, int *pbufsize, int isrecord)	/* get next input record */
105 {			/* note: cares whether buf == record */
106 	int c;
107 	char *buf = *pbuf;
108 	uschar saveb0;
109 	int bufsize = *pbufsize, savebufsize = bufsize;
110 
111 	if (firsttime) {
112 		firsttime = 0;
113 		initgetrec();
114 	}
115 	   dprintf( ("RS=<%s>, FS=<%s>, ARGC=%g, FILENAME=%s\n",
116 		*RS, *FS, *ARGC, *FILENAME) );
117 	if (isrecord) {
118 		donefld = 0;
119 		donerec = 1;
120 	}
121 	saveb0 = buf[0];
122 	buf[0] = 0;
123 	while (argno < *ARGC || infile == stdin) {
124 		   dprintf( ("argno=%d, file=|%s|\n", argno, file) );
125 		if (infile == NULL) {	/* have to open a new file */
126 			file = getargv(argno);
127 			if (*file == '\0') {	/* it's been zapped */
128 				argno++;
129 				continue;
130 			}
131 			if (isclvar(file)) {	/* a var=value arg */
132 				setclvar(file);
133 				argno++;
134 				continue;
135 			}
136 			*FILENAME = file;
137 			   dprintf( ("opening file %s\n", file) );
138 			if (*file == '-' && *(file+1) == '\0')
139 				infile = stdin;
140 			else if ((infile = fopen(file, "r")) == NULL)
141 				FATAL("can't open file %s", file);
142 			setfval(fnrloc, 0.0);
143 		}
144 		c = readrec(&buf, &bufsize, infile);
145 		if (c != 0 || buf[0] != '\0') {	/* normal record */
146 			if (isrecord) {
147 				if (freeable(fldtab[0]))
148 					xfree(fldtab[0]->sval);
149 				fldtab[0]->sval = buf;	/* buf == record */
150 				fldtab[0]->tval = REC | STR | DONTFREE;
151 				if (is_number(fldtab[0]->sval)) {
152 					fldtab[0]->fval = atof(fldtab[0]->sval);
153 					fldtab[0]->tval |= NUM;
154 				}
155 			}
156 			setfval(nrloc, nrloc->fval+1);
157 			setfval(fnrloc, fnrloc->fval+1);
158 			*pbuf = buf;
159 			*pbufsize = bufsize;
160 			return 1;
161 		}
162 		/* EOF arrived on this file; set up next */
163 		if (infile != stdin)
164 			fclose(infile);
165 		infile = NULL;
166 		argno++;
167 	}
168 	buf[0] = saveb0;
169 	*pbuf = buf;
170 	*pbufsize = savebufsize;
171 	return 0;	/* true end of file */
172 }
173 
174 void nextfile(void)
175 {
176 	if (infile != NULL && infile != stdin)
177 		fclose(infile);
178 	infile = NULL;
179 	argno++;
180 }
181 
182 int readrec(char **pbuf, int *pbufsize, FILE *inf)	/* read one record into buf */
183 {
184 	int sep, c;
185 	char *rr, *buf = *pbuf;
186 	int bufsize = *pbufsize;
187 
188 	if (strlen(*FS) >= sizeof(inputFS))
189 		FATAL("field separator %.10s... is too long", *FS);
190 	strcpy(inputFS, *FS);	/* for subsequent field splitting */
191 	if ((sep = **RS) == 0) {
192 		sep = '\n';
193 		while ((c=getc(inf)) == '\n' && c != EOF)	/* skip leading \n's */
194 			;
195 		if (c != EOF)
196 			ungetc(c, inf);
197 	}
198 	for (rr = buf; ; ) {
199 		for (; (c=getc(inf)) != sep && c != EOF; ) {
200 			if (rr-buf+1 > bufsize)
201 				if (!adjbuf(&buf, &bufsize, 1+rr-buf, recsize, &rr, "readrec 1"))
202 					FATAL("input record `%.30s...' too long", buf);
203 			*rr++ = c;
204 		}
205 		if (**RS == sep || c == EOF)
206 			break;
207 		if ((c = getc(inf)) == '\n' || c == EOF) /* 2 in a row */
208 			break;
209 		if (!adjbuf(&buf, &bufsize, 2+rr-buf, recsize, &rr, "readrec 2"))
210 			FATAL("input record `%.30s...' too long", buf);
211 		*rr++ = '\n';
212 		*rr++ = c;
213 	}
214 	if (!adjbuf(&buf, &bufsize, 1+rr-buf, recsize, &rr, "readrec 3"))
215 		FATAL("input record `%.30s...' too long", buf);
216 	*rr = 0;
217 	   dprintf( ("readrec saw <%s>, returns %d\n", buf, c == EOF && rr == buf ? 0 : 1) );
218 	*pbuf = buf;
219 	*pbufsize = bufsize;
220 	return c == EOF && rr == buf ? 0 : 1;
221 }
222 
223 char *getargv(int n)	/* get ARGV[n] */
224 {
225 	Cell *x;
226 	char *s, temp[50];
227 	extern Array *ARGVtab;
228 
229 	sprintf(temp, "%d", n);
230 	x = setsymtab(temp, "", 0.0, STR, ARGVtab);
231 	s = getsval(x);
232 	   dprintf( ("getargv(%d) returns |%s|\n", n, s) );
233 	return s;
234 }
235 
236 void setclvar(char *s)	/* set var=value from s */
237 {
238 	char *p;
239 	Cell *q;
240 
241 	for (p=s; *p != '='; p++)
242 		;
243 	*p++ = 0;
244 	p = qstring(p, '\0');
245 	q = setsymtab(s, p, 0.0, STR, symtab);
246 	setsval(q, p);
247 	if (is_number(q->sval)) {
248 		q->fval = atof(q->sval);
249 		q->tval |= NUM;
250 	}
251 	   dprintf( ("command line set %s to |%s|\n", s, p) );
252 }
253 
254 
255 void fldbld(void)	/* create fields from current record */
256 {
257 	/* this relies on having fields[] the same length as $0 */
258 	/* the fields are all stored in this one array with \0's */
259 	/* possibly with a final trailing \0 not associated with any field */
260 	char *r, *fr, sep;
261 	Cell *p;
262 	int i, j, n;
263 
264 	if (donefld)
265 		return;
266 	if (!isstr(fldtab[0]))
267 		getsval(fldtab[0]);
268 	r = fldtab[0]->sval;
269 	n = strlen(r);
270 	if (n > fieldssize) {
271 		xfree(fields);
272 		if ((fields = (char *) malloc(n+2)) == NULL) /* possibly 2 final \0s */
273 			FATAL("out of space for fields in fldbld %d", n);
274 		fieldssize = n;
275 	}
276 	fr = fields;
277 	i = 0;	/* number of fields accumulated here */
278 	strcpy(inputFS, *FS);
279 	if (strlen(inputFS) > 1) {	/* it's a regular expression */
280 		i = refldbld(r, inputFS);
281 	} else if ((sep = *inputFS) == ' ') {	/* default whitespace */
282 		for (i = 0; ; ) {
283 			while (*r == ' ' || *r == '\t' || *r == '\n')
284 				r++;
285 			if (*r == 0)
286 				break;
287 			i++;
288 			if (i > nfields)
289 				growfldtab(i);
290 			if (freeable(fldtab[i]))
291 				xfree(fldtab[i]->sval);
292 			fldtab[i]->sval = fr;
293 			fldtab[i]->tval = FLD | STR | DONTFREE;
294 			do
295 				*fr++ = *r++;
296 			while (*r != ' ' && *r != '\t' && *r != '\n' && *r != '\0');
297 			*fr++ = 0;
298 		}
299 		*fr = 0;
300 	} else if ((sep = *inputFS) == 0) {		/* new: FS="" => 1 char/field */
301 		for (i = 0; *r != 0; r++) {
302 			char buf[2];
303 			i++;
304 			if (i > nfields)
305 				growfldtab(i);
306 			if (freeable(fldtab[i]))
307 				xfree(fldtab[i]->sval);
308 			buf[0] = *r;
309 			buf[1] = 0;
310 			fldtab[i]->sval = tostring(buf);
311 			fldtab[i]->tval = FLD | STR;
312 		}
313 		*fr = 0;
314 	} else if (*r != 0) {	/* if 0, it's a null field */
315 		/* subtlecase : if length(FS) == 1 && length(RS > 0)
316 		 * \n is NOT a field separator (cf awk book 61,84).
317 		 * this variable is tested in the inner while loop.
318 		 */
319 		int rtest = '\n';  /* normal case */
320 		if (strlen(*RS) > 0)
321 			rtest = '\0';
322 		for (;;) {
323 			i++;
324 			if (i > nfields)
325 				growfldtab(i);
326 			if (freeable(fldtab[i]))
327 				xfree(fldtab[i]->sval);
328 			fldtab[i]->sval = fr;
329 			fldtab[i]->tval = FLD | STR | DONTFREE;
330 			while (*r != sep && *r != rtest && *r != '\0')	/* \n is always a separator */
331 				*fr++ = *r++;
332 			*fr++ = 0;
333 			if (*r++ == 0)
334 				break;
335 		}
336 		*fr = 0;
337 	}
338 	if (i > nfields)
339 		FATAL("record `%.30s...' has too many fields; can't happen", r);
340 	cleanfld(i+1, lastfld);	/* clean out junk from previous record */
341 	lastfld = i;
342 	donefld = 1;
343 	for (j = 1; j <= lastfld; j++) {
344 		p = fldtab[j];
345 		if(is_number(p->sval)) {
346 			p->fval = atof(p->sval);
347 			p->tval |= NUM;
348 		}
349 	}
350 	setfval(nfloc, (Awkfloat) lastfld);
351 	if (dbg) {
352 		for (j = 0; j <= lastfld; j++) {
353 			p = fldtab[j];
354 			printf("field %d (%s): |%s|\n", j, p->nval, p->sval);
355 		}
356 	}
357 }
358 
359 void cleanfld(int n1, int n2)	/* clean out fields n1 .. n2 inclusive */
360 {				/* nvals remain intact */
361 	Cell *p;
362 	int i;
363 
364 	for (i = n1; i <= n2; i++) {
365 		p = fldtab[i];
366 		if (freeable(p))
367 			xfree(p->sval);
368 		p->sval = "";
369 		p->tval = FLD | STR | DONTFREE;
370 	}
371 }
372 
373 void newfld(int n)	/* add field n after end of existing lastfld */
374 {
375 	if (n > nfields)
376 		growfldtab(n);
377 	cleanfld(lastfld+1, n);
378 	lastfld = n;
379 	setfval(nfloc, (Awkfloat) n);
380 }
381 
382 Cell *fieldadr(int n)	/* get nth field */
383 {
384 	if (n < 0)
385 		FATAL("trying to access out of range field %d", n);
386 	if (n > nfields)	/* fields after NF are empty */
387 		growfldtab(n);	/* but does not increase NF */
388 	return(fldtab[n]);
389 }
390 
391 void growfldtab(int n)	/* make new fields up to at least $n */
392 {
393 	int nf = 2 * nfields;
394 	size_t s;
395 
396 	if (n > nf)
397 		nf = n;
398 	s = (nf+1) * (sizeof (struct Cell *));  /* freebsd: how much do we need? */
399 	if (s / sizeof(struct Cell *) - 1 == nf) /* didn't overflow */
400 		fldtab = (Cell **) realloc(fldtab, s);
401 	else					/* overflow sizeof int */
402 		xfree(fldtab);	/* make it null */
403 	if (fldtab == NULL)
404 		FATAL("out of space creating %d fields", nf);
405 	makefields(nfields+1, nf);
406 	nfields = nf;
407 }
408 
409 int refldbld(const char *rec, const char *fs)	/* build fields from reg expr in FS */
410 {
411 	/* this relies on having fields[] the same length as $0 */
412 	/* the fields are all stored in this one array with \0's */
413 	char *fr;
414 	int i, tempstat, n;
415 	fa *pfa;
416 
417 	n = strlen(rec);
418 	if (n > fieldssize) {
419 		xfree(fields);
420 		if ((fields = (char *) malloc(n+1)) == NULL)
421 			FATAL("out of space for fields in refldbld %d", n);
422 		fieldssize = n;
423 	}
424 	fr = fields;
425 	*fr = '\0';
426 	if (*rec == '\0')
427 		return 0;
428 	pfa = makedfa(fs, 1);
429 	   dprintf( ("into refldbld, rec = <%s>, pat = <%s>\n", rec, fs) );
430 	tempstat = pfa->initstat;
431 	for (i = 1; ; i++) {
432 		if (i > nfields)
433 			growfldtab(i);
434 		if (freeable(fldtab[i]))
435 			xfree(fldtab[i]->sval);
436 		fldtab[i]->tval = FLD | STR | DONTFREE;
437 		fldtab[i]->sval = fr;
438 		   dprintf( ("refldbld: i=%d\n", i) );
439 		if (nematch(pfa, rec)) {
440 			pfa->initstat = 2;	/* horrible coupling to b.c */
441 			   dprintf( ("match %s (%d chars)\n", patbeg, patlen) );
442 			strncpy(fr, rec, patbeg-rec);
443 			fr += patbeg - rec + 1;
444 			*(fr-1) = '\0';
445 			rec = patbeg + patlen;
446 		} else {
447 			   dprintf( ("no match %s\n", rec) );
448 			strcpy(fr, rec);
449 			pfa->initstat = tempstat;
450 			break;
451 		}
452 	}
453 	return i;
454 }
455 
456 void recbld(void)	/* create $0 from $1..$NF if necessary */
457 {
458 	int i;
459 	char *r, *p;
460 
461 	if (donerec == 1)
462 		return;
463 	r = record;
464 	for (i = 1; i <= *NF; i++) {
465 		p = getsval(fldtab[i]);
466 		if (!adjbuf(&record, &recsize, 1+strlen(p)+r-record, recsize, &r, "recbld 1"))
467 			FATAL("created $0 `%.30s...' too long", record);
468 		while ((*r = *p++) != 0)
469 			r++;
470 		if (i < *NF) {
471 			if (!adjbuf(&record, &recsize, 2+strlen(*OFS)+r-record, recsize, &r, "recbld 2"))
472 				FATAL("created $0 `%.30s...' too long", record);
473 			for (p = *OFS; (*r = *p++) != 0; )
474 				r++;
475 		}
476 	}
477 	if (!adjbuf(&record, &recsize, 2+r-record, recsize, &r, "recbld 3"))
478 		FATAL("built giant record `%.30s...'", record);
479 	*r = '\0';
480 	   dprintf( ("in recbld inputFS=%s, fldtab[0]=%p\n", inputFS, fldtab[0]) );
481 
482 	if (freeable(fldtab[0]))
483 		xfree(fldtab[0]->sval);
484 	fldtab[0]->tval = REC | STR | DONTFREE;
485 	fldtab[0]->sval = record;
486 
487 	   dprintf( ("in recbld inputFS=%s, fldtab[0]=%p\n", inputFS, fldtab[0]) );
488 	   dprintf( ("recbld = |%s|\n", record) );
489 	donerec = 1;
490 }
491 
492 int	errorflag	= 0;
493 
494 void yyerror(const char *s)
495 {
496 	SYNTAX("%s", s);
497 }
498 
499 void SYNTAX(const char *fmt, ...)
500 {
501 	extern char *cmdname, *curfname;
502 	static int been_here = 0;
503 	va_list varg;
504 
505 	if (been_here++ > 2)
506 		return;
507 	fprintf(stderr, "%s: ", cmdname);
508 	va_start(varg, fmt);
509 	vfprintf(stderr, fmt, varg);
510 	va_end(varg);
511 	fprintf(stderr, " at source line %d", lineno);
512 	if (curfname != NULL)
513 		fprintf(stderr, " in function %s", curfname);
514 	if (compile_time == 1 && cursource() != NULL)
515 		fprintf(stderr, " source file %s", cursource());
516 	fprintf(stderr, "\n");
517 	errorflag = 2;
518 	eprint();
519 }
520 
521 void fpecatch(int n)
522 {
523 	FATAL("floating point exception %d", n);
524 }
525 
526 extern int bracecnt, brackcnt, parencnt;
527 
528 void bracecheck(void)
529 {
530 	int c;
531 	static int beenhere = 0;
532 
533 	if (beenhere++)
534 		return;
535 	while ((c = input()) != EOF && c != '\0')
536 		bclass(c);
537 	bcheck2(bracecnt, '{', '}');
538 	bcheck2(brackcnt, '[', ']');
539 	bcheck2(parencnt, '(', ')');
540 }
541 
542 void bcheck2(int n, int c1, int c2)
543 {
544 	if (n == 1)
545 		fprintf(stderr, "\tmissing %c\n", c2);
546 	else if (n > 1)
547 		fprintf(stderr, "\t%d missing %c's\n", n, c2);
548 	else if (n == -1)
549 		fprintf(stderr, "\textra %c\n", c2);
550 	else if (n < -1)
551 		fprintf(stderr, "\t%d extra %c's\n", -n, c2);
552 }
553 
554 void FATAL(const char *fmt, ...)
555 {
556 	extern char *cmdname;
557 	va_list varg;
558 
559 	fflush(stdout);
560 	fprintf(stderr, "%s: ", cmdname);
561 	va_start(varg, fmt);
562 	vfprintf(stderr, fmt, varg);
563 	va_end(varg);
564 	error();
565 	if (dbg > 1)		/* core dump if serious debugging on */
566 		abort();
567 	exit(2);
568 }
569 
570 void WARNING(const char *fmt, ...)
571 {
572 	extern char *cmdname;
573 	va_list varg;
574 
575 	fflush(stdout);
576 	fprintf(stderr, "%s: ", cmdname);
577 	va_start(varg, fmt);
578 	vfprintf(stderr, fmt, varg);
579 	va_end(varg);
580 	error();
581 }
582 
583 void error()
584 {
585 	extern Node *curnode;
586 
587 	fprintf(stderr, "\n");
588 	if (compile_time != 2 && NR && *NR > 0) {
589 		fprintf(stderr, " input record number %d", (int) (*FNR));
590 		if (strcmp(*FILENAME, "-") != 0)
591 			fprintf(stderr, ", file %s", *FILENAME);
592 		fprintf(stderr, "\n");
593 	}
594 	if (compile_time != 2 && curnode)
595 		fprintf(stderr, " source line number %d", curnode->lineno);
596 	else if (compile_time != 2 && lineno)
597 		fprintf(stderr, " source line number %d", lineno);
598 	if (compile_time == 1 && cursource() != NULL)
599 		fprintf(stderr, " source file %s", cursource());
600 	fprintf(stderr, "\n");
601 	eprint();
602 }
603 
604 void eprint(void)	/* try to print context around error */
605 {
606 	char *p, *q;
607 	int c;
608 	static int been_here = 0;
609 	extern char ebuf[], *ep;
610 
611 	if (compile_time == 2 || compile_time == 0 || been_here++ > 0)
612 		return;
613 	p = ep - 1;
614 	if (p > ebuf && *p == '\n')
615 		p--;
616 	for ( ; p > ebuf && *p != '\n' && *p != '\0'; p--)
617 		;
618 	while (*p == '\n')
619 		p++;
620 	fprintf(stderr, " context is\n\t");
621 	for (q=ep-1; q>=p && *q!=' ' && *q!='\t' && *q!='\n'; q--)
622 		;
623 	for ( ; p < q; p++)
624 		if (*p)
625 			putc(*p, stderr);
626 	fprintf(stderr, " >>> ");
627 	for ( ; p < ep; p++)
628 		if (*p)
629 			putc(*p, stderr);
630 	fprintf(stderr, " <<< ");
631 	if (*ep)
632 		while ((c = input()) != '\n' && c != '\0' && c != EOF) {
633 			putc(c, stderr);
634 			bclass(c);
635 		}
636 	putc('\n', stderr);
637 	ep = ebuf;
638 }
639 
640 void bclass(int c)
641 {
642 	switch (c) {
643 	case '{': bracecnt++; break;
644 	case '}': bracecnt--; break;
645 	case '[': brackcnt++; break;
646 	case ']': brackcnt--; break;
647 	case '(': parencnt++; break;
648 	case ')': parencnt--; break;
649 	}
650 }
651 
652 double errcheck(double x, const char *s)
653 {
654 
655 	if (errno == EDOM) {
656 		errno = 0;
657 		WARNING("%s argument out of domain", s);
658 		x = 1;
659 	} else if (errno == ERANGE) {
660 		errno = 0;
661 		WARNING("%s result out of range", s);
662 		x = 1;
663 	}
664 	return x;
665 }
666 
667 int isclvar(const char *s)	/* is s of form var=something ? */
668 {
669 	const char *os = s;
670 
671 	if (!isalpha((uschar) *s) && *s != '_')
672 		return 0;
673 	for ( ; *s; s++)
674 		if (!(isalnum((uschar) *s) || *s == '_'))
675 			break;
676 	return *s == '=' && s > os && *(s+1) != '=';
677 }
678 
679 /* strtod is supposed to be a proper test of what's a valid number */
680 /* appears to be broken in gcc on linux: thinks 0x123 is a valid FP number */
681 /* wrong: violates 4.10.1.4 of ansi C standard */
682 
683 #include <math.h>
684 int is_number(const char *s)
685 {
686 	double r;
687 	char *ep;
688 	errno = 0;
689 	r = strtod(s, &ep);
690 	if (ep == s || r == HUGE_VAL || errno == ERANGE)
691 		return 0;
692 	while (*ep == ' ' || *ep == '\t' || *ep == '\n')
693 		ep++;
694 	if (*ep == '\0')
695 		return 1;
696 	else
697 		return 0;
698 }
699