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