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