1 /* 2 * Copyright (c) 2005 The DragonFly Project. All rights reserved. 3 * 4 * This code is derived from software contributed to The DragonFly Project 5 * by Brian Ginsbach. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of The DragonFly Project nor the names of its 16 * contributors may be used to endorse or promote products derived 17 * from this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 24 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 25 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 27 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 29 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 * 32 * $DragonFly: src/usr.bin/seq/seq.c,v 1.1 2005/01/22 19:09:40 joerg Exp $ 33 */ 34 35 #include <ctype.h> 36 #include <err.h> 37 #include <errno.h> 38 #include <math.h> 39 #include <locale.h> 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <unistd.h> 44 45 #define ZERO '0' 46 #define SPACE ' ' 47 48 #define MAX(a, b) (((a) < (b))? (b) : (a)) 49 #define ISSIGN(c) ((int)(c) == '-' || (int)(c) == '+') 50 #define ISEXP(c) ((int)(c) == 'e' || (int)(c) == 'E') 51 #define ISODIGIT(c) ((int)(c) >= '0' && (int)(c) <= '7') 52 53 /* Globals */ 54 55 const char *decimal_point = "."; /* default */ 56 char default_format[] = { "%g" }; /* default */ 57 58 /* Prototypes */ 59 60 double e_atof(const char *); 61 62 int decimal_places(const char *); 63 int numeric(const char *); 64 int valid_format(const char *); 65 66 char *generate_format(double, double, double, int, char); 67 char *unescape(char *); 68 69 /* 70 * The seq command will print out a numeric sequence from 1, the default, 71 * to a user specified upper limit by 1. The lower bound and increment 72 * maybe indicated by the user on the command line. The sequence can 73 * be either whole, the default, or decimal numbers. 74 */ 75 int 76 main(int argc, char **argv) 77 { 78 int c = 0, errflg = 0; 79 int equalize = 0; 80 double first = 1.0; 81 double last = 0.0; 82 double incr = 0.0; 83 struct lconv *locale; 84 char *fmt = NULL; 85 const char *sep = "\n"; 86 const char *term = NULL; 87 char pad = ZERO; 88 89 /* Determine the locale's decimal point. */ 90 locale = localeconv(); 91 if (locale && locale->decimal_point && locale->decimal_point[0] != '\0') 92 decimal_point = locale->decimal_point; 93 94 /* 95 * Process options, but handle negative numbers separately 96 * least they trip up getopt(3). 97 */ 98 while ((optind < argc) && !numeric(argv[optind]) && 99 (c = getopt(argc, argv, "f:hs:t:w")) != -1) { 100 101 switch (c) { 102 case 'f': /* format (plan9) */ 103 fmt = optarg; 104 equalize = 0; 105 break; 106 case 's': /* separator (GNU) */ 107 sep = unescape(optarg); 108 break; 109 case 't': /* terminator (new) */ 110 term = unescape(optarg); 111 break; 112 case 'w': /* equal width (plan9) */ 113 if (!fmt) 114 if (equalize++) 115 pad = SPACE; 116 break; 117 case 'h': /* help (GNU) */ 118 default: 119 errflg++; 120 break; 121 } 122 } 123 124 argc -= optind; 125 argv += optind; 126 if (argc < 1 || argc > 3) 127 errflg++; 128 129 if (errflg) { 130 fprintf(stderr, 131 "usage: %s [-f format] [-s str] [-t str] [-w] [first [incr]] last\n", 132 getprogname()); 133 exit(1); 134 } 135 136 last = e_atof(argv[argc - 1]); 137 138 if (argc > 1) 139 first = e_atof(argv[0]); 140 141 if (argc > 2) { 142 incr = e_atof(argv[1]); 143 /* Plan 9/GNU don't do zero */ 144 if (incr == 0.0) 145 errx(1, "zero %screment", (first < last)? "in" : "de"); 146 } 147 148 /* default is one for Plan 9/GNU work alike */ 149 if (incr == 0.0) 150 incr = (first < last) ? 1.0 : -1.0; 151 152 if (incr <= 0.0 && first < last) 153 errx(1, "needs positive increment"); 154 155 if (incr >= 0.0 && first > last) 156 errx(1, "needs negative decrement"); 157 158 if (fmt != NULL) { 159 if (!valid_format(fmt)) 160 errx(1, "invalid format string: `%s'", fmt); 161 fmt = unescape(fmt); 162 /* 163 * XXX to be bug for bug compatible with Plan 9 add a 164 * newline if none found at the end of the format string. 165 */ 166 } else 167 fmt = generate_format(first, incr, last, equalize, pad); 168 169 if (incr > 0) { 170 for (; first <= last; first += incr) { 171 printf(fmt, first); 172 fputs(sep, stdout); 173 } 174 } else { 175 for (; first >= last; first += incr) { 176 printf(fmt, first); 177 fputs(sep, stdout); 178 } 179 } 180 if (term != NULL) 181 fputs(term, stdout); 182 183 return(0); 184 } 185 186 /* 187 * numeric - verify that string is numeric 188 */ 189 int 190 numeric(const char *s) 191 { 192 int seen_decimal_pt, decimal_pt_len; 193 194 /* skip any sign */ 195 if (ISSIGN((unsigned char)*s)) 196 s++; 197 198 seen_decimal_pt = 0; 199 decimal_pt_len = strlen(decimal_point); 200 while (*s != '\0') { 201 if (!isdigit((unsigned char)*s)) { 202 if (!seen_decimal_pt && 203 strncmp(s, decimal_point, decimal_pt_len) == 0) { 204 s += decimal_pt_len; 205 seen_decimal_pt = 1; 206 continue; 207 } 208 if (ISEXP((unsigned char)*s)) { 209 s++; 210 if (ISSIGN((unsigned char)*s)) { 211 s++; 212 continue; 213 } 214 } 215 break; 216 } 217 s++; 218 } 219 return(*s == '\0'); 220 } 221 222 /* 223 * valid_format - validate user specified format string 224 */ 225 int 226 valid_format(const char *fmt) 227 { 228 int conversions = 0; 229 230 while (*fmt != '\0') { 231 /* scan for conversions */ 232 if (*fmt != '\0' && *fmt != '%') { 233 do { 234 fmt++; 235 } while (*fmt != '\0' && *fmt != '%'); 236 } 237 /* scan a conversion */ 238 if (*fmt != '\0') { 239 do { 240 fmt++; 241 242 /* ok %% */ 243 if (*fmt == '%') { 244 fmt++; 245 break; 246 } 247 /* valid conversions */ 248 if (strchr("eEfgG", *fmt) && 249 conversions++ < 1) { 250 fmt++; 251 break; 252 } 253 /* flags, width and precsision */ 254 if (isdigit((unsigned char)*fmt) || 255 strchr("+- 0#.", *fmt)) 256 continue; 257 258 /* oops! bad conversion format! */ 259 return(0); 260 } while (*fmt != '\0'); 261 } 262 } 263 264 return(conversions <= 1); 265 } 266 267 /* 268 * unescape - handle C escapes in a string 269 */ 270 char * 271 unescape(char *orig) 272 { 273 char c, *cp, *new = orig; 274 int i; 275 276 for (cp = orig; (*orig = *cp) != '\0'; cp++, orig++) { 277 if (*cp != '\\') 278 continue; 279 280 switch (*++cp) { 281 case 'a': /* alert (bell) */ 282 *orig = '\a'; 283 continue; 284 case 'b': /* backspace */ 285 *orig = '\b'; 286 continue; 287 case 'e': /* escape */ 288 *orig = '\e'; 289 continue; 290 case 'f': /* formfeed */ 291 *orig = '\f'; 292 continue; 293 case 'n': /* newline */ 294 *orig = '\n'; 295 continue; 296 case 'r': /* carriage return */ 297 *orig = '\r'; 298 continue; 299 case 't': /* horizontal tab */ 300 *orig = '\t'; 301 continue; 302 case 'v': /* vertical tab */ 303 *orig = '\v'; 304 continue; 305 case '\\': /* backslash */ 306 *orig = '\\'; 307 continue; 308 case '\'': /* single quote */ 309 *orig = '\''; 310 continue; 311 case '\"': /* double quote */ 312 *orig = '"'; 313 continue; 314 case '0': 315 case '1': 316 case '2': 317 case '3': /* octal */ 318 case '4': 319 case '5': 320 case '6': 321 case '7': /* number */ 322 for (i = 0, c = 0; 323 ISODIGIT((unsigned char)*cp) && i < 3; 324 i++, cp++) { 325 c <<= 3; 326 c |= (*cp - '0'); 327 } 328 *orig = c; 329 continue; 330 case 'x': /* hexidecimal number */ 331 cp++; /* skip 'x' */ 332 for (i = 0, c = 0; 333 isxdigit((unsigned char)*cp) && i < 2; 334 i++, cp++) { 335 c <<= 4; 336 if (isdigit((unsigned char)*cp)) 337 c |= (*cp - '0'); 338 else 339 c |= ((toupper((unsigned char)*cp) - 340 'A') + 10); 341 } 342 *orig = c; 343 continue; 344 default: 345 --cp; 346 break; 347 } 348 } 349 350 return(new); 351 } 352 353 /* 354 * e_atof - convert an ASCII string to a double 355 * exit if string is not a valid double, or if converted value would 356 * cause overflow or underflow 357 */ 358 double 359 e_atof(const char *num) 360 { 361 char *endp; 362 double dbl; 363 364 errno = 0; 365 dbl = strtod(num, &endp); 366 367 if (errno == ERANGE) 368 /* under or overflow */ 369 err(2, "%s", num); 370 else if (*endp != '\0') 371 /* "junk" left in number */ 372 errx(2, "invalid floating point argument: %s", num); 373 374 /* zero shall have no sign */ 375 if (dbl == -0.0) 376 dbl = 0; 377 return(dbl); 378 } 379 380 /* 381 * decimal_places - count decimal places in a number (string) 382 */ 383 int 384 decimal_places(const char *number) 385 { 386 int places = 0; 387 char *dp; 388 389 /* look for a decimal point */ 390 if ((dp = strstr(number, decimal_point)) != NULL) { 391 dp += strlen(decimal_point); 392 393 while (isdigit((unsigned char)*dp++)) 394 places++; 395 } 396 return(places); 397 } 398 399 /* 400 * generate_format - create a format string 401 * 402 * XXX to be bug for bug compatable with Plan9 and GNU return "%g" 403 * when "%g" prints as "%e" (this way no width adjustments are made) 404 */ 405 char * 406 generate_format(double first, double incr, double last, int equalize, char pad) 407 { 408 static char buf[256]; 409 char cc = '\0'; 410 int precision, width1, width2, places; 411 412 if (equalize == 0) 413 return(default_format); 414 415 /* figure out "last" value printed */ 416 if (first > last) 417 last = first - incr * floor((first - last) / incr); 418 else 419 last = first + incr * floor((last - first) / incr); 420 421 snprintf(buf, sizeof(buf), "%g", incr); 422 if (strchr(buf, 'e')) 423 cc = 'e'; 424 precision = decimal_places(buf); 425 426 width1 = snprintf(buf, sizeof(buf), "%g", first); 427 if (strchr(buf, 'e')) 428 cc = 'e'; 429 if ((places = decimal_places(buf))) 430 width1 -= (places + strlen(decimal_point)); 431 432 precision = MAX(places, precision); 433 434 width2 = snprintf(buf, sizeof(buf), "%g", last); 435 if (strchr(buf, 'e')) 436 cc = 'e'; 437 if ((places = decimal_places(buf))) 438 width2 -= (places + strlen(decimal_point)); 439 440 if (precision) { 441 snprintf(buf, sizeof(buf), "%%%c%d.%d%c", pad, 442 MAX(width1, width2) + (int) strlen(decimal_point) + 443 precision, precision, (cc) ? cc : 'f'); 444 } else { 445 snprintf(buf, sizeof(buf), "%%%c%d%c", pad, 446 MAX(width1, width2), (cc) ? cc : 'g'); 447 } 448 449 return(buf); 450 } 451