[dev] [sbase] printf(1)

From: Maurice Quennet <mjq_AT_web.de>
Date: Wed, 18 Dec 2013 23:36:12 +0100

Hello dev_AT_,

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
_AT_@ -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
_AT_@ -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
_AT_@ -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;
+}
+
Received on Wed Dec 18 2013 - 23:36:12 CET

This archive was generated by hypermail 2.3.0 : Wed Dec 18 2013 - 23:48:06 CET