OpenSolaris_b135/cmd/awk_xpg4/awk2.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright 1986, 1994 by Mortice Kern Systems Inc.  All rights reserved.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * awk -- process input files, field extraction, output
 *
 * Based on MKS awk(1) ported to be /usr/xpg4/bin/awk with POSIX/XCU4 changes
 */

#include "awk.h"
#include "y.tab.h"

static FILE	*awkinfp;		/* Input file pointer */
static int	reclen;			/* Length of last record */
static int	exstat;			/* Exit status */

static FILE	*openfile(NODE *np, int flag, int fatal);
static FILE	*newfile(void);
static NODE	*nextarg(NODE **npp);
static void	adjust_buf(wchar_t **, int *, wchar_t **, char *, size_t);
static void	awk_putwc(wchar_t, FILE *);

/*
 * mainline for awk execution
 */
void
awk()
{
	running = 1;
	dobegin();
	while (nextrecord(linebuf, awkinfp) > 0)
		execute(yytree);
	doend(exstat);
}

/*
 * "cp" is the buffer to fill.  There is a special case if this buffer is
 * "linebuf" ($0)
 * Return 1 if OK, zero on EOF, -1 on error.
 */
int
nextrecord(wchar_t *cp, FILE *fp)
{
	wchar_t *ep = cp;

nextfile:
	if (fp == FNULL && (fp = newfile()) == FNULL)
		return (0);
	if ((*awkrecord)(ep, NLINE, fp) == NULL) {
		if (fp == awkinfp) {
			if (fp != stdin)
				(void) fclose(awkinfp);
			awkinfp = fp = FNULL;
			goto nextfile;
		}
		if (ferror(fp))
			return (-1);
		return (0);
	}
	if (fp == awkinfp) {
		if (varNR->n_flags & FINT)
			++varNR->n_int;
		else
			(void) exprreduce(incNR);
		if (varFNR->n_flags & FINT)
			++varFNR->n_int;
		else
			(void) exprreduce(incFNR);
	}
	if (cp == linebuf) {
		lbuflen = reclen;
		splitdone = 0;
		if (needsplit)
			fieldsplit();
	}
	/* if record length is too long then bail out */
	if (reclen > NLINE - 2) {
		awkerr(gettext("Record too long (LIMIT: %d bytes)"),
		    NLINE - 1);
		/* Not Reached */
	}
	return (1);
}

/*
 * isclvar()
 *
 * Returns 1 if the input string, arg, is a variable assignment,
 * otherwise returns 0.
 *
 * An argument to awk can be either a pathname of a file, or a variable
 * assignment.  An operand that begins with an undersore or alphabetic
 * character from the portable character set, followed by a sequence of
 * underscores, digits, and alphabetics from the portable character set,
 * followed by the '=' character, shall specify a variable assignment
 * rather than a pathname.
 */
int
isclvar(wchar_t *arg)
{
	wchar_t	*tmpptr = arg;

	if (tmpptr != NULL) {

		/* Begins with an underscore or alphabetic character */
		if (iswalpha(*tmpptr) || *tmpptr == '_') {

			/*
			 * followed by a sequence of underscores, digits,
			 * and alphabetics
			 */
			for (tmpptr++; *tmpptr; tmpptr++) {
				if (!(iswalnum(*tmpptr) || (*tmpptr == '_'))) {
					break;
				}
			}
			return (*tmpptr == '=');
		}
	}

	return (0);
}

/*
 * Return the next file from the command line.
 * Return FNULL when no more files.
 * Sets awkinfp variable to the new current input file.
 */
static FILE *
newfile()
{
	static int argindex = 1;
	static int filedone;
	wchar_t *ap;
	int argc;
	wchar_t *arg;
	extern void strescape(wchar_t *);

	argc = (int)exprint(varARGC);
	for (;;) {
		if (argindex >= argc) {
			if (filedone)
				return (FNULL);
			++filedone;
			awkinfp = stdin;
			arg = M_MB_L("-");
			break;
		}
		constant->n_int = argindex++;
		arg = (wchar_t *)exprstring(ARGVsubi);
		/*
		 * If the argument contains a '=', determine if the
		 * argument needs to be treated as a variable assignment
		 * or as the pathname of a file.
		 */
		if (((ap = wcschr(arg, '=')) != NULL) && isclvar(arg)) {
			*ap = '\0';
			strescape(ap+1);
			strassign(vlook(arg), linebuf, FALLOC|FSENSE,
			    wcslen(linebuf));
			*ap = '=';
			continue;
		}
		if (arg[0] == '\0')
			continue;
		++filedone;
		if (arg[0] == '-' && arg[1] == '\0') {
			awkinfp = stdin;
			break;
		}
		if ((awkinfp = fopen(mbunconvert(arg), r)) == FNULL) {
			(void) fprintf(stderr, gettext("input file \"%s\""),
			    mbunconvert(arg));
			exstat = 1;
			continue;
		}
		break;
	}
	strassign(varFILENAME, arg, FALLOC, wcslen(arg));
	if (varFNR->n_flags & FINT)
		varFNR->n_int = 0;
	else
		(void) exprreduce(clrFNR);
	return (awkinfp);
}

/*
 * Default record reading code
 * Uses fgets for potential speedups found in some (e.g. MKS)
 * stdio packages.
 */
wchar_t *
defrecord(wchar_t *bp, int lim, FILE *fp)
{
	wchar_t *endp;

	if (fgetws(bp, lim, fp) == NULL) {
		*bp = '\0';
		return (NULL);
	}
/*
 * XXXX
 *	switch (fgetws(bp, lim, fp)) {
 *	case M_FGETS_EOF:
 *		*bp = '\0';
 *		return (NULL);
 *	case M_FGETS_BINARY:
 *		awkerr(gettext("file is binary"));
 *	case M_FGETS_LONG:
 *		awkerr(gettext("line too long: limit %d"),
 *			lim);
 *	case M_FGETS_ERROR:
 *		awkperr(gettext("error reading file"));
 *	}
 */

	if (*(endp = (bp + (reclen = wcslen(bp))-1)) == '\n') {
		*endp = '\0';
		reclen--;
	}
	return (bp);
}

/*
 * Read a record separated by one character in the RS.
 * Compatible calling sequence with fgets, but don't include
 * record separator character in string.
 */
wchar_t *
charrecord(wchar_t *abp, int alim, FILE *fp)
{
	wchar_t *bp;
	wint_t c;
	int limit = alim;
	wint_t endc;

	bp = abp;
	endc = *(wchar_t *)varRS->n_string;
	while (--limit > 0 && (c = getwc(fp)) != endc && c != WEOF)
		*bp++ = c;
	*bp = '\0';
	reclen = bp-abp;
	return (c == WEOF && bp == abp ? NULL : abp);
}

/*
 * Special routine for multiple line records.
 */
wchar_t *
multirecord(wchar_t *abp, int limit, FILE *fp)
{
	wchar_t *bp;
	int c;

	while ((c = getwc(fp)) == '\n')
		;
	bp = abp;
	if (c != WEOF) do {
		if (--limit == 0)
			break;
		if (c == '\n' && bp[-1] == '\n')
			break;

		*bp++ = c;
	} while ((c = getwc(fp)) != WEOF);
	*bp = '\0';
	if (bp > abp)
		*--bp = '\0';
	reclen = bp-abp;
	return (c == WEOF && bp == abp ? NULL : abp);
}

/*
 * Look for fields separated by spaces, tabs or newlines.
 * Extract the next field, given pointer to start address.
 * Return pointer to beginning of field or NULL.
 * Reset end of field reference, which is the beginning of the
 * next field.
 */
wchar_t *
whitefield(wchar_t **endp)
{
	wchar_t *sp;
	wchar_t *ep;

	sp = *endp;
	while (*sp == ' ' || *sp == '\t' || *sp == '\n')
		++sp;
	if (*sp == '\0')
		return (NULL);
	for (ep = sp; *ep != ' ' && *ep != '\0' && *ep != '\t' &&
	    *ep != '\n'; ++ep)
		;
	*endp = ep;
	return (sp);
}

/*
 * Look for fields separated by non-whitespace characters.
 * Same calling sequence as whitefield().
 */
wchar_t *
blackfield(wchar_t **endp)
{
	wchar_t *cp;
	int endc;

	endc = *(wchar_t *)varFS->n_string;
	cp = *endp;
	if (*cp == '\0')
		return (NULL);
	if (*cp == endc && fcount != 0)
		cp++;
	if ((*endp = wcschr(cp, endc)) == NULL)
		*endp = wcschr(cp, '\0');
	return (cp);
}

/*
 * This field separation routine uses the same logic as
 * blackfield but uses a regular expression to separate
 * the fields.
 */
wchar_t *
refield(wchar_t **endpp)
{
	wchar_t *cp, *start;
	int flags;
	static	REGWMATCH_T match[10];
	int result;

	cp = *endpp;
	if (*cp == '\0') {
		match[0].rm_ep = NULL;
		return (NULL);
	}
	if (match[0].rm_ep != NULL) {
		flags = REG_NOTBOL;
		cp = (wchar_t *)match[0].rm_ep;
	} else
		flags = 0;
	start = cp;
again:
	switch ((result = REGWEXEC(resep, cp, 10, match, flags))) {
	case REG_OK:
		/*
		 * Check to see if a null string was matched. If this is the
		 * case, then move the current pointer beyond this position.
		 */
		if (match[0].rm_sp == match[0].rm_ep) {
			cp = (wchar_t *)match[0].rm_sp;
			if (*cp++ != '\0') {
				goto again;
			}
		}
		*endpp = (wchar_t *)match[0].rm_sp;
		break;
	case REG_NOMATCH:
		match[0].rm_ep = NULL;
		*endpp = wcschr(cp, '\0');
		break;
	default:
		(void) REGWERROR(result, resep, (char *)linebuf,
		    sizeof (linebuf));
		awkerr(gettext("error splitting record: %s"),
		    (char *)linebuf);
	}
	return (start);
}

/*
 * do begin processing
 */
void
dobegin()
{
	/*
	 * Free all keyword nodes to save space.
	 */
	{
		NODE *np;
		int nbuck;
		NODE *knp;

		np = NNULL;
		nbuck = 0;
		while ((knp = symwalk(&nbuck, &np)) != NNULL)
			if (knp->n_type == KEYWORD)
				delsymtab(knp, 1);
	}
	/*
	 * Copy ENVIRON array only if needed.
	 * Note the convoluted work to assign to an array
	 * and that the temporary nodes will be freed by
	 * freetemps() because we are "running".
	 */
	if (needenviron) {
		char **app;
		wchar_t *name, *value;
		NODE *namep = stringnode(_null, FSTATIC, 0);
		NODE *valuep = stringnode(_null, FSTATIC, 0);
		NODE *ENVsubname = node(INDEX, varENVIRON, namep);
		extern char **environ;

		/* (void) m_setenv(); XXX what's this do? */
		for (app = environ; *app != NULL; /* empty */) {
			name = mbstowcsdup(*app++);

			if ((value = wcschr(name, '=')) != NULL) {
				*value++ = '\0';
				valuep->n_strlen = wcslen(value);
				valuep->n_string = value;
			} else {
				valuep->n_strlen = 0;
				valuep->n_string = _null;
			}
			namep->n_strlen = wcslen(namep->n_string = name);
			(void) assign(ENVsubname, valuep);
			if (value != NULL)
				value[-1] = '=';
		}
	}
	phase = BEGIN;
	execute(yytree);
	phase = 0;
	if (npattern == 0)
		doend(0);
	/*
	 * Delete all pattern/action rules that are BEGIN at this
	 * point to save space.
	 * NOTE: this is not yet implemented.
	 */
}

/*
 * Do end processing.
 * Exit with a status
 */
void
doend(int s)
{
	OFILE *op;

	if (phase != END) {
		phase = END;
		awkinfp = stdin;
		execute(yytree);
	}
	for (op = &ofiles[0]; op < &ofiles[NIOSTREAM]; op++)
		if (op->f_fp != FNULL)
			awkclose(op);
	if (awkinfp == stdin)
		(void) fflush(awkinfp);
	exit(s);
}

/*
 * Print statement.
 */
void
s_print(NODE *np)
{
	FILE *fp;
	NODE *listp;
	char *ofs;
	int notfirst = 0;

	fp = openfile(np->n_right, 1, 1);
	if (np->n_left == NNULL)
		(void) fputs(mbunconvert(linebuf), fp);
	else {
		ofs = wcstombsdup((isstring(varOFS->n_flags)) ?
		    (wchar_t *)varOFS->n_string :
		    (wchar_t *)exprstring(varOFS));
		listp = np->n_left;
		while ((np = getlist(&listp)) != NNULL) {
			if (notfirst++)
				(void) fputs(ofs, fp);
			np = exprreduce(np);
			if (np->n_flags & FINT)
				(void) fprintf(fp, "%lld", (INT)np->n_int);
			else if (isstring(np->n_flags))
				(void) fprintf(fp, "%S", np->n_string);
			else
				(void) fprintf(fp,
				    mbunconvert((wchar_t *)exprstring(varOFMT)),
				    (double)np->n_real);
		}
		free(ofs);
	}
	(void) fputs(mbunconvert(isstring(varORS->n_flags) ?
	    (wchar_t *)varORS->n_string : (wchar_t *)exprstring(varORS)),
	    fp);
	if (ferror(fp))
		awkperr("error on print");
}

/*
 * printf statement.
 */
void
s_prf(NODE *np)
{
	FILE *fp;

	fp = openfile(np->n_right, 1, 1);
	(void) xprintf(np->n_left, fp, (wchar_t **)NULL);
	if (ferror(fp))
		awkperr("error on printf");
}

/*
 * Get next input line.
 * Read into variable on left of node (or $0 if NULL).
 * Read from pipe or file on right of node (or from regular
 * input if NULL).
 * This is an oddball inasmuch as it is a function
 * but parses more like the keywords print, etc.
 */
NODE *
f_getline(NODE *np)
{
	wchar_t *cp;
	INT ret;
	FILE *fp;
	size_t len;

	if (np->n_right == NULL && phase == END) {
		/* Pretend we've reached end of (the non-existant) file. */
		return (intnode(0));
	}

	if ((fp = openfile(np->n_right, 0, 0)) != FNULL) {
		if (np->n_left == NNULL) {
			ret = nextrecord(linebuf, fp);
		} else {
			cp = emalloc(NLINE * sizeof (wchar_t));
			ret = nextrecord(cp, fp);
			np = np->n_left;
			len = wcslen(cp);
			cp = erealloc(cp, (len+1)*sizeof (wchar_t));
			if (isleaf(np->n_flags)) {
				if (np->n_type == PARM)
					np = np->n_next;
				strassign(np, cp, FNOALLOC, len);
			} else
				(void) assign(np, stringnode(cp,
				    FNOALLOC, len));
		}
	} else
		ret = -1;
	return (intnode(ret));
}

/*
 * Open a file.  Flag is non-zero for output.
 */
static FILE *
openfile(NODE *np, int flag, int fatal)
{
	OFILE *op;
	char *cp;
	FILE *fp;
	int type;
	OFILE *fop;

	if (np == NNULL) {
		if (flag)
			return (stdout);
		if (awkinfp == FNULL)
			awkinfp = newfile();
		return (awkinfp);
	}
	if ((type = np->n_type) == APPEND)
		type = WRITE;
	cp = mbunconvert(exprstring(np->n_left));
	fop = (OFILE *)NULL;
	for (op = &ofiles[0]; op < &ofiles[NIOSTREAM]; op++) {
		if (op->f_fp == FNULL) {
			if (fop == (OFILE *)NULL)
				fop = op;
			continue;
		}
		if (op->f_mode == type && strcmp(op->f_name, cp) == 0)
			return (op->f_fp);
	}
	if (fop == (OFILE *)NULL)
		awkerr(gettext("too many open streams to %s onto \"%s\""),
		    flag ? "print/printf" : "getline", cp);
	(void) fflush(stdout);
	op = fop;
	if (cp[0] == '-' && cp[1] == '\0') {
		fp = flag ? stdout : stdin;
	} else {
		switch (np->n_type) {
		case WRITE:
			if ((fp = fopen(cp, w)) != FNULL) {
				if (isatty(fileno(fp)))
					(void) setvbuf(fp, 0, _IONBF, 0);
			}
			break;

		case APPEND:
			fp = fopen(cp, "a");
			break;

		case PIPE:
			fp = popen(cp, w);
			(void) setvbuf(fp, (char *)0, _IOLBF, 0);
			break;

		case PIPESYM:
			fp = popen(cp, r);
			break;

		case LT:
			fp = fopen(cp, r);
			break;

		default:
			awkerr(interr, "openfile");
		}
	}
	if (fp != FNULL) {
		op->f_name = strdup(cp);
		op->f_fp = fp;
		op->f_mode = type;
	} else if (fatal) {
		awkperr(flag ? gettext("output file \"%s\"") :
		    gettext("input file \"%s\""), cp);
	}
	return (fp);
}

/*
 * Close a stream.
 */
void
awkclose(OFILE *op)
{
	if (op->f_mode == PIPE || op->f_mode == PIPESYM)
		(void) pclose(op->f_fp);
	else if (fclose(op->f_fp) == EOF)
		awkperr("error on stream \"%s\"", op->f_name);
	op->f_fp = FNULL;
	free(op->f_name);
	op->f_name = NULL;
}

/*
 * Internal routine common to printf, sprintf.
 * The node is that describing the arguments.
 * Returns the number of characters written to file
 * pointer `fp' or the length of the string return
 * in cp. If cp is NULL then the file pointer is used. If
 * cp points to a string pointer, a pointer to an allocated
 * buffer will be returned in it.
 */
size_t
xprintf(NODE *np, FILE *fp, wchar_t **cp)
{
	wchar_t *fmt;
	int c;
	wchar_t *bptr = (wchar_t *)NULL;
	char fmtbuf[40];
	size_t length = 0;
	char *ofmtp;
	NODE *fnp;
	wchar_t *fmtsave;
	int slen;
	int cplen;

	fnp = getlist(&np);
	if (isleaf(fnp->n_flags) && fnp->n_type == PARM)
		fnp = fnp->n_next;
	if (isstring(fnp->n_flags)) {
		fmt = fnp->n_string;
		fmtsave = NULL;
	} else
		fmtsave = fmt = (wchar_t *)strsave(exprstring(fnp));

	/*
	 * if a char * pointer has been passed in then allocate an initial
	 * buffer for the string. Make it LINE_MAX plus the length of
	 * the format string but do reallocs only based LINE_MAX.
	 */
	if (cp != (wchar_t **)NULL) {
		cplen = LINE_MAX;
		bptr = *cp = emalloc(sizeof (wchar_t) * (cplen + wcslen(fmt)));
	}

	while ((c = *fmt++) != '\0') {
		if (c != '%') {
			if (bptr == (wchar_t *)NULL)
				awk_putwc(c, fp);
			else
				*bptr++ = c;
			++length;
			continue;
		}
		ofmtp = fmtbuf;
		*ofmtp++ = (char)c;
	nextc:
		switch (c = *fmt++) {
		case '%':
			if (bptr == (wchar_t *)NULL)
				awk_putwc(c, fp);
			else
				*bptr++ = c;
			++length;
			continue;

		case 'c':
			*ofmtp++ = 'w';
			*ofmtp++ = 'c';
			*ofmtp = '\0';
			fnp = exprreduce(nextarg(&np));
			if (isnumber(fnp->n_flags))
				c = exprint(fnp);
			else
				c = *(wchar_t *)exprstring(fnp);
			if (bptr == (wchar_t *)NULL)
				length += fprintf(fp, fmtbuf, c);
			else {
				/*
				 * Make sure that the buffer is long
				 * enough to hold the formatted string.
				 */
				adjust_buf(cp, &cplen, &bptr, fmtbuf, 0);
				/*
				 * Since the call to adjust_buf() has already
				 * guaranteed that the buffer will be long
				 * enough, just pass in INT_MAX as
				 * the length.
				 */
				(void) wsprintf(bptr, (const char *) fmtbuf, c);
				bptr += (slen = wcslen(bptr));
				length += slen;
			}
			continue;
/* XXXX Is this bogus? Figure out what s & S mean - look at original code */
		case 's':
		case 'S':
			*ofmtp++ = 'w';
			*ofmtp++ = 's';
			*ofmtp = '\0';
			if (bptr == (wchar_t *)NULL)
				length += fprintf(fp, fmtbuf,
				    (wchar_t *)exprstring(nextarg(&np)));
			else {
				wchar_t *ts = exprstring(nextarg(&np));

				adjust_buf(cp, &cplen, &bptr, fmtbuf,
				    wcslen(ts));
				(void) wsprintf(bptr, (const char *) fmtbuf,
				    ts);
				bptr += (slen = wcslen(bptr));
				length += slen;
			}
			continue;

		case 'o':
		case 'O':
		case 'X':
		case 'x':
		case 'd':
		case 'i':
		case 'D':
		case 'U':
		case 'u':
			*ofmtp++ = 'l';
			*ofmtp++ = 'l'; /* now dealing with long longs */
			*ofmtp++ = c;
			*ofmtp = '\0';
			if (bptr == (wchar_t *)NULL)
				length += fprintf(fp, fmtbuf,
				    exprint(nextarg(&np)));
			else {
				adjust_buf(cp, &cplen, &bptr, fmtbuf, 0);
				(void) wsprintf(bptr, (const char *) fmtbuf,
				    exprint(nextarg(&np)));
				bptr += (slen = wcslen(bptr));
				length += slen;
			}
			continue;

		case 'e':
		case 'E':
		case 'f':
		case 'F':
		case 'g':
		case 'G':
			*ofmtp++ = c;
			*ofmtp = '\0';
			if (bptr == (wchar_t *)NULL)
				length += fprintf(fp, fmtbuf,
				    exprreal(nextarg(&np)));
			else {
				adjust_buf(cp, &cplen, &bptr, fmtbuf, 0);
				(void) wsprintf(bptr, (const char *) fmtbuf,
				    exprreal(nextarg(&np)));
				bptr += (slen = wcslen(bptr));
				length += slen;
			}
			continue;

		case 'l':
		case 'L':
			break;

		case '*':
#ifdef M_BSD_SPRINTF
			sprintf(ofmtp, "%lld", (INT)exprint(nextarg(&np)));
			ofmtp += strlen(ofmtp);
#else
			ofmtp += sprintf(ofmtp, "%lld",
			    (INT)exprint(nextarg(&np)));
#endif
			break;

		default:
			if (c == '\0') {
				*ofmtp = (wchar_t)NULL;
				(void) fprintf(fp, "%s", fmtbuf);
				continue;
			} else {
				*ofmtp++ = (wchar_t)c;
				break;
			}
		}
		goto nextc;
	}
	if (fmtsave != NULL)
		free(fmtsave);
	/*
	 * If printing to a character buffer then make sure it is
	 * null-terminated and only uses as much space as required.
	 */
	if (bptr != (wchar_t *)NULL) {
		*bptr = '\0';
		*cp = erealloc(*cp, (length+1) * sizeof (wchar_t));
	}
	return (length);
}

/*
 * Return the next argument from the list.
 */
static NODE *
nextarg(NODE **npp)
{
	NODE *np;

	if ((np = getlist(npp)) == NNULL)
		awkerr(gettext("insufficient arguments to printf or sprintf"));
	if (isleaf(np->n_flags) && np->n_type == PARM)
		return (np->n_next);
	return (np);
}


/*
 * Check and adjust the length of the buffer that has been passed in
 * to make sure that it has space to accomodate the sequence string
 * described in fmtstr. This routine is used by xprintf() to allow
 * for arbitrarily long sprintf() strings.
 *
 * bp		= start of current buffer
 * len		= length of current buffer
 * offset	= offset in current buffer
 * fmtstr	= format string to check
 * slen		= size of string for %s formats
 */
static void
adjust_buf(wchar_t **bp, int *len, wchar_t **offset, char *fmtstr, size_t slen)
{
	int ioff;
	int width = 0;
	int prec = 0;

	do {
		fmtstr++;
	} while (strchr("-+ 0", *fmtstr) != (char *)0 || *fmtstr == ('#'));
	if (*fmtstr != '*') {
		if (isdigit(*fmtstr)) {
			width = *fmtstr-'0';
			while (isdigit(*++fmtstr))
				width = width * 10 + *fmtstr - '0';
		}
	} else
		fmtstr++;
	if (*fmtstr == '.') {
		if (*++fmtstr != '*') {
			prec = *fmtstr-'0';
			while (isdigit(*++fmtstr))
				prec = prec * 10 + *fmtstr - '0';
		} else
			fmtstr++;
	}
	if (strchr("Llh", *fmtstr) != (char *)0)
		fmtstr++;
	if (*fmtstr == 'S') {
		if (width && slen < width)
			slen = width;
		if (prec && slen > prec)
			slen = prec;
		width = slen+1;
	} else
		if (width == 0)
			width = NUMSIZE;

	if (*offset+ width > *bp+ *len) {
		ioff = *offset-*bp;
		*len += width+1;
		*bp = erealloc(*bp, *len * sizeof (wchar_t));
		*offset = *bp+ioff;
	}
}

static void
awk_putwc(wchar_t c, FILE *fp)
{
	char mb[MB_LEN_MAX];
	size_t mbl;

	if ((mbl = wctomb(mb, c)) > 0) {
		mb[mbl] = '\0';
		(void) fputs(mb, fp);
	} else
		awkerr(gettext("invalid wide character %x"), c);
}