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