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