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}