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