master xplshn/aruu / cmd / posix / printf.c
  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}