xref: /openbsd/usr.bin/awk/tran.c (revision 88689b4c)
1 /*	$OpenBSD: tran.c,v 1.40 2024/06/03 00:55:05 millert Exp $	*/
2 /****************************************************************
3 Copyright (C) Lucent Technologies 1997
4 All Rights Reserved
5 
6 Permission to use, copy, modify, and distribute this software and
7 its documentation for any purpose and without fee is hereby
8 granted, provided that the above copyright notice appear in all
9 copies and that both that the copyright notice and this
10 permission notice and warranty disclaimer appear in supporting
11 documentation, and that the name Lucent Technologies or any of
12 its entities not be used in advertising or publicity pertaining
13 to distribution of the software without specific, written prior
14 permission.
15 
16 LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
17 INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
18 IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
19 SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
21 IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
22 ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
23 THIS SOFTWARE.
24 ****************************************************************/
25 
26 #define	DEBUG
27 #include <stdio.h>
28 #include <math.h>
29 #include <ctype.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include "awk.h"
33 
34 #define	FULLTAB	2	/* rehash when table gets this x full */
35 #define	GROWTAB 4	/* grow table by this factor */
36 
37 Array	*symtab;	/* main symbol table */
38 
39 char	**FS;		/* initial field sep */
40 char	**RS;		/* initial record sep */
41 char	**OFS;		/* output field sep */
42 char	**ORS;		/* output record sep */
43 char	**OFMT;		/* output format for numbers */
44 char	**CONVFMT;	/* format for conversions in getsval */
45 Awkfloat *NF;		/* number of fields in current record */
46 Awkfloat *NR;		/* number of current record */
47 Awkfloat *FNR;		/* number of current record in current file */
48 char	**FILENAME;	/* current filename argument */
49 Awkfloat *ARGC;		/* number of arguments from command line */
50 char	**SUBSEP;	/* subscript separator for a[i,j,k]; default \034 */
51 Awkfloat *RSTART;	/* start of re matched with ~; origin 1 (!) */
52 Awkfloat *RLENGTH;	/* length of same */
53 
54 Cell	*fsloc;		/* FS */
55 Cell	*nrloc;		/* NR */
56 Cell	*nfloc;		/* NF */
57 Cell	*fnrloc;	/* FNR */
58 Cell	*ofsloc;	/* OFS */
59 Cell	*orsloc;	/* ORS */
60 Cell	*rsloc;		/* RS */
61 Cell	*ARGVcell;	/* cell with symbol table containing ARGV[...] */
62 Cell	*rstartloc;	/* RSTART */
63 Cell	*rlengthloc;	/* RLENGTH */
64 Cell	*subseploc;	/* SUBSEP */
65 Cell	*symtabloc;	/* SYMTAB */
66 
67 Cell	*nullloc;	/* a guaranteed empty cell */
68 Node	*nullnode;	/* zero&null, converted into a node for comparisons */
69 Cell	*literal0;
70 
71 extern Cell **fldtab;
72 
syminit(void)73 void syminit(void)	/* initialize symbol table with builtin vars */
74 {
75 	literal0 = setsymtab("0", "0", 0.0, NUM|STR|CON|DONTFREE, symtab);
76 	/* this is used for if(x)... tests: */
77 	nullloc = setsymtab("$zero&null", "", 0.0, NUM|STR|CON|DONTFREE, symtab);
78 	nullnode = celltonode(nullloc, CCON);
79 
80 	fsloc = setsymtab("FS", " ", 0.0, STR|DONTFREE, symtab);
81 	FS = &fsloc->sval;
82 	rsloc = setsymtab("RS", "\n", 0.0, STR|DONTFREE, symtab);
83 	RS = &rsloc->sval;
84 	ofsloc = setsymtab("OFS", " ", 0.0, STR|DONTFREE, symtab);
85 	OFS = &ofsloc->sval;
86 	orsloc = setsymtab("ORS", "\n", 0.0, STR|DONTFREE, symtab);
87 	ORS = &orsloc->sval;
88 	OFMT = &setsymtab("OFMT", "%.6g", 0.0, STR|DONTFREE, symtab)->sval;
89 	CONVFMT = &setsymtab("CONVFMT", "%.6g", 0.0, STR|DONTFREE, symtab)->sval;
90 	FILENAME = &setsymtab("FILENAME", "", 0.0, STR|DONTFREE, symtab)->sval;
91 	nfloc = setsymtab("NF", "", 0.0, NUM, symtab);
92 	NF = &nfloc->fval;
93 	nrloc = setsymtab("NR", "", 0.0, NUM, symtab);
94 	NR = &nrloc->fval;
95 	fnrloc = setsymtab("FNR", "", 0.0, NUM, symtab);
96 	FNR = &fnrloc->fval;
97 	subseploc = setsymtab("SUBSEP", "\034", 0.0, STR|DONTFREE, symtab);
98 	SUBSEP = &subseploc->sval;
99 	rstartloc = setsymtab("RSTART", "", 0.0, NUM, symtab);
100 	RSTART = &rstartloc->fval;
101 	rlengthloc = setsymtab("RLENGTH", "", 0.0, NUM, symtab);
102 	RLENGTH = &rlengthloc->fval;
103 	symtabloc = setsymtab("SYMTAB", "", 0.0, ARR, symtab);
104 	free(symtabloc->sval);
105 	symtabloc->sval = (char *) symtab;
106 }
107 
arginit(int ac,char ** av)108 void arginit(int ac, char **av)	/* set up ARGV and ARGC */
109 {
110 	Array *ap;
111 	Cell *cp;
112 	int i;
113 	char temp[50];
114 
115 	ARGC = &setsymtab("ARGC", "", (Awkfloat) ac, NUM, symtab)->fval;
116 	cp = setsymtab("ARGV", "", 0.0, ARR, symtab);
117 	ap = makesymtab(NSYMTAB);	/* could be (int) ARGC as well */
118 	free(cp->sval);
119 	cp->sval = (char *) ap;
120 	for (i = 0; i < ac; i++) {
121 		double result;
122 
123 		snprintf(temp, sizeof(temp), "%d", i);
124 		if (is_number(*av, & result))
125 			setsymtab(temp, *av, result, STR|NUM, ap);
126 		else
127 			setsymtab(temp, *av, 0.0, STR, ap);
128 		av++;
129 	}
130 	ARGVcell = cp;
131 }
132 
envinit(char ** envp)133 void envinit(char **envp)	/* set up ENVIRON variable */
134 {
135 	Array *ap;
136 	Cell *cp;
137 	char *p;
138 
139 	cp = setsymtab("ENVIRON", "", 0.0, ARR, symtab);
140 	ap = makesymtab(NSYMTAB);
141 	free(cp->sval);
142 	cp->sval = (char *) ap;
143 	for ( ; *envp; envp++) {
144 		double result;
145 
146 		if ((p = strchr(*envp, '=')) == NULL)
147 			continue;
148 		if( p == *envp ) /* no left hand side name in env string */
149 			continue;
150 		*p++ = 0;	/* split into two strings at = */
151 		if (is_number(p, & result))
152 			setsymtab(*envp, p, result, STR|NUM, ap);
153 		else
154 			setsymtab(*envp, p, 0.0, STR, ap);
155 		p[-1] = '=';	/* restore in case env is passed down to a shell */
156 	}
157 }
158 
makesymtab(int n)159 Array *makesymtab(int n)	/* make a new symbol table */
160 {
161 	Array *ap;
162 	Cell **tp;
163 
164 	ap = (Array *) malloc(sizeof(*ap));
165 	tp = (Cell **) calloc(n, sizeof(*tp));
166 	if (ap == NULL || tp == NULL)
167 		FATAL("out of space in makesymtab");
168 	ap->nelem = 0;
169 	ap->size = n;
170 	ap->tab = tp;
171 	return(ap);
172 }
173 
freesymtab(Cell * ap)174 void freesymtab(Cell *ap)	/* free a symbol table */
175 {
176 	Cell *cp, *temp;
177 	Array *tp;
178 	int i;
179 
180 	if (!isarr(ap))
181 		return;
182 	tp = (Array *) ap->sval;
183 	if (tp == NULL)
184 		return;
185 	for (i = 0; i < tp->size; i++) {
186 		for (cp = tp->tab[i]; cp != NULL; cp = temp) {
187 			xfree(cp->nval);
188 			if (freeable(cp))
189 				xfree(cp->sval);
190 			temp = cp->cnext;	/* avoids freeing then using */
191 			free(cp);
192 			tp->nelem--;
193 		}
194 		tp->tab[i] = NULL;
195 	}
196 	if (tp->nelem != 0)
197 		WARNING("can't happen: inconsistent element count freeing %s", ap->nval);
198 	free(tp->tab);
199 	free(tp);
200 }
201 
freeelem(Cell * ap,const char * s)202 void freeelem(Cell *ap, const char *s)	/* free elem s from ap (i.e., ap["s"] */
203 {
204 	Array *tp;
205 	Cell *p, *prev = NULL;
206 	int h;
207 
208 	tp = (Array *) ap->sval;
209 	h = hash(s, tp->size);
210 	for (p = tp->tab[h]; p != NULL; prev = p, p = p->cnext)
211 		if (strcmp(s, p->nval) == 0) {
212 			if (prev == NULL)	/* 1st one */
213 				tp->tab[h] = p->cnext;
214 			else			/* middle somewhere */
215 				prev->cnext = p->cnext;
216 			if (freeable(p))
217 				xfree(p->sval);
218 			free(p->nval);
219 			free(p);
220 			tp->nelem--;
221 			return;
222 		}
223 }
224 
setsymtab(const char * n,const char * s,Awkfloat f,unsigned t,Array * tp)225 Cell *setsymtab(const char *n, const char *s, Awkfloat f, unsigned t, Array *tp)
226 {
227 	int h;
228 	Cell *p;
229 
230 	if (n != NULL && (p = lookup(n, tp)) != NULL) {
231 		DPRINTF("setsymtab found %p: n=%s s=\"%s\" f=%g t=%o\n",
232 			(void*)p, NN(p->nval), NN(p->sval), p->fval, p->tval);
233 		return(p);
234 	}
235 	p = (Cell *) malloc(sizeof(*p));
236 	if (p == NULL)
237 		FATAL("out of space for symbol table at %s", n);
238 	p->nval = tostring(n);
239 	p->sval = s ? tostring(s) : tostring("");
240 	p->fval = f;
241 	p->tval = t;
242 	p->csub = CUNK;
243 	p->ctype = OCELL;
244 	tp->nelem++;
245 	if (tp->nelem > FULLTAB * tp->size)
246 		rehash(tp);
247 	h = hash(n, tp->size);
248 	p->cnext = tp->tab[h];
249 	tp->tab[h] = p;
250 	DPRINTF("setsymtab set %p: n=%s s=\"%s\" f=%g t=%o\n",
251 		(void*)p, p->nval, p->sval, p->fval, p->tval);
252 	return(p);
253 }
254 
hash(const char * s,int n)255 int hash(const char *s, int n)	/* form hash value for string s */
256 {
257 	unsigned hashval;
258 
259 	for (hashval = 0; *s != '\0'; s++)
260 		hashval = (*s + 31 * hashval);
261 	return hashval % n;
262 }
263 
rehash(Array * tp)264 void rehash(Array *tp)	/* rehash items in small table into big one */
265 {
266 	int i, nh, nsz;
267 	Cell *cp, *op, **np;
268 
269 	nsz = GROWTAB * tp->size;
270 	np = (Cell **) calloc(nsz, sizeof(*np));
271 	if (np == NULL)		/* can't do it, but can keep running. */
272 		return;		/* someone else will run out later. */
273 	for (i = 0; i < tp->size; i++) {
274 		for (cp = tp->tab[i]; cp; cp = op) {
275 			op = cp->cnext;
276 			nh = hash(cp->nval, nsz);
277 			cp->cnext = np[nh];
278 			np[nh] = cp;
279 		}
280 	}
281 	free(tp->tab);
282 	tp->tab = np;
283 	tp->size = nsz;
284 }
285 
lookup(const char * s,Array * tp)286 Cell *lookup(const char *s, Array *tp)	/* look for s in tp */
287 {
288 	Cell *p;
289 	int h;
290 
291 	h = hash(s, tp->size);
292 	for (p = tp->tab[h]; p != NULL; p = p->cnext)
293 		if (strcmp(s, p->nval) == 0)
294 			return(p);	/* found it */
295 	return(NULL);			/* not found */
296 }
297 
setfval(Cell * vp,Awkfloat f)298 Awkfloat setfval(Cell *vp, Awkfloat f)	/* set float val of a Cell */
299 {
300 	int fldno;
301 
302 	f += 0.0;		/* normalise negative zero to positive zero */
303 	if ((vp->tval & (NUM | STR)) == 0)
304 		funnyvar(vp, "assign to");
305 	if (isfld(vp)) {
306 		donerec = false;	/* mark $0 invalid */
307 		fldno = atoi(vp->nval);
308 		if (fldno > *NF)
309 			newfld(fldno);
310 		DPRINTF("setting field %d to %g\n", fldno, f);
311 	} else if (&vp->fval == NF) {
312 		donerec = false;	/* mark $0 invalid */
313 		setlastfld(f);
314 		DPRINTF("setfval: setting NF to %g\n", f);
315 	} else if (isrec(vp)) {
316 		donefld = false;	/* mark $1... invalid */
317 		donerec = true;
318 		savefs();
319 	} else if (vp == ofsloc) {
320 		if (!donerec)
321 			recbld();
322 	}
323 	if (freeable(vp))
324 		xfree(vp->sval); /* free any previous string */
325 	vp->tval &= ~(STR|CONVC|CONVO); /* mark string invalid */
326 	vp->fmt = NULL;
327 	vp->tval |= NUM;	/* mark number ok */
328 	if (f == -0)  /* who would have thought this possible? */
329 		f = 0;
330 	DPRINTF("setfval %p: %s = %g, t=%o\n", (void*)vp, NN(vp->nval), f, vp->tval);
331 	return vp->fval = f;
332 }
333 
funnyvar(Cell * vp,const char * rw)334 void funnyvar(Cell *vp, const char *rw)
335 {
336 	if (isarr(vp))
337 		FATAL("can't %s %s; it's an array name.", rw, vp->nval);
338 	if (vp->tval & FCN)
339 		FATAL("can't %s %s; it's a function.", rw, vp->nval);
340 	WARNING("funny variable %p: n=%s s=\"%s\" f=%g t=%o",
341 		(void *)vp, vp->nval, vp->sval, vp->fval, vp->tval);
342 }
343 
setsval(Cell * vp,const char * s)344 char *setsval(Cell *vp, const char *s)	/* set string val of a Cell */
345 {
346 	char *t;
347 	int fldno;
348 	Awkfloat f;
349 
350 	DPRINTF("starting setsval %p: %s = \"%s\", t=%o, r,f=%d,%d\n",
351 		(void*)vp, NN(vp->nval), s, vp->tval, donerec, donefld);
352 	if ((vp->tval & (NUM | STR)) == 0)
353 		funnyvar(vp, "assign to");
354 	if (CSV && (vp == rsloc))
355 		WARNING("danger: don't set RS when --csv is in effect");
356 	if (CSV && (vp == fsloc))
357 		WARNING("danger: don't set FS when --csv is in effect");
358 	if (isfld(vp)) {
359 		donerec = false;	/* mark $0 invalid */
360 		fldno = atoi(vp->nval);
361 		if (fldno > *NF)
362 			newfld(fldno);
363 		DPRINTF("setting field %d to %s (%p)\n", fldno, s, (const void*)s);
364 	} else if (isrec(vp)) {
365 		donefld = false;	/* mark $1... invalid */
366 		donerec = true;
367 		savefs();
368 	} else if (vp == ofsloc) {
369 		if (!donerec)
370 			recbld();
371 	}
372 	t = s ? tostring(s) : tostring("");	/* in case it's self-assign */
373 	if (freeable(vp))
374 		xfree(vp->sval);
375 	vp->tval &= ~(NUM|DONTFREE|CONVC|CONVO);
376 	vp->tval |= STR;
377 	vp->fmt = NULL;
378 	DPRINTF("setsval %p: %s = \"%s (%p) \", t=%o r,f=%d,%d\n",
379 		(void*)vp, NN(vp->nval), t, (void*)t, vp->tval, donerec, donefld);
380 	vp->sval = t;
381 	if (&vp->fval == NF) {
382 		donerec = false;	/* mark $0 invalid */
383 		f = getfval(vp);
384 		setlastfld(f);
385 		DPRINTF("setsval: setting NF to %g\n", f);
386 	}
387 
388 	return(vp->sval);
389 }
390 
getfval(Cell * vp)391 Awkfloat getfval(Cell *vp)	/* get float val of a Cell */
392 {
393 	if ((vp->tval & (NUM | STR)) == 0)
394 		funnyvar(vp, "read value of");
395 	if (isfld(vp) && !donefld)
396 		fldbld();
397 	else if (isrec(vp) && !donerec)
398 		recbld();
399 	if (!isnum(vp)) {	/* not a number */
400 		double fval;
401 		bool no_trailing;
402 
403 		if (is_valid_number(vp->sval, true, & no_trailing, & fval)) {
404 			vp->fval = fval;
405 			if (no_trailing && !(vp->tval&CON))
406 				vp->tval |= NUM;	/* make NUM only sparingly */
407 		} else
408 			vp->fval = 0.0;
409 	}
410 	DPRINTF("getfval %p: %s = %g, t=%o\n",
411 		(void*)vp, NN(vp->nval), vp->fval, vp->tval);
412 	return(vp->fval);
413 }
414 
get_inf_nan(double d)415 static const char *get_inf_nan(double d)
416 {
417 	if (isinf(d)) {
418 		return (d < 0 ? "-inf" : "+inf");
419 	} else if (isnan(d)) {
420 		return (signbit(d) != 0 ? "-nan" : "+nan");
421 	} else
422 		return NULL;
423 }
424 
get_str_val(Cell * vp,char ** fmt)425 static char *get_str_val(Cell *vp, char **fmt)        /* get string val of a Cell */
426 {
427 	int n;
428 	double dtemp;
429 	const char *p;
430 
431 	if ((vp->tval & (NUM | STR)) == 0)
432 		funnyvar(vp, "read value of");
433 	if (isfld(vp) && ! donefld)
434 		fldbld();
435 	else if (isrec(vp) && ! donerec)
436 		recbld();
437 
438 	/*
439 	 * ADR: This is complicated and more fragile than is desirable.
440 	 * Retrieving a string value for a number associates the string
441 	 * value with the scalar.  Previously, the string value was
442 	 * sticky, meaning if converted via OFMT that became the value
443 	 * (even though POSIX wants it to be via CONVFMT). Or if CONVFMT
444 	 * changed after a string value was retrieved, the original value
445 	 * was maintained and used.  Also not per POSIX.
446 	 *
447 	 * We work around this design by adding two additional flags,
448 	 * CONVC and CONVO, indicating how the string value was
449 	 * obtained (via CONVFMT or OFMT) and _also_ maintaining a copy
450 	 * of the pointer to the xFMT format string used for the
451 	 * conversion.  This pointer is only read, **never** dereferenced.
452 	 * The next time we do a conversion, if it's coming from the same
453 	 * xFMT as last time, and the pointer value is different, we
454 	 * know that the xFMT format string changed, and we need to
455 	 * redo the conversion. If it's the same, we don't have to.
456 	 *
457 	 * There are also several cases where we don't do a conversion,
458 	 * such as for a field (see the checks below).
459 	 */
460 
461 	/* Don't duplicate the code for actually updating the value */
462 #define update_str_val(vp) \
463 	{ \
464 		if (freeable(vp)) \
465 			xfree(vp->sval); \
466 		if ((p = get_inf_nan(vp->fval)) != NULL) \
467 			n = (vp->sval = strdup(p)) ? 0 : -1; \
468 		else if (modf(vp->fval, &dtemp) == 0)	/* it's integral */ \
469 			n = asprintf(&vp->sval, "%.30g", vp->fval); \
470 		else \
471 			n = asprintf(&vp->sval, *fmt, vp->fval); \
472 		if (n == -1) \
473 			FATAL("out of space in get_str_val"); \
474 		vp->tval &= ~DONTFREE; \
475 		vp->tval |= STR; \
476 	}
477 
478 	if (isstr(vp) == 0) {
479 		update_str_val(vp);
480 		if (fmt == OFMT) {
481 			vp->tval &= ~CONVC;
482 			vp->tval |= CONVO;
483 		} else {
484 			/* CONVFMT */
485 			vp->tval &= ~CONVO;
486 			vp->tval |= CONVC;
487 		}
488 		vp->fmt = *fmt;
489 	} else if ((vp->tval & DONTFREE) != 0 || ! isnum(vp) || isfld(vp)) {
490 		goto done;
491 	} else if (isstr(vp)) {
492 		if (fmt == OFMT) {
493 			if ((vp->tval & CONVC) != 0
494 			    || ((vp->tval & CONVO) != 0 && vp->fmt != *fmt)) {
495 				update_str_val(vp);
496 				vp->tval &= ~CONVC;
497 				vp->tval |= CONVO;
498 				vp->fmt = *fmt;
499 			}
500 		} else {
501 			/* CONVFMT */
502 			if ((vp->tval & CONVO) != 0
503 			    || ((vp->tval & CONVC) != 0 && vp->fmt != *fmt)) {
504 				update_str_val(vp);
505 				vp->tval &= ~CONVO;
506 				vp->tval |= CONVC;
507 				vp->fmt = *fmt;
508 			}
509 		}
510 	}
511 done:
512 	DPRINTF("getsval %p: %s = \"%s (%p)\", t=%o\n",
513 		(void*)vp, NN(vp->nval), vp->sval, (void*)vp->sval, vp->tval);
514 	return(vp->sval);
515 }
516 
getsval(Cell * vp)517 char *getsval(Cell *vp)       /* get string val of a Cell */
518 {
519       return get_str_val(vp, CONVFMT);
520 }
521 
getpssval(Cell * vp)522 char *getpssval(Cell *vp)     /* get string val of a Cell for print */
523 {
524       return get_str_val(vp, OFMT);
525 }
526 
527 
tostring(const char * s)528 char *tostring(const char *s)	/* make a copy of string s */
529 {
530 	char *p = strdup(s);
531 	if (p == NULL)
532 		FATAL("out of space in tostring on %s", s);
533 	return(p);
534 }
535 
tostringN(const char * s,size_t n)536 char *tostringN(const char *s, size_t n)	/* make a copy of string s */
537 {
538 	char *p;
539 
540 	p = (char *) malloc(n);
541 	if (p == NULL)
542 		FATAL("out of space in tostringN %zu", n);
543 	if (strlcpy(p, s, n) >= n)
544 		FATAL("out of space in tostringN on %s", s);
545 	return(p);
546 }
547 
catstr(Cell * a,Cell * b)548 Cell *catstr(Cell *a, Cell *b) /* concatenate a and b */
549 {
550 	Cell *c;
551 	char *p;
552 	char *sa = getsval(a);
553 	char *sb = getsval(b);
554 	size_t l = strlen(sa) + strlen(sb) + 1;
555 	p = (char *) malloc(l);
556 	if (p == NULL)
557 		FATAL("out of space concatenating %s and %s", sa, sb);
558 	snprintf(p, l, "%s%s", sa, sb);
559 
560 	l++;	// add room for ' '
561 	char *newbuf = (char *) malloc(l);
562 	if (newbuf == NULL)
563 		FATAL("out of space concatenating %s and %s", sa, sb);
564 	// See string() in lex.c; a string "xx" is stored in the symbol
565 	// table as "xx ".
566 	snprintf(newbuf, l, "%s ", p);
567 	c = setsymtab(newbuf, p, 0.0, CON|STR|DONTFREE, symtab);
568 	free(p);
569 	free(newbuf);
570 	return c;
571 }
572 
qstring(const char * is,int delim)573 char *qstring(const char *is, int delim)	/* collect string up to next delim */
574 {
575 	int c, n;
576 	const uschar *s = (const uschar *) is;
577 	uschar *buf, *bp;
578 
579 	if ((buf = (uschar *) malloc(strlen(is)+3)) == NULL)
580 		FATAL( "out of space in qstring(%s)", s);
581 	for (bp = buf; (c = *s) != delim; s++) {
582 		if (c == '\n')
583 			SYNTAX( "newline in string %.20s...", is );
584 		else if (c != '\\')
585 			*bp++ = c;
586 		else {	/* \something */
587 			c = *++s;
588 			if (c == 0) {	/* \ at end */
589 				*bp++ = '\\';
590 				break;	/* for loop */
591 			}
592 			switch (c) {
593 			case '\\':	*bp++ = '\\'; break;
594 			case 'n':	*bp++ = '\n'; break;
595 			case 't':	*bp++ = '\t'; break;
596 			case 'b':	*bp++ = '\b'; break;
597 			case 'f':	*bp++ = '\f'; break;
598 			case 'r':	*bp++ = '\r'; break;
599 			case 'v':	*bp++ = '\v'; break;
600 			case 'a':	*bp++ = '\a'; break;
601 			default:
602 				if (!isdigit(c)) {
603 					*bp++ = c;
604 					break;
605 				}
606 				n = c - '0';
607 				if (isdigit(s[1])) {
608 					n = 8 * n + *++s - '0';
609 					if (isdigit(s[1]))
610 						n = 8 * n + *++s - '0';
611 				}
612 				*bp++ = n;
613 				break;
614 			}
615 		}
616 	}
617 	*bp++ = 0;
618 	return (char *) buf;
619 }
620 
flags2str(int flags)621 const char *flags2str(int flags)
622 {
623 	static const struct ftab {
624 		const char *name;
625 		int value;
626 	} flagtab[] = {
627 		{ "NUM", NUM },
628 		{ "STR", STR },
629 		{ "DONTFREE", DONTFREE },
630 		{ "CON", CON },
631 		{ "ARR", ARR },
632 		{ "FCN", FCN },
633 		{ "FLD", FLD },
634 		{ "REC", REC },
635 		{ "CONVC", CONVC },
636 		{ "CONVO", CONVO },
637 		{ NULL, 0 }
638 	};
639 	static char buf[100];
640 	int i, len;
641 	char *cp = buf;
642 
643 	for (i = 0; flagtab[i].name != NULL; i++) {
644 		if ((flags & flagtab[i].value) != 0) {
645 			len = snprintf(cp, sizeof(buf) - (cp - buf),
646 			    "%s%s", cp > buf ? "|" : "", flagtab[i].name);
647 			if (len < 0 || (size_t)len >= sizeof(buf) - (cp - buf))
648 				FATAL("out of space in flags2str");
649 			cp += len;
650 		}
651 	}
652 
653 	return buf;
654 }
655