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.14 2024/05/04 13:29:41 mlelstv 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 char default_format_fmt[] = { "%%.%uf" }; 60 #define MAXPRECISION 40 61 62 /* Prototypes */ 63 64 double e_atof(const char *); 65 66 int decimal_places(const char *); 67 int numeric(const char *); 68 int valid_format(const char *); 69 70 unsigned get_precision(const char *, unsigned); 71 char *generate_format(double, double, double, int, char, char *); 72 char *unescape(char *); 73 74 unsigned 75 get_precision(const char *number, unsigned minprec) 76 { 77 const char *p; 78 unsigned prec; 79 80 p = strstr(number, decimal_point); 81 if (p) { 82 prec = strlen(number) - (p - number); 83 if (prec > 0) 84 prec -= 1; 85 if (prec > MAXPRECISION) 86 prec = MAXPRECISION; 87 } else 88 prec = 0; 89 90 return prec < minprec ? minprec : prec; 91 } 92 93 /* 94 * The seq command will print out a numeric sequence from 1, the default, 95 * to a user specified upper limit by 1. The lower bound and increment 96 * maybe indicated by the user on the command line. The sequence can 97 * be either whole, the default, or decimal numbers. 98 */ 99 int 100 main(int argc, char *argv[]) 101 { 102 int c = 0, errflg = 0; 103 int equalize = 0; 104 unsigned prec; 105 double first = 1.0; 106 double last = 0.0; 107 double incr = 0.0; 108 double prev; 109 struct lconv *locale; 110 char *fmt = NULL; 111 const char *sep = "\n"; 112 const char *term = "\n"; 113 char pad = ZERO; 114 char buf[6]; /* %.MAXPRECISIONf */ 115 116 117 /* Determine the locale's decimal point. */ 118 locale = localeconv(); 119 if (locale && locale->decimal_point && locale->decimal_point[0] != '\0') 120 decimal_point = locale->decimal_point; 121 122 /* 123 * Process options, but handle negative numbers separately 124 * least they trip up getopt(3). 125 */ 126 while ((optind < argc) && !numeric(argv[optind]) && 127 (c = getopt(argc, argv, "f:hs:t:w")) != -1) { 128 129 switch (c) { 130 case 'f': /* format (plan9) */ 131 fmt = optarg; 132 equalize = 0; 133 break; 134 case 's': /* separator (GNU) */ 135 sep = unescape(optarg); 136 break; 137 case 't': /* terminator (new) */ 138 term = unescape(optarg); 139 break; 140 case 'w': /* equal width (plan9) */ 141 if (!fmt) 142 if (equalize++) 143 pad = SPACE; 144 break; 145 case 'h': /* help (GNU) */ 146 default: 147 errflg++; 148 break; 149 } 150 } 151 152 argc -= optind; 153 argv += optind; 154 if (argc < 1 || argc > 3) 155 errflg++; 156 157 if (errflg) { 158 fprintf(stderr, 159 "usage: %s [-w] [-f format] [-s string] [-t string] [first [incr]] last\n", 160 getprogname()); 161 exit(1); 162 } 163 164 last = e_atof(argv[argc - 1]); 165 prec = get_precision(argv[argc - 1], 0); 166 167 if (argc > 1) { 168 first = e_atof(argv[0]); 169 prec = get_precision(argv[0], prec); 170 } 171 172 if (argc > 2) { 173 incr = e_atof(argv[1]); 174 prec = get_precision(argv[1], prec); 175 /* Plan 9/GNU don't do zero */ 176 if (incr == 0.0) 177 errx(1, "zero %screment", (first < last)? "in" : "de"); 178 } 179 180 /* default is one for Plan 9/GNU work alike */ 181 if (incr == 0.0) 182 incr = (first < last) ? 1.0 : -1.0; 183 184 if (incr <= 0.0 && first < last) 185 errx(1, "needs positive increment"); 186 187 if (incr >= 0.0 && first > last) 188 errx(1, "needs negative decrement"); 189 190 if (fmt != NULL) { 191 if (!valid_format(fmt)) 192 errx(1, "invalid format string: `%s'", fmt); 193 fmt = unescape(fmt); 194 if (!valid_format(fmt)) 195 errx(1, "invalid format string"); 196 /* 197 * XXX to be bug for bug compatible with Plan 9 add a 198 * newline if none found at the end of the format string. 199 */ 200 } else { 201 if (prec == 0) 202 fmt = default_format; 203 else { 204 sprintf(buf, default_format_fmt, prec); 205 fmt = buf; 206 } 207 fmt = generate_format(first, incr, last, equalize, pad, fmt); 208 } 209 210 if (incr > 0) { 211 printf(fmt, first); 212 prev = first; 213 for (first += incr; first <= last; first += incr) { 214 if (first <= prev) 215 errx(1, "increment too small\n"); 216 fputs(sep, stdout); 217 printf(fmt, first); 218 prev = first; 219 } 220 } else { 221 printf(fmt, first); 222 prev = first; 223 for (first += incr; first >= last; first += incr) { 224 if (first >= prev) 225 errx(1, "increment too small\n"); 226 fputs(sep, stdout); 227 printf(fmt, first); 228 prev = first; 229 } 230 } 231 if (term != NULL) 232 fputs(term, stdout); 233 234 return (0); 235 } 236 237 /* 238 * numeric - verify that string is numeric 239 */ 240 int 241 numeric(const char *s) 242 { 243 int seen_decimal_pt, decimal_pt_len; 244 245 /* skip any sign */ 246 if (ISSIGN((unsigned char)*s)) 247 s++; 248 249 seen_decimal_pt = 0; 250 decimal_pt_len = strlen(decimal_point); 251 while (*s) { 252 if (!isdigit((unsigned char)*s)) { 253 if (!seen_decimal_pt && 254 strncmp(s, decimal_point, decimal_pt_len) == 0) { 255 s += decimal_pt_len; 256 seen_decimal_pt = 1; 257 continue; 258 } 259 if (ISEXP((unsigned char)*s)) { 260 s++; 261 /* optional sign */ 262 if (ISSIGN((unsigned char)*s)) 263 s++; 264 continue; 265 } 266 break; 267 } 268 s++; 269 } 270 return (*s == '\0'); 271 } 272 273 /* 274 * valid_format - validate user specified format string 275 */ 276 int 277 valid_format(const char *fmt) 278 { 279 unsigned conversions = 0; 280 281 while (*fmt != '\0') { 282 /* scan for conversions */ 283 if (*fmt != '%') { 284 fmt++; 285 continue; 286 } 287 fmt++; 288 289 /* allow %% but not things like %10% */ 290 if (*fmt == '%') { 291 fmt++; 292 continue; 293 } 294 295 /* flags */ 296 while (*fmt != '\0' && strchr("#0- +'", *fmt)) { 297 fmt++; 298 } 299 300 /* field width */ 301 while (*fmt != '\0' && strchr("0123456789", *fmt)) { 302 fmt++; 303 } 304 305 /* precision */ 306 if (*fmt == '.') { 307 fmt++; 308 while (*fmt != '\0' && strchr("0123456789", *fmt)) { 309 fmt++; 310 } 311 } 312 313 /* conversion */ 314 switch (*fmt) { 315 case 'A': 316 case 'a': 317 case 'E': 318 case 'e': 319 case 'F': 320 case 'f': 321 case 'G': 322 case 'g': 323 /* floating point formats are accepted */ 324 conversions++; 325 break; 326 default: 327 /* anything else is not */ 328 return 0; 329 } 330 } 331 332 return (conversions <= 1); 333 } 334 335 /* 336 * unescape - handle C escapes in a string 337 */ 338 char * 339 unescape(char *orig) 340 { 341 char c, *cp, *new = orig; 342 int i; 343 344 for (cp = orig; (*orig = *cp); cp++, orig++) { 345 if (*cp != '\\') 346 continue; 347 348 switch (*++cp) { 349 case 'a': /* alert (bell) */ 350 *orig = '\a'; 351 continue; 352 case 'b': /* backspace */ 353 *orig = '\b'; 354 continue; 355 case 'e': /* escape */ 356 *orig = '\x1B'; 357 continue; 358 case 'f': /* formfeed */ 359 *orig = '\f'; 360 continue; 361 case 'n': /* newline */ 362 *orig = '\n'; 363 continue; 364 case 'r': /* carriage return */ 365 *orig = '\r'; 366 continue; 367 case 't': /* horizontal tab */ 368 *orig = '\t'; 369 continue; 370 case 'v': /* vertical tab */ 371 *orig = '\v'; 372 continue; 373 case '\\': /* backslash */ 374 *orig = '\\'; 375 continue; 376 case '\'': /* single quote */ 377 *orig = '\''; 378 continue; 379 case '\"': /* double quote */ 380 *orig = '"'; 381 continue; 382 case '0': 383 case '1': 384 case '2': 385 case '3': /* octal */ 386 case '4': 387 case '5': 388 case '6': 389 case '7': /* number */ 390 for (i = 0, c = 0; 391 ISODIGIT((unsigned char)*cp) && i < 3; 392 i++, cp++) { 393 c <<= 3; 394 c |= (*cp - '0'); 395 } 396 *orig = c; 397 --cp; 398 continue; 399 case 'x': /* hexadecimal number */ 400 cp++; /* skip 'x' */ 401 for (i = 0, c = 0; 402 isxdigit((unsigned char)*cp) && i < 2; 403 i++, cp++) { 404 c <<= 4; 405 if (isdigit((unsigned char)*cp)) 406 c |= (*cp - '0'); 407 else 408 c |= ((toupper((unsigned char)*cp) - 409 'A') + 10); 410 } 411 *orig = c; 412 --cp; 413 continue; 414 default: 415 --cp; 416 break; 417 } 418 } 419 420 return (new); 421 } 422 423 /* 424 * e_atof - convert an ASCII string to a double 425 * exit if string is not a valid double, or if converted value would 426 * cause overflow or underflow 427 */ 428 double 429 e_atof(const char *num) 430 { 431 char *endp; 432 double dbl; 433 434 errno = 0; 435 dbl = strtod(num, &endp); 436 437 if (errno == ERANGE) 438 /* under or overflow */ 439 err(2, "%s", num); 440 else if (*endp != '\0') 441 /* "junk" left in number */ 442 errx(2, "invalid floating point argument: %s", num); 443 444 /* zero shall have no sign */ 445 if (dbl == -0.0) 446 dbl = 0; 447 return (dbl); 448 } 449 450 /* 451 * decimal_places - count decimal places in a number (string) 452 */ 453 int 454 decimal_places(const char *number) 455 { 456 int places = 0; 457 char *dp; 458 459 /* look for a decimal point */ 460 if ((dp = strstr(number, decimal_point))) { 461 dp += strlen(decimal_point); 462 463 while (isdigit((unsigned char)*dp++)) 464 places++; 465 } 466 return (places); 467 } 468 469 /* 470 * generate_format - create a format string 471 * 472 * XXX to be bug for bug compatible with Plan9 and GNU return "%g" 473 * when "%g" prints as "%e" (this way no width adjustments are made) 474 */ 475 char * 476 generate_format(double first, double incr, double last, 477 int equalize, char pad, char *deffmt) 478 { 479 static char buf[256]; 480 char cc = '\0'; 481 int precision, width1, width2, places; 482 483 if (equalize == 0) 484 return deffmt; 485 486 /* figure out "last" value printed */ 487 if (first > last) 488 last = first - incr * floor((first - last) / incr); 489 else 490 last = first + incr * floor((last - first) / incr); 491 492 sprintf(buf, deffmt, incr); 493 if (strchr(buf, 'e')) 494 cc = 'e'; 495 precision = decimal_places(buf); 496 497 width1 = sprintf(buf, deffmt, first); 498 if (strchr(buf, 'e')) 499 cc = 'e'; 500 if ((places = decimal_places(buf))) 501 width1 -= (places + strlen(decimal_point)); 502 503 precision = MAX(places, precision); 504 505 width2 = sprintf(buf, deffmt, last); 506 if (strchr(buf, 'e')) 507 cc = 'e'; 508 if ((places = decimal_places(buf))) 509 width2 -= (places + strlen(decimal_point)); 510 511 if (precision) { 512 sprintf(buf, "%%%c%d.%d%c", pad, 513 MAX(width1, width2) + (int) strlen(decimal_point) + 514 precision, precision, (cc) ? cc : 'f'); 515 } else { 516 sprintf(buf, "%%%c%d%c", pad, MAX(width1, width2), 517 (cc) ? cc : 'g'); 518 } 519 520 return (buf); 521 } 522