[mod.sources] v09i023: Printf

sources-request@mirror.UUCP (03/13/87)

Submitted by: Fred Blonder <fred@mimsy.umd.edu>
Mod.sources: Volume 9, Issue 23
Archive-name: printf

Here's a transportable version of the ``printf'' program.
[  I wrote the Makefile and spliced it into the shar.  This program isn't
   as much fun as Fred's original one, which ended with a call to
   "printf()" but it is more transportable....  --r$  ]

================================ CUT HERE ===============================
: Run this shell script with "sh" not "csh"
PATH=/bin:/usr/bin:/usr/ucb:/etc:$PATH
export PATH
all=FALSE
sed 's/^X//' <<'//go.sysin dd *' >Makefile
# Quickie makefile
CFLAGS	= -C
printf:		printf.c
	$(CC) $(CFLAGS) -o printf printf.c
# Edit appropriately
DESTPROG=/usr/local/bin/printf
DESTMAN =/usr/man/man1/printf.1
install:	printf
	cp printf $(DESTPROG)
	chmod 755 $(DESTPROG)
	strip $(DESTPROG)
	cp printf.1 $(DESTMAN)
	chmod 444 $(DESTMAN)
//go.sysin dd *
if [ x$1 = x-a ]; then
	all=TRUE
fi
echo Extracting printf.1
sed 's/^X//' <<'//go.sysin dd *' >printf.1
X.\"	@(#)printf.1	8-Jan-1987
X.\"
X.TH PRINTF 1 "8-Jan-1987"
X.AT 3
X.SH NAME
printf \- formatted print at shell command level
X.SH SYNOPSIS
X.B "printf <format-string>"
[
X.B arg1
] [
X.B arg2
] ...
X.SH DESCRIPTION
X.I Printf
duplicates \- as far as possible \- the standard C library routine of the
same name, at the shell command level. It is similar to
X.I echo,
except that it formats its arguments according to conversion specifications
given in the
X.B format-string,
before writing them to the standard output.
XFor a thorough explanation of format specifications, see the manual entry
for the printf subroutine.
X.PP
XFor the sake of perversity,
X.I printf
implements one format conversion
X.B not
supported by the standard printf subroutine: the %r conversion, which prints
integers as Roman numerals.
X.PP
As a convenience, within the
X.I format-string
and any string or character arguments,
X.I printf
converts "backslash notation" \- as defined in the ANSII draft C
standard \- into the appropriate control characters.
X.SH EXAMPLES
X.nf
X.na
X.sp 2
% printf 'Today is %s the %d of %s.\\n' Monday 1 April
Today is Monday the 1 of April.
X.sp 3
% printf 'Interesting Numbers\\n\\n\\tPie: %*.*f\\n\\tFoo: %g\\n' \\
	6 4 3.1415927 42
Interesting Numbers

	Pie: 3.1416
	Foo: 42
X.sp 3
% printf '%s %d, %r\\n' July 4 1776
July 4, MCCCCCCCLXXVI
X.sp 2
X.fi
X.ad
X.SH AUTHOR
XFred Blonder <fred@Mimsy.umd.edu>
X.SH "SEE ALSO"
echo(1), printf(3)

//go.sysin dd *
if [ `wc -c < printf.1` != 1420 ]; then
	made=FALSE
	echo error transmitting printf.1 --
	echo length should be 1420, not `wc -c < printf.1`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 printf.1
	echo -n '	'; ls -ld printf.1
fi
echo Extracting printf.c
sed 's/^X//' <<'//go.sysin dd *' >printf.c
#ifndef lint
static char sccsid[] = "@(#)printf.c	(U of Maryland) FLB 6-Jan-1987";
static char RCSid[] = "@(#)$Header: printf.c,v 1.4 87/01/29 20:52:30 fred Exp $";
#endif

X/*
 * Printf - Duplicate the C library routine of the same name, but from
 *	    the shell command level.
 *
 * Fred Blonder <fred@Mimsy.umd.edu>
 *
 * To Compile:
 %	cc -s -O printf.c -o printf
 *
 * $Log:	printf.c,v $
 * Revision 1.4  87/01/29  20:52:30  fred
 * Re-installed backslash-notation conversion for string & char arguments.
 * 
 * Revision 1.3  87/01/29  20:44:23  fred
 * Converted to portable algorithm.
 * Added Roman format for integers.
 * 	29-Jan-87  FLB
 * 
 * Revision 1.2  87/01/09  19:10:57  fred
 * Fixed bug in argument-count error-checking.
 * Changed backslash escapes within strings to correspond to ANSII C
 * 	draft standard.  (9-Jan-87 FLB)
 * 
 */

#include <stdio.h>
#include <sysexits.h>

X/****************************************************************************/

main(argc, argv)
int argc;
char *argv[];
{
register char *cp, *conv_spec, **argp, **ep;
char *strncpy(), *index(), *ctor();
double atof();

if (argc < 2) {
	fprintf(stderr,
		"printf: Usage: printf <format-string> [ arg1 . . . ]\n");
	exit(EX_USAGE);
	}

argp = &argv[2];	/* Point at first arg (if any) beyond format string. */
ep = &argv[argc];	/* Point beyond last arg. */

ctrl(argv[1]);	/* Change backslash notation to control chars in fmt string. */

X/* Scan format string for conversion specifications, and do appropriate
   conversion on the corresponding argument. */
for (cp = argv[1]; *cp; cp++) {
register int dynamic_count;

	/* Look for next conversion spec. */
	while (*cp && *cp != '%') {
		putchar(*cp++);
		}

	if (!*cp)	/* End of format string */
		break;
		
	dynamic_count = 0;	/* Begin counting dynamic field width specs. */
	conv_spec = cp++;	/* Remember where this conversion begins. */

	for (;*cp; cp++) {	/* Scan until conversion character. */
		char conv_buf[BUFSIZ];	/* Save conversion string here. */
		register int conv_len;	/* Length of ``conv_buf''. */

		switch (*cp) {	/* Field-width spec.: Keep scanning. */
			case '.': case '0': case '1': case '2': case '3':
			case '4': case '5': case '6': case '7': case '8':
			case '9':
				continue;

			case '*':	/* Dynamic field-width spec */
				dynamic_count++;
				continue;

			case 's':	/* String */
				if (&argp[dynamic_count] >= ep) {
					fprintf(stderr,
					"printf: Not enough args for format.\n"
						);
					exit(EX_USAGE);
					}

				(void) strncpy(conv_buf, conv_spec,
					conv_len = cp - conv_spec + 1);
				conv_buf[conv_len] = '\0';

				switch (dynamic_count) {
					case 0:
						ctrl(*argp);
						printf(conv_buf, *argp++);
						break;

					case 1:
						{
						register int a1;

						a1 = atoi(*argp++);
						ctrl(*argp);
						printf(conv_buf, a1, *argp++);
						}
						break;

					case 2:
						{
						register int a1, a2;

						a1 = atoi(*argp++);
						a2 = atoi(*argp++);
						ctrl(*argp);
						printf(conv_buf, a1, a2, *argp++);
						}
						break;

					}
				goto out;

			case 'c':	/* Char */
				if (&argp[dynamic_count] >= ep) {
					fprintf(stderr,
					"printf: Not enough args for format.\n"
						);
					exit(EX_USAGE);
					}

				(void) strncpy(conv_buf, conv_spec,
					conv_len = cp - conv_spec + 1);
				conv_buf[conv_len] = '\0';

				switch (dynamic_count) {
					case 0:
						ctrl(*argp);
						printf(conv_buf, **argp++);
						break;

					case 1:
						{
						register int a1;

						a1 = atoi(*argp++);
						ctrl(*argp);
						printf(conv_buf, a1, **argp++);
						}
						break;

					case 2:
						{
						register int a1, a2;

						a1 = atoi(*argp++);
						a2 = atoi(*argp++);
						ctrl(*argp);
						printf(conv_buf, a1, a2, **argp++);
						}
						break;
					}
				goto out;

			case 'd':	/* Integer */
			case 'o':
			case 'x':
			case 'u':
				if (&argp[dynamic_count] >= ep) {
					fprintf(stderr,
					"printf: Not enough args for format.\n"
						);
					exit(EX_USAGE);
					}

				(void) strncpy(conv_buf, conv_spec,
					conv_len = cp - conv_spec + 1);
				conv_buf[conv_len] = '\0';

				switch (dynamic_count) {
					case 0:
						printf(conv_buf, atoi(*argp++));
						break;

					case 1:
						{
						register int a1;

						a1 = atoi(*argp++);
						printf(conv_buf, a1, atoi(*argp++));
						}
						break;

					case 2:
						{
						register int a1, a2;

						a1 = atoi(*argp++);
						a2 = atoi(*argp++);
						printf(conv_buf, a1, a2, atoi(*argp++));
						}
						break;

					}
				goto out;

			case 'f':	/* Real */
			case 'e':
			case 'g':
				if (&argp[dynamic_count] >= ep) {
					fprintf(stderr,
					"printf: Not enough args for format.\n"
						);
					exit(EX_USAGE);
					}

				(void) strncpy(conv_buf, conv_spec,
					conv_len = cp - conv_spec + 1);
				conv_buf[conv_len] = '\0';

				switch (dynamic_count) {
					case 0:
						printf(conv_buf, atof(*argp++));
						break;

					case 1:
						{
						register int a1;

						a1 = atoi(*argp++);
						printf(conv_buf, a1, atof(*argp++));
						}
						break;

					case 2:
						{
						register int a1, a2;

						a1 = atoi(*argp++);
						a2 = atoi(*argp++);
						printf(conv_buf, a1, a2, atof(*argp++));
						}
						break;

					}
				goto out;

			case 'r':	/* Roman (Well, why not?) */
				if (&argp[dynamic_count] >= ep) {
					fprintf(stderr,
					"printf: Not enough args for format.\n"
						);
					exit(EX_USAGE);
					}

				(void) strncpy(conv_buf, conv_spec,
					conv_len = cp - conv_spec + 1);
				conv_buf[conv_len] = '\0';
				conv_buf[conv_len - 1] = 's';

				switch (dynamic_count) {
					case 0:
						printf(conv_buf,
							ctor(atoi(*argp++)));
						break;

					case 1:
						{
						register int a1;

						a1 = atoi(*argp++);
						printf(conv_buf, a1,
							ctor(atoi(*argp++)));
						}
						break;

					case 2:
						{
						register int a1, a2;

						a1 = atoi(*argp++);
						a2 = atoi(*argp++);
						printf(conv_buf, a1, a2,
							ctor(atoi(*argp++)));
						}
						break;

					}
				goto out;

			case '%':	/* Boring */
				putchar('%');
				break;

			default:	/* Probably an error, but let user
					   have his way. */
				continue;
			}
		}
	out: ;
	}

exit(EX_OK);
}

X/****************************************************************************/

X/* Convert backslash notation to control characters, in place. */

ctrl(s)
register char *s;
{
register char *op;
static int val;

for (op = s; *s; s++)
	if (*s == '\\')
		switch (*++s) {
			case '\0':	/* End-of-string: user goofed */
				goto out;

			case '\\':	/* Backslash */
				*op++ = '\\';
				break;

			case 'n':	/* newline */
				*op++ = '\n';
				break;

			case 't':	/* horizontal tab */
				*op++ = '\t';
				break;

			case 'r':	/* carriage-return */
				*op++ = '\r';
				break;

			case 'f':	/* form-feed */
				*op++ = '\f';
				break;

			case 'b':	/* backspace */
				*op++ = '\b';
				break;

			case 'v':	/* vertical tab */
				*op++ = '\13';
				break;

			case 'a':	/* WARNING! DANGER! DANGER! DANGER! */
				*op++ = '\7';
				break;

			case '0': case '1': case '2': case '3':
			case '4': case '5': case '6': case '7':
				{	/* octal constant */
				register int digits;

				val = 0;
				(void) sscanf(s, "%3o", &val);
				*op++ = val;
				for (digits = 3; s[1] &&
					(int)index("01234567", s[1])
					&& --digits > 0;
						s++);
				}
				break;

			case 'x':	/* hex constant */
				s++;
				{
				register int digits;

				val = 0;
				(void) sscanf(s, "%3x", &val);
				*op++ = val;
				for (digits = 3; *s && s[1] &&
					(int)index("0123456789abcdefABCDEF",
									s[1])
					&& --digits > 0;
						s++);
				}
				break;

			}
	else
		*op++ = *s;

out:

*op = '\0';
}

X/****************************************************************************/

X/* Convert integer to Roman Numerals. (Have have you survived without it?) */

struct roman {
	unsigned r_mag;
	char r_units, r_fives;
	} roman[] = {
		1000, 'M', '\0',
		 100, 'C', '\0',
		  10, 'X', 'L',
		   1, 'I', 'V'
		};

char *
ctor(x)
register int x;
{
register struct roman *mp;
static char buf[BUFSIZ];
register char *cp = buf;

X/* I've never actually seen a roman numeral with a minus-sign.
   Probably ought to print out some appropriate latin phrase instead. */
if (x < 0) {
	*cp++ = '-';
	x = -x;
	}

for (mp = roman; x; mp++) {
	register unsigned units;

	units = x / mp->r_mag;
	x = x % mp->r_mag;

	if (cp > &buf[BUFSIZ-2])
		return "???";

	if (units == 9 && mp > roman) {	/* Do inverse notation: Eg: ``IX''. */
		*cp++ = mp->r_units;
		*cp++ = mp[-1].r_units;
		}
	else if (units == 4 && mp->r_fives) {
		/* Inverse notation for half-decades: Eg: ``IV'' */
		*cp++ = mp->r_units;
		*cp++ = mp->r_fives;
		}
	else {	/* Additive notation */
		if (units >= 5 && mp->r_fives) {
			*cp++ = mp->r_fives;
			units -= 5;
			}
		while (units--) {
			*cp++ = mp->r_units;
			if (cp > &buf[BUFSIZ-5])
				return "???";
			}
		}
	}

*cp = '\0';

return buf;
}

X/****************************************************************************/
//go.sysin dd *
if [ `wc -c < printf.c` != 9057 ]; then
	made=FALSE
	echo error transmitting printf.c --
	echo length should be 9057, not `wc -c < printf.c`
else
	made=TRUE
fi
if [ $made = TRUE ]; then
	chmod 644 printf.c
	echo -n '	'; ls -ld printf.c
fi
-- 
					Fred Blonder (301) 454-7690
					seismo!mimsy!fred
					Fred@Mimsy.umd.edu