master xplshn/aruu / cmd / posix / awk / lib.c
  1/****************************************************************
  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 "awk.h"
 27#include <ctype.h>
 28#include <errno.h>
 29#include <limits.h>
 30#include <math.h>
 31#include <stdarg.h>
 32#include <stdio.h>
 33#include <stdlib.h>
 34#include <string.h>
 35#include <strings.h>
 36
 37extern int u8_nextlen(const char *s);
 38
 39char  EMPTY[] = {'\0'};
 40FILE *infile  = NULL;
 41bool  innew; /* true = infile has not been read by readrec */
 42char *file = EMPTY;
 43char *record;
 44int   recsize = RECSIZE;
 45char *fields;
 46int   fieldssize = RECSIZE;
 47
 48Cell        **fldtab; /* pointers to Cells */
 49static size_t len_inputFS = 0;
 50static char  *inputFS     = NULL; /* FS at time of input, for field splitting */
 51
 52#define MAXFLD 2
 53int nfields = MAXFLD; /* last allocated slot for $i */
 54
 55bool donefld; /* true = implies rec broken into fields */
 56bool donerec; /* true = record is valid (no flds have changed) */
 57
 58int              lastfld = 0; /* last used field */
 59int              argno   = 1; /* current input argument number */
 60extern Awkfloat *ARGC;
 61
 62static Cell dollar0 = {OCELL, CFLD, NULL, EMPTY, 0.0, REC | STR | DONTFREE, NULL, NULL};
 63static Cell dollar1 = {OCELL, CFLD, NULL, EMPTY, 0.0, FLD | STR | DONTFREE, NULL, NULL};
 64
 65void
 66recinit(unsigned int n)
 67{
 68  if ((record = (char *)malloc(n)) == NULL || (fields = (char *)malloc(n + 1)) == NULL
 69      || (fldtab = (Cell **)calloc(nfields + 2, sizeof(*fldtab))) == NULL
 70      || (fldtab[0] = (Cell *)malloc(sizeof(**fldtab))) == NULL)
 71    FATAL("out of space for $0 and fields");
 72  *record         = '\0';
 73  *fldtab[0]      = dollar0;
 74  fldtab[0]->sval = record;
 75  fldtab[0]->nval = tostring("0");
 76  makefields(1, nfields);
 77}
 78
 79void
 80makefields(int n1, int n2) /* create $n1..$n2 inclusive */
 81{
 82  char temp[50];
 83  int  i;
 84
 85  for (i = n1; i <= n2; i++) {
 86    fldtab[i] = (Cell *)malloc(sizeof(**fldtab));
 87    if (fldtab[i] == NULL)
 88      FATAL("out of space in makefields %d", i);
 89    *fldtab[i] = dollar1;
 90    snprintf(temp, sizeof(temp), "%d", i);
 91    fldtab[i]->nval = tostring(temp);
 92  }
 93}
 94
 95void
 96initgetrec(void)
 97{
 98  int   i;
 99  char *p;
100
101  for (i = 1; i < *ARGC; i++) {
102    p = getargv(i);                /* find 1st real filename */
103    if (p == NULL || *p == '\0') { /* deleted or zapped */
104      argno++;
105      continue;
106    }
107    if (!isclvar(p)) {
108      setsval(lookup("FILENAME", symtab), p);
109      return;
110    }
111    setclvar(p); /* a commandline assignment before filename */
112    argno++;
113  }
114  infile = stdin; /* no filenames, so use stdin */
115  innew  = true;
116}
117
118/*
119 * POSIX specifies that fields are supposed to be evaluated as if they were
120 * split using the value of FS at the time that the record's value ($0) was
121 * read.
122 *
123 * Since field-splitting is done lazily, we save the current value of FS
124 * whenever a new record is read in (implicitly or via getline), or when
125 * a new value is assigned to $0.
126 */
127void
128savefs(void)
129{
130  size_t len;
131  if ((len = strlen(getsval(fsloc))) < len_inputFS) {
132    strcpy(inputFS, *FS); /* for subsequent field splitting */
133    return;
134  }
135
136  len_inputFS = len + 1;
137  inputFS     = (char *)realloc(inputFS, len_inputFS);
138  if (inputFS == NULL)
139    FATAL("field separator %.10s... is too long", *FS);
140  memcpy(inputFS, *FS, len_inputFS);
141}
142
143static bool firsttime = true;
144
145int
146getrec(char **pbuf, int *pbufsize, bool isrecord) /* get next input record */
147{                                                 /* note: cares whether buf == record */
148  int    c;
149  char  *buf = *pbuf;
150  uschar saveb0;
151  int    bufsize = *pbufsize, savebufsize = bufsize;
152
153  if (firsttime) {
154    firsttime = false;
155    initgetrec();
156  }
157  DPRINTF("RS=<%s>, FS=<%s>, ARGC=%g, FILENAME=%s\n", *RS, *FS, *ARGC, *FILENAME);
158  saveb0 = buf[0];
159  buf[0] = 0;
160  while (argno < *ARGC || infile == stdin) {
161    DPRINTF("argno=%d, file=|%s|\n", argno, file);
162    if (infile == NULL) { /* have to open a new file */
163      file = getargv(argno);
164      if (file == NULL || *file == '\0') { /* deleted or zapped */
165        argno++;
166        continue;
167      }
168      if (isclvar(file)) { /* a var=value arg */
169        setclvar(file);
170        argno++;
171        continue;
172      }
173      *FILENAME = file;
174      DPRINTF("opening file %s\n", file);
175      if (*file == '-' && *(file + 1) == '\0')
176        infile = stdin;
177      else if ((infile = fopen(file, "r")) == NULL)
178        FATAL("can't open file %s", file);
179      innew = true;
180      setfval(fnrloc, 0.0);
181    }
182    c = readrec(&buf, &bufsize, infile, innew);
183    if (innew)
184      innew = false;
185    if (c != 0 || buf[0] != '\0') { /* normal record */
186      if (isrecord) {
187        double result;
188
189        if (freeable(fldtab[0]))
190          xfree(fldtab[0]->sval);
191        fldtab[0]->sval = buf; /* buf == record */
192        fldtab[0]->tval = REC | STR | DONTFREE;
193        if (is_number(fldtab[0]->sval, &result)) {
194          fldtab[0]->fval = result;
195          fldtab[0]->tval |= NUM;
196        }
197        donefld = false;
198        donerec = true;
199        savefs();
200      }
201      setfval(nrloc, nrloc->fval + 1);
202      setfval(fnrloc, fnrloc->fval + 1);
203      *pbuf     = buf;
204      *pbufsize = bufsize;
205      return 1;
206    }
207    /* EOF arrived on this file; set up next */
208    if (infile != stdin)
209      fclose(infile);
210    infile = NULL;
211    argno++;
212  }
213  buf[0]    = saveb0;
214  *pbuf     = buf;
215  *pbufsize = savebufsize;
216  return 0; /* true end of file */
217}
218
219void
220nextfile(void)
221{
222  if (infile != NULL && infile != stdin)
223    fclose(infile);
224  infile = NULL;
225  argno++;
226}
227
228extern int readcsvrec(char **pbuf, int *pbufsize, FILE *inf, bool newflag);
229
230int
231readrec(char **pbuf, int *pbufsize, FILE *inf, bool newflag) /* read one record into buf */
232{
233  int   sep, c, isrec; // POTENTIAL BUG? isrec is a macro in awk.h
234  char *rr = *pbuf, *buf = *pbuf;
235  int   bufsize = *pbufsize;
236  char *rs      = getsval(rsloc);
237
238  if (CSV) {
239    c     = readcsvrec(&buf, &bufsize, inf, newflag);
240    isrec = (c == EOF && rr == buf) ? false : true;
241  } else if (*rs && rs[1]) {
242    bool found;
243
244    memset(buf, 0, bufsize);
245    fa *pfa = makedfa(rs, 1);
246    if (newflag)
247      found = fnematch(pfa, inf, &buf, &bufsize, recsize);
248    else {
249      int tempstat  = pfa->initstat;
250      pfa->initstat = 2;
251      found         = fnematch(pfa, inf, &buf, &bufsize, recsize);
252      pfa->initstat = tempstat;
253    }
254    if (found)
255      setptr(patbeg, '\0');
256    isrec = (found == 0 && *buf == '\0') ? false : true;
257
258  } else {
259    if ((sep = *rs) == 0) {
260      sep = '\n';
261      while ((c = getc(inf)) == '\n' && c != EOF) /* skip leading \n's */
262        ;
263      if (c != EOF)
264        ungetc(c, inf);
265    }
266    for (rr = buf;;) {
267      for (; (c = getc(inf)) != sep && c != EOF;) {
268        if (rr - buf + 1 > bufsize)
269          if (!adjbuf(&buf, &bufsize, 1 + rr - buf, recsize, &rr, "readrec 1"))
270            FATAL(
271                "input record `%.30s...' "
272                "too long",
273                buf
274            );
275        *rr++ = c;
276      }
277      if (*rs == sep || c == EOF)
278        break;
279      if ((c = getc(inf)) == '\n' || c == EOF) /* 2 in a row */
280        break;
281      if (!adjbuf(&buf, &bufsize, 2 + rr - buf, recsize, &rr, "readrec 2"))
282        FATAL("input record `%.30s...' too long", buf);
283      *rr++ = '\n';
284      *rr++ = c;
285    }
286    if (!adjbuf(&buf, &bufsize, 1 + rr - buf, recsize, &rr, "readrec 3"))
287      FATAL("input record `%.30s...' too long", buf);
288    *rr   = 0;
289    isrec = (c == EOF && rr == buf) ? false : true;
290  }
291  *pbuf     = buf;
292  *pbufsize = bufsize;
293  DPRINTF("readrec saw <%s>, returns %d\n", buf, isrec);
294  return isrec;
295}
296
297/*******************
298 * loose ends here:
299 *   \r\n should become \n
300 *   what about bare \r?  Excel uses that for embedded newlines
301 *   can't have "" in unquoted fields, according to RFC 4180
302 */
303
304int
305readcsvrec(char **pbuf, int *pbufsize, FILE *inf, bool newflag) /* csv can have \n's */
306{ /* so read a complete record that might be multiple lines */
307  int sep, c;
308
309  (void)newflag;
310  char *rr = *pbuf, *buf = *pbuf;
311  int   bufsize  = *pbufsize;
312  bool  in_quote = false;
313
314  sep = '\n'; /* the only separator; have to skip over \n embedded in
315           "..." */
316  rr = buf;
317  while ((c = getc(inf)) != EOF) {
318    if (c == sep) {
319      if (!in_quote)
320        break;
321      if (rr > buf && rr[-1] == '\r') // remove \r if was \r\n
322        rr--;
323    }
324
325    if (rr - buf + 1 > bufsize)
326      if (!adjbuf(&buf, &bufsize, 1 + rr - buf, recsize, &rr, "readcsvrec 1"))
327        FATAL("input record `%.30s...' too long", buf);
328    *rr++ = c;
329    if (c == '"')
330      in_quote = !in_quote;
331  }
332  if (c == '\n' && rr > buf && rr[-1] == '\r') // remove \r if was \r\n
333    rr--;
334
335  if (!adjbuf(&buf, &bufsize, 1 + rr - buf, recsize, &rr, "readcsvrec 4"))
336    FATAL("input record `%.30s...' too long", buf);
337  *rr       = 0;
338  *pbuf     = buf;
339  *pbufsize = bufsize;
340  DPRINTF("readcsvrec saw <%s>, returns %d\n", buf, c);
341  return c;
342}
343
344char *
345getargv(int n) /* get ARGV[n] */
346{
347  Array       *ap;
348  Cell        *x;
349  char        *s, temp[50];
350  extern Cell *ARGVcell;
351
352  ap = (Array *)ARGVcell->sval;
353  snprintf(temp, sizeof(temp), "%d", n);
354  if (lookup(temp, ap) == NULL)
355    return NULL;
356  x = setsymtab(temp, "", 0.0, STR, ap);
357  s = getsval(x);
358  DPRINTF("getargv(%d) returns |%s|\n", n, s);
359  return s;
360}
361
362void
363setclvar(char *s) /* set var=value from s */
364{
365  char  *e, *p;
366  Cell  *q;
367  double result;
368
369  /* commit f3d9187d4e0f02294fb1b0e31152070506314e67 broke T.argv test */
370  /* I don't understand why it was changed. */
371
372  for (p = s; *p != '='; p++)
373    ;
374  e    = p;
375  *p++ = 0;
376  p    = qstring(p, '\0');
377  q    = setsymtab(s, p, 0.0, STR, symtab);
378  setsval(q, p);
379  if (is_number(q->sval, &result)) {
380    q->fval = result;
381    q->tval |= NUM;
382  }
383  DPRINTF("command line set %s to |%s|\n", s, p);
384  free(p);
385  *e = '=';
386}
387
388void
389fldbld(void) /* create fields from current record */
390{
391  /* this relies on having fields[] the same length as $0 */
392  /* the fields are all stored in this one array with \0's */
393  /* possibly with a final trailing \0 not associated with any field */
394  char *r, *fr, sep;
395  Cell *p;
396  int   i, j, n;
397
398  if (donefld)
399    return;
400  if (!isstr(fldtab[0]))
401    getsval(fldtab[0]);
402  r = fldtab[0]->sval;
403  n = strlen(r);
404  if (n > fieldssize) {
405    xfree(fields);
406    if ((fields = (char *)malloc(n + 2)) == NULL) /* possibly 2 final \0s */
407      FATAL("out of space for fields in fldbld %d", n);
408    fieldssize = n;
409  }
410  fr = fields;
411  i  = 0;              /* number of fields accumulated here */
412  if (inputFS == NULL) /* make sure we have a copy of FS */
413    savefs();
414  if (!CSV && strlen(inputFS) > 1) { /* it's a regular expression */
415    i = refldbld(r, inputFS);
416  } else if (!CSV && (sep = *inputFS) == ' ') { /* default whitespace */
417    for (i = 0;;) {
418      while (*r == ' ' || *r == '\t' || *r == '\n')
419        r++;
420      if (*r == 0)
421        break;
422      i++;
423      if (i > nfields)
424        growfldtab(i);
425      if (freeable(fldtab[i]))
426        xfree(fldtab[i]->sval);
427      fldtab[i]->sval = fr;
428      fldtab[i]->tval = FLD | STR | DONTFREE;
429      do
430        *fr++ = *r++;
431      while (*r != ' ' && *r != '\t' && *r != '\n' && *r != '\0');
432      *fr++ = 0;
433    }
434    *fr = 0;
435  } else if (CSV) { /* CSV processing.  no error handling */
436    if (*r != 0) {
437      for (;;) {
438        i++;
439        if (i > nfields)
440          growfldtab(i);
441        if (freeable(fldtab[i]))
442          xfree(fldtab[i]->sval);
443        fldtab[i]->sval = fr;
444        fldtab[i]->tval = FLD | STR | DONTFREE;
445        if (*r == '"') { /* start of "..." */
446          for (r++; *r != '\0';) {
447            if (*r == '"' && r[1] != '\0' && r[1] == '"') {
448              r += 2; /* doubled quote
449                       */
450              *fr++ = '"';
451            } else if (*r == '"' && (r[1] == '\0' || r[1] == ',')) {
452              r++; /* skip over
453                closing quote */
454              break;
455            } else {
456              *fr++ = *r++;
457            }
458          }
459          *fr++ = 0;
460        } else { /* unquoted field */
461          while (*r != ',' && *r != '\0')
462            *fr++ = *r++;
463          *fr++ = 0;
464        }
465        if (*r++ == 0)
466          break;
467      }
468    }
469    *fr = 0;
470  } else if ((sep = *inputFS) == 0) { /* new: FS="" => 1 char/field */
471    for (i = 0; *r != '\0';) {
472      char buf[10];
473      i++;
474      if (i > nfields)
475        growfldtab(i);
476      if (freeable(fldtab[i]))
477        xfree(fldtab[i]->sval);
478      n = u8_nextlen(r);
479      for (j = 0; j < n; j++)
480        buf[j] = *r++;
481      buf[j]          = '\0';
482      fldtab[i]->sval = tostring(buf);
483      fldtab[i]->tval = FLD | STR;
484    }
485    *fr = 0;
486  } else if (*r != 0) { /* if 0, it's a null field */
487    /* subtle case: if length(FS) == 1 && length(RS > 0)
488     * \n is NOT a field separator (cf awk book 61,84).
489     * this variable is tested in the inner while loop.
490     */
491    int rtest = '\n'; /* normal case */
492    if (strlen(*RS) > 0)
493      rtest = '\0';
494    for (;;) {
495      i++;
496      if (i > nfields)
497        growfldtab(i);
498      if (freeable(fldtab[i]))
499        xfree(fldtab[i]->sval);
500      fldtab[i]->sval = fr;
501      fldtab[i]->tval = FLD | STR | DONTFREE;
502      while (*r != sep && *r != rtest && *r != '\0') /* \n is always a separator */
503        *fr++ = *r++;
504      *fr++ = 0;
505      if (*r++ == 0)
506        break;
507    }
508    *fr = 0;
509  }
510  if (i > nfields)
511    FATAL("record `%.30s...' has too many fields; can't happen", r);
512  cleanfld(i + 1, lastfld); /* clean out junk from previous record */
513  lastfld = i;
514  donefld = true;
515  for (j = 1; j <= lastfld; j++) {
516    double result;
517
518    p = fldtab[j];
519    if (is_number(p->sval, &result)) {
520      p->fval = result;
521      p->tval |= NUM;
522    }
523  }
524  setfval(nfloc, (Awkfloat)lastfld);
525  donerec = true; /* restore */
526  if (dbg) {
527    for (j = 0; j <= lastfld; j++) {
528      p = fldtab[j];
529      printf("field %d (%s): |%s|\n", j, p->nval, p->sval);
530    }
531  }
532}
533
534void
535cleanfld(int n1, int n2) /* clean out fields n1 .. n2 inclusive */
536{                        /* nvals remain intact */
537  Cell *p;
538  int   i;
539
540  for (i = n1; i <= n2; i++) {
541    p = fldtab[i];
542    if (freeable(p))
543      xfree(p->sval);
544    p->sval = EMPTY, p->tval = FLD | STR | DONTFREE;
545  }
546}
547
548void
549newfld(int n) /* add field n after end of existing lastfld */
550{
551  if (n > nfields)
552    growfldtab(n);
553  cleanfld(lastfld + 1, n);
554  lastfld = n;
555  setfval(nfloc, (Awkfloat)n);
556}
557
558void
559setlastfld(int n) /* set lastfld cleaning fldtab cells if necessary */
560{
561  if (n < 0)
562    FATAL("cannot set NF to a negative value");
563  if (n > nfields)
564    growfldtab(n);
565
566  if (lastfld < n)
567    cleanfld(lastfld + 1, n);
568  else
569    cleanfld(n + 1, lastfld);
570
571  lastfld = n;
572}
573
574Cell *
575fieldadr(int n) /* get nth field */
576{
577  if (n < 0)
578    FATAL("trying to access out of range field %d", n);
579  if (n > nfields) /* fields after NF are empty */
580    growfldtab(n); /* but does not increase NF */
581  return (fldtab[n]);
582}
583
584void
585growfldtab(int n) /* make new fields up to at least $n */
586{
587  int    nf = 2 * nfields;
588  size_t s;
589
590  if (n > nf)
591    nf = n;
592  s = (nf + 1) * (sizeof(struct Cell *));          /* freebsd: how much do we need? */
593  if (s / sizeof(struct Cell *) - 1 == (size_t)nf) /* didn't overflow */
594    fldtab = (Cell **)realloc(fldtab, s);
595  else             /* overflow sizeof int */
596    xfree(fldtab); /* make it null */
597  if (fldtab == NULL)
598    FATAL("out of space creating %d fields", nf);
599  makefields(nfields + 1, nf);
600  nfields = nf;
601}
602
603int
604refldbld(const char *rec, const char *fs) /* build fields from reg expr in FS */
605{
606  /* this relies on having fields[] the same length as $0 */
607  /* the fields are all stored in this one array with \0's */
608  char *fr;
609  int   i, tempstat, n;
610  fa   *pfa;
611
612  n = strlen(rec);
613  if (n > fieldssize) {
614    xfree(fields);
615    if ((fields = (char *)malloc(n + 1)) == NULL)
616      FATAL("out of space for fields in refldbld %d", n);
617    fieldssize = n;
618  }
619  fr  = fields;
620  *fr = '\0';
621  if (*rec == '\0')
622    return 0;
623  pfa = makedfa(fs, 1);
624  DPRINTF("into refldbld, rec = <%s>, pat = <%s>\n", rec, fs);
625  tempstat = pfa->initstat;
626  for (i = 1;; i++) {
627    if (i > nfields)
628      growfldtab(i);
629    if (freeable(fldtab[i]))
630      xfree(fldtab[i]->sval);
631    fldtab[i]->tval = FLD | STR | DONTFREE;
632    fldtab[i]->sval = fr;
633    DPRINTF("refldbld: i=%d\n", i);
634    if (nematch(pfa, rec)) {
635      pfa->initstat = 2; /* horrible coupling to b.c */
636      DPRINTF("match %s (%d chars)\n", patbeg, patlen);
637      strncpy(fr, rec, patbeg - rec);
638      fr += patbeg - rec + 1;
639      *(fr - 1) = '\0';
640      rec       = patbeg + patlen;
641    } else {
642      DPRINTF("no match %s\n", rec);
643      strcpy(fr, rec);
644      pfa->initstat = tempstat;
645      break;
646    }
647  }
648  return i;
649}
650
651void
652recbld(void) /* create $0 from $1..$NF if necessary */
653{
654  int   i;
655  char *r, *p;
656  char *sep = getsval(ofsloc);
657
658  if (donerec)
659    return;
660  r = record;
661  for (i = 1; i <= *NF; i++) {
662    p = getsval(fldtab[i]);
663    if (!adjbuf(&record, &recsize, 1 + strlen(p) + r - record, recsize, &r, "recbld 1"))
664      FATAL("created $0 `%.30s...' too long", record);
665    while ((*r = *p++) != 0)
666      r++;
667    if (i < *NF) {
668      if (!adjbuf(&record, &recsize, 2 + strlen(sep) + r - record, recsize, &r, "recbld 2"))
669        FATAL("created $0 `%.30s...' too long", record);
670      for (p = sep; (*r = *p++) != 0;)
671        r++;
672    }
673  }
674  if (!adjbuf(&record, &recsize, 2 + r - record, recsize, &r, "recbld 3"))
675    FATAL("built giant record `%.30s...'", record);
676  *r = '\0';
677  DPRINTF("in recbld inputFS=%s, fldtab[0]=%p\n", inputFS, (void *)fldtab[0]);
678
679  if (freeable(fldtab[0]))
680    xfree(fldtab[0]->sval);
681  fldtab[0]->tval = REC | STR | DONTFREE;
682  fldtab[0]->sval = record;
683
684  DPRINTF("in recbld inputFS=%s, fldtab[0]=%p\n", inputFS, (void *)fldtab[0]);
685  DPRINTF("recbld = |%s|\n", record);
686  donerec = true;
687}
688
689int errorflag = 0;
690
691void
692yyerror(const char *s)
693{
694  SYNTAX("%s", s);
695}
696
697void
698SYNTAX(const char *fmt, ...)
699{
700  extern char *cmdname, *curfname;
701  static int   been_here = 0;
702  va_list      varg;
703
704  if (been_here++ > 2)
705    return;
706  fprintf(stderr, "%s: ", cmdname);
707  va_start(varg, fmt);
708  vfprintf(stderr, fmt, varg);
709  va_end(varg);
710  fprintf(stderr, " at source line %d", lineno);
711  if (curfname != NULL)
712    fprintf(stderr, " in function %s", curfname);
713  if (compile_time == COMPILING && cursource() != NULL)
714    fprintf(stderr, " source file %s", cursource());
715  fprintf(stderr, "\n");
716  errorflag = 2;
717  eprint();
718}
719
720extern int bracecnt, brackcnt, parencnt;
721
722void
723bracecheck(void)
724{
725  int        c;
726  static int beenhere = 0;
727
728  if (beenhere++)
729    return;
730  while ((c = input()) != EOF && c != '\0')
731    bclass(c);
732  bcheck2(bracecnt, '{', '}');
733  bcheck2(brackcnt, '[', ']');
734  bcheck2(parencnt, '(', ')');
735}
736
737void
738bcheck2(int n, int c1, int c2)
739{
740  (void)c1;
741  if (n == 1)
742    fprintf(stderr, "\tmissing %c\n", c2);
743  else if (n > 1)
744    fprintf(stderr, "\t%d missing %c's\n", n, c2);
745  else if (n == -1)
746    fprintf(stderr, "\textra %c\n", c2);
747  else if (n < -1)
748    fprintf(stderr, "\t%d extra %c's\n", -n, c2);
749}
750
751void
752FATAL(const char *fmt, ...)
753{
754  extern char *cmdname;
755  va_list      varg;
756
757  fflush(stdout);
758  fprintf(stderr, "%s: ", cmdname);
759  va_start(varg, fmt);
760  vfprintf(stderr, fmt, varg);
761  va_end(varg);
762  error();
763  if (dbg > 1) /* core dump if serious debugging on */
764    abort();
765  exit(2);
766}
767
768void
769WARNING(const char *fmt, ...)
770{
771  extern char *cmdname;
772  va_list      varg;
773
774  fflush(stdout);
775  fprintf(stderr, "%s: ", cmdname);
776  va_start(varg, fmt);
777  vfprintf(stderr, fmt, varg);
778  va_end(varg);
779  error();
780}
781
782void
783error()
784{
785  extern Node *curnode;
786
787  fprintf(stderr, "\n");
788  if (compile_time != ERROR_PRINTING) {
789    if (NR && *NR > 0) {
790      fprintf(stderr, " input record number %d", (int)(*FNR));
791      if (strcmp(*FILENAME, "-") != 0)
792        fprintf(stderr, ", file %s", *FILENAME);
793      fprintf(stderr, "\n");
794    }
795    if (curnode)
796      fprintf(stderr, " source line number %d", curnode->lineno);
797    else if (lineno)
798      fprintf(stderr, " source line number %d", lineno);
799    if (compile_time == COMPILING && cursource() != NULL)
800      fprintf(stderr, " source file %s", cursource());
801    fprintf(stderr, "\n");
802    eprint();
803  }
804}
805
806void
807eprint(void) /* try to print context around error */
808{
809  char       *p, *q;
810  int         c;
811  static int  been_here = 0;
812  extern char ebuf[], *ep;
813
814  if (compile_time != COMPILING || been_here++ > 0 || ebuf == ep)
815    return;
816  if (ebuf == ep)
817    return;
818  p = ep - 1;
819  if (p > ebuf && *p == '\n')
820    p--;
821  for (; p > ebuf && *p != '\n' && *p != '\0'; p--)
822    ;
823  while (*p == '\n')
824    p++;
825  fprintf(stderr, " context is\n\t");
826  for (q = ep - 1; q >= p && *q != ' ' && *q != '\t' && *q != '\n'; q--)
827    ;
828  for (; p < q; p++)
829    if (*p)
830      putc(*p, stderr);
831  fprintf(stderr, " >>> ");
832  for (; p < ep; p++)
833    if (*p)
834      putc(*p, stderr);
835  fprintf(stderr, " <<< ");
836  if (*ep)
837    while ((c = input()) != '\n' && c != '\0' && c != EOF) {
838      putc(c, stderr);
839      bclass(c);
840    }
841  putc('\n', stderr);
842  ep = ebuf;
843}
844
845void
846bclass(int c)
847{
848  switch (c) {
849    case '{':
850      bracecnt++;
851      break;
852    case '}':
853      bracecnt--;
854      break;
855    case '[':
856      brackcnt++;
857      break;
858    case ']':
859      brackcnt--;
860      break;
861    case '(':
862      parencnt++;
863      break;
864    case ')':
865      parencnt--;
866      break;
867  }
868}
869
870int
871isclvar(const char *s) /* is s of form var=something ? */
872{
873  const char *os = s;
874
875  if (!isalpha((int)*s) && *s != '_')
876    return 0;
877  for (; *s; s++)
878    if (!(isalnum((int)*s) || *s == '_'))
879      break;
880  return *s == '=' && s > os;
881}
882
883/* strtod is supposed to be a proper test of what's a valid number */
884/* appears to be broken in gcc on linux: thinks 0x123 is a valid FP number */
885/* wrong: violates 4.10.1.4 of ansi C standard */
886
887/* well, not quite. As of C99, hex floating point is allowed. so this is
888 * a bit of a mess. We work around the mess by checking for a hexadecimal
889 * value and disallowing it. Similarly, we now follow gawk and allow only
890 * +nan, -nan, +inf, and -inf for NaN and infinity values.
891 */
892
893/*
894 * This routine now has a more complicated interface, the main point
895 * being to avoid the double conversion of a string to double, and
896 * also to convey out, if requested, the information that the numeric
897 * value was a leading string or is all of the string. The latter bit
898 * is used in getfval().
899 */
900
901bool
902is_valid_number(const char *s, bool trailing_stuff_ok, bool *no_trailing, double *result)
903{
904  double r;
905  char  *ep;
906  bool   retval = false;
907  bool   is_nan = false;
908  bool   is_inf = false;
909
910  if (no_trailing)
911    *no_trailing = false;
912
913  while (isspace((int)*s))
914    s++;
915
916  /* no hex floating point, sorry */
917  if (s[0] == '0' && tolower(s[1]) == 'x' && isxdigit(s[2]))
918    return false;
919
920  /* allow +nan, -nan, +inf, -inf, any other letter, no */
921  if (s[0] == '+' || s[0] == '-') {
922    is_nan = (strncasecmp(s + 1, "nan", 3) == 0);
923    is_inf = (strncasecmp(s + 1, "inf", 3) == 0);
924    if ((is_nan || is_inf) && (isspace((int)s[4]) || s[4] == '\0'))
925      goto convert;
926    else if (!isdigit(s[1]) && s[1] != '.')
927      return false;
928  } else if (!isdigit(s[0]) && s[0] != '.')
929    return false;
930
931convert:
932  errno = 0;
933  r     = strtod(s, &ep);
934  if (ep == s || errno == ERANGE)
935    return false;
936
937  if (isnan(r) && s[0] == '-' && signbit(r) == 0)
938    r = -r;
939
940  if (result != NULL)
941    *result = r;
942
943  /*
944   * check for trailing stuff
945   */
946  while (isspace((int)*ep))
947    ep++;
948
949  if (no_trailing != NULL)
950    *no_trailing = (*ep == '\0');
951
952  /* return true if found the end, or trailing stuff is allowed */
953  retval = *ep == '\0' || trailing_stuff_ok;
954
955  return retval;
956}