aboutsummaryrefslogtreecommitdiff
path: root/toys/posix/printf.c
diff options
context:
space:
mode:
authorRob Landley <rob@landley.net>2015-01-11 10:17:58 -0600
committerRob Landley <rob@landley.net>2015-01-11 10:17:58 -0600
commitd3d633ff13187c34c3a04ce20b9ffcfb85c2c383 (patch)
treede3f9bd6e5655100ac0d8a8aaafa834d2b834fec /toys/posix/printf.c
parentd0dead30a53c0353fd1c31aa5183fdcc2b30491b (diff)
downloadtoybox-d3d633ff13187c34c3a04ce20b9ffcfb85c2c383.tar.gz
Promote printf.
Diffstat (limited to 'toys/posix/printf.c')
-rw-r--r--toys/posix/printf.c143
1 files changed, 143 insertions, 0 deletions
diff --git a/toys/posix/printf.c b/toys/posix/printf.c
new file mode 100644
index 00000000..5fec4f96
--- /dev/null
+++ b/toys/posix/printf.c
@@ -0,0 +1,143 @@
+/* printf.c - Format and Print the data.
+ *
+ * Copyright 2014 Sandeep Sharma <sandeep.jack2756@gmail.com>
+ * Copyright 2014 Kyungwan Han <asura321@gmail.com>
+ *
+ * See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html
+ *
+ * todo: *m$ ala printf("%1$d:%2$.*3$d:%4$.*3$d\n", hour, min, precision, sec);
+
+USE_PRINTF(NEWTOY(printf, "<1", TOYFLAG_USR|TOYFLAG_BIN))
+
+config PRINTF
+ bool "printf"
+ default y
+ help
+ usage: printf FORMAT [ARGUMENT...]
+
+ Format and print ARGUMENT(s) according to FORMAT, using C printf syntax
+ (% escapes for cdeEfgGiosuxX, \ escapes for abefnrtv0 or \OCTAL or \xHEX).
+*/
+
+#define FOR_printf
+#include "toys.h"
+
+// Detect matching character (return true/valse) and advance pointer if match.
+static int eat(char **s, char c)
+{
+ int x = (**s == c);
+
+ if (x) ++*s;
+
+ return x;
+}
+
+// Parse escape sequences.
+static int handle_slash(char **esc_val)
+{
+ char *ptr = *esc_val;
+ int len, base = 0;
+ unsigned result = 0, num;
+
+ if (*ptr == 'c') xexit();
+
+ // 0x12 hex escapes have 1-2 digits, \123 octal escapes have 1-3 digits.
+ if (eat(&ptr, 'x')) base = 16;
+ else if (*ptr >= '0' && *ptr <= '8') base = 8;
+ len = (char []){0,3,2}[base/8];
+
+ // Not a hex or octal escape? (This catches trailing \)
+ if (!len) {
+ if (!(result = unescape(*ptr))) result = '\\';
+ else ++*esc_val;
+
+ return result;
+ }
+
+ while (len) {
+ num = tolower(*ptr) - '0';
+ if (num >= 'a'-'0') num += '0'-'a'+10;
+ if (num >= base) {
+ // Don't parse invalid hex value ala "\xvd", print it verbatim
+ if (base == 16 && len == 2) {
+ ptr--;
+ result = '\\';
+ }
+ break;
+ }
+ result = (result*base)+num;
+ ptr++;
+ len--;
+ }
+ *esc_val = ptr;
+
+ return result;
+}
+
+void printf_main(void)
+{
+ char **arg = toys.optargs+1;
+
+ // Repeat format until arguments consumed
+ for (;;) {
+ int seen = 0;
+ char *f = *toys.optargs;
+
+ // Loop through characters in format
+ while (*f) {
+ if (eat(&f, '\\')) putchar(handle_slash(&f));
+ else if (!eat(&f, '%') || *f == '%') putchar(*f++);
+
+ // Handle %escape
+ else {
+ char c, *end = 0, *aa, *to = toybuf;
+ int wp[] = {0,-1}, i;
+
+ // Parse width.precision between % and type indicator.
+ *to++ = '%';
+ while (strchr("-+# '0", *f) && (to-toybuf)<10) *to++ = *f++;
+ for (i=0; i<2; i++) {
+ if (eat(&f, '*')) {
+ if (*arg) wp[i] = atolx(*arg++);
+ } else while (*f >= '0' && *f <= '9') {
+ if (wp[i]<0) wp[i] = 0;
+ wp[i] = (wp[i]*10)+(*f++)-'0';
+ }
+ if (!eat(&f, '.')) break;
+ }
+ c = *f++;
+ seen = sprintf(to, "*.*%c", c);;
+ errno = 0;
+ aa = *arg ? *arg++ : "";
+
+ // Output %esc using parsed format string
+ if (c == 'b') {
+ while (*aa) putchar(eat(&aa, '\\') ? handle_slash(&aa) : *aa++);
+
+ continue;
+ } else if (c == 'c') printf(toybuf, wp[0], wp[1], *aa);
+ else if (c == 's') printf(toybuf, wp[0], wp[1], aa);
+ else if (strchr("diouxX", c)) {
+ long ll;
+
+ if (*aa == '\'' || *aa == '"') ll = aa[1];
+ else ll = strtoll(aa, &end, 0);
+
+ sprintf(to, "*.*ll%c", c);
+ printf(toybuf, wp[0], wp[1], ll);
+ } else if (strchr("feEgG", c)) {
+ long double ld = strtold(aa, &end);
+
+ sprintf(to, "*.*L%c", c);
+ printf(toybuf, wp[0], wp[1], ld);
+ } else error_exit("bad %%%c@%ld", c, f-*toys.optargs);
+
+ if (end && (errno || *end)) perror_msg("bad %%%c %s", c, aa);
+ }
+ }
+
+ // Posix says to keep looping through format until we consume all args.
+ // This only works if the format actually consumed at least one arg.
+ if (!seen || !*arg) break;
+ }
+}