master xplshn/aruu / cmd / posix / printf.c
  1/* See LICENSE file for copyright and license details. */
  2
  3#include <ctype.h>
  4#include <errno.h>
  5#include <limits.h>
  6#include <stdio.h>
  7#include <stdlib.h>
  8#include <string.h>
  9
 10#include "utf.h"
 11#include "util.h"
 12
 13#define is_odigit(c) ('0' <= (c) && (c) <= '7')
 14
 15static size_t
 16unescape_pct(char *s, char *is_pct)
 17{
 18  static const char escapes[256] = {
 19      ['"']  = '"',
 20      ['\''] = '\'',
 21      ['\\'] = '\\',
 22      ['a']  = '\a',
 23      ['b']  = '\b',
 24      ['E']  = 033,
 25      ['e']  = 033,
 26      ['f']  = '\f',
 27      ['n']  = '\n',
 28      ['r']  = '\r',
 29      ['t']  = '\t',
 30      ['v']  = '\v'
 31  };
 32  size_t m, q;
 33  char  *r, *w;
 34
 35  for (r = w = s; *r;) {
 36    if (*r != '\\') {
 37      is_pct[w - s] = (*r == '%');
 38      *w++          = *r++;
 39      continue;
 40    }
 41    r++;
 42    if (!*r) {
 43      eprintf("null escape sequence\n");
 44    } else if (escapes[(unsigned char)*r]) {
 45      is_pct[w - s] = 0;
 46      *w++          = escapes[(unsigned char)*r++];
 47    } else if (is_odigit(*r)) {
 48      for (q = 0, m = 3; m && is_odigit(*r); m--, r++)
 49        q = q * 8 + (*r - '0');
 50      is_pct[w - s] = 0;
 51      *w++          = MIN(q, (size_t)255);
 52    } else if (*r == 'x' && isxdigit(r[1])) {
 53      r++;
 54      for (q = 0, m = 2; m && isxdigit(*r); m--, r++)
 55        if (isdigit(*r))
 56          q = q * 16 + (*r - '0');
 57        else
 58          q = q * 16 + (tolower(*r) - 'a' + 10);
 59      is_pct[w - s] = 0;
 60      *w++          = q;
 61    } else {
 62      eprintf("invalid escape sequence '\\%c'\n", *r);
 63    }
 64  }
 65  *w = '\0';
 66
 67  return w - s;
 68}
 69
 70static void
 71usage(void)
 72{
 73  eprintf("usage: %s format [arg ...]\n", argv0);
 74}
 75
 76// ?man printf: format and print data
 77// ?man arguments: format [arg ...]
 78// ?man format and print arguments to standard output
 79int
 80main(int argc, char *argv[])
 81{
 82  Rune     *rarg;
 83  size_t    i, j, f, formatlen, blen, nflags;
 84  long long num;
 85  double    dou;
 86  int       cooldown = 0, width, precision, ret = 0, argi, lastargi;
 87  int       has_width, has_precision;
 88  char     *format, *tmp, *arg, *fmt, *fmt_ptr, *is_pct;
 89
 90  argv0 = argv[0];
 91  if (argc < 2)
 92    usage();
 93
 94  format = argv[1];
 95  if ((tmp = strstr(format, "\\c"))) {
 96    *tmp     = 0;
 97    cooldown = 1;
 98  }
 99  is_pct    = ecalloc(strlen(format) + 1, sizeof(*is_pct));
100  formatlen = unescape_pct(format, is_pct);
101  if (formatlen == 0) {
102    free(is_pct);
103    return 0;
104  }
105  lastargi = 0;
106  for (i = 0, argi = 2; !cooldown || i < formatlen; i++, i = cooldown ? i : (i % formatlen)) {
107    if (i == 0) {
108      if (lastargi == argi)
109        break;
110      lastargi = argi;
111    }
112
113    if (format[i] != '%' || !is_pct[i]) {
114      putchar(format[i]);
115      continue;
116    }
117
118    /* flag */
119    f      = ++i;
120    nflags = strspn(&format[f], "#-+ 0");
121    i += nflags;
122
123    if (nflags > INT_MAX)
124      eprintf("Too many flags in format\n");
125
126    /* field width */
127    has_width = 0;
128    width     = 0;
129    if (format[i] == '*') {
130      has_width = 1;
131      if (argi < argc)
132        width = estrtonum(argv[argi++], INT_MIN, INT_MAX);
133      else
134        cooldown = 1;
135      i++;
136    } else {
137      j = i;
138      i += strspn(&format[i], "+-0123456789");
139      if (j != i) {
140        has_width = 1;
141        tmp       = estrndup(format + j, i - j);
142        width     = estrtonum(tmp, INT_MIN, INT_MAX);
143        free(tmp);
144      }
145    }
146
147    /* field precision */
148    has_precision = 0;
149    precision     = 0;
150    if (format[i] == '.') {
151      has_precision = 1;
152      if (format[++i] == '*') {
153        if (argi < argc)
154          precision = estrtonum(argv[argi++], INT_MIN, INT_MAX);
155        else
156          cooldown = 1;
157        i++;
158      } else {
159        j = i;
160        i += strspn(&format[i], "+-0123456789");
161        if (j != i) {
162          tmp       = estrndup(format + j, i - j);
163          precision = estrtonum(tmp, INT_MIN, INT_MAX);
164          free(tmp);
165        }
166      }
167    }
168
169    if (format[i] != '%' || !is_pct[i]) {
170      if (argi < argc)
171        arg = argv[argi++];
172      else {
173        arg      = "";
174        cooldown = 1;
175      }
176    } else {
177      putchar('%');
178      continue;
179    }
180
181    switch (format[i]) {
182      case 'b':
183        if ((tmp = strstr(arg, "\\c"))) {
184          *tmp = 0;
185          blen = unescape(arg);
186          fwrite(arg, sizeof(*arg), blen, stdout);
187          free(is_pct);
188          return 0;
189        }
190        blen = unescape(arg);
191        fwrite(arg, sizeof(*arg), blen, stdout);
192        break;
193      case 'c':
194        unescape(arg);
195        rarg = ereallocarray(NULL, utflen(arg) + 1, sizeof(*rarg));
196        utftorunestr(arg, rarg);
197        efputrune(rarg, stdout, "<stdout>");
198        free(rarg);
199        break;
200      case 's':
201        fmt        = emalloc(nflags + 10);
202        fmt_ptr    = fmt;
203        *fmt_ptr++ = '%';
204        memcpy(fmt_ptr, &format[f], nflags);
205        fmt_ptr += nflags;
206        if (has_width)
207          *fmt_ptr++ = '*';
208        if (has_precision) {
209          *fmt_ptr++ = '.';
210          *fmt_ptr++ = '*';
211        }
212        *fmt_ptr++ = 's';
213        *fmt_ptr   = '\0';
214
215        if (has_width && has_precision)
216          printf(fmt, width, precision, arg);
217        else if (has_width)
218          printf(fmt, width, arg);
219        else if (has_precision)
220          printf(fmt, precision, arg);
221        else
222          printf(fmt, arg);
223        free(fmt);
224        break;
225      case 'd':
226      case 'i':
227      case 'o':
228      case 'u':
229      case 'x':
230      case 'X':
231        for (j = 0; isspace(arg[j]); j++)
232          ;
233        if (arg[j] == '\'' || arg[j] == '\"') {
234          arg += j + 1;
235          unescape(arg);
236          rarg = ereallocarray(NULL, utflen(arg) + 1, sizeof(*rarg));
237          utftorunestr(arg, rarg);
238          num = rarg[0];
239        } else if (arg[0]) {
240          errno = 0;
241          if (format[i] == 'd' || format[i] == 'i')
242            num = strtol(arg, &tmp, 0);
243          else
244            num = strtoul(arg, &tmp, 0);
245
246          if (tmp == arg || *tmp != '\0') {
247            ret = 1;
248            weprintf("%%%c %s: conversion error\n", format[i], arg);
249          }
250          if (errno == ERANGE) {
251            ret = 1;
252            weprintf("%%%c %s: out of range\n", format[i], arg);
253          }
254        } else {
255          num = 0;
256        }
257        fmt        = emalloc(nflags + 15);
258        fmt_ptr    = fmt;
259        *fmt_ptr++ = '%';
260        memcpy(fmt_ptr, &format[f], nflags);
261        fmt_ptr += nflags;
262        if (has_width)
263          *fmt_ptr++ = '*';
264        if (has_precision) {
265          *fmt_ptr++ = '.';
266          *fmt_ptr++ = '*';
267        }
268        *fmt_ptr++ = 'l';
269        *fmt_ptr++ = 'l';
270        *fmt_ptr++ = format[i];
271        *fmt_ptr   = '\0';
272
273        if (has_width && has_precision)
274          printf(fmt, width, precision, num);
275        else if (has_width)
276          printf(fmt, width, num);
277        else if (has_precision)
278          printf(fmt, precision, num);
279        else
280          printf(fmt, num);
281        free(fmt);
282        break;
283      case 'a':
284      case 'A':
285      case 'e':
286      case 'E':
287      case 'f':
288      case 'F':
289      case 'g':
290      case 'G':
291        fmt        = emalloc(nflags + 15);
292        fmt_ptr    = fmt;
293        *fmt_ptr++ = '%';
294        memcpy(fmt_ptr, &format[f], nflags);
295        fmt_ptr += nflags;
296        if (has_width)
297          *fmt_ptr++ = '*';
298        if (has_precision) {
299          *fmt_ptr++ = '.';
300          *fmt_ptr++ = '*';
301        }
302        *fmt_ptr++ = format[i];
303        *fmt_ptr   = '\0';
304
305        dou = (strlen(arg) > 0) ? estrtod(arg) : 0;
306        if (has_width && has_precision)
307          printf(fmt, width, precision, dou);
308        else if (has_width)
309          printf(fmt, width, dou);
310        else if (has_precision)
311          printf(fmt, precision, dou);
312        else
313          printf(fmt, dou);
314        free(fmt);
315        break;
316      case '\0':
317        eprintf("Missing format specifier.\n");
318        break;
319      default:
320        eprintf("Invalid format specifier '%c'.\n", format[i]);
321    }
322    if (argi >= argc)
323      cooldown = 1;
324  }
325
326  free(is_pct);
327  return fshut(stdout, "<stdout>") | ret;
328}