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