printf.c (4127B)
1 /* See LICENSE file for copyright and license details. */ 2 #include <ctype.h> 3 #include <errno.h> 4 #include <limits.h> 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 9 #include "utf.h" 10 #include "util.h" 11 12 static void 13 usage(void) 14 { 15 eprintf("usage: %s format [arg ...]\n", argv0); 16 } 17 18 int 19 main(int argc, char *argv[]) 20 { 21 Rune *rarg; 22 size_t i, j, f, argi, lastargi, formatlen, blen, nflags; 23 long long num; 24 double dou; 25 int cooldown = 0, width, precision, ret = 0; 26 char *format, *tmp, *arg, *fmt; 27 28 argv0 = argv[0]; 29 if (argc < 2) 30 usage(); 31 32 format = argv[1]; 33 if ((tmp = strstr(format, "\\c"))) { 34 *tmp = 0; 35 cooldown = 1; 36 } 37 formatlen = unescape(format); 38 if (formatlen == 0) 39 return 0; 40 lastargi = 0; 41 for (i = 0, argi = 2; !cooldown || i < formatlen; i++, i = cooldown ? i : (i % formatlen)) { 42 if (i == 0) { 43 if (lastargi == argi) 44 break; 45 lastargi = argi; 46 } 47 48 if (format[i] != '%') { 49 putchar(format[i]); 50 continue; 51 } 52 53 /* flag */ 54 f = ++i; 55 nflags = strspn(&format[f], "#-+ 0"); 56 i += nflags; 57 58 if (nflags > INT_MAX) 59 eprintf("Too many flags in format\n"); 60 61 /* field width */ 62 width = -1; 63 if (format[i] == '*') { 64 if (argi < argc) 65 width = estrtonum(argv[argi++], 0, INT_MAX); 66 else 67 cooldown = 1; 68 i++; 69 } else { 70 j = i; 71 i += strspn(&format[i], "+-0123456789"); 72 if (j != i) { 73 tmp = estrndup(format + j, i - j); 74 width = estrtonum(tmp, 0, INT_MAX); 75 free(tmp); 76 } else { 77 width = 0; 78 } 79 } 80 81 /* field precision */ 82 precision = -1; 83 if (format[i] == '.') { 84 if (format[++i] == '*') { 85 if (argi < argc) 86 precision = estrtonum(argv[argi++], 0, INT_MAX); 87 else 88 cooldown = 1; 89 i++; 90 } else { 91 j = i; 92 i += strspn(&format[i], "+-0123456789"); 93 if (j != i) { 94 tmp = estrndup(format + j, i - j); 95 precision = estrtonum(tmp, 0, INT_MAX); 96 free(tmp); 97 } else { 98 precision = 0; 99 } 100 } 101 } 102 103 if (format[i] != '%') { 104 if (argi < argc) 105 arg = argv[argi++]; 106 else { 107 arg = ""; 108 cooldown = 1; 109 } 110 } else { 111 putchar('%'); 112 continue; 113 } 114 115 switch (format[i]) { 116 case 'b': 117 if ((tmp = strstr(arg, "\\c"))) { 118 *tmp = 0; 119 blen = unescape(arg); 120 fwrite(arg, sizeof(*arg), blen, stdout); 121 return 0; 122 } 123 blen = unescape(arg); 124 fwrite(arg, sizeof(*arg), blen, stdout); 125 break; 126 case 'c': 127 unescape(arg); 128 rarg = ereallocarray(NULL, utflen(arg) + 1, sizeof(*rarg)); 129 utftorunestr(arg, rarg); 130 efputrune(rarg, stdout, "<stdout>"); 131 free(rarg); 132 break; 133 case 's': 134 fmt = emalloc(sizeof("%*.*s" + nflags)); 135 sprintf(fmt, "%%%.*s*.*s", (int)nflags, &format[f]); 136 printf(fmt, width, precision, arg); 137 free(fmt); 138 break; 139 case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': 140 for (j = 0; isspace(arg[j]); j++); 141 if (arg[j] == '\'' || arg[j] == '\"') { 142 arg += j + 1; 143 unescape(arg); 144 rarg = ereallocarray(NULL, utflen(arg) + 1, sizeof(*rarg)); 145 utftorunestr(arg, rarg); 146 num = rarg[0]; 147 } else if (arg[0]) { 148 errno = 0; 149 if (format[i] == 'd' || format[i] == 'i') 150 num = strtol(arg, &tmp, 0); 151 else 152 num = strtoul(arg, &tmp, 0); 153 154 if (tmp == arg || *tmp != '\0') { 155 ret = 1; 156 weprintf("%%%c %s: conversion error\n", 157 format[i], arg); 158 } 159 if (errno == ERANGE) { 160 ret = 1; 161 weprintf("%%%c %s: out of range\n", 162 format[i], arg); 163 } 164 } else { 165 num = 0; 166 } 167 fmt = emalloc(sizeof("%*.*ll#") + nflags); 168 sprintf(fmt, "%%%.*s*.*ll%c", (int)nflags, &format[f], format[i]); 169 printf(fmt, width, precision, num); 170 free(fmt); 171 break; 172 case 'a': case 'A': case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': 173 fmt = emalloc(sizeof("%*.*#") + nflags); 174 sprintf(fmt, "%%%.*s*.*%c", (int)nflags, &format[f], format[i]); 175 dou = (strlen(arg) > 0) ? estrtod(arg) : 0; 176 printf(fmt, width, precision, dou); 177 free(fmt); 178 break; 179 case '\0': 180 eprintf("Missing format specifier.\n"); 181 default: 182 eprintf("Invalid format specifier '%c'.\n", format[i]); 183 } 184 if (argi >= argc) 185 cooldown = 1; 186 } 187 188 return fshut(stdout, "<stdout>") | ret; 189 }