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