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