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