master xplshn/aruu / cmd / posix / 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 "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 * cant 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 dont 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) { /* its 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, its 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) /* didnt 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}