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 const char *decimal_point = "."; /* default */ 55 char default_format[] = { "%g" }; /* default */ 56 57 /* Prototypes */ 58 59 double e_atof(const char *); 60 61 int decimal_places(const char *); 62 int main(int, 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 [-w] [-f format] [-s string] [-t string] [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) { 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 isdigit((unsigned char)*s)) { 212 s++; 213 continue; 214 } 215 } 216 break; 217 } 218 s++; 219 } 220 return (*s == '\0'); 221 } 222 223 /* 224 * valid_format - validate user specified format string 225 */ 226 int 227 valid_format(const char *fmt) 228 { 229 int conversions = 0; 230 231 while (*fmt != '\0') { 232 /* scan for conversions */ 233 if (*fmt != '\0' && *fmt != '%') { 234 do { 235 fmt++; 236 } while (*fmt != '\0' && *fmt != '%'); 237 } 238 /* scan a conversion */ 239 if (*fmt != '\0') { 240 do { 241 fmt++; 242 243 /* ok %% */ 244 if (*fmt == '%') { 245 fmt++; 246 break; 247 } 248 /* valid conversions */ 249 if (strchr("eEfgG", *fmt) && 250 conversions++ < 1) { 251 fmt++; 252 break; 253 } 254 /* flags, width and precsision */ 255 if (isdigit((unsigned char)*fmt) || 256 strchr("+- 0#.", *fmt)) 257 continue; 258 259 /* oops! bad conversion format! */ 260 return (0); 261 } while (*fmt != '\0'); 262 } 263 } 264 265 return (conversions <= 1); 266 } 267 268 /* 269 * unescape - handle C escapes in a string 270 */ 271 char * 272 unescape(char *orig) 273 { 274 char c, *cp, *new = orig; 275 int i; 276 277 for (cp = orig; (*orig = *cp); cp++, orig++) { 278 if (*cp != '\\') 279 continue; 280 281 switch (*++cp) { 282 case 'a': /* alert (bell) */ 283 *orig = '\a'; 284 continue; 285 case 'b': /* backspace */ 286 *orig = '\b'; 287 continue; 288 case 'e': /* escape */ 289 *orig = '\e'; 290 continue; 291 case 'f': /* formfeed */ 292 *orig = '\f'; 293 continue; 294 case 'n': /* newline */ 295 *orig = '\n'; 296 continue; 297 case 'r': /* carriage return */ 298 *orig = '\r'; 299 continue; 300 case 't': /* horizontal tab */ 301 *orig = '\t'; 302 continue; 303 case 'v': /* vertical tab */ 304 *orig = '\v'; 305 continue; 306 case '\\': /* backslash */ 307 *orig = '\\'; 308 continue; 309 case '\'': /* single quote */ 310 *orig = '\''; 311 continue; 312 case '\"': /* double quote */ 313 *orig = '"'; 314 continue; 315 case '0': 316 case '1': 317 case '2': 318 case '3': /* octal */ 319 case '4': 320 case '5': 321 case '6': 322 case '7': /* number */ 323 for (i = 0, c = 0; 324 ISODIGIT((unsigned char)*cp) && i < 3; 325 i++, cp++) { 326 c <<= 3; 327 c |= (*cp - '0'); 328 } 329 *orig = c; 330 --cp; 331 continue; 332 case 'x': /* hexidecimal number */ 333 cp++; /* skip 'x' */ 334 for (i = 0, c = 0; 335 isxdigit((unsigned char)*cp) && i < 2; 336 i++, cp++) { 337 c <<= 4; 338 if (isdigit((unsigned char)*cp)) 339 c |= (*cp - '0'); 340 else 341 c |= ((toupper((unsigned char)*cp) - 342 'A') + 10); 343 } 344 *orig = c; 345 --cp; 346 continue; 347 default: 348 --cp; 349 break; 350 } 351 } 352 353 return (new); 354 } 355 356 /* 357 * e_atof - convert an ASCII string to a double 358 * exit if string is not a valid double, or if converted value would 359 * cause overflow or underflow 360 */ 361 double 362 e_atof(const char *num) 363 { 364 char *endp; 365 double dbl; 366 367 errno = 0; 368 dbl = strtod(num, &endp); 369 370 if (errno == ERANGE) 371 /* under or overflow */ 372 err(2, "%s", num); 373 else if (*endp != '\0') 374 /* "junk" left in number */ 375 errx(2, "invalid floating point argument: %s", num); 376 377 /* zero shall have no sign */ 378 if (dbl == -0.0) 379 dbl = 0; 380 return (dbl); 381 } 382 383 /* 384 * decimal_places - count decimal places in a number (string) 385 */ 386 int 387 decimal_places(const char *number) 388 { 389 int places = 0; 390 char *dp; 391 392 /* look for a decimal point */ 393 if ((dp = strstr(number, decimal_point))) { 394 dp += strlen(decimal_point); 395 396 while (isdigit((unsigned char)*dp++)) 397 places++; 398 } 399 return (places); 400 } 401 402 /* 403 * generate_format - create a format string 404 * 405 * XXX to be bug for bug compatable with Plan9 and GNU return "%g" 406 * when "%g" prints as "%e" (this way no width adjustments are made) 407 */ 408 char * 409 generate_format(double first, double incr, double last, int equalize, char pad) 410 { 411 static char buf[256]; 412 char cc = '\0'; 413 int precision, width1, width2, places; 414 415 if (equalize == 0) 416 return (default_format); 417 418 /* figure out "last" value printed */ 419 if (first > last) 420 last = first - incr * floor((first - last) / incr); 421 else 422 last = first + incr * floor((last - first) / incr); 423 424 sprintf(buf, "%g", incr); 425 if (strchr(buf, 'e')) 426 cc = 'e'; 427 precision = decimal_places(buf); 428 429 width1 = sprintf(buf, "%g", first); 430 if (strchr(buf, 'e')) 431 cc = 'e'; 432 if ((places = decimal_places(buf))) 433 width1 -= (places + strlen(decimal_point)); 434 435 precision = MAX(places, precision); 436 437 width2 = sprintf(buf, "%g", last); 438 if (strchr(buf, 'e')) 439 cc = 'e'; 440 if ((places = decimal_places(buf))) 441 width2 -= (places + strlen(decimal_point)); 442 443 if (precision) { 444 sprintf(buf, "%%%c%d.%d%c", pad, 445 MAX(width1, width2) + (int) strlen(decimal_point) + 446 precision, precision, (cc) ? cc : 'f'); 447 } else { 448 sprintf(buf, "%%%c%d%c", pad, MAX(width1, width2), 449 (cc) ? cc : 'g'); 450 } 451 452 return (buf); 453 } 454