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