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