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