Hello dev@, I was searching for a programming project so I looked into TODO and picked printf. I hacked something up a few weeks ago, but I didn't publish it until now, since I didn't have a man page yet and I was a little busy (a patch follows at the end of the mail). In the following I will tell a little bit about my implementation and point out differences to OpenBSD's printf implementation (since I am a OpenBSD user): First for my `algorithm': There is nothing much to say. My implementation simply skips through the format string until it finds a '\' or a '%' and prints everything before that character. If a '\' was found, the appropriate escape sequence is printed. If a '%' was found, it skips forward until it finds a supported conversion specifier, and passes everything starting from the '%' up until that point to printf(3) (plus the approprietly converted command line argument). And now for the differences: For a large part my implementation is similar to OpenBSD's version. Both support the same escape sequences, flags and conversion specifiers (which, as far as tested, behave the same). The major difference is error handling. While OpenBSD's version prints a warning to stderr and continues parsing its input, my version exits immediately. Also, my implementation does not check the syntax of the conversion specifier flags, but simply skips over them. This is for the following two reasons: 1.) printf(1) is mostly used for shell scripting, so I think it is ok to expect the user to pass a well formed format string and to check his arguments. Even if using a POSIX compliant printf(1), if you put in garbage you will in return get garbage (or at least not what you expected to get). 2.) This way of error handling makes the code simpler/shorter. For the man page: well, I really suck at writing man pages, especially since my english is quite shaky. Also, while writing the man page, I was wondering if it wasn't possible to simply use OpenBSD's man page (with a few modifications) since both implementations seem to be largely equivalent. I have the feeling that I forgot to mention something, but, well, it probably will come up again. Anyways I hope you like it.
Best regards, Maurice Quennet diff --git a/Makefile b/Makefile index 2a72a1c..e93f570 100644 --- a/Makefile +++ b/Makefile @@ -64,6 +64,7 @@ SRC = \ nohup.c \ paste.c \ printenv.c \ + printf.c \ pwd.c \ readlink.c \ renice.c \ diff --git a/printf.1 b/printf.1 new file mode 100644 index 0000000..e1b8f88 --- /dev/null +++ b/printf.1 @@ -0,0 +1,79 @@ +.TH PRINTF 1 sbase-VERSION +.SH NAME +printf \- formatted output +.SH "SYNOPSIS" +.PP +.B printf +.I format +[ +.I arguments ... +] +.SH DESCRIPTION +.B printf +formats and prints its +.I arguments +as instructed by the +.I format +string. +.P +If an encoding error occurs, such as an unsupported conversion specifier or +escape sequence, or if an argument is missing, +.B printf +will exit immediately without printing any further output. +.SH ESCAPE SEQUENCES +.B printf +supports the following escape sequences: +.TP +.B \ea +bell character +.TP +.B \eb +backspace character +.TP +.B \ee +escape character +.TP +.B \ef +form feed character +.TP +.B \en +new line character +.TP +.B \er +carriage return character +.TP +.B \et +tab character +.TP +.B \ev +vertical tab character +.TP +.B \e' +single quote character +.TP +.B \e\e +backslash character +.TP +.BI \ex hh +ascii character which is represented by the 1- or 2-digit hexadecimal number +.I hh +.TP +.BI \e num +ascii character which is represented by the 1-, 2- or 3-digit octal number +.I num +.SH CONVERSION SPECIFIERS +.B printf +supports the following conversion specifiers: a, A, c, d, e, E, f, F, g, G, i, o, u, x, X. +Since +.B printf +relies on its implementation, see +.IR printf (3) +for a detailed description of those conversion specifiers and their flags. +.P +Additionally the `b' conversion specifier is supported, which takes no flags and +prints its argument, expanding escape sequences. +.SH EXIT VALUES +.B printf +exits 0 on successful completion, and >0 if an error occurs. +.SH SEE ALSO +.IR printf (3) \ No newline at end of file diff --git a/printf.c b/printf.c new file mode 100644 index 0000000..3516a73 --- /dev/null +++ b/printf.c @@ -0,0 +1,203 @@ +#include <errno.h> +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> + +#include "util.h" + +/* macros from OpenBSD's printf(1) implementation */ +#define isodigit(c) ('0' <= (c) && (c) <= '7') +#define hextobin(c) ('A' <= (c) && (c) <= 'F' ? (c) - 'A' + 10 : \ + 'a' <= (c) && (c) <= 'f' ? (c) - 'a' + 10 : (c) - '0') +#define octtobin(c) ((c) - '0') + +static char **arg; +static char *end; +static char *fmt; + +static void printesc(void); +static void printfmt(void); +static void printhex(void); +static void printoct(void); +static void usage(void); + +void +printesc(void) +{ + if (isodigit(*fmt)) { + printoct(); + end = fmt; + return; + } + + switch (*fmt++) { + case 'x' : printhex(); break; + case 'e' : putchar(0x1B); break; + case 'a' : putchar('\a'); break; + case 'b' : putchar('\b'); break; + case 'f' : putchar('\f'); break; + case 'n' : putchar('\n'); break; + case 'r' : putchar('\r'); break; + case 't' : putchar('\t'); break; + case 'v' : putchar('\v'); break; + case '\'': putchar('\''); break; + case '\\': putchar('\\'); break; + default : eprintf("unknown escape sequence\n"); + } + + end = fmt; +} + +void +printfmt(void) +{ + int e; + long l; + double d; + char c, f, *tmp; + + if (*end == '%') { + putchar('%'); + fmt = ++end; + return; + } + + if (*arg == NULL) + eprintf("missing argument\n"); + + if (*end == 'b') { + fmt = *arg; + tmp = end; + + while (*fmt) { + if (*fmt == '\\') { + ++fmt; + printesc(); + continue; + } + putchar(*fmt); + ++fmt; + } + + fmt = end = tmp + 1; + return; + } + + for (; *end; ++end) { + if (!isdigit(*end) && *end != '#' && *end != '.' && + *end != '+' && *end != '-' && *end != ' ') + break; + } + + if (*end == '\0') + eprintf("%s: invalid directive\n", fmt); + + f = *end; + c = *++end; + *end = '\0'; + + switch (f) { + case 'c': + printf(fmt, **arg++); + break; + case 's': + printf(fmt, *arg++); + break; + case 'd': case 'i': case 'o': + case 'u': case 'X': case 'x': + e = errno; + errno = 0; + l = strtol(*arg, NULL, 0); + if (errno) + eprintf("%s: invalid value\n", *arg); + printf(fmt, l); + ++arg; + errno = e; + break; + case 'a': case 'A': case 'e': case 'E': + case 'f': case 'F': case 'g': case 'G': + e = errno; + errno = 0; + d = strtod(*arg, NULL); + if (errno) + eprintf("%s: invalid value\n", *arg); + printf(fmt, d); + ++arg; + errno = e; + break; + default: + eprintf("%s: invalid directive\n", fmt); + } + + *end = c; + fmt = end; +} + +void +printhex(void) +{ + int c, i; + + for (c = i = 0; i < 2 && isxdigit(*fmt); ++i, ++fmt) + c = (c << 4) | hextobin(*fmt); + + putchar(c); +} + +void +printoct(void) +{ + int c, i; + + for (c = i = 0; i < 3 && isodigit(*fmt); ++i, ++fmt) + c = (c << 3) | octtobin(*fmt); + + putchar(c); +} + +void +usage(void) +{ + eprintf("usage: %s format [args ...]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + argv0 = argv[0]; + + if (argc < 2) + usage(); + + arg = argv + 2; + end = argv[1]; + fmt = argv[1]; + + while (*end) { + switch (*end) { + case '\\': + *end = '\0'; + fputs(fmt, stdout); + *end = '\\'; + fmt = end + 1; + printesc(); + break; + case '%': + *end = '\0'; + fputs(fmt, stdout); + *end = '%'; + fmt = end++; + printfmt(); + break; + default: + ++end; + break; + } + } + + if (*fmt) + fputs(fmt, stdout); + + return EXIT_SUCCESS; +} +