xref: /freebsd/contrib/one-true-awk/lib.c (revision 8fc257994d0ce2396196d7a06d50d20c8015f4b7)
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 	char *r, *fr, sep;
260 	Cell *p;
261 	int i, j, n;
262 
263 	if (donefld)
264 		return;
265 	if (!isstr(fldtab[0]))
266 		getsval(fldtab[0]);
267 	r = fldtab[0]->sval;
268 	n = strlen(r);
269 	if (n > fieldssize) {
270 		xfree(fields);
271 		if ((fields = (char *) malloc(n+1)) == NULL)
272 			FATAL("out of space for fields in fldbld %d", n);
273 		fieldssize = n;
274 	}
275 	fr = fields;
276 	i = 0;	/* number of fields accumulated here */
277 	strcpy(inputFS, *FS);
278 	if (strlen(inputFS) > 1) {	/* it's a regular expression */
279 		i = refldbld(r, inputFS);
280 	} else if ((sep = *inputFS) == ' ') {	/* default whitespace */
281 		for (i = 0; ; ) {
282 			while (*r == ' ' || *r == '\t' || *r == '\n')
283 				r++;
284 			if (*r == 0)
285 				break;
286 			i++;
287 			if (i > nfields)
288 				growfldtab(i);
289 			if (freeable(fldtab[i]))
290 				xfree(fldtab[i]->sval);
291 			fldtab[i]->sval = fr;
292 			fldtab[i]->tval = FLD | STR | DONTFREE;
293 			do
294 				*fr++ = *r++;
295 			while (*r != ' ' && *r != '\t' && *r != '\n' && *r != '\0');
296 			*fr++ = 0;
297 		}
298 		*fr = 0;
299 	} else if ((sep = *inputFS) == 0) {		/* new: FS="" => 1 char/field */
300 		for (i = 0; *r != 0; r++) {
301 			char buf[2];
302 			i++;
303 			if (i > nfields)
304 				growfldtab(i);
305 			if (freeable(fldtab[i]))
306 				xfree(fldtab[i]->sval);
307 			buf[0] = *r;
308 			buf[1] = 0;
309 			fldtab[i]->sval = tostring(buf);
310 			fldtab[i]->tval = FLD | STR;
311 		}
312 		*fr = 0;
313 	} else if (*r != 0) {	/* if 0, it's a null field */
314 		/* subtlecase : if length(FS) == 1 && length(RS > 0)
315 		 * \n is NOT a field separator (cf awk book 61,84).
316 		 * this variable is tested in the inner while loop.
317 		 */
318 		int rtest = '\n';  /* normal case */
319 		if (strlen(*RS) > 0)
320 			rtest = '\0';
321 		for (;;) {
322 			i++;
323 			if (i > nfields)
324 				growfldtab(i);
325 			if (freeable(fldtab[i]))
326 				xfree(fldtab[i]->sval);
327 			fldtab[i]->sval = fr;
328 			fldtab[i]->tval = FLD | STR | DONTFREE;
329 			while (*r != sep && *r != rtest && *r != '\0')	/* \n is always a separator */
330 				*fr++ = *r++;
331 			*fr++ = 0;
332 			if (*r++ == 0)
333 				break;
334 		}
335 		*fr = 0;
336 	}
337 	if (i > nfields)
338 		FATAL("record `%.30s...' has too many fields; can't happen", r);
339 	cleanfld(i+1, lastfld);	/* clean out junk from previous record */
340 	lastfld = i;
341 	donefld = 1;
342 	for (j = 1; j <= lastfld; j++) {
343 		p = fldtab[j];
344 		if(is_number(p->sval)) {
345 			p->fval = atof(p->sval);
346 			p->tval |= NUM;
347 		}
348 	}
349 	setfval(nfloc, (Awkfloat) lastfld);
350 	if (dbg) {
351 		for (j = 0; j <= lastfld; j++) {
352 			p = fldtab[j];
353 			printf("field %d (%s): |%s|\n", j, p->nval, p->sval);
354 		}
355 	}
356 }
357 
358 void cleanfld(int n1, int n2)	/* clean out fields n1 .. n2 inclusive */
359 {				/* nvals remain intact */
360 	Cell *p;
361 	int i;
362 
363 	for (i = n1; i <= n2; i++) {
364 		p = fldtab[i];
365 		if (freeable(p))
366 			xfree(p->sval);
367 		p->sval = "";
368 		p->tval = FLD | STR | DONTFREE;
369 	}
370 }
371 
372 void newfld(int n)	/* add field n after end of existing lastfld */
373 {
374 	if (n > nfields)
375 		growfldtab(n);
376 	cleanfld(lastfld+1, n);
377 	lastfld = n;
378 	setfval(nfloc, (Awkfloat) n);
379 }
380 
381 Cell *fieldadr(int n)	/* get nth field */
382 {
383 	if (n < 0)
384 		FATAL("trying to access out of range field %d", n);
385 	if (n > nfields)	/* fields after NF are empty */
386 		growfldtab(n);	/* but does not increase NF */
387 	return(fldtab[n]);
388 }
389 
390 void growfldtab(int n)	/* make new fields up to at least $n */
391 {
392 	int nf = 2 * nfields;
393 	size_t s;
394 
395 	if (n > nf)
396 		nf = n;
397 	s = (nf+1) * (sizeof (struct Cell *));  /* freebsd: how much do we need? */
398 	if (s / sizeof(struct Cell *) - 1 == nf) /* didn't overflow */
399 		fldtab = (Cell **) realloc(fldtab, s);
400 	else					/* overflow sizeof int */
401 		xfree(fldtab);	/* make it null */
402 	if (fldtab == NULL)
403 		FATAL("out of space creating %d fields", nf);
404 	makefields(nfields+1, nf);
405 	nfields = nf;
406 }
407 
408 int refldbld(const char *rec, const char *fs)	/* build fields from reg expr in FS */
409 {
410 	/* this relies on having fields[] the same length as $0 */
411 	/* the fields are all stored in this one array with \0's */
412 	char *fr;
413 	int i, tempstat, n;
414 	fa *pfa;
415 
416 	n = strlen(rec);
417 	if (n > fieldssize) {
418 		xfree(fields);
419 		if ((fields = (char *) malloc(n+1)) == NULL)
420 			FATAL("out of space for fields in refldbld %d", n);
421 		fieldssize = n;
422 	}
423 	fr = fields;
424 	*fr = '\0';
425 	if (*rec == '\0')
426 		return 0;
427 	pfa = makedfa(fs, 1);
428 	   dprintf( ("into refldbld, rec = <%s>, pat = <%s>\n", rec, fs) );
429 	tempstat = pfa->initstat;
430 	for (i = 1; ; i++) {
431 		if (i > nfields)
432 			growfldtab(i);
433 		if (freeable(fldtab[i]))
434 			xfree(fldtab[i]->sval);
435 		fldtab[i]->tval = FLD | STR | DONTFREE;
436 		fldtab[i]->sval = fr;
437 		   dprintf( ("refldbld: i=%d\n", i) );
438 		if (nematch(pfa, rec)) {
439 			pfa->initstat = 2;	/* horrible coupling to b.c */
440 			   dprintf( ("match %s (%d chars)\n", patbeg, patlen) );
441 			strncpy(fr, rec, patbeg-rec);
442 			fr += patbeg - rec + 1;
443 			*(fr-1) = '\0';
444 			rec = patbeg + patlen;
445 		} else {
446 			   dprintf( ("no match %s\n", rec) );
447 			strcpy(fr, rec);
448 			pfa->initstat = tempstat;
449 			break;
450 		}
451 	}
452 	return i;
453 }
454 
455 void recbld(void)	/* create $0 from $1..$NF if necessary */
456 {
457 	int i;
458 	char *r, *p;
459 
460 	if (donerec == 1)
461 		return;
462 	r = record;
463 	for (i = 1; i <= *NF; i++) {
464 		p = getsval(fldtab[i]);
465 		if (!adjbuf(&record, &recsize, 1+strlen(p)+r-record, recsize, &r, "recbld 1"))
466 			FATAL("created $0 `%.30s...' too long", record);
467 		while ((*r = *p++) != 0)
468 			r++;
469 		if (i < *NF) {
470 			if (!adjbuf(&record, &recsize, 2+strlen(*OFS)+r-record, recsize, &r, "recbld 2"))
471 				FATAL("created $0 `%.30s...' too long", record);
472 			for (p = *OFS; (*r = *p++) != 0; )
473 				r++;
474 		}
475 	}
476 	if (!adjbuf(&record, &recsize, 2+r-record, recsize, &r, "recbld 3"))
477 		FATAL("built giant record `%.30s...'", record);
478 	*r = '\0';
479 	   dprintf( ("in recbld inputFS=%s, fldtab[0]=%p\n", inputFS, fldtab[0]) );
480 
481 	if (freeable(fldtab[0]))
482 		xfree(fldtab[0]->sval);
483 	fldtab[0]->tval = REC | STR | DONTFREE;
484 	fldtab[0]->sval = record;
485 
486 	   dprintf( ("in recbld inputFS=%s, fldtab[0]=%p\n", inputFS, fldtab[0]) );
487 	   dprintf( ("recbld = |%s|\n", record) );
488 	donerec = 1;
489 }
490 
491 int	errorflag	= 0;
492 
493 void yyerror(const char *s)
494 {
495 	SYNTAX("%s", s);
496 }
497 
498 void SYNTAX(const char *fmt, ...)
499 {
500 	extern char *cmdname, *curfname;
501 	static int been_here = 0;
502 	va_list varg;
503 
504 	if (been_here++ > 2)
505 		return;
506 	fprintf(stderr, "%s: ", cmdname);
507 	va_start(varg, fmt);
508 	vfprintf(stderr, fmt, varg);
509 	va_end(varg);
510 	fprintf(stderr, " at source line %d", lineno);
511 	if (curfname != NULL)
512 		fprintf(stderr, " in function %s", curfname);
513 	if (compile_time == 1 && cursource() != NULL)
514 		fprintf(stderr, " source file %s", cursource());
515 	fprintf(stderr, "\n");
516 	errorflag = 2;
517 	eprint();
518 }
519 
520 void fpecatch(int n)
521 {
522 	FATAL("floating point exception %d", n);
523 }
524 
525 extern int bracecnt, brackcnt, parencnt;
526 
527 void bracecheck(void)
528 {
529 	int c;
530 	static int beenhere = 0;
531 
532 	if (beenhere++)
533 		return;
534 	while ((c = input()) != EOF && c != '\0')
535 		bclass(c);
536 	bcheck2(bracecnt, '{', '}');
537 	bcheck2(brackcnt, '[', ']');
538 	bcheck2(parencnt, '(', ')');
539 }
540 
541 void bcheck2(int n, int c1, int c2)
542 {
543 	if (n == 1)
544 		fprintf(stderr, "\tmissing %c\n", c2);
545 	else if (n > 1)
546 		fprintf(stderr, "\t%d missing %c's\n", n, c2);
547 	else if (n == -1)
548 		fprintf(stderr, "\textra %c\n", c2);
549 	else if (n < -1)
550 		fprintf(stderr, "\t%d extra %c's\n", -n, c2);
551 }
552 
553 void FATAL(const char *fmt, ...)
554 {
555 	extern char *cmdname;
556 	va_list varg;
557 
558 	fflush(stdout);
559 	fprintf(stderr, "%s: ", cmdname);
560 	va_start(varg, fmt);
561 	vfprintf(stderr, fmt, varg);
562 	va_end(varg);
563 	error();
564 	if (dbg > 1)		/* core dump if serious debugging on */
565 		abort();
566 	exit(2);
567 }
568 
569 void WARNING(const char *fmt, ...)
570 {
571 	extern char *cmdname;
572 	va_list varg;
573 
574 	fflush(stdout);
575 	fprintf(stderr, "%s: ", cmdname);
576 	va_start(varg, fmt);
577 	vfprintf(stderr, fmt, varg);
578 	va_end(varg);
579 	error();
580 }
581 
582 void error()
583 {
584 	extern Node *curnode;
585 
586 	fprintf(stderr, "\n");
587 	if (compile_time != 2 && NR && *NR > 0) {
588 		fprintf(stderr, " input record number %d", (int) (*FNR));
589 		if (strcmp(*FILENAME, "-") != 0)
590 			fprintf(stderr, ", file %s", *FILENAME);
591 		fprintf(stderr, "\n");
592 	}
593 	if (compile_time != 2 && curnode)
594 		fprintf(stderr, " source line number %d", curnode->lineno);
595 	else if (compile_time != 2 && lineno)
596 		fprintf(stderr, " source line number %d", lineno);
597 	if (compile_time == 1 && cursource() != NULL)
598 		fprintf(stderr, " source file %s", cursource());
599 	fprintf(stderr, "\n");
600 	eprint();
601 }
602 
603 void eprint(void)	/* try to print context around error */
604 {
605 	char *p, *q;
606 	int c;
607 	static int been_here = 0;
608 	extern char ebuf[], *ep;
609 
610 	if (compile_time == 2 || compile_time == 0 || been_here++ > 0)
611 		return;
612 	p = ep - 1;
613 	if (p > ebuf && *p == '\n')
614 		p--;
615 	for ( ; p > ebuf && *p != '\n' && *p != '\0'; p--)
616 		;
617 	while (*p == '\n')
618 		p++;
619 	fprintf(stderr, " context is\n\t");
620 	for (q=ep-1; q>=p && *q!=' ' && *q!='\t' && *q!='\n'; q--)
621 		;
622 	for ( ; p < q; p++)
623 		if (*p)
624 			putc(*p, stderr);
625 	fprintf(stderr, " >>> ");
626 	for ( ; p < ep; p++)
627 		if (*p)
628 			putc(*p, stderr);
629 	fprintf(stderr, " <<< ");
630 	if (*ep)
631 		while ((c = input()) != '\n' && c != '\0' && c != EOF) {
632 			putc(c, stderr);
633 			bclass(c);
634 		}
635 	putc('\n', stderr);
636 	ep = ebuf;
637 }
638 
639 void bclass(int c)
640 {
641 	switch (c) {
642 	case '{': bracecnt++; break;
643 	case '}': bracecnt--; break;
644 	case '[': brackcnt++; break;
645 	case ']': brackcnt--; break;
646 	case '(': parencnt++; break;
647 	case ')': parencnt--; break;
648 	}
649 }
650 
651 double errcheck(double x, const char *s)
652 {
653 
654 	if (errno == EDOM) {
655 		errno = 0;
656 		WARNING("%s argument out of domain", s);
657 		x = 1;
658 	} else if (errno == ERANGE) {
659 		errno = 0;
660 		WARNING("%s result out of range", s);
661 		x = 1;
662 	}
663 	return x;
664 }
665 
666 int isclvar(const char *s)	/* is s of form var=something ? */
667 {
668 	const char *os = s;
669 
670 	if (!isalpha((uschar) *s) && *s != '_')
671 		return 0;
672 	for ( ; *s; s++)
673 		if (!(isalnum((uschar) *s) || *s == '_'))
674 			break;
675 	return *s == '=' && s > os && *(s+1) != '=';
676 }
677 
678 /* strtod is supposed to be a proper test of what's a valid number */
679 /* appears to be broken in gcc on linux: thinks 0x123 is a valid FP number */
680 /* wrong: violates 4.10.1.4 of ansi C standard */
681 
682 #include <math.h>
683 int is_number(const char *s)
684 {
685 	double r;
686 	char *ep;
687 	errno = 0;
688 	r = strtod(s, &ep);
689 	if (ep == s || r == HUGE_VAL || errno == ERANGE)
690 		return 0;
691 	while (*ep == ' ' || *ep == '\t' || *ep == '\n')
692 		ep++;
693 	if (*ep == '\0')
694 		return 1;
695 	else
696 		return 0;
697 }
698