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