1 /* $NetBSD: seq.c,v 1.5 2008/07/21 14:19:26 lukem 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 /* 162 * XXX to be bug for bug compatible with Plan 9 add a 163 * newline if none found at the end of the format string. 164 */ 165 } else 166 fmt = generate_format(first, incr, last, equalize, pad); 167 168 if (incr > 0) { 169 for (; first <= last; first += incr) { 170 printf(fmt, first); 171 fputs(sep, stdout); 172 } 173 } else { 174 for (; first >= last; first += incr) { 175 printf(fmt, first); 176 fputs(sep, stdout); 177 } 178 } 179 if (term != NULL) 180 fputs(term, stdout); 181 182 return (0); 183 } 184 185 /* 186 * numeric - verify that string is numeric 187 */ 188 static int 189 numeric(const char *s) 190 { 191 int seen_decimal_pt, decimal_pt_len; 192 193 /* skip any sign */ 194 if (ISSIGN((unsigned char)*s)) 195 s++; 196 197 seen_decimal_pt = 0; 198 decimal_pt_len = strlen(decimal_point); 199 while (*s) { 200 if (!isdigit((unsigned char)*s)) { 201 if (!seen_decimal_pt && 202 strncmp(s, decimal_point, decimal_pt_len) == 0) { 203 s += decimal_pt_len; 204 seen_decimal_pt = 1; 205 continue; 206 } 207 if (ISEXP((unsigned char)*s)) { 208 s++; 209 if (ISSIGN((unsigned char)*s) || 210 isdigit((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 static 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 precision */ 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 static char * 271 unescape(char *orig) 272 { 273 char c, *cp, *new = orig; 274 int i; 275 276 for (cp = orig; (*orig = *cp); 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 --cp; 330 continue; 331 case 'x': /* hexadecimal number */ 332 cp++; /* skip 'x' */ 333 for (i = 0, c = 0; 334 isxdigit((unsigned char)*cp) && i < 2; 335 i++, cp++) { 336 c <<= 4; 337 if (isdigit((unsigned char)*cp)) 338 c |= (*cp - '0'); 339 else 340 c |= ((toupper((unsigned char)*cp) - 341 'A') + 10); 342 } 343 *orig = c; 344 --cp; 345 continue; 346 default: 347 --cp; 348 break; 349 } 350 } 351 352 return (new); 353 } 354 355 /* 356 * e_atof - convert an ASCII string to a double 357 * exit if string is not a valid double, or if converted value would 358 * cause overflow or underflow 359 */ 360 static double 361 e_atof(const char *num) 362 { 363 char *endp; 364 double dbl; 365 366 errno = 0; 367 dbl = strtod(num, &endp); 368 369 if (errno == ERANGE) 370 /* under or overflow */ 371 err(2, "%s", num); 372 else if (*endp != '\0') 373 /* "junk" left in number */ 374 errx(2, "invalid floating point argument: %s", num); 375 376 /* zero shall have no sign */ 377 if (dbl == -0.0) 378 dbl = 0; 379 return (dbl); 380 } 381 382 /* 383 * decimal_places - count decimal places in a number (string) 384 */ 385 static int 386 decimal_places(const char *number) 387 { 388 int places = 0; 389 char *dp; 390 391 /* look for a decimal point */ 392 if ((dp = strstr(number, decimal_point))) { 393 dp += strlen(decimal_point); 394 395 while (isdigit((unsigned char)*dp++)) 396 places++; 397 } 398 return (places); 399 } 400 401 /* 402 * generate_format - create a format string 403 * 404 * XXX to be bug for bug compatible with Plan9 and GNU return "%g" 405 * when "%g" prints as "%e" (this way no width adjustments are made) 406 */ 407 static char * 408 generate_format(double first, double incr, double last, int equalize, char pad) 409 { 410 static char buf[256]; 411 char cc = '\0'; 412 int precision, width1, width2, places; 413 414 if (equalize == 0) 415 return (default_format); 416 417 /* figure out "last" value printed */ 418 if (first > last) 419 last = first - incr * floor((first - last) / incr); 420 else 421 last = first + incr * floor((last - first) / incr); 422 423 sprintf(buf, "%g", incr); 424 if (strchr(buf, 'e')) 425 cc = 'e'; 426 precision = decimal_places(buf); 427 428 width1 = sprintf(buf, "%g", first); 429 if (strchr(buf, 'e')) 430 cc = 'e'; 431 if ((places = decimal_places(buf))) 432 width1 -= (places + strlen(decimal_point)); 433 434 precision = MAX(places, precision); 435 436 width2 = sprintf(buf, "%g", last); 437 if (strchr(buf, 'e')) 438 cc = 'e'; 439 if ((places = decimal_places(buf))) 440 width2 -= (places + strlen(decimal_point)); 441 442 if (precision) { 443 sprintf(buf, "%%%c%d.%d%c", pad, 444 MAX(width1, width2) + (int) strlen(decimal_point) + 445 precision, precision, (cc) ? cc : 'f'); 446 } else { 447 sprintf(buf, "%%%c%d%c", pad, MAX(width1, width2), 448 (cc) ? cc : 'g'); 449 } 450 451 return (buf); 452 } 453