minix/commands/simple/printf.c
2005-04-21 14:53:53 +00:00

472 lines
9 KiB
C
Executable file

#if ever
static char sccsid[] = "@(#)printf.c (U of Maryland) FLB 6-Jan-1987";
static char RCSid[] = "@(#)$Header$";
#endif
/*
* 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$
* Revision 1.1 2005/04/21 14:55:31 beng
* Initial revision
*
* Revision 1.1.1.1 2005/04/20 13:33:30 beng
* Initial import of minix 2.0.4
*
* 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 <stdlib.h>
#include <string.h>
#define EX_OK 0
#define EX_USAGE 1
int ctrl(char *s);
#define atoi(a) strtoul((a), NULL, 0)
/****************************************************************************/
int main(int argc, char *argv[])
{
register char *cp, *conv_spec, **argp, **ep;
char *ctor(int x);
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. */
/* 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 '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);
}
/****************************************************************************/
/* Convert backslash notation to control characters, in place. */
int ctrl(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] &&
strchr("01234567", s[1])
&& --digits > 0;
s++);
}
break;
case 'x': /* hex constant */
case 'X':
s++;
{
register int digits;
val = 0;
(void) sscanf(s, "%3x", &val);
*op++ = val;
for (digits = 3; *s && s[1] &&
strchr("0123456789abcdefABCDEF",
s[1])
&& --digits > 0;
s++);
}
break;
}
else
*op++ = *s;
out:
*op = '\0';
}
/****************************************************************************/
/* 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', 'D', },
{ 10, 'X', 'L', },
{ 1, 'I', 'V', },
};
char *ctor(int x)
{
register struct roman *mp;
static char buf[BUFSIZ];
register char *cp = buf;
/* 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;
}
/****************************************************************************/